package common import ( "bytes" "compress/gzip" "crypto/tls" "fmt" "io" "math/rand" "net/http" "net/http/httputil" "net/url" "strconv" "strings" "time" "github.com/andybalholm/brotli" utls "github.com/refraction-networking/utls" ) var ( 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") EDGE_SVC_URL, _ = url.Parse("https://edgeservices.bing.com") BING_SR_URT, _ = url.Parse("https://sr.bing.com") BING_SOURCE_URL, _ = url.Parse("https://th.bing.com") DISIGNER_URL, _ = url.Parse("https://designer.microsoft.com") DISIGNER_CDN_URL, _ = url.Parse("https://cdn.designerapp.osi.office.net") DISIGNER_APP_URL, _ = url.Parse("https://designerapp.officeapps.live.com") DISIGNER_APP_EDOG_URL, _ = url.Parse("https://designerapp.edog.officeapps.live.com") DISIGNER_DOCUMENT_URL, _ = url.Parse("https://document.designerapp.officeapps.live.com") DISIGNER_USERASSETS_URL, _ = url.Parse("https://userassets.designerapp.officeapps.live.com") DISIGNER_MEDIASUGGESTION_URL, _ = url.Parse("https://mediasuggestion.designerapp.officeapps.live.com") DISIGNER_RTC_URL, _ = url.Parse("https://rtc.designerapp.officeapps.live.com") KEEP_REQ_HEADER_MAP = map[string]bool{ "Accept": true, "Accept-Encoding": true, "Accept-Language": true, "Authorization": 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, "Sec-Ms-Gec": true, "Sec-Ms-Gec-Version": true, "X-Client-Data": true, "X-Ms-Client-Request-Id": true, "X-Ms-Useragent": true, } DEL_LOCATION_DOMAINS = []string{ "https://cn.bing.com", "https://www.bing.com", } USER_TOKEN_COOKIE_NAME = "_U" USER_KievRPSSecAuth_COOKIE_NAME = "KievRPSSecAuth" User_MUID_COOKIE_NAME = "MUID" USER_RwBf_COOKIE_NAME = "_RwBf" RAND_COOKIE_INDEX_NAME = "BingAI_Rand_CK" RAND_IP_COOKIE_NAME = "BingAI_Rand_IP" PASS_SERVER_COOKIE_NAME = "BingAI_Pass_Server" PROXY_WEB_PREFIX_PATH = "/web/" PROXY_WEB_PAGE_PATH = PROXY_WEB_PREFIX_PATH + "index.html" DEBUG_PROXY_WEB, _ = url.Parse("http://localhost:4000") ) func NewSingleHostReverseProxy(target *url.URL) *httputil.ReverseProxy { originalScheme := "http" httpsSchemeName := "https" var originalHost string var originalPath string // var originalDomain string var randIP string var resCKRandIndex string 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 // originalDomain = fmt.Sprintf("%s:%s", originalScheme, originalHost) req.URL.Scheme = target.Scheme req.URL.Host = target.Host req.Host = target.Host if strings.Contains(req.Referer(), "web/compose.html") { req.Header.Set("Referer", fmt.Sprintf("%s/edgesvc/compose", EDGE_SVC_URL.String())) req.Header.Set("Origin", EDGE_SVC_URL.String()) } else if strings.Contains(originalPath, "/sydney/") { req.Header.Set("Referer", fmt.Sprintf("%s/chat?q=Bing+AI", BING_URL.String())) req.Header.Set("Origin", BING_URL.String()) req.Header.Set("Host", BING_SYDNEY_URL.Host) } else { req.Header.Set("Referer", fmt.Sprintf("%s/chat?q=Bing+AI", BING_URL.String())) req.Header.Set("Origin", target.String()) } // 同一会话尽量保持相同的随机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) ckUserMUID, _ := req.Cookie(User_MUID_COOKIE_NAME) if (ckUserMUID == nil || ckUserMUID.Value == "") && USER_MUID != "" { // 添加 MUID Cookie req.AddCookie(&http.Cookie{ Name: User_MUID_COOKIE_NAME, Value: USER_MUID, }) } ckUserKievRPSSecAuth, _ := req.Cookie(USER_KievRPSSecAuth_COOKIE_NAME) if (ckUserKievRPSSecAuth == nil || ckUserKievRPSSecAuth.Value == "") && USER_KievRPSSecAuth != "" { // 添加 KievRPSSecAuth Cookie req.AddCookie(&http.Cookie{ Name: USER_KievRPSSecAuth_COOKIE_NAME, Value: USER_KievRPSSecAuth, }) } ckUserRwBf, _ := req.Cookie(USER_RwBf_COOKIE_NAME) if (ckUserRwBf == nil || ckUserRwBf.Value == "") && USER_RwBf != "" { // 添加 RwBf Cookie req.AddCookie(&http.Cookie{ Name: USER_RwBf_COOKIE_NAME, Value: USER_RwBf, }) } // 未登录用户 ckUserToken, _ := req.Cookie(USER_TOKEN_COOKIE_NAME) if ckUserToken == nil || ckUserToken.Value == "" { randCKIndex, randCkVal := getRandCookie(req) if randCkVal != "" { resCKRandIndex = strconv.Itoa(randCKIndex) req.AddCookie(&http.Cookie{ Name: USER_TOKEN_COOKIE_NAME, Value: randCkVal, }) } // ua := req.UserAgent() // if !strings.Contains(ua, "iPhone") || !strings.Contains(ua, "Mobile") { // req.Header.Set("User-Agent", "iPhone Mobile "+ua) // } } cookies := req.Cookies() for i, cookie := range cookies { // 删除 `BingAI_Rand_IP` Cookie, 以使用多语种问答 if cookie.Name == RAND_IP_COOKIE_NAME { // 删除切片中的元素 cookies = append(cookies[:i], cookies[i+1:]...) break } } // 重新设置 Cookie 头 req.Header.Del("Cookie") for _, cookie := range cookies { req.AddCookie(cookie) } ua := req.UserAgent() isMobile := strings.Contains(ua, "Mobile") || strings.Contains(ua, "Android") // m pc 画图大小不一样 if isMobile { req.Header.Set("User-Agent", User_Agent_Mobile) } else { req.Header.Set("User-Agent", User_Agent) } for hKey := range req.Header { if _, ok := KEEP_REQ_HEADER_MAP[hKey]; !ok { req.Header.Del(hKey) } } // reqHeaderByte, _ := json.Marshal(req.Header) // log.Println("剩余请求头 : ", string(reqHeaderByte)) } //改写返回信息 modifyFunc := func(res *http.Response) error { cookies := res.Cookies() res.Header.Set("Set-Cookie", "") for _, cookie := range cookies { if strings.Contains(cookie.String(), ";") { values := strings.Split(cookie.String(), ";") res.Header.Add("Set-Cookie", values[0]+"; "+values[1]) } else { res.Header.Add("Set-Cookie", cookie.String()) } } contentType := res.Header.Get("Content-Type") if strings.Contains(contentType, "text/javascript") || strings.Contains(contentType, "application/javascript") || strings.Contains(contentType, "text/html") { contentEncoding := res.Header.Get("Content-Encoding") switch contentEncoding { case "gzip": Logger.Debug("ContentEncoding : ", contentEncoding, " Path : ", originalPath) modifyGzipBody(res, originalScheme, originalHost) case "br": Logger.Debug("ContentEncoding : ", contentEncoding, " Path : ", originalPath) modifyBrBody(res, originalScheme, originalHost) default: Logger.Debug("ContentEncoding default : ", contentEncoding, " Path : ", originalPath) modifyDefaultBody(res, originalScheme, originalHost) } } // 修改响应 cookie 域 resCookies := res.Header.Values("Set-Cookie") if len(resCookies) > 0 { res.Header.Del("Set-Cookie") for _, v := range resCookies { if v != "" { res.Header.Add("Set-Cookie", strings.Split(v, "; ")[0]+"; path=/") } } } // 设置服务器 cookie 对应索引 if resCKRandIndex != "" { ckRandIndex := &http.Cookie{ Name: RAND_COOKIE_INDEX_NAME, Value: resCKRandIndex, Path: "/", } res.Header.Add("Set-Cookie", ckRandIndex.String()) } // 删除 CSP res.Header.Del("Content-Security-Policy-Report-Only") res.Header.Del("Report-To") // 删除重定向前缀域名 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):]) Logger.Debug("Del Location Domain :", location) Logger.Debug("RandIP : ", randIP) // 换新ip randIP = GetRandomIP() } } } // 设置随机ip cookie ckRandIP := &http.Cookie{ Name: RAND_IP_COOKIE_NAME, Value: randIP, Path: "/", } res.Header.Add("Set-Cookie", ckRandIP.String()) // 跨域 // if IS_DEBUG_MODE { // res.Header.Set("Access-Control-Allow-Origin", "*") // res.Header.Set("Access-Control-Allow-Methods", "*") // res.Header.Set("Access-Control-Allow-Headers", "*") // } return nil } errorHandler := func(res http.ResponseWriter, req *http.Request, err error) { Logger.Error("代理异常 :", err) res.Write([]byte(err.Error())) } // tr := &http.Transport{ // TLSClientConfig: &tls.Config{ // // 如果只设置 InsecureSkipVerify: true对于这个问题不会有任何改变 // InsecureSkipVerify: true, // ClientAuth: tls.NoClientCert, // }, // } // 为 http.DefaultTransport 添加 JA3 浏览器指纹 transport := http.DefaultTransport.(*http.Transport).Clone() transport.DisableKeepAlives = false c, _ := utls.UTLSIdToSpec(utls.HelloRandomized) transport.TLSClientConfig = &tls.Config{ InsecureSkipVerify: true, MinVersion: c.TLSVersMin, MaxVersion: c.TLSVersMax, CipherSuites: c.CipherSuites, ClientSessionCache: tls.NewLRUClientSessionCache(32), } // 代理请求 请求回来的内容 报错自动调用 reverseProxy := &httputil.ReverseProxy{ Director: director, ModifyResponse: modifyFunc, ErrorHandler: errorHandler, Transport: transport, } return reverseProxy } // 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 { Logger.Error("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] } func replaceResBody(originalBody string, originalScheme string, originalHost string) string { modifiedBodyStr := originalBody if originalScheme == "https" { modifiedBodyStr = strings.ReplaceAll(modifiedBodyStr, BING_URL.Host, originalHost) modifiedBodyStr = strings.ReplaceAll(modifiedBodyStr, EDGE_SVC_URL.Host, originalHost) modifiedBodyStr = strings.ReplaceAll(modifiedBodyStr, BING_SR_URT.Host, originalHost) modifiedBodyStr = strings.ReplaceAll(modifiedBodyStr, BING_SOURCE_URL.Host, originalHost+"/th") modifiedBodyStr = strings.ReplaceAll(modifiedBodyStr, DISIGNER_CDN_URL.Host, originalHost+"/designer/cdn") modifiedBodyStr = strings.ReplaceAll(modifiedBodyStr, DISIGNER_APP_EDOG_URL.Host, originalHost+"/designer/app-edog") modifiedBodyStr = strings.ReplaceAll(modifiedBodyStr, DISIGNER_DOCUMENT_URL.Host, originalHost+"/designer/document") modifiedBodyStr = strings.ReplaceAll(modifiedBodyStr, DISIGNER_USERASSETS_URL.Host, originalHost+"/designer/userassets") modifiedBodyStr = strings.ReplaceAll(modifiedBodyStr, DISIGNER_MEDIASUGGESTION_URL.Host, originalHost+"/designer/mediasuggestion") modifiedBodyStr = strings.ReplaceAll(modifiedBodyStr, DISIGNER_RTC_URL.Host, originalHost+"/designer/rtc") modifiedBodyStr = strings.ReplaceAll(modifiedBodyStr, DISIGNER_APP_URL.Host, originalHost+"/designer/app") modifiedBodyStr = strings.ReplaceAll(modifiedBodyStr, DISIGNER_URL.Host, originalHost+"/designer") } else { originalDomain := fmt.Sprintf("%s://%s", originalScheme, originalHost) modifiedBodyStr = strings.ReplaceAll(modifiedBodyStr, BING_URL.String(), originalDomain) modifiedBodyStr = strings.ReplaceAll(modifiedBodyStr, EDGE_SVC_URL.Host, originalDomain) modifiedBodyStr = strings.ReplaceAll(modifiedBodyStr, BING_SR_URT.String(), originalDomain) modifiedBodyStr = strings.ReplaceAll(modifiedBodyStr, BING_SOURCE_URL.String(), originalDomain+"/th") modifiedBodyStr = strings.ReplaceAll(modifiedBodyStr, DISIGNER_CDN_URL.String(), originalDomain+"/designer/cdn") modifiedBodyStr = strings.ReplaceAll(modifiedBodyStr, DISIGNER_APP_EDOG_URL.String(), originalDomain+"/designer/app-edog") modifiedBodyStr = strings.ReplaceAll(modifiedBodyStr, DISIGNER_DOCUMENT_URL.String(), originalDomain+"/designer/document") modifiedBodyStr = strings.ReplaceAll(modifiedBodyStr, DISIGNER_USERASSETS_URL.String(), originalDomain+"/designer/userassets") modifiedBodyStr = strings.ReplaceAll(modifiedBodyStr, DISIGNER_MEDIASUGGESTION_URL.String(), originalDomain+"/designer/mediasuggestion") modifiedBodyStr = strings.ReplaceAll(modifiedBodyStr, DISIGNER_RTC_URL.String(), originalDomain+"/designer/rtc") modifiedBodyStr = strings.ReplaceAll(modifiedBodyStr, DISIGNER_APP_URL.String(), originalDomain+"/designer/app") modifiedBodyStr = strings.ReplaceAll(modifiedBodyStr, DISIGNER_URL.String(), originalDomain+"/designer") } // 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 } err = writer.Flush() if err != nil { return err } err = writer.Close() if err != nil { return err } // 修改 Content-Length 头 res.Header.Set("Content-Length", strconv.Itoa(buf.Len())) // 修改响应内容 res.Body = io.NopCloser(&buf) 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 }