2023-05-04 11:29:40 +08:00
package common
import (
"bytes"
"compress/gzip"
"fmt"
"io"
"log"
2023-05-16 15:43:28 +08:00
"math/rand"
2023-05-04 11:29:40 +08:00
"net/http"
"net/http/httputil"
"net/url"
2023-05-09 07:04:01 +08:00
"os"
2023-05-04 11:29:40 +08:00
"strconv"
"strings"
2023-05-16 15:43:28 +08:00
"time"
2023-05-04 11:29:40 +08:00
"github.com/andybalholm/brotli"
2023-05-09 07:04:01 +08:00
"golang.org/x/net/proxy"
2023-05-04 11:29:40 +08:00
)
var (
2023-05-25 18:09:32 +08:00
BING_SYDNEY_DOMAIN = "https://sydney.bing.com"
// BING_CHAT_URL, _ = url.Parse(BING_CHAT_DOMAIN + "/sydney/ChatHub")
BING_SYDNEY_URL , _ = url . Parse ( BING_SYDNEY_DOMAIN )
BING_URL , _ = url . Parse ( "https://www.bing.com" )
2023-05-08 12:07:55 +08:00
// EDGE_SVC_URL, _ = url.Parse("https://edgeservices.bing.com")
KEEP_REQ_HEADER_MAP = map [ string ] bool {
2023-05-25 18:09:32 +08:00
"Accept" : true ,
"Accept-Encoding" : true ,
"Accept-Language" : true ,
"Referer" : true ,
"Connection" : true ,
"Cookie" : true ,
"Upgrade" : true ,
"User-Agent" : true ,
"Sec-Websocket-Extensions" : true ,
"Sec-Websocket-Key" : true ,
"Sec-Websocket-Version" : true ,
"X-Request-Id" : true ,
"X-Forwarded-For" : true ,
"Content-Length" : true ,
"Content-Type" : true ,
"Access-Control-Request-Headers" : true ,
"Access-Control-Request-Method" : true ,
2023-05-04 11:29:40 +08:00
}
2023-05-06 11:08:57 +08:00
DEL_LOCATION_DOMAINS = [ ] string {
"https://cn.bing.com" ,
"https://www.bing.com" ,
}
2023-05-16 15:43:28 +08:00
USER_TOKEN_COOKIE_NAME = "_U"
RAND_IP_COOKIE_NAME = "BingAI_Rand_IP"
PROXY_WEB_PREFIX_PATH = "/web/"
PROXY_WEB_PAGE_PATH = PROXY_WEB_PREFIX_PATH + "index.html"
USER_TOKEN_ENV_NAME_PREFIX = "Go_Proxy_BingAI_USER_TOKEN"
USER_TOKEN_LIST [ ] string
RAND_COOKIE_INDEX_NAME = "BingAI_Rand_CK"
2023-05-25 18:09:32 +08:00
// socks
SOCKS_URL string
SOCKS_USER string
SOCKS_PWD string
// 访问权限密钥,可选
AUTH_KEY string
AUTH_KEY_COOKIE_NAME = "BingAI_Auth_Key"
2023-05-04 11:29:40 +08:00
)
2023-05-16 15:43:28 +08:00
func init ( ) {
2023-05-25 18:09:32 +08:00
initEnv ( )
2023-05-16 15:43:28 +08:00
initUserToken ( )
}
2023-05-25 18:09:32 +08:00
func initEnv ( ) {
// socks
SOCKS_URL = os . Getenv ( "Go_Proxy_BingAI_SOCKS_URL" )
SOCKS_USER = os . Getenv ( "Go_Proxy_BingAI_SOCKS_USER" )
SOCKS_PWD = os . Getenv ( "Go_Proxy_BingAI_SOCKS_PWD" )
// auth
AUTH_KEY = os . Getenv ( "Go_Proxy_BingAI_AUTH_KEY" )
}
2023-05-16 15:43:28 +08:00
func initUserToken ( ) {
for _ , env := range os . Environ ( ) {
if strings . HasPrefix ( env , USER_TOKEN_ENV_NAME_PREFIX ) {
parts := strings . SplitN ( env , "=" , 2 )
USER_TOKEN_LIST = append ( USER_TOKEN_LIST , parts [ 1 ] )
}
}
}
2023-05-04 11:29:40 +08:00
func NewSingleHostReverseProxy ( target * url . URL ) * httputil . ReverseProxy {
originalScheme := "http"
httpsSchemeName := "https"
var originalHost string
var originalPath string
2023-05-25 18:09:32 +08:00
// var originalDomain string
2023-05-06 11:08:57 +08:00
var randIP string
2023-05-16 15:43:28 +08:00
var resCKRandIndex string
2023-05-04 11:29:40 +08:00
director := func ( req * http . Request ) {
if req . URL . Scheme == httpsSchemeName || req . Header . Get ( "X-Forwarded-Proto" ) == httpsSchemeName {
originalScheme = httpsSchemeName
}
originalHost = req . Host
originalPath = req . URL . Path
2023-05-25 18:09:32 +08:00
// originalDomain = fmt.Sprintf("%s:%s", originalScheme, originalHost)
2023-05-04 11:29:40 +08:00
req . URL . Scheme = target . Scheme
req . URL . Host = target . Host
req . Host = target . Host
2023-05-25 18:09:32 +08:00
// originalRefer := req.Referer()
// if originalRefer != "" && !strings.Contains(originalRefer, originalDomain) {
// req.Header.Set("Referer", strings.ReplaceAll(originalRefer, originalDomain, BING_URL.String()))
// } else {
req . Header . Set ( "Referer" , fmt . Sprintf ( "%s/search?q=Bing+AI" , BING_URL . String ( ) ) )
// }
2023-05-04 11:29:40 +08:00
2023-05-06 11:08:57 +08:00
// 同一会话尽量保持相同的随机IP
ckRandIP , _ := req . Cookie ( RAND_IP_COOKIE_NAME )
if ckRandIP != nil && ckRandIP . Value != "" {
randIP = ckRandIP . Value
}
if randIP == "" {
randIP = GetRandomIP ( )
}
req . Header . Set ( "X-Forwarded-For" , randIP )
2023-05-04 11:29:40 +08:00
2023-05-16 15:43:28 +08:00
// 未登录用户
2023-05-06 17:47:45 +08:00
ckUserToken , _ := req . Cookie ( USER_TOKEN_COOKIE_NAME )
if ckUserToken == nil || ckUserToken . Value == "" {
2023-05-16 15:43:28 +08:00
randCKIndex , randCkVal := getRandCookie ( req )
if randCkVal != "" {
resCKRandIndex = strconv . Itoa ( randCKIndex )
req . AddCookie ( & http . Cookie {
Name : USER_TOKEN_COOKIE_NAME ,
Value : randCkVal ,
} )
2023-05-06 17:47:45 +08:00
}
2023-05-16 15:43:28 +08:00
// ua := req.UserAgent()
// if !strings.Contains(ua, "iPhone") || !strings.Contains(ua, "Mobile") {
// req.Header.Set("User-Agent", "iPhone Mobile "+ua)
// }
2023-05-06 17:47:45 +08:00
}
2023-05-17 09:29:29 +08:00
ua := req . UserAgent ( )
isMobile := strings . Contains ( ua , "Mobile" ) || strings . Contains ( ua , "Android" )
// m pc 画图大小不一样
if isMobile {
req . Header . Set ( "User-Agent" , "Mozilla/5.0 (iPhone; CPU iPhone OS 15_7 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.7 Mobile/15E148 Safari/605.1.15 BingSapphire/1.0.410427012" )
} else {
req . Header . Set ( "User-Agent" , "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36 Edg/113.0.1774.35" )
}
2023-05-06 17:47:45 +08:00
2023-05-04 11:29:40 +08:00
for hKey , _ := range req . Header {
2023-05-08 12:07:55 +08:00
if _ , ok := KEEP_REQ_HEADER_MAP [ hKey ] ; ! ok {
2023-05-04 11:29:40 +08:00
req . Header . Del ( hKey )
}
}
// reqHeaderByte, _ := json.Marshal(req.Header)
// log.Println("剩余请求头 : ", string(reqHeaderByte))
}
//改写返回信息
modifyFunc := func ( res * http . Response ) error {
contentType := res . Header . Get ( "Content-Type" )
if strings . Contains ( contentType , "text/javascript" ) {
contentEncoding := res . Header . Get ( "Content-Encoding" )
switch contentEncoding {
case "gzip" :
// log.Println("ContentEncoding : ", contentEncoding, " Path : ", originalPath)
modifyGzipBody ( res , originalScheme , originalHost )
case "br" :
// log.Println("ContentEncoding : ", contentEncoding, " Path : ", originalPath)
modifyBrBody ( res , originalScheme , originalHost )
default :
log . Println ( "ContentEncoding default : " , contentEncoding , " Path : " , originalPath )
modifyDefaultBody ( res , originalScheme , originalHost )
}
}
// 修改响应 cookie 域
// resCookies := res.Header.Values("Set-Cookie")
// if len(resCookies) > 0 {
// for i, v := range resCookies {
// resCookies[i] = strings.ReplaceAll(strings.ReplaceAll(v, ".bing.com", originalHost), "bing.com", originalHost)
// }
// }
2023-05-06 11:08:57 +08:00
// 设置随机ip cookie
ckRandIP := & http . Cookie {
Name : RAND_IP_COOKIE_NAME ,
Value : randIP ,
Path : "/" ,
}
res . Header . Set ( "Set-Cookie" , ckRandIP . String ( ) )
2023-05-04 11:29:40 +08:00
2023-05-16 15:43:28 +08:00
// 设置服务器 cookie 对应索引
if resCKRandIndex != "" {
ckRandIndex := & http . Cookie {
Name : RAND_COOKIE_INDEX_NAME ,
Value : resCKRandIndex ,
Path : "/" ,
}
res . Header . Set ( "Set-Cookie" , ckRandIndex . String ( ) )
}
2023-05-05 14:04:06 +08:00
// 删除 CSP
res . Header . Del ( "Content-Security-Policy-Report-Only" )
res . Header . Del ( "Report-To" )
2023-05-06 11:08:57 +08:00
// 删除重定向前缀域名 cn.bing.com www.bing.com 等
location := res . Header . Get ( "Location" )
if location != "" {
for _ , delLocationDomain := range DEL_LOCATION_DOMAINS {
if strings . HasPrefix ( location , delLocationDomain ) {
res . Header . Set ( "Location" , location [ len ( delLocationDomain ) : ] )
log . Println ( "Del Location Domain : " , location )
2023-05-22 12:43:15 +08:00
log . Println ( "RandIP : " , randIP )
2023-05-06 11:08:57 +08:00
}
}
}
2023-05-05 14:04:06 +08:00
2023-05-15 15:19:09 +08:00
// 跨域
2023-05-25 18:09:32 +08:00
// res.Header.Set("Access-Control-Allow-Origin", "*")
// res.Header.Set("Access-Control-Allow-Methods", "*")
// res.Header.Set("Access-Control-Allow-Headers", "*")
2023-05-15 15:19:09 +08:00
2023-05-04 11:29:40 +08:00
return nil
}
errorHandler := func ( res http . ResponseWriter , req * http . Request , err error ) {
log . Println ( "代理异常 : " , err )
res . Write ( [ ] byte ( err . Error ( ) ) )
}
2023-05-05 10:05:45 +08:00
// tr := &http.Transport{
// TLSClientConfig: &tls.Config{
// // 如果只设置 InsecureSkipVerify: true对于这个问题不会有任何改变
// InsecureSkipVerify: true,
// ClientAuth: tls.NoClientCert,
// },
// }
2023-05-04 11:29:40 +08:00
// 代理请求 请求回来的内容 报错自动调用
2023-05-09 07:04:01 +08:00
reverseProxy := & httputil . ReverseProxy {
Director : director ,
ModifyResponse : modifyFunc ,
ErrorHandler : errorHandler ,
}
// socks
2023-05-25 18:09:32 +08:00
if SOCKS_URL != "" {
2023-05-09 07:04:01 +08:00
var socksAuth * proxy . Auth
2023-05-25 18:09:32 +08:00
if SOCKS_USER != "" && SOCKS_PWD != "" {
2023-05-09 07:04:01 +08:00
socksAuth = & proxy . Auth {
2023-05-25 18:09:32 +08:00
User : SOCKS_USER ,
Password : SOCKS_PWD ,
2023-05-09 07:04:01 +08:00
}
}
2023-05-25 18:09:32 +08:00
s5Proxy , err := proxy . SOCKS5 ( "tcp" , SOCKS_URL , socksAuth , proxy . Direct )
2023-05-09 07:04:01 +08:00
if err != nil {
panic ( err )
}
tr := & http . Transport {
Dial : s5Proxy . Dial ,
}
reverseProxy . Transport = tr
}
return reverseProxy
2023-05-04 11:29:40 +08:00
}
2023-05-16 15:43:28 +08:00
// return cookie index and cookie
func getRandCookie ( req * http . Request ) ( int , string ) {
utLen := len ( USER_TOKEN_LIST )
if utLen == 0 {
return 0 , ""
}
if utLen == 1 {
return 0 , USER_TOKEN_LIST [ 0 ]
}
ckRandIndex , _ := req . Cookie ( RAND_COOKIE_INDEX_NAME )
if ckRandIndex != nil && ckRandIndex . Value != "" {
tmpIndex , err := strconv . Atoi ( ckRandIndex . Value )
if err != nil {
log . Println ( "ckRandIndex err : " , err )
return 0 , ""
}
if tmpIndex < utLen {
return tmpIndex , USER_TOKEN_LIST [ tmpIndex ]
}
}
seed := time . Now ( ) . UnixNano ( )
rng := rand . New ( rand . NewSource ( seed ) )
randIndex := rng . Intn ( len ( USER_TOKEN_LIST ) )
return randIndex , USER_TOKEN_LIST [ randIndex ]
}
2023-05-04 11:29:40 +08:00
func replaceResBody ( originalBody string , originalScheme string , originalHost string ) string {
modifiedBodyStr := originalBody
2023-05-17 09:29:29 +08:00
if originalScheme == "https" {
if strings . Contains ( modifiedBodyStr , BING_URL . Host ) {
modifiedBodyStr = strings . ReplaceAll ( modifiedBodyStr , BING_URL . Host , originalHost )
}
} else {
originalDomain := fmt . Sprintf ( "%s://%s" , originalScheme , originalHost )
if strings . Contains ( modifiedBodyStr , BING_URL . String ( ) ) {
modifiedBodyStr = strings . ReplaceAll ( modifiedBodyStr , BING_URL . String ( ) , originalDomain )
}
2023-05-04 11:29:40 +08:00
}
// 对话暂时支持国内网络,而且 Vercel 还不支持 Websocket ,先不用
// if strings.Contains(modifiedBodyStr, BING_CHAT_DOMAIN) {
// modifiedBodyStr = strings.ReplaceAll(modifiedBodyStr, BING_CHAT_DOMAIN, originalDomain)
// }
// if strings.Contains(modifiedBodyStr, "https://www.bingapis.com") {
// modifiedBodyStr = strings.ReplaceAll(modifiedBodyStr, "https://www.bingapis.com", "https://bing.vcanbb.top")
// }
return modifiedBodyStr
}
func modifyGzipBody ( res * http . Response , originalScheme string , originalHost string ) error {
gz , err := gzip . NewReader ( res . Body )
if err != nil {
return err
}
defer gz . Close ( )
bodyByte , err := io . ReadAll ( gz )
if err != nil {
return err
}
originalBody := string ( bodyByte )
modifiedBodyStr := replaceResBody ( originalBody , originalScheme , originalHost )
// 修改响应内容
modifiedBody := [ ] byte ( modifiedBodyStr )
// gzip 压缩
var buf bytes . Buffer
writer := gzip . NewWriter ( & buf )
defer writer . Close ( )
_ , err = writer . Write ( modifiedBody )
if err != nil {
return err
}
2023-05-05 20:12:23 +08:00
err = writer . Flush ( )
if err != nil {
return err
}
err = writer . Close ( )
if err != nil {
return err
}
2023-05-04 11:29:40 +08:00
// 修改 Content-Length 头
res . Header . Set ( "Content-Length" , strconv . Itoa ( buf . Len ( ) ) )
// 修改响应内容
2023-05-05 20:12:23 +08:00
res . Body = io . NopCloser ( & buf )
2023-05-04 11:29:40 +08:00
return nil
}
func modifyBrBody ( res * http . Response , originalScheme string , originalHost string ) error {
reader := brotli . NewReader ( res . Body )
var uncompressed bytes . Buffer
uncompressed . ReadFrom ( reader )
originalBody := uncompressed . String ( )
modifiedBodyStr := replaceResBody ( originalBody , originalScheme , originalHost )
// 修改响应内容
modifiedBody := [ ] byte ( modifiedBodyStr )
// br 压缩
var buf bytes . Buffer
writer := brotli . NewWriter ( & buf )
writer . Write ( modifiedBody )
writer . Close ( )
// 修改 Content-Length 头
// res.ContentLength = int64(buf.Len())
res . Header . Set ( "Content-Length" , strconv . Itoa ( buf . Len ( ) ) )
// 修改响应内容
res . Body = io . NopCloser ( bytes . NewReader ( buf . Bytes ( ) ) )
return nil
}
func modifyDefaultBody ( res * http . Response , originalScheme string , originalHost string ) error {
bodyByte , err := io . ReadAll ( res . Body )
if err != nil {
return err
}
originalBody := string ( bodyByte )
modifiedBodyStr := replaceResBody ( originalBody , originalScheme , originalHost )
// 修改响应内容
modifiedBody := [ ] byte ( modifiedBodyStr )
// 修改 Content-Length 头
// res.ContentLength = int64(buf.Len())
res . Header . Set ( "Content-Length" , strconv . Itoa ( len ( modifiedBody ) ) )
// 修改响应内容
res . Body = io . NopCloser ( bytes . NewReader ( modifiedBody ) )
return nil
}