wuyuncheng-26's picture
upload files from Harry-zklcdc/go-proxy-bingai
d669ddb verified
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
}