├── frontend ├── .env ├── src │ ├── assets │ │ ├── css │ │ │ ├── base.css │ │ │ ├── main.css │ │ │ └── conversation.css │ │ └── img │ │ │ └── setting.svg │ ├── api │ │ ├── model │ │ │ ├── sysconf │ │ │ │ └── SysConfig.ts │ │ │ └── ApiResult.ts │ │ └── sysconf.ts │ ├── main.ts │ ├── stores │ │ ├── index.ts │ │ └── modules │ │ │ ├── chat │ │ │ └── index.ts │ │ │ ├── prompt │ │ │ └── index.ts │ │ │ └── user │ │ │ └── index.ts │ ├── views │ │ └── chat │ │ │ ├── index.vue │ │ │ └── components │ │ │ └── Chat │ │ │ ├── ChatPromptItem.vue │ │ │ └── Chat.vue │ ├── components │ │ ├── ChatNav │ │ │ ├── ChatNavItem.vue │ │ │ └── ChatNav.vue │ │ ├── ReloadPWA │ │ │ └── ReloadPWA.vue │ │ ├── ChatPromptStore │ │ │ ├── ChatPromptItem.vue │ │ │ └── ChatPromptStore.vue │ │ ├── LoadingSpinner │ │ │ └── LoadingSpinner.vue │ │ ├── CreateImage │ │ │ └── CreateImage.vue │ │ └── ChatServiceSelect │ │ │ └── ChatServiceSelect.vue │ ├── router │ │ └── index.ts │ ├── utils │ │ ├── cookies.ts │ │ └── utils.ts │ ├── App.vue │ └── sw.ts ├── public │ ├── favicon.ico │ ├── img │ │ ├── pwa │ │ │ ├── logo-192.png │ │ │ └── logo-512.png │ │ ├── compose.svg │ │ └── logo.svg │ └── js │ │ └── bing │ │ └── chat │ │ ├── core.js │ │ ├── global.js │ │ ├── lib.js │ │ └── amd.js ├── postcss.config.js ├── tsconfig.json ├── types │ ├── env.d.ts │ ├── global.d.ts │ ├── vue3-virtual-scroll-list.d.ts │ └── bing │ │ └── index.d.ts ├── .vscode │ └── extensions.json ├── .prettierrc.config.js ├── tailwind.config.js ├── .eslintrc.cjs ├── tsconfig.node.json ├── .gitignore ├── tsconfig.app.json ├── package.json ├── README.md ├── vite.config.ts └── index.html ├── .vscode ├── extensions.json └── launch.json ├── web ├── favicon.ico ├── img │ ├── pwa │ │ ├── logo-192.png │ │ └── logo-512.png │ ├── compose.svg │ └── logo.svg ├── registerSW.js ├── manifest.webmanifest ├── assets │ ├── index-1dc749ba.css │ ├── setting-c6ca7b14.svg │ └── index-29dab197.css ├── js │ └── bing │ │ └── chat │ │ ├── core.js │ │ ├── global.js │ │ ├── lib.js │ │ └── amd.js ├── web.go └── index.html ├── docs └── img │ ├── bing-clear.png │ ├── bing-draw.png │ ├── railway-1.png │ ├── railway-2.png │ ├── railway-3.png │ ├── render-1.png │ ├── render-2.png │ ├── vercel-1.png │ ├── vercel-2.png │ ├── bing-cookie.png │ ├── bing-login-1.png │ ├── bing-nologin.png │ ├── sidebar-add.png │ ├── sidebar-chat.png │ ├── bing-m-nologin.png │ ├── bing-prompt-1.png │ ├── bing-prompt-2.png │ ├── sidebar-compose.png │ └── bing-sydney-service-1.png ├── go.mod ├── .gitignore ├── render.yaml ├── docker ├── Dockerfile └── docker-compose.yml ├── go.sum ├── api ├── sydney.go ├── sys-config.go ├── index.go ├── web.go └── helper │ └── helper.go ├── vercel.json ├── main.go ├── common ├── env.go ├── ip.go └── proxy.go ├── LICENSE ├── cloudflare ├── index.html └── worker.js ├── .github └── workflows │ └── build.yml ├── README.md └── Taskfile.yaml /frontend/.env: -------------------------------------------------------------------------------- 1 | VITE_BASE_API_URL=https://devbing.vcanbb.top -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "golang.go" 4 | ] 5 | } -------------------------------------------------------------------------------- /web/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timathon/go-proxy-bingai/master/web/favicon.ico -------------------------------------------------------------------------------- /frontend/src/assets/css/base.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; -------------------------------------------------------------------------------- /docs/img/bing-clear.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timathon/go-proxy-bingai/master/docs/img/bing-clear.png -------------------------------------------------------------------------------- /docs/img/bing-draw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timathon/go-proxy-bingai/master/docs/img/bing-draw.png -------------------------------------------------------------------------------- /docs/img/railway-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timathon/go-proxy-bingai/master/docs/img/railway-1.png -------------------------------------------------------------------------------- /docs/img/railway-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timathon/go-proxy-bingai/master/docs/img/railway-2.png -------------------------------------------------------------------------------- /docs/img/railway-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timathon/go-proxy-bingai/master/docs/img/railway-3.png -------------------------------------------------------------------------------- /docs/img/render-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timathon/go-proxy-bingai/master/docs/img/render-1.png -------------------------------------------------------------------------------- /docs/img/render-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timathon/go-proxy-bingai/master/docs/img/render-2.png -------------------------------------------------------------------------------- /docs/img/vercel-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timathon/go-proxy-bingai/master/docs/img/vercel-1.png -------------------------------------------------------------------------------- /docs/img/vercel-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timathon/go-proxy-bingai/master/docs/img/vercel-2.png -------------------------------------------------------------------------------- /docs/img/bing-cookie.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timathon/go-proxy-bingai/master/docs/img/bing-cookie.png -------------------------------------------------------------------------------- /docs/img/bing-login-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timathon/go-proxy-bingai/master/docs/img/bing-login-1.png -------------------------------------------------------------------------------- /docs/img/bing-nologin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timathon/go-proxy-bingai/master/docs/img/bing-nologin.png -------------------------------------------------------------------------------- /docs/img/sidebar-add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timathon/go-proxy-bingai/master/docs/img/sidebar-add.png -------------------------------------------------------------------------------- /docs/img/sidebar-chat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timathon/go-proxy-bingai/master/docs/img/sidebar-chat.png -------------------------------------------------------------------------------- /frontend/src/assets/css/main.css: -------------------------------------------------------------------------------- 1 | @import './base.css'; 2 | 3 | .cib-serp-main { 4 | overflow: hidden; 5 | } -------------------------------------------------------------------------------- /web/img/pwa/logo-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timathon/go-proxy-bingai/master/web/img/pwa/logo-192.png -------------------------------------------------------------------------------- /web/img/pwa/logo-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timathon/go-proxy-bingai/master/web/img/pwa/logo-512.png -------------------------------------------------------------------------------- /docs/img/bing-m-nologin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timathon/go-proxy-bingai/master/docs/img/bing-m-nologin.png -------------------------------------------------------------------------------- /docs/img/bing-prompt-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timathon/go-proxy-bingai/master/docs/img/bing-prompt-1.png -------------------------------------------------------------------------------- /docs/img/bing-prompt-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timathon/go-proxy-bingai/master/docs/img/bing-prompt-2.png -------------------------------------------------------------------------------- /docs/img/sidebar-compose.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timathon/go-proxy-bingai/master/docs/img/sidebar-compose.png -------------------------------------------------------------------------------- /frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timathon/go-proxy-bingai/master/frontend/public/favicon.ico -------------------------------------------------------------------------------- /docs/img/bing-sydney-service-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timathon/go-proxy-bingai/master/docs/img/bing-sydney-service-1.png -------------------------------------------------------------------------------- /frontend/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /frontend/src/api/model/sysconf/SysConfig.ts: -------------------------------------------------------------------------------- 1 | export interface SysConfig { 2 | isSysCK: boolean; 3 | isAuth: boolean; 4 | } 5 | -------------------------------------------------------------------------------- /frontend/public/img/pwa/logo-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timathon/go-proxy-bingai/master/frontend/public/img/pwa/logo-192.png -------------------------------------------------------------------------------- /frontend/public/img/pwa/logo-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timathon/go-proxy-bingai/master/frontend/public/img/pwa/logo-512.png -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module adams549659584/go-proxy-bingai 2 | 3 | go 1.20 4 | 5 | require ( 6 | github.com/andybalholm/brotli v1.0.5 7 | golang.org/x/net v0.10.0 8 | ) 9 | -------------------------------------------------------------------------------- /web/registerSW.js: -------------------------------------------------------------------------------- 1 | if('serviceWorker' in navigator) {window.addEventListener('load', () => {navigator.serviceWorker.register('/web/sw.js', { scope: '/web/' })})} -------------------------------------------------------------------------------- /frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [], 3 | "references": [ 4 | { 5 | "path": "./tsconfig.node.json" 6 | }, 7 | { 8 | "path": "./tsconfig.app.json" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Editor directories and files 2 | .vscode/* 3 | !.vscode/extensions.json 4 | !.vscode/launch.json 5 | .idea 6 | *.suo 7 | *.ntvs* 8 | *.njsproj 9 | *.sln 10 | *.sw? 11 | 12 | .DS_Store 13 | 14 | release 15 | -------------------------------------------------------------------------------- /frontend/types/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | declare module '*.vue' { 4 | import type { DefineComponent } from 'vue'; 5 | const component: DefineComponent<{}, {}, any>; 6 | export default component; 7 | } 8 | -------------------------------------------------------------------------------- /frontend/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "vue.volar", 4 | "vue.vscode-typescript-vue-plugin", 5 | "bradlc.vscode-tailwindcss", 6 | "esbenp.prettier-vscode", 7 | "dbaeumer.vscode-eslint" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /render.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | - type: web 3 | name: go-proxy-bingai 4 | # oregon: frankfurt 5 | plan: free 6 | env: go 7 | buildCommand: go build -ldflags="-s -w" -tags netgo -trimpath -o go-proxy-bingai main.go 8 | startCommand: ./go-proxy-bingai -------------------------------------------------------------------------------- /frontend/.prettierrc.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [require('prettier-plugin-tailwindcss')], 3 | $schema: 'https://json.schemastore.org/prettierrc', 4 | semi: true, 5 | tabWidth: 2, 6 | singleQuote: true, 7 | printWidth: 100, 8 | trailingComma: 'none', 9 | }; 10 | -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:alpine AS builder 2 | WORKDIR /app 3 | COPY . . 4 | RUN go build -ldflags="-s -w" -tags netgo -trimpath -o go-proxy-bingai main.go 5 | 6 | FROM alpine 7 | WORKDIR /app 8 | COPY --from=builder /app/go-proxy-bingai . 9 | 10 | EXPOSE 8080 11 | CMD ["/app/go-proxy-bingai"] -------------------------------------------------------------------------------- /frontend/types/global.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 当前web版本信息 3 | */ 4 | declare const __APP_INFO__: { 5 | buildTimestamp: number; 6 | name: string; 7 | version: string; 8 | dependencies: { 9 | [key in string]: string; 10 | }; 11 | devDependencies: { 12 | [key in string]: string; 13 | }; 14 | }; -------------------------------------------------------------------------------- /frontend/src/api/model/ApiResult.ts: -------------------------------------------------------------------------------- 1 | export interface ApiResult { 2 | code: ApiResultCode; 3 | message: string; 4 | data: T; 5 | } 6 | 7 | export enum ApiResultCode { 8 | /** 9 | * 成功 10 | */ 11 | OK = 200, 12 | 13 | /** 14 | * 未授权 15 | */ 16 | Unauthorized = 401, 17 | } 18 | -------------------------------------------------------------------------------- /frontend/src/main.ts: -------------------------------------------------------------------------------- 1 | import './assets/css/main.css'; 2 | 3 | import { createApp } from 'vue'; 4 | import { setupStore } from './stores'; 5 | 6 | import App from './App.vue'; 7 | import router from './router'; 8 | 9 | const app = createApp(App); 10 | 11 | setupStore(app); 12 | app.use(router); 13 | 14 | app.mount('#app'); 15 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs= 2 | github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= 3 | golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= 4 | golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= 5 | -------------------------------------------------------------------------------- /frontend/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | // 禁用预加载,修复tailwind样式 与 naive-ui button等样式等冲突问题 4 | corePlugins: { 5 | preflight: false, 6 | }, 7 | content: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'], 8 | theme: { 9 | extend: {}, 10 | }, 11 | plugins: [], 12 | }; 13 | -------------------------------------------------------------------------------- /frontend/src/stores/index.ts: -------------------------------------------------------------------------------- 1 | import type { App } from 'vue'; 2 | import { createPinia } from 'pinia'; 3 | import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'; 4 | 5 | export const store = createPinia(); 6 | store.use(piniaPluginPersistedstate); 7 | 8 | export function setupStore(app: App) { 9 | app.use(store); 10 | } 11 | -------------------------------------------------------------------------------- /api/sydney.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "adams549659584/go-proxy-bingai/api/helper" 5 | "adams549659584/go-proxy-bingai/common" 6 | "net/http" 7 | ) 8 | 9 | func Sydney(w http.ResponseWriter, r *http.Request) { 10 | if !helper.CheckAuth(r) { 11 | helper.UnauthorizedResult(w) 12 | return 13 | } 14 | common.NewSingleHostReverseProxy(common.BING_SYDNEY_URL).ServeHTTP(w, r) 15 | } 16 | -------------------------------------------------------------------------------- /frontend/src/views/chat/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 14 | -------------------------------------------------------------------------------- /frontend/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | require('@rushstack/eslint-patch/modern-module-resolution') 3 | 4 | module.exports = { 5 | root: true, 6 | 'extends': [ 7 | 'plugin:vue/vue3-essential', 8 | 'eslint:recommended', 9 | '@vue/eslint-config-typescript', 10 | '@vue/eslint-config-prettier/skip-formatting' 11 | ], 12 | parserOptions: { 13 | ecmaVersion: 'latest' 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /frontend/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/node18/tsconfig.json", 3 | "include": [ 4 | "vite.config.*", 5 | "vitest.config.*", 6 | "cypress.config.*", 7 | "playwright.config.*", 8 | "package.json" 9 | ], 10 | "compilerOptions": { 11 | "composite": true, 12 | "resolveJsonModule": true, 13 | "module": "ESNext", 14 | "types": [ 15 | "node" 16 | ] 17 | }, 18 | } -------------------------------------------------------------------------------- /web/manifest.webmanifest: -------------------------------------------------------------------------------- 1 | {"name":"BingAI","short_name":"BingAI","start_url":"/web/","display":"standalone","background_color":"#ffffff","lang":"en","scope":"/web/","theme_color":"#ffffff","icons":[{"src":"./img/pwa/logo-192.png","sizes":"192x192","type":"image/png"},{"src":"./img/pwa/logo-512.png","sizes":"512x512","type":"image/png"},{"src":"./img/pwa/logo-512.png","sizes":"512x512","type":"image/png","purpose":"any maskable"}]} 2 | -------------------------------------------------------------------------------- /frontend/src/components/ChatNav/ChatNavItem.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 16 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | .DS_Store 12 | dist 13 | dist-ssr 14 | coverage 15 | *.local 16 | 17 | /cypress/videos/ 18 | /cypress/screenshots/ 19 | 20 | # Editor directories and files 21 | .vscode/* 22 | !.vscode/extensions.json 23 | .idea 24 | *.suo 25 | *.ntvs* 26 | *.njsproj 27 | *.sln 28 | *.sw? 29 | -------------------------------------------------------------------------------- /frontend/src/router/index.ts: -------------------------------------------------------------------------------- 1 | import { createRouter, createWebHashHistory } from 'vue-router'; 2 | 3 | const router = createRouter({ 4 | // history: createWebHistory(import.meta.env.BASE_URL), 5 | history: createWebHashHistory(import.meta.env.BASE_URL), 6 | routes: [ 7 | { 8 | path: '/', 9 | name: 'chat', 10 | component: () => import('@/views/chat/index.vue'), 11 | }, 12 | ], 13 | }); 14 | 15 | export default router; 16 | -------------------------------------------------------------------------------- /frontend/src/api/sysconf.ts: -------------------------------------------------------------------------------- 1 | import type { ApiResult } from './model/ApiResult'; 2 | import type { SysConfig } from './model/sysconf/SysConfig'; 3 | 4 | export async function getSysConfig() { 5 | const url = '/sysconf'; 6 | return fetch(url, { 7 | credentials: 'include', 8 | }).then((res) => res.json() as unknown as ApiResult); 9 | } 10 | 11 | const sysConfApi = { 12 | getSysConfig, 13 | }; 14 | 15 | export default sysConfApi; 16 | -------------------------------------------------------------------------------- /api/sys-config.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "adams549659584/go-proxy-bingai/api/helper" 5 | "adams549659584/go-proxy-bingai/common" 6 | "net/http" 7 | ) 8 | 9 | type SysConfig struct { 10 | // 是否系统配置 cookie 11 | IsSysCK bool `json:"isSysCK"` 12 | // 是否已授权 13 | IsAuth bool `json:"isAuth"` 14 | } 15 | 16 | func SysConf(w http.ResponseWriter, r *http.Request) { 17 | isAuth := helper.CheckAuth(r) 18 | conf := SysConfig{ 19 | IsSysCK: len(common.USER_TOKEN_LIST) > 0, 20 | IsAuth: isAuth, 21 | } 22 | helper.SuccessResult(w, conf) 23 | } 24 | -------------------------------------------------------------------------------- /frontend/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@vue/tsconfig/tsconfig.dom.json", 3 | "include": [ 4 | "types/**/*.d.ts", 5 | "src/**/*", 6 | "src/**/*.vue", 7 | ], 8 | "exclude": [ 9 | "src/**/__tests__/*" 10 | ], 11 | "compilerOptions": { 12 | "composite": true, 13 | "baseUrl": ".", 14 | "paths": { 15 | "@/*": [ 16 | "./src/*" 17 | ] 18 | }, 19 | "types": [ 20 | "vite-plugin-pwa/client" 21 | ], 22 | "lib": [ 23 | "ESNext", 24 | "DOM", 25 | "WebWorker" 26 | ] 27 | } 28 | } -------------------------------------------------------------------------------- /vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "go-proxy-bingai", 3 | "version": 2, 4 | "builds": [ 5 | { 6 | "src": "/api/{index,web,sydney,sys-config}.go", 7 | "use": "@vercel/go" 8 | } 9 | ], 10 | "routes": [ 11 | { 12 | "src": "/sysconf", 13 | "dest": "/api/sys-config.go" 14 | }, 15 | { 16 | "src": "/sydney/.*", 17 | "dest": "/api/sydney.go" 18 | }, 19 | { 20 | "src": "/web/.*", 21 | "dest": "/api/web.go" 22 | }, 23 | { 24 | "src": "/.*", 25 | "dest": "/api/index.go" 26 | } 27 | ] 28 | } -------------------------------------------------------------------------------- /api/index.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "adams549659584/go-proxy-bingai/api/helper" 5 | "adams549659584/go-proxy-bingai/common" 6 | "net/http" 7 | "strings" 8 | ) 9 | 10 | func Index(w http.ResponseWriter, r *http.Request) { 11 | if r.URL.Path == "/" { 12 | http.Redirect(w, r, common.PROXY_WEB_PAGE_PATH, http.StatusFound) 13 | return 14 | } 15 | if strings.HasPrefix(r.URL.Path, "/turing") { 16 | if !helper.CheckAuth(r) { 17 | helper.UnauthorizedResult(w) 18 | return 19 | } 20 | } 21 | common.NewSingleHostReverseProxy(common.BING_URL).ServeHTTP(w, r) 22 | 23 | } 24 | -------------------------------------------------------------------------------- /frontend/src/assets/css/conversation.css: -------------------------------------------------------------------------------- 1 | /* 移除顶部背景遮挡 */ 2 | .scroller>.top { 3 | display: none !important; 4 | } 5 | 6 | /* 移除顶部边距 */ 7 | .scroller>.scroller-positioner>.content { 8 | padding-top: 0 !important; 9 | } 10 | 11 | /* 聊天记录 */ 12 | .scroller .side-panel { 13 | position: fixed; 14 | right: 10px; 15 | } 16 | 17 | @media screen and (max-width: 1536px) { 18 | :host([side-panel]) { 19 | --side-panel-width: 280px; 20 | } 21 | } 22 | 23 | @media screen and (max-width: 767px) { 24 | :host([side-panel]) { 25 | --side-panel-width: 0; 26 | } 27 | } -------------------------------------------------------------------------------- /api/web.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "adams549659584/go-proxy-bingai/api/helper" 5 | "adams549659584/go-proxy-bingai/common" 6 | "adams549659584/go-proxy-bingai/web" 7 | "net/http" 8 | ) 9 | 10 | func WebStatic(w http.ResponseWriter, r *http.Request) { 11 | if _, ok := web.WEB_PATH_MAP[r.URL.Path]; ok || r.URL.Path == common.PROXY_WEB_PREFIX_PATH { 12 | http.StripPrefix(common.PROXY_WEB_PREFIX_PATH, http.FileServer(web.GetWebFS())).ServeHTTP(w, r) 13 | } else { 14 | if !helper.CheckAuth(r) { 15 | helper.UnauthorizedResult(w) 16 | return 17 | } 18 | common.NewSingleHostReverseProxy(common.BING_URL).ServeHTTP(w, r) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /frontend/src/utils/cookies.ts: -------------------------------------------------------------------------------- 1 | export function get(name: string) { 2 | const v = document.cookie.match('(^|;) ?' + name + '=([^;]*)(;|$)'); 3 | return v ? v[2] : null; 4 | } 5 | 6 | export function set(name: string, value: string, minutes = 0, path = '/', domain = '') { 7 | let cookie = name + '=' + value + ';path=' + path; 8 | if (domain) { 9 | cookie += ';domain=' + domain; 10 | } 11 | if (minutes > 0) { 12 | const d = new Date(); 13 | d.setTime(d.getTime() + minutes * 60 * 1000); 14 | cookie += ';expires=' + d.toUTCString(); 15 | } 16 | document.cookie = cookie; 17 | } 18 | 19 | const cookies = { 20 | get, 21 | set, 22 | }; 23 | export default cookies; 24 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "adams549659584/go-proxy-bingai/api" 5 | "log" 6 | "net/http" 7 | "os" 8 | "time" 9 | ) 10 | 11 | func main() { 12 | http.HandleFunc("/sysconf", api.SysConf) 13 | 14 | http.HandleFunc("/sydney/", api.Sydney) 15 | 16 | http.HandleFunc("/web/", api.WebStatic) 17 | 18 | http.HandleFunc("/", api.Index) 19 | 20 | port := os.Getenv("PORT") 21 | if port == "" { 22 | port = "8080" 23 | } 24 | addr := ":" + port 25 | 26 | log.Println("Starting BingAI Proxy At " + addr) 27 | 28 | srv := &http.Server{ 29 | Addr: addr, 30 | WriteTimeout: 15 * time.Second, 31 | ReadTimeout: 15 * time.Second, 32 | } 33 | log.Fatal(srv.ListenAndServe()) 34 | } 35 | -------------------------------------------------------------------------------- /frontend/src/App.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 24 | -------------------------------------------------------------------------------- /web/assets/index-1dc749ba.css: -------------------------------------------------------------------------------- 1 | .fade-enter-active[data-v-4813a901],.fade-leave-active[data-v-4813a901]{transition:opacity 2.5s ease}.fade-enter-from[data-v-4813a901],.fade-leave-to[data-v-4813a901]{opacity:0}.loading-spinner[data-v-4813a901]{display:flex;justify-content:center;align-items:center;height:100vh}.loading-spinner>div[data-v-4813a901]{width:30px;height:30px;background:linear-gradient(90deg,#2870ea 10.79%,#1b4aef 87.08%);border-radius:100%;display:inline-block;animation:sk-bouncedelay-4813a901 1.4s infinite ease-in-out both}.loading-spinner .bounce1[data-v-4813a901]{animation-delay:-.32s}.loading-spinner .bounce2[data-v-4813a901]{animation-delay:-.16s}@keyframes sk-bouncedelay-4813a901{0%,80%,to{transform:scale(0)}40%{transform:scale(1)}} 2 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // 使用 IntelliSense 了解相关属性。 3 | // 悬停以查看现有属性的描述。 4 | // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Launch Package", 9 | "type": "go", 10 | "request": "launch", 11 | "mode": "auto", 12 | "program": "${workspaceFolder}", 13 | "env": { 14 | "PORT": "8899", 15 | "Go_Proxy_BingAI_Debug": "true", 16 | "Go_Proxy_BingAI_SOCKS_URL": "192.168.0.88:1070", 17 | // "Go_Proxy_BingAI_SOCKS_USER": "xxx", 18 | // "Go_Proxy_BingAI_SOCKS_PWD": "xxx", 19 | // "Go_Proxy_BingAI_USER_TOKEN_1": "xxx", 20 | // "Go_Proxy_BingAI_USER_TOKEN_2": "xxx" 21 | // "Go_Proxy_BingAI_AUTH_KEY": "xxx", 22 | } 23 | } 24 | ] 25 | } -------------------------------------------------------------------------------- /web/img/compose.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/public/img/compose.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /web/js/bing/chat/core.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | (function (n, t) { 3 | onload = function () { 4 | _G.BPT = new Date(); 5 | // n && n(); 6 | !_w.sb_ppCPL && 7 | t && 8 | sb_st(function () { 9 | t(new Date()); 10 | }, 0); 11 | }; 12 | })(_w.onload, _w.si_PP); 13 | _w.rms.js( 14 | { 'A:rms:answers:Shared:BingCore.Bundle': '/rp/oJ7sDoXkkNOICsnFb57ZJHBrHcw.br.js' }, 15 | { 'A:rms:answers:Web:SydneyFSCHelper': '/rp/XBHyxbMN-5ifYmS8GGYyywmwILI.br.js' }, 16 | { 'A:rms:answers:VisualSystem:ConversationScope': '/rp/YFRe970EMtFzujI9pBYZBGpdHEo.br.js' }, 17 | { 'A:rms:answers:CodexBundle:cib-bundle': '/rp/-2UI-r71AEUWE8zNKc6Vdf8wVfc.br.js' }, 18 | { 'A:rms:answers:SharedStaticAssets:speech-sdk': '/rp/6slp3E-BqFf904Cz6cCWPY1bh9E.br.js' }, 19 | { 'A:rms:answers:Web:SydneyFullScreenConv': '/rp/R-NU1gYWw5NsYTEXrFn1hwhdP5g.br.js' }, 20 | ); 21 | -------------------------------------------------------------------------------- /frontend/public/js/bing/chat/core.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | (function (n, t) { 3 | onload = function () { 4 | _G.BPT = new Date(); 5 | // n && n(); 6 | !_w.sb_ppCPL && 7 | t && 8 | sb_st(function () { 9 | t(new Date()); 10 | }, 0); 11 | }; 12 | })(_w.onload, _w.si_PP); 13 | _w.rms.js( 14 | { 'A:rms:answers:Shared:BingCore.Bundle': '/rp/oJ7sDoXkkNOICsnFb57ZJHBrHcw.br.js' }, 15 | { 'A:rms:answers:Web:SydneyFSCHelper': '/rp/XBHyxbMN-5ifYmS8GGYyywmwILI.br.js' }, 16 | { 'A:rms:answers:VisualSystem:ConversationScope': '/rp/YFRe970EMtFzujI9pBYZBGpdHEo.br.js' }, 17 | { 'A:rms:answers:CodexBundle:cib-bundle': '/rp/-2UI-r71AEUWE8zNKc6Vdf8wVfc.br.js' }, 18 | { 'A:rms:answers:SharedStaticAssets:speech-sdk': '/rp/6slp3E-BqFf904Cz6cCWPY1bh9E.br.js' }, 19 | { 'A:rms:answers:Web:SydneyFullScreenConv': '/rp/R-NU1gYWw5NsYTEXrFn1hwhdP5g.br.js' }, 20 | ); 21 | -------------------------------------------------------------------------------- /common/env.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "os" 5 | "strings" 6 | ) 7 | 8 | var ( 9 | // is debug 10 | IS_DEBUG_MODE bool 11 | // socks 12 | SOCKS_URL string 13 | SOCKS_USER string 14 | SOCKS_PWD string 15 | // user token 16 | USER_TOKEN_ENV_NAME_PREFIX = "Go_Proxy_BingAI_USER_TOKEN" 17 | USER_TOKEN_LIST []string 18 | // 访问权限密钥,可选 19 | AUTH_KEY string 20 | AUTH_KEY_COOKIE_NAME = "BingAI_Auth_Key" 21 | ) 22 | 23 | func init() { 24 | initEnv() 25 | initUserToken() 26 | } 27 | 28 | func initEnv() { 29 | // is debug 30 | IS_DEBUG_MODE = os.Getenv("Go_Proxy_BingAI_Debug") != "" 31 | // socks 32 | SOCKS_URL = os.Getenv("Go_Proxy_BingAI_SOCKS_URL") 33 | SOCKS_USER = os.Getenv("Go_Proxy_BingAI_SOCKS_USER") 34 | SOCKS_PWD = os.Getenv("Go_Proxy_BingAI_SOCKS_PWD") 35 | // auth 36 | AUTH_KEY = os.Getenv("Go_Proxy_BingAI_AUTH_KEY") 37 | } 38 | 39 | func initUserToken() { 40 | for _, env := range os.Environ() { 41 | if strings.HasPrefix(env, USER_TOKEN_ENV_NAME_PREFIX) { 42 | parts := strings.SplitN(env, "=", 2) 43 | USER_TOKEN_LIST = append(USER_TOKEN_LIST, parts[1]) 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License Copyright (c) 2023 adams549659584 2 | 3 | Permission is hereby granted, free 4 | of charge, to any person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, copy, modify, merge, 7 | publish, distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to the 9 | following conditions: 10 | 11 | The above copyright notice and this permission notice 12 | (including the next paragraph) shall be included in all copies or substantial 13 | portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO 18 | EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR 19 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 20 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /frontend/src/utils/utils.ts: -------------------------------------------------------------------------------- 1 | export const isMobile = () => { 2 | const mobileRegex = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i; 3 | return mobileRegex.test(navigator.userAgent); 4 | }; 5 | 6 | export const sleep = (timeout: number) => new Promise((resolve, reject) => setTimeout(resolve, timeout)); 7 | 8 | export const copy = async (text: string) => { 9 | if (navigator.clipboard && navigator.clipboard.writeText) { 10 | // 使用Clipboard API复制文本到剪贴板 11 | try { 12 | await navigator.clipboard.writeText(text); 13 | return true; 14 | } catch (error) { 15 | console.error('error : ', error); 16 | } 17 | } 18 | // 使用备用方案 19 | try { 20 | const ele = document.createElement('textarea'); 21 | ele.value = text; 22 | ele.setAttribute('readonly', ''); 23 | ele.style.position = 'absolute'; 24 | ele.style.left = '-9999px'; 25 | document.body.appendChild(ele); 26 | ele.select(); 27 | document.execCommand('copy'); 28 | document.body.removeChild(ele); 29 | return true; 30 | } catch (error) { 31 | console.error(error); 32 | } 33 | return false; 34 | }; 35 | -------------------------------------------------------------------------------- /docker/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | go-proxy-bingai: 5 | # 镜像名称 6 | image: adams549659584/go-proxy-bingai 7 | # 容器名称 8 | container_name: go-proxy-bingai 9 | # 自启动 10 | restart: unless-stopped 11 | ports: 12 | - 8080:8080 13 | # environment: 14 | # - Go_Proxy_BingAI_SOCKS_URL=192.168.0.88:1070 15 | # - Go_Proxy_BingAI_SOCKS_USER=xxx 16 | # - Go_Proxy_BingAI_SOCKS_PWD=xxx 17 | # - Go_Proxy_BingAI_USER_TOKEN_1=xxx 18 | # - Go_Proxy_BingAI_USER_TOKEN_2=xxx 19 | 20 | # go-proxy-bingai: 21 | # # 镜像名称 22 | # image: adams549659584/go-proxy-bingai 23 | # # 容器名称 24 | # container_name: go-proxy-bingai 25 | # build: 26 | # context: ../ 27 | # dockerfile: docker/Dockerfile 28 | # # 自启动 29 | # restart: unless-stopped 30 | # # 加入指定网络 31 | # networks: 32 | # - MyNetwork 33 | # ports: 34 | # - 8888:8080 35 | # environment: 36 | # - Go_Proxy_BingAI_SOCKS_URL=192.168.0.88:1070 37 | # # - Go_Proxy_BingAI_SOCKS_USER=xxx 38 | # # - Go_Proxy_BingAI_SOCKS_PWD=xxx 39 | 40 | # networks: 41 | # MyNetwork: 42 | # external: true 43 | 44 | -------------------------------------------------------------------------------- /web/web.go: -------------------------------------------------------------------------------- 1 | package web 2 | 3 | import ( 4 | "adams549659584/go-proxy-bingai/common" 5 | "embed" 6 | "io/fs" 7 | "net/http" 8 | "path/filepath" 9 | ) 10 | 11 | //go:embed * 12 | var webFS embed.FS 13 | 14 | var WEB_PATH_MAP = make(map[string]bool) 15 | 16 | func init() { 17 | var err error 18 | if common.IS_DEBUG_MODE { 19 | err = initWebPathMapByDir() 20 | } else { 21 | err = initWebPathMapByFS() 22 | } 23 | if err != nil { 24 | panic(err) 25 | } 26 | } 27 | 28 | func initWebPathMapByDir() error { 29 | err := filepath.WalkDir("web", func(path string, d fs.DirEntry, err error) error { 30 | if err != nil { 31 | return err 32 | } 33 | if !d.IsDir() { 34 | WEB_PATH_MAP["/"+path] = true 35 | } 36 | return nil 37 | }) 38 | return err 39 | } 40 | 41 | func initWebPathMapByFS() error { 42 | err := fs.WalkDir(webFS, ".", func(path string, d fs.DirEntry, err error) error { 43 | if err != nil { 44 | return err 45 | } 46 | if !d.IsDir() { 47 | WEB_PATH_MAP["/web/"+path] = true 48 | } 49 | return nil 50 | }) 51 | return err 52 | } 53 | 54 | func GetWebFS() http.FileSystem { 55 | if common.IS_DEBUG_MODE { 56 | return http.Dir("web") 57 | } else { 58 | return http.FS(webFS) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /api/helper/helper.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import ( 4 | "adams549659584/go-proxy-bingai/common" 5 | "encoding/json" 6 | "net/http" 7 | ) 8 | 9 | type Response struct { 10 | Code int `json:"code"` 11 | Message string `json:"message"` 12 | Data interface{} `json:"data"` 13 | } 14 | 15 | func CommonResult(w http.ResponseWriter, code int, msg string, data interface{}) error { 16 | res := Response{ 17 | Code: code, 18 | Message: msg, 19 | Data: data, 20 | } 21 | w.Header().Set("Content-Type", "application/json") 22 | err := json.NewEncoder(w).Encode(res) 23 | if err != nil { 24 | return err 25 | } 26 | return nil 27 | } 28 | 29 | func SuccessResult(w http.ResponseWriter, data interface{}) error { 30 | return CommonResult(w, http.StatusOK, "success", data) 31 | } 32 | 33 | func ErrorResult(w http.ResponseWriter, code int, msg string) error { 34 | return CommonResult(w, code, msg, nil) 35 | } 36 | 37 | func UnauthorizedResult(w http.ResponseWriter) error { 38 | return ErrorResult(w, http.StatusUnauthorized, "unauthorized") 39 | } 40 | 41 | func CheckAuth(r *http.Request) bool { 42 | isAuth := true 43 | if len(common.AUTH_KEY) > 0 { 44 | ckAuthKey, _ := r.Cookie(common.AUTH_KEY_COOKIE_NAME) 45 | isAuth = ckAuthKey != nil && len(ckAuthKey.Value) > 0 && common.AUTH_KEY == ckAuthKey.Value 46 | } 47 | return isAuth 48 | } 49 | -------------------------------------------------------------------------------- /frontend/src/views/chat/components/Chat/ChatPromptItem.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 42 | -------------------------------------------------------------------------------- /frontend/src/components/ReloadPWA/ReloadPWA.vue: -------------------------------------------------------------------------------- 1 | 40 | 41 | 49 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "go-proxy-bingai", 3 | "version": "1.8.7", 4 | "private": true, 5 | "scripts": { 6 | "dev": "vite", 7 | "build": "run-p type-check build-only", 8 | "preview": "vite preview", 9 | "build-only": "vite build", 10 | "type-check": "vue-tsc --noEmit -p tsconfig.app.json --composite false", 11 | "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore", 12 | "format": "prettier --write src/" 13 | }, 14 | "dependencies": { 15 | "naive-ui": "^2.34.4", 16 | "pinia": "^2.0.36", 17 | "pinia-plugin-persistedstate": "^3.1.0", 18 | "vue": "^3.3.2", 19 | "vue-router": "^4.2.0", 20 | "vue3-virtual-scroll-list": "^0.2.1" 21 | }, 22 | "devDependencies": { 23 | "@rushstack/eslint-patch": "^1.2.0", 24 | "@tsconfig/node18": "^2.0.1", 25 | "@types/node": "^18.16.8", 26 | "@vitejs/plugin-vue": "^4.2.3", 27 | "@vue/eslint-config-prettier": "^7.1.0", 28 | "@vue/eslint-config-typescript": "^11.0.3", 29 | "@vue/tsconfig": "^0.4.0", 30 | "autoprefixer": "^10.4.14", 31 | "eslint": "^8.39.0", 32 | "eslint-plugin-vue": "^9.11.0", 33 | "npm-run-all": "^4.1.5", 34 | "postcss": "^8.4.23", 35 | "prettier": "^2.8.8", 36 | "prettier-plugin-tailwindcss": "^0.2.8", 37 | "tailwindcss": "^3.3.2", 38 | "typescript": "~5.0.4", 39 | "vite": "^4.3.5", 40 | "vite-plugin-pwa": "^0.14.7", 41 | "vue-tsc": "^1.6.4", 42 | "workbox-cacheable-response": "^6.6.0", 43 | "workbox-expiration": "^6.6.0", 44 | "workbox-precaching": "^6.6.0", 45 | "workbox-routing": "^6.6.0", 46 | "workbox-strategies": "^6.6.0", 47 | "workbox-window": "^6.6.0" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /frontend/src/components/ChatPromptStore/ChatPromptItem.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 43 | -------------------------------------------------------------------------------- /frontend/README.md: -------------------------------------------------------------------------------- 1 | # go-proxy-bingai 2 | 3 | This template should help get you started developing with Vue 3 in Vite. 4 | 5 | ## Recommended IDE Setup 6 | 7 | [VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin). 8 | 9 | ## Type Support for `.vue` Imports in TS 10 | 11 | TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin) to make the TypeScript language service aware of `.vue` types. 12 | 13 | If the standalone TypeScript plugin doesn't feel fast enough to you, Volar has also implemented a [Take Over Mode](https://github.com/johnsoncodehk/volar/discussions/471#discussioncomment-1361669) that is more performant. You can enable it by the following steps: 14 | 15 | 1. Disable the built-in TypeScript Extension 16 | 1) Run `Extensions: Show Built-in Extensions` from VSCode's command palette 17 | 2) Find `TypeScript and JavaScript Language Features`, right click and select `Disable (Workspace)` 18 | 2. Reload the VSCode window by running `Developer: Reload Window` from the command palette. 19 | 20 | ## Customize configuration 21 | 22 | See [Vite Configuration Reference](https://vitejs.dev/config/). 23 | 24 | ## Project Setup 25 | 26 | ```sh 27 | pnpm install 28 | ``` 29 | 30 | ### Compile and Hot-Reload for Development 31 | 32 | ```sh 33 | pnpm dev 34 | ``` 35 | 36 | ### Type-Check, Compile and Minify for Production 37 | 38 | ```sh 39 | pnpm build 40 | ``` 41 | 42 | ### Lint with [ESLint](https://eslint.org/) 43 | 44 | ```sh 45 | pnpm lint 46 | ``` 47 | -------------------------------------------------------------------------------- /frontend/src/components/LoadingSpinner/LoadingSpinner.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 16 | 17 | 82 | -------------------------------------------------------------------------------- /frontend/src/sw.ts: -------------------------------------------------------------------------------- 1 | import { cleanupOutdatedCaches, createHandlerBoundToURL, precacheAndRoute } from 'workbox-precaching'; 2 | import { NavigationRoute, registerRoute } from 'workbox-routing'; 3 | import { CacheFirst, StaleWhileRevalidate } from 'workbox-strategies'; 4 | import { CacheableResponsePlugin } from 'workbox-cacheable-response'; 5 | import { ExpirationPlugin } from 'workbox-expiration'; 6 | 7 | declare let self: ServiceWorkerGlobalScope; 8 | const CACHE_NAME_PREFIX = 'BingAI'; 9 | 10 | self.addEventListener('message', (event) => { 11 | if (event.data && event.data.type === 'SKIP_WAITING') { 12 | self.skipWaiting(); 13 | } 14 | }); 15 | 16 | // self.__WB_MANIFEST is default injection point 17 | precacheAndRoute(self.__WB_MANIFEST); 18 | 19 | // clean old assets 20 | cleanupOutdatedCaches(); 21 | 22 | // to allow work offline 23 | registerRoute(new NavigationRoute(createHandlerBoundToURL('./index.html'))); 24 | 25 | registerRoute( 26 | ({ request, url }) => { 27 | return request.destination === 'style' || request.destination === 'manifest' || request.destination === 'script' || request.destination === 'worker'; 28 | }, 29 | new StaleWhileRevalidate({ 30 | cacheName: `${CACHE_NAME_PREFIX}-assets`, 31 | plugins: [new CacheableResponsePlugin({ statuses: [200] })], 32 | }) 33 | ); 34 | 35 | registerRoute( 36 | ({ request, url }) => { 37 | if (url.pathname.includes('hm.gif') || url.pathname.includes('/fd/ls/')) { 38 | return false; 39 | } 40 | return request.destination === 'image'; 41 | }, 42 | new CacheFirst({ 43 | cacheName: `${CACHE_NAME_PREFIX}-images`, 44 | plugins: [ 45 | new CacheableResponsePlugin({ statuses: [200] }), 46 | // 100 entries max, 30 days max 47 | new ExpirationPlugin({ maxEntries: 100, maxAgeSeconds: 60 * 60 * 24 * 30 }), 48 | ], 49 | }) 50 | ); 51 | 52 | self.addEventListener('install', (ev) => { 53 | self.skipWaiting(); 54 | }); 55 | -------------------------------------------------------------------------------- /web/js/bing/chat/global.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | try { 3 | const logPathReg = new RegExp('/fd/ls/|/web/xls.aspx'); 4 | // hack sb log 5 | const _oldSendBeacon = navigator.sendBeacon; 6 | navigator.sendBeacon = function (url, data) { 7 | if (logPathReg.test(url)) { 8 | return true; 9 | } 10 | return _oldSendBeacon.call(this, url, data); 11 | }; 12 | // hack xhr log 13 | const xhrOpen = window.XMLHttpRequest.prototype.open; 14 | window.XMLHttpRequest.prototype.open = function (method, url) { 15 | const that = this; 16 | if (logPathReg.test(url)) { 17 | that.isLog = true; 18 | } 19 | return xhrOpen.apply(that, [method, url]); 20 | }; 21 | const xhrSend = window.XMLHttpRequest.prototype.send; 22 | window.XMLHttpRequest.prototype.send = function (...args) { 23 | const that = this; 24 | if (that.isLog) { 25 | return that.abort(); 26 | } 27 | return xhrSend.apply(that, args); 28 | }; 29 | // const OriginalImage = Image; 30 | // Image = function () { 31 | // const image = new OriginalImage(); 32 | // const originalSet = image.__proto__.__lookupSetter__('src'); 33 | // image.__proto__.__defineSetter__('src', function (value) { 34 | // if (logPathReg.test(value)) { 35 | // return; 36 | // } 37 | // originalSet.call(this, value); 38 | // }); 39 | // return image; 40 | // }; 41 | } catch (error) { 42 | console.error(error); 43 | } 44 | _G = { 45 | Region: 'US', 46 | Lang: 'zh-CN', 47 | ST: typeof si_ST !== 'undefined' ? si_ST : new Date(), 48 | Mkt: 'en-US', 49 | RevIpCC: 'us', 50 | RTL: false, 51 | Ver: '22', 52 | IG: '0', 53 | EventID: '646ae52b797848988e6618d6205cf436', 54 | V: 'web', 55 | P: 'SERP', 56 | DA: 'PUSE01', 57 | SUIH: 'BtAuR605UV0NIg083Tp9HA', 58 | adc: 'b_ad', 59 | // logsb 启用 sendBeacon 推送日志,并在 sendBeacon 阻止 60 | EF: { cookss: 1, bmcov: 1, crossdomainfix: 1, bmasynctrigger: 1, bmasynctrigger3: 1, newtabsloppyclick: 1, chevroncheckmousemove: 1, logsb: 1 }, 61 | gpUrl: '/fd/ls/GLinkPing.aspx?', 62 | }; 63 | _G.lsUrl = '/fd/ls/l?IG=' + _G.IG; 64 | curUrl = '/search'; 65 | function si_T(a) { 66 | // if (document.images) { 67 | // _G.GPImg = new Image(); 68 | // _G.GPImg.src = _G.gpUrl + 'IG=' + _G.IG + '&' + a; 69 | // } 70 | return true; 71 | } 72 | _G.CTT = '3000'; 73 | -------------------------------------------------------------------------------- /frontend/public/js/bing/chat/global.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | try { 3 | const logPathReg = new RegExp('/fd/ls/|/web/xls.aspx'); 4 | // hack sb log 5 | const _oldSendBeacon = navigator.sendBeacon; 6 | navigator.sendBeacon = function (url, data) { 7 | if (logPathReg.test(url)) { 8 | return true; 9 | } 10 | return _oldSendBeacon.call(this, url, data); 11 | }; 12 | // hack xhr log 13 | const xhrOpen = window.XMLHttpRequest.prototype.open; 14 | window.XMLHttpRequest.prototype.open = function (method, url) { 15 | const that = this; 16 | if (logPathReg.test(url)) { 17 | that.isLog = true; 18 | } 19 | return xhrOpen.apply(that, [method, url]); 20 | }; 21 | const xhrSend = window.XMLHttpRequest.prototype.send; 22 | window.XMLHttpRequest.prototype.send = function (...args) { 23 | const that = this; 24 | if (that.isLog) { 25 | return that.abort(); 26 | } 27 | return xhrSend.apply(that, args); 28 | }; 29 | // const OriginalImage = Image; 30 | // Image = function () { 31 | // const image = new OriginalImage(); 32 | // const originalSet = image.__proto__.__lookupSetter__('src'); 33 | // image.__proto__.__defineSetter__('src', function (value) { 34 | // if (logPathReg.test(value)) { 35 | // return; 36 | // } 37 | // originalSet.call(this, value); 38 | // }); 39 | // return image; 40 | // }; 41 | } catch (error) { 42 | console.error(error); 43 | } 44 | _G = { 45 | Region: 'US', 46 | Lang: 'zh-CN', 47 | ST: typeof si_ST !== 'undefined' ? si_ST : new Date(), 48 | Mkt: 'en-US', 49 | RevIpCC: 'us', 50 | RTL: false, 51 | Ver: '22', 52 | IG: '0', 53 | EventID: '646ae52b797848988e6618d6205cf436', 54 | V: 'web', 55 | P: 'SERP', 56 | DA: 'PUSE01', 57 | SUIH: 'BtAuR605UV0NIg083Tp9HA', 58 | adc: 'b_ad', 59 | // logsb 启用 sendBeacon 推送日志,并在 sendBeacon 阻止 60 | EF: { cookss: 1, bmcov: 1, crossdomainfix: 1, bmasynctrigger: 1, bmasynctrigger3: 1, newtabsloppyclick: 1, chevroncheckmousemove: 1, logsb: 1 }, 61 | gpUrl: '/fd/ls/GLinkPing.aspx?', 62 | }; 63 | _G.lsUrl = '/fd/ls/l?IG=' + _G.IG; 64 | curUrl = '/search'; 65 | function si_T(a) { 66 | // if (document.images) { 67 | // _G.GPImg = new Image(); 68 | // _G.GPImg.src = _G.gpUrl + 'IG=' + _G.IG + '&' + a; 69 | // } 70 | return true; 71 | } 72 | _G.CTT = '3000'; 73 | -------------------------------------------------------------------------------- /frontend/src/components/CreateImage/CreateImage.vue: -------------------------------------------------------------------------------- 1 | 45 | 46 | 66 | -------------------------------------------------------------------------------- /frontend/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { fileURLToPath, URL } from 'node:url'; 2 | import { defineConfig, loadEnv } from 'vite'; 3 | import vue from '@vitejs/plugin-vue'; 4 | import pkg from './package.json'; 5 | import { VitePWA, VitePWAOptions } from 'vite-plugin-pwa'; 6 | 7 | const { name, version, dependencies, devDependencies } = pkg; 8 | const __APP_INFO__ = { 9 | buildTimestamp: Date.now(), 10 | name, 11 | version, 12 | dependencies, 13 | devDependencies, 14 | }; 15 | 16 | const initPwaOptions = (env: Record) => { 17 | const pwaOptions: Partial = { 18 | srcDir: 'src', 19 | filename: 'sw.ts', 20 | includeAssets: ['img/logo.svg'], 21 | manifest: { 22 | name: 'BingAI', 23 | short_name: 'BingAI', 24 | theme_color: '#ffffff', 25 | icons: [ 26 | { 27 | src: './img/pwa/logo-192.png', 28 | sizes: '192x192', 29 | type: 'image/png', 30 | }, 31 | { 32 | src: './img/pwa/logo-512.png', 33 | sizes: '512x512', 34 | type: 'image/png', 35 | }, 36 | { 37 | src: './img/pwa/logo-512.png', 38 | sizes: '512x512', 39 | type: 'image/png', 40 | purpose: 'any maskable', 41 | }, 42 | ], 43 | }, 44 | // devOptions: { 45 | // enabled: true, 46 | // type: 'module', 47 | // }, 48 | strategies: 'injectManifest', 49 | // workbox: { 50 | // cleanupOutdatedCaches: true, 51 | // clientsClaim: true, 52 | // skipWaiting: true, 53 | // }, 54 | // 取消注册服务工作进程 55 | // selfDestroying: true, 56 | registerType: 'autoUpdate', 57 | }; 58 | return pwaOptions; 59 | }; 60 | 61 | // https://vitejs.dev/config/ 62 | export default defineConfig(({ command, mode }) => { 63 | const env = loadEnv(mode, process.cwd(), ''); 64 | return { 65 | base: '/web', 66 | server: { 67 | port: 4000, 68 | open: false, 69 | host: '0.0.0.0', 70 | proxy: { 71 | '^/(?!web)': { 72 | ws: true, 73 | target: env.VITE_BASE_API_URL, 74 | changeOrigin: true, 75 | }, 76 | }, 77 | }, 78 | define: { 79 | __APP_INFO__: JSON.stringify(__APP_INFO__), 80 | }, 81 | plugins: [vue(), VitePWA(initPwaOptions(env))], 82 | resolve: { 83 | alias: { 84 | '@': fileURLToPath(new URL('./src', import.meta.url)), 85 | }, 86 | }, 87 | build: { 88 | outDir: '../web', 89 | }, 90 | }; 91 | }); 92 | -------------------------------------------------------------------------------- /web/assets/setting-c6ca7b14.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/assets/img/setting.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/types/vue3-virtual-scroll-list.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'vue3-virtual-scroll-list' { 2 | import type { defineComponent } from 'vue'; 3 | 4 | const VirtualProps = { 5 | dataKey: { 6 | type: [String, Function], 7 | required: true, 8 | }, 9 | dataSources: { 10 | type: Array, 11 | required: true, 12 | default: () => [], 13 | }, 14 | dataComponent: { 15 | type: [Object, Function], 16 | required: true, 17 | }, 18 | 19 | keeps: { 20 | type: Number, 21 | default: 30, 22 | }, 23 | extraProps: { 24 | type: Object, 25 | }, 26 | estimateSize: { 27 | type: Number, 28 | default: 50, 29 | }, 30 | 31 | direction: { 32 | type: String as PropType<'vertical' | 'horizontal'>, 33 | default: 'vertical', // the other value is horizontal 34 | }, 35 | start: { 36 | type: Number, 37 | default: 0, 38 | }, 39 | offset: { 40 | type: Number, 41 | default: 0, 42 | }, 43 | topThreshold: { 44 | type: Number, 45 | default: 0, 46 | }, 47 | bottomThreshold: { 48 | type: Number, 49 | default: 0, 50 | }, 51 | pageMode: { 52 | type: Boolean, 53 | default: false, 54 | }, 55 | rootTag: { 56 | type: String, 57 | default: 'div', 58 | }, 59 | wrapTag: { 60 | type: String, 61 | default: 'div', 62 | }, 63 | wrapClass: { 64 | type: String, 65 | default: 'wrap', 66 | }, 67 | wrapStyle: { 68 | type: Object, 69 | }, 70 | itemTag: { 71 | type: String, 72 | default: 'div', 73 | }, 74 | itemClass: { 75 | type: String, 76 | default: '', 77 | }, 78 | itemClassAdd: { 79 | type: Function, 80 | }, 81 | itemStyle: { 82 | type: Object, 83 | }, 84 | headerTag: { 85 | type: String, 86 | default: 'div', 87 | }, 88 | headerClass: { 89 | type: String, 90 | default: '', 91 | }, 92 | headerStyle: { 93 | type: Object, 94 | }, 95 | footerTag: { 96 | type: String, 97 | default: 'div', 98 | }, 99 | footerClass: { 100 | type: String, 101 | default: '', 102 | }, 103 | footerStyle: { 104 | type: Object, 105 | }, 106 | itemScopedSlots: { 107 | type: Object, 108 | }, 109 | }; 110 | const VirtualList = defineComponent({ 111 | name: 'VirtualList', 112 | props: VirtualProps, 113 | }); 114 | export default VirtualList; 115 | } 116 | -------------------------------------------------------------------------------- /frontend/src/stores/modules/chat/index.ts: -------------------------------------------------------------------------------- 1 | import { ref } from 'vue'; 2 | import { defineStore } from 'pinia'; 3 | 4 | export interface SydneyConfig { 5 | baseUrl: string; 6 | label: string; 7 | isUsable?: boolean; 8 | delay?: number; 9 | isCus?: boolean; 10 | } 11 | 12 | export interface CheckSydneyConfigResult { 13 | isUsable: boolean; 14 | errorMsg?: string; 15 | delay?: number; 16 | } 17 | 18 | export const useChatStore = defineStore( 19 | 'chat-store', 20 | () => { 21 | const chatHubPath = '/sydney/ChatHub'; 22 | 23 | const isShowChatServiceSelectModal = ref(false); 24 | const selectedSydneyBaseUrl = ref(''); 25 | const sydneyConfigs = ref([ 26 | { 27 | baseUrl: 'https://sydney.bing.com', 28 | label: 'Bing 官方', 29 | }, 30 | { 31 | baseUrl: 'https://sydney.vcanbb.chat', 32 | label: 'Cloudflare', 33 | }, 34 | { 35 | baseUrl: location.origin, 36 | label: '本站', 37 | }, 38 | { 39 | baseUrl: '', 40 | label: '自定义', 41 | isCus: true, 42 | }, 43 | ]); 44 | const sydneyCheckTimeoutMS = 3000; 45 | 46 | const checkSydneyConfig = async (config: SydneyConfig): Promise => { 47 | if (!config.baseUrl) { 48 | return { 49 | isUsable: false, 50 | errorMsg: '链接不可为空', 51 | }; 52 | } 53 | try { 54 | const startTime = Date.now(); 55 | const ws = new WebSocket(config.baseUrl.replace('http', 'ws') + chatHubPath); 56 | const wsTimer = setTimeout(() => { 57 | ws.close(); 58 | }, sydneyCheckTimeoutMS); 59 | await new Promise((resolve, reject) => { 60 | ws.onopen = () => { 61 | clearTimeout(wsTimer); 62 | resolve(ws.close()); 63 | }; 64 | ws.onerror = () => { 65 | clearTimeout(wsTimer); 66 | reject(new Error(`聊天服务器 ${config.baseUrl} 连接失败`)); 67 | }; 68 | ws.onclose = () => reject(new Error(`聊天服务器 ${config.baseUrl} 连接超时`)); 69 | }); 70 | return { 71 | isUsable: true, 72 | delay: Date.now() - startTime, 73 | }; 74 | } catch (error) { 75 | return { 76 | isUsable: false, 77 | errorMsg: error instanceof Error ? error.message : '', 78 | }; 79 | } 80 | }; 81 | 82 | const checkAllSydneyConfig = async () => { 83 | const checkAllConfigPromises = sydneyConfigs.value 84 | .filter((x) => x.baseUrl) 85 | .map(async (config) => { 86 | const checkResult = await checkSydneyConfig(config); 87 | config.isUsable = checkResult.isUsable; 88 | config.delay = checkResult.delay; 89 | }); 90 | await Promise.all(checkAllConfigPromises); 91 | }; 92 | 93 | return { 94 | isShowChatServiceSelectModal, 95 | sydneyConfigs, 96 | selectedSydneyBaseUrl, 97 | checkSydneyConfig, 98 | checkAllSydneyConfig, 99 | }; 100 | }, 101 | { 102 | persist: { 103 | key: 'chat-store', 104 | storage: localStorage, 105 | paths: ['selectedSydneyBaseUrl', 'sydneyConfigs'], 106 | }, 107 | } 108 | ); 109 | -------------------------------------------------------------------------------- /web/js/bing/chat/lib.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | var Lib; 3 | (function (n) { 4 | var t; 5 | (function (n) { 6 | function u(n, t) { 7 | var r, i; 8 | if (t == null || n == null) throw new TypeError('Null element passed to Lib.CssClass'); 9 | if (n.indexOf) return n.indexOf(t); 10 | for (r = n.length, i = 0; i < r; i++) if (n[i] === t) return i; 11 | return -1; 12 | } 13 | function f(n, u) { 14 | if (n == null) throw new TypeError('Null element passed to Lib.CssClass. add className:' + u); 15 | if (!i(n, u)) 16 | if (r && n.classList) n.classList.add(u); 17 | else { 18 | var f = t(n) + ' ' + u; 19 | o(n, f); 20 | } 21 | } 22 | function e(n, f) { 23 | var e, s, h; 24 | if (n == null) 25 | throw new TypeError('Null element passed to Lib.CssClass. remove className:' + f); 26 | i(n, f) && 27 | (r && n.classList 28 | ? n.classList.remove(f) 29 | : ((e = t(n).split(' ')), 30 | (s = u(e, f)), 31 | s >= 0 && e.splice(s, 1), 32 | (h = e.join(' ')), 33 | o(n, h))); 34 | } 35 | function s(n, t) { 36 | if (n == null) 37 | throw new TypeError('Null element passed to Lib.CssClass. toggle className:' + t); 38 | r && n.classList ? n.classList.toggle(t) : i(n, t) ? e(n, t) : f(n, t); 39 | } 40 | function i(n, i) { 41 | var f, e; 42 | if (n == null) 43 | throw new TypeError('Null element passed to Lib.CssClass. contains className:' + i); 44 | return r && n.classList 45 | ? n.classList.contains(i) 46 | : ((f = t(n)), f) 47 | ? ((e = f.split(' ')), u(e, i) >= 0) 48 | : !1; 49 | } 50 | function h(n, i) { 51 | var f, e, r, u, o; 52 | if (n.getElementsByClassName) return n.getElementsByClassName(i); 53 | for (f = n.getElementsByTagName('*'), e = [], r = 0; r < f.length; r++) 54 | (u = f[r]), u && ((o = t(u)), o && o.indexOf(i) !== -1 && e.push(u)); 55 | return e; 56 | } 57 | function o(n, t) { 58 | n instanceof SVGElement ? n.setAttribute('class', t) : (n.className = t); 59 | } 60 | function t(n) { 61 | return n instanceof SVGElement ? n.getAttribute('class') : n.className; 62 | } 63 | var r = typeof document.body.classList != 'undefined'; 64 | n.add = f; 65 | n.remove = e; 66 | n.toggle = s; 67 | n.contains = i; 68 | n.getElementByClassName = h; 69 | n.getClassAttribute = t; 70 | })((t = n.CssClass || (n.CssClass = {}))); 71 | })(Lib || (Lib = {})); 72 | 73 | function getBrowserWidth() { 74 | var t = _d.documentElement, 75 | n = Math.round(_w.innerWidth || t.clientWidth); 76 | return n < 100 && (n = 1496), n; 77 | } 78 | function getBrowserHeight() { 79 | var t = _d.documentElement, 80 | n = Math.round(_w.innerHeight || t.clientHeight); 81 | return n < 100 && (n = 796), n; 82 | } 83 | function getBrowserScrollWidth() { 84 | var n = Math.round(_d.body.clientWidth); 85 | return n < 100 && (n = 1496), n; 86 | } 87 | function getBrowserScrollHeight() { 88 | var n = Math.round(_d.body.clientHeight); 89 | return n < 100 && (n = 796), n; 90 | } 91 | 92 | window.ClientObserver = { 93 | getBrowserWidth: getBrowserWidth, 94 | getBrowserHeight: getBrowserHeight, 95 | getBrowserScrollWidth: getBrowserScrollWidth, 96 | getBrowserScrollHeight: getBrowserScrollHeight 97 | }; 98 | -------------------------------------------------------------------------------- /frontend/public/js/bing/chat/lib.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | var Lib; 3 | (function (n) { 4 | var t; 5 | (function (n) { 6 | function u(n, t) { 7 | var r, i; 8 | if (t == null || n == null) throw new TypeError('Null element passed to Lib.CssClass'); 9 | if (n.indexOf) return n.indexOf(t); 10 | for (r = n.length, i = 0; i < r; i++) if (n[i] === t) return i; 11 | return -1; 12 | } 13 | function f(n, u) { 14 | if (n == null) throw new TypeError('Null element passed to Lib.CssClass. add className:' + u); 15 | if (!i(n, u)) 16 | if (r && n.classList) n.classList.add(u); 17 | else { 18 | var f = t(n) + ' ' + u; 19 | o(n, f); 20 | } 21 | } 22 | function e(n, f) { 23 | var e, s, h; 24 | if (n == null) 25 | throw new TypeError('Null element passed to Lib.CssClass. remove className:' + f); 26 | i(n, f) && 27 | (r && n.classList 28 | ? n.classList.remove(f) 29 | : ((e = t(n).split(' ')), 30 | (s = u(e, f)), 31 | s >= 0 && e.splice(s, 1), 32 | (h = e.join(' ')), 33 | o(n, h))); 34 | } 35 | function s(n, t) { 36 | if (n == null) 37 | throw new TypeError('Null element passed to Lib.CssClass. toggle className:' + t); 38 | r && n.classList ? n.classList.toggle(t) : i(n, t) ? e(n, t) : f(n, t); 39 | } 40 | function i(n, i) { 41 | var f, e; 42 | if (n == null) 43 | throw new TypeError('Null element passed to Lib.CssClass. contains className:' + i); 44 | return r && n.classList 45 | ? n.classList.contains(i) 46 | : ((f = t(n)), f) 47 | ? ((e = f.split(' ')), u(e, i) >= 0) 48 | : !1; 49 | } 50 | function h(n, i) { 51 | var f, e, r, u, o; 52 | if (n.getElementsByClassName) return n.getElementsByClassName(i); 53 | for (f = n.getElementsByTagName('*'), e = [], r = 0; r < f.length; r++) 54 | (u = f[r]), u && ((o = t(u)), o && o.indexOf(i) !== -1 && e.push(u)); 55 | return e; 56 | } 57 | function o(n, t) { 58 | n instanceof SVGElement ? n.setAttribute('class', t) : (n.className = t); 59 | } 60 | function t(n) { 61 | return n instanceof SVGElement ? n.getAttribute('class') : n.className; 62 | } 63 | var r = typeof document.body.classList != 'undefined'; 64 | n.add = f; 65 | n.remove = e; 66 | n.toggle = s; 67 | n.contains = i; 68 | n.getElementByClassName = h; 69 | n.getClassAttribute = t; 70 | })((t = n.CssClass || (n.CssClass = {}))); 71 | })(Lib || (Lib = {})); 72 | 73 | function getBrowserWidth() { 74 | var t = _d.documentElement, 75 | n = Math.round(_w.innerWidth || t.clientWidth); 76 | return n < 100 && (n = 1496), n; 77 | } 78 | function getBrowserHeight() { 79 | var t = _d.documentElement, 80 | n = Math.round(_w.innerHeight || t.clientHeight); 81 | return n < 100 && (n = 796), n; 82 | } 83 | function getBrowserScrollWidth() { 84 | var n = Math.round(_d.body.clientWidth); 85 | return n < 100 && (n = 1496), n; 86 | } 87 | function getBrowserScrollHeight() { 88 | var n = Math.round(_d.body.clientHeight); 89 | return n < 100 && (n = 796), n; 90 | } 91 | 92 | window.ClientObserver = { 93 | getBrowserWidth: getBrowserWidth, 94 | getBrowserHeight: getBrowserHeight, 95 | getBrowserScrollWidth: getBrowserScrollWidth, 96 | getBrowserScrollHeight: getBrowserScrollHeight 97 | }; 98 | -------------------------------------------------------------------------------- /frontend/src/components/ChatServiceSelect/ChatServiceSelect.vue: -------------------------------------------------------------------------------- 1 | 39 | 40 | 75 | -------------------------------------------------------------------------------- /frontend/src/stores/modules/prompt/index.ts: -------------------------------------------------------------------------------- 1 | import { ref, computed } from 'vue'; 2 | import { defineStore } from 'pinia'; 3 | 4 | export interface IPrompt { 5 | act: string; 6 | prompt: string; 7 | } 8 | export interface IPromptDownloadConfig { 9 | type: 1 | 2; 10 | name: string; 11 | url: string; 12 | refer: string; 13 | isDownloading?: boolean; 14 | } 15 | 16 | export interface IOptPromptResult { 17 | result: boolean; 18 | msg?: string; 19 | data?: T; 20 | } 21 | 22 | export const usePromptStore = defineStore( 23 | 'prompt-store', 24 | () => { 25 | const promptDownloadConfig = ref>([ 26 | { 27 | type: 1, 28 | name: 'ChatGPT 中文调教指南 - 简体', 29 | url: './data/prompts/prompts-zh.json', 30 | refer: 'https://github.com/PlexPt/awesome-chatgpt-prompts-zh', 31 | }, 32 | { 33 | type: 1, 34 | name: 'ChatGPT 中文调教指南 - 繁体', 35 | url: './data/prompts/prompts-zh-TW.json', 36 | refer: 'https://github.com/PlexPt/awesome-chatgpt-prompts-zh', 37 | }, 38 | { 39 | type: 1, 40 | name: 'Awesome ChatGPT Prompts', 41 | url: './data/prompts/prompts.csv', 42 | refer: 'https://github.com/f/awesome-chatgpt-prompts', 43 | }, 44 | { 45 | type: 2, 46 | name: '', 47 | url: '', 48 | refer: '', 49 | }, 50 | ]); 51 | const isShowPromptSotre = ref(false); 52 | const isShowChatPrompt = ref(false); 53 | const promptList = ref>([]); 54 | const keyword = ref(''); 55 | const selectedPromptIndex = ref(0); 56 | 57 | const optPromptConfig = ref<{ 58 | isShow: boolean; 59 | type?: 'add' | 'edit'; 60 | title?: '添加提示词' | '编辑提示词'; 61 | tmpPrompt?: IPrompt; 62 | newPrompt: IPrompt; 63 | }>({ 64 | isShow: false, 65 | newPrompt: { 66 | act: '', 67 | prompt: '', 68 | }, 69 | }); 70 | 71 | const searchPromptList = computed(() => { 72 | if (!keyword.value) { 73 | return promptList.value; 74 | } 75 | return promptList.value?.filter((x) => x.act.includes(keyword.value) || x.prompt.includes(keyword.value)); 76 | }); 77 | 78 | function addPrompt(list: Array): IOptPromptResult<{ successCount: number }> { 79 | if (list instanceof Array && list.every((x) => x.act && x.prompt)) { 80 | if (promptList.value.length === 0) { 81 | promptList.value.push(...list); 82 | return { 83 | result: true, 84 | data: { 85 | successCount: list.length, 86 | }, 87 | }; 88 | } 89 | const newPromptList = list.filter((x) => promptList.value?.every((exist) => x.act !== exist.act && x.prompt !== exist.prompt)); 90 | promptList.value.push(...newPromptList); 91 | return { 92 | result: true, 93 | data: { 94 | successCount: newPromptList.length, 95 | }, 96 | }; 97 | } else { 98 | return { 99 | result: false, 100 | msg: '提示词格式有误', 101 | }; 102 | } 103 | } 104 | 105 | return { promptDownloadConfig, isShowPromptSotre, isShowChatPrompt, promptList, keyword, searchPromptList, selectedPromptIndex, optPromptConfig, addPrompt }; 106 | }, 107 | { 108 | persist: { 109 | key: 'prompt-store', 110 | storage: localStorage, 111 | paths: ['promptList'], 112 | }, 113 | } 114 | ); 115 | -------------------------------------------------------------------------------- /web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | BingAI - 聊天 17 | 21 | 22 | 23 | 30 | 31 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 |
49 |
50 |
51 |
52 |
53 | 54 | 55 |
56 | 104 | 105 |
106 |
107 |
108 |
109 |
110 | 111 |
112 | 113 | 114 | 115 | -------------------------------------------------------------------------------- /cloudflare/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 11 | 12 | 13 | BingAI - 聊天 14 | 15 | 16 | 17 | 24 | 25 | 34 | 35 | 36 | 37 |
38 |
39 |
40 |
42 |
43 |
44 | BingAI 45 |
46 |
聊天服务器已部署完成
47 |
48 |
49 |
    50 |
  • 51 | 53 | 54 | 55 | 56 |

    57 | 在基于 go-proxy-bingai 搭建的站点中 58 | 设置 => 服务选择 59 | 添加此站点即可 60 |

    61 |
  • 62 |
  • 63 | 65 | 66 | 67 | 68 |

    69 | 如有疑问,请参考 70 | issues #81 72 |

    73 |
  • 74 |
75 |
76 | 81 |
82 |
83 |
84 |
85 | 86 | 87 | -------------------------------------------------------------------------------- /frontend/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | BingAI - 聊天 17 | 21 | 22 | 23 | 30 | 31 | 40 | 41 | 42 | 43 | 44 | 45 | 46 |
47 |
48 |
49 |
50 |
51 | 52 | 53 |
54 | 120 | 121 |
122 |
123 |
124 |
125 |
126 | 127 |
128 | 129 | 130 | 131 | -------------------------------------------------------------------------------- /frontend/src/stores/modules/user/index.ts: -------------------------------------------------------------------------------- 1 | import { ref } from 'vue'; 2 | import { defineStore } from 'pinia'; 3 | import cookies from '@/utils/cookies'; 4 | import { sleep } from '@/utils/utils'; 5 | import sysconfApi from '@/api/sysconf'; 6 | import { ApiResultCode } from '@/api/model/ApiResult'; 7 | import type { SysConfig } from '@/api/model/sysconf/SysConfig'; 8 | 9 | export const useUserStore = defineStore( 10 | 'user-store', 11 | () => { 12 | const maxTryCreateConversationIdCount = 10; 13 | const userTokenCookieName = '_U'; 14 | const randIpCookieName = 'BingAI_Rand_IP'; 15 | const authKeyCookieName = 'BingAI_Auth_Key'; 16 | 17 | const sysConfig = ref(); 18 | 19 | const getSysConfig = async () => { 20 | const res = await sysconfApi.getSysConfig(); 21 | if (res.code === ApiResultCode.OK) { 22 | sysConfig.value = { 23 | ...sysConfig.value, 24 | ...res.data, 25 | }; 26 | } 27 | return res; 28 | }; 29 | 30 | const getConversationExpiry = () => { 31 | const B = new Date(); 32 | return B.setMinutes(B.getMinutes() + CIB.config.sydney.expiryInMinutes), B; 33 | }; 34 | 35 | const tryCreateConversationId = async (tryCount = 0) => { 36 | if (tryCount >= maxTryCreateConversationIdCount) { 37 | console.log(`已重试 ${tryCount} 次,自动创建停止`); 38 | return; 39 | } 40 | const conversationRes = await fetch('/turing/conversation/create', { 41 | credentials: 'include', 42 | }) 43 | .then((res) => res.json()) 44 | .catch((err) => `error`); 45 | if (conversationRes?.result?.value === 'Success') { 46 | console.log('成功创建会话ID : ', conversationRes.conversationId); 47 | CIB.manager.conversation.updateId(conversationRes.conversationId, getConversationExpiry(), conversationRes.clientId, conversationRes.conversationSignature); 48 | } else { 49 | await sleep(300); 50 | tryCount += 1; 51 | console.log(`开始第 ${tryCount} 次重试创建会话ID`); 52 | cookies.set(randIpCookieName, '', -1); 53 | tryCreateConversationId(tryCount); 54 | } 55 | }; 56 | 57 | const getUserToken = () => { 58 | const userCookieVal = cookies.get(userTokenCookieName) || ''; 59 | return userCookieVal; 60 | }; 61 | 62 | const checkUserToken = () => { 63 | const token = getUserToken(); 64 | if (!token) { 65 | // 未登录不显示历史记录 66 | CIB.config.features.enableGetChats = false; 67 | CIB.vm.sidePanel.isVisibleMobile = false; 68 | CIB.vm.sidePanel.isVisibleDesktop = false; 69 | // 创建会话id 70 | tryCreateConversationId(); 71 | } 72 | }; 73 | 74 | const saveUserToken = (token: string) => { 75 | cookies.set(userTokenCookieName, token, 7 * 24 * 60, '/'); 76 | }; 77 | 78 | const setAuthKey = (authKey: string) => { 79 | cookies.set(authKeyCookieName, authKey); 80 | }; 81 | 82 | const clearCache = async () => { 83 | // del storage 84 | localStorage.clear(); 85 | sessionStorage.clear(); 86 | // del sw cache 87 | const cacheKeys = await caches.keys(); 88 | for (const cacheKey of cacheKeys) { 89 | await caches.delete(cacheKey); 90 | console.log(`del cache : `, cacheKey); 91 | // await caches.open(cacheKey).then(async (cache) => { 92 | // const requests = await cache.keys(); 93 | // return await Promise.all( 94 | // requests.map((request) => { 95 | // console.log(`del cache : `, request.url); 96 | // return cache.delete(request); 97 | // }) 98 | // ); 99 | // }); 100 | } 101 | }; 102 | 103 | const resetCache = async () => { 104 | cookies.set(userTokenCookieName, '', -1); 105 | cookies.set(randIpCookieName, '', -1); 106 | cookies.set(authKeyCookieName, '', -1); 107 | await clearCache(); 108 | }; 109 | 110 | return { 111 | sysConfig, 112 | getSysConfig, 113 | getUserToken, 114 | checkUserToken, 115 | saveUserToken, 116 | resetCache, 117 | setAuthKey, 118 | }; 119 | }, 120 | { 121 | persist: { 122 | key: 'user-store', 123 | storage: localStorage, 124 | paths: [], 125 | }, 126 | } 127 | ); 128 | -------------------------------------------------------------------------------- /cloudflare/worker.js: -------------------------------------------------------------------------------- 1 | const SYDNEY_ORIGIN = 'https://sydney.bing.com'; 2 | const KEEP_REQ_HEADERS = [ 3 | 'accept', 4 | 'accept-encoding', 5 | 'accept-language', 6 | 'connection', 7 | 'cookie', 8 | 'upgrade', 9 | 'user-agent', 10 | 'sec-websocket-extensions', 11 | 'sec-websocket-key', 12 | 'sec-websocket-version', 13 | 'x-request-id', 14 | 'content-length', 15 | 'content-type', 16 | 'access-control-request-headers', 17 | 'access-control-request-method', 18 | ]; 19 | const IP_RANGE = [ 20 | ['3.2.50.0', '3.5.31.255'], //192,000 21 | ['3.12.0.0', '3.23.255.255'], //786,432 22 | ['3.30.0.0', '3.33.34.255'], //205,568 23 | ['3.40.0.0', '3.63.255.255'], //1,572,864 24 | ['3.80.0.0', '3.95.255.255'], //1,048,576 25 | ['3.100.0.0', '3.103.255.255'], //262,144 26 | ['3.116.0.0', '3.119.255.255'], //262,144 27 | ['3.128.0.0', '3.247.255.255'], //7,864,320 28 | ]; 29 | 30 | /** 31 | * 随机整数 [min,max) 32 | * @param {number} min 33 | * @param {number} max 34 | * @returns 35 | */ 36 | const getRandomInt = (min, max) => Math.floor(Math.random() * (max - min)) + min; 37 | 38 | /** 39 | * ip 转 int 40 | * @param {string} ip 41 | * @returns 42 | */ 43 | const ipToInt = (ip) => { 44 | const ipArr = ip.split('.'); 45 | let result = 0; 46 | result += +ipArr[0] << 24; 47 | result += +ipArr[1] << 16; 48 | result += +ipArr[2] << 8; 49 | result += +ipArr[3]; 50 | return result; 51 | }; 52 | 53 | /** 54 | * int 转 ip 55 | * @param {number} intIP 56 | * @returns 57 | */ 58 | const intToIp = (intIP) => { 59 | return `${(intIP >> 24) & 255}.${(intIP >> 16) & 255}.${(intIP >> 8) & 255}.${intIP & 255}`; 60 | }; 61 | 62 | const getRandomIP = () => { 63 | const randIndex = getRandomInt(0, IP_RANGE.length); 64 | const startIp = IP_RANGE[randIndex][0]; 65 | const endIp = IP_RANGE[randIndex][1]; 66 | const startIPInt = ipToInt(startIp); 67 | const endIPInt = ipToInt(endIp); 68 | const randomInt = getRandomInt(startIPInt, endIPInt); 69 | const randomIP = intToIp(randomInt); 70 | return randomIP; 71 | }; 72 | 73 | /** 74 | * home 75 | * @param {string} pathname 76 | * @returns 77 | */ 78 | const home = async (pathname) => { 79 | const baseUrl = 'https://raw.githubusercontent.com/adams549659584/go-proxy-bingai/master/'; 80 | let url; 81 | // if (pathname.startsWith('/github/')) { 82 | if (pathname.indexOf('/github/') === 0) { 83 | url = pathname.replace('/github/', baseUrl); 84 | } else { 85 | url = baseUrl + 'cloudflare/index.html'; 86 | } 87 | const res = await fetch(url); 88 | const newRes = new Response(res.body, res); 89 | if (pathname === '/') { 90 | newRes.headers.delete('content-security-policy'); 91 | newRes.headers.set('content-type', 'text/html; charset=utf-8'); 92 | } 93 | return newRes; 94 | }; 95 | 96 | export default { 97 | /** 98 | * fetch 99 | * @param {Request} request 100 | * @param {*} env 101 | * @param {*} ctx 102 | * @returns 103 | */ 104 | async fetch(request, env, ctx) { 105 | const currentUrl = new URL(request.url); 106 | // if (currentUrl.pathname === '/' || currentUrl.pathname.startsWith('/github/')) { 107 | if (currentUrl.pathname === '/' || currentUrl.pathname.indexOf('/github/') === 0) { 108 | return home(currentUrl.pathname); 109 | } 110 | const targetUrl = new URL(SYDNEY_ORIGIN + currentUrl.pathname + currentUrl.search); 111 | 112 | const newHeaders = new Headers(); 113 | request.headers.forEach((value, key) => { 114 | // console.log(`old : ${key} : ${value}`); 115 | if (KEEP_REQ_HEADERS.includes(key)) { 116 | newHeaders.set(key, value); 117 | } 118 | }); 119 | newHeaders.set('host', targetUrl.host); 120 | newHeaders.set('origin', targetUrl.origin); 121 | newHeaders.set('referer', 'https://www.bing.com/search?q=Bing+AI'); 122 | const randIP = getRandomIP(); 123 | // console.log('randIP : ', randIP); 124 | newHeaders.set('X-Forwarded-For', randIP); 125 | const oldUA = request.headers.get('user-agent'); 126 | const isMobile = oldUA.includes('Mobile') || oldUA.includes('Android'); 127 | if (isMobile) { 128 | newHeaders.set( 129 | 'user-agent', 130 | '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' 131 | ); 132 | } else { 133 | newHeaders.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'); 134 | } 135 | 136 | // newHeaders.forEach((value, key) => console.log(`${key} : ${value}`)); 137 | const newReq = new Request(targetUrl, { 138 | method: request.method, 139 | headers: newHeaders, 140 | body: request.body, 141 | }); 142 | // console.log('request url : ', newReq.url); 143 | const res = await fetch(newReq); 144 | return res; 145 | }, 146 | }; 147 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build Go-Proxy-BingAI 2 | on: 3 | push: 4 | tags: 5 | - v* 6 | 7 | jobs: 8 | release: 9 | runs-on: ubuntu-latest 10 | outputs: 11 | upload_url: ${{ steps.create_release.outputs.upload_url }} 12 | steps: 13 | - name: Create Release 14 | id: create_release 15 | uses: actions/create-release@v1 16 | env: 17 | GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} 18 | with: 19 | tag_name: ${{ github.ref }} 20 | release_name: ${{ github.ref }} 21 | draft: false 22 | prerelease: false 23 | 24 | amd64build: 25 | name: build amd64 version 26 | needs: release 27 | runs-on: ubuntu-latest 28 | steps: 29 | - uses: actions/checkout@v2 30 | - name: Set up Go 31 | uses: actions/setup-go@v4 32 | 33 | - name: build linux amd64 version 34 | run: | 35 | go build -ldflags="-s -w" -tags netgo -trimpath -o go-proxy-bingai main.go 36 | 37 | - name: package linux amd64 38 | run: tar -zcvf go-proxy-bingai-linux-amd64.tar.gz go-proxy-bingai 39 | 40 | - name: upload linux amd64 41 | uses: actions/upload-release-asset@v1 42 | env: 43 | GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} 44 | with: 45 | upload_url: ${{ needs.release.outputs.upload_url }} 46 | asset_path: go-proxy-bingai-linux-amd64.tar.gz 47 | asset_name: go-proxy-bingai-linux-amd64.tar.gz 48 | asset_content_type: application/gzip 49 | 50 | - name: build windows amd64 version 51 | run: | 52 | GOOS=windows GOARCH=amd64 go build -ldflags="-s -w" -tags netgo -trimpath -o go-proxy-bingai.exe main.go 53 | 54 | - name: package windows amd64 55 | run: tar -zcvf go-proxy-bingai-windows-amd64.tar.gz go-proxy-bingai.exe 56 | 57 | - name: upload windows amd64 58 | uses: actions/upload-release-asset@v1 59 | env: 60 | GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} 61 | with: 62 | upload_url: ${{ needs.release.outputs.upload_url }} 63 | asset_path: go-proxy-bingai-windows-amd64.tar.gz 64 | asset_name: go-proxy-bingai-windows-amd64.tar.gz 65 | asset_content_type: application/gzip 66 | 67 | arm64build: 68 | name: build arm64 version 69 | needs: release 70 | runs-on: ubuntu-latest 71 | steps: 72 | - uses: actions/checkout@v2 73 | - name: Set up Go 74 | uses: actions/setup-go@v4 75 | - name: build linux arm64 version 76 | run: | 77 | go build -ldflags="-s -w" -tags netgo -trimpath -o go-proxy-bingai main.go 78 | 79 | - name: package linux arm64 80 | run: tar -zcvf go-proxy-bingai-linux-arm64.tar.gz go-proxy-bingai 81 | 82 | - name: upload linux arm64 83 | uses: actions/upload-release-asset@v1 84 | env: 85 | GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} 86 | with: 87 | upload_url: ${{ needs.release.outputs.upload_url }} 88 | asset_path: go-proxy-bingai-linux-arm64.tar.gz 89 | asset_name: go-proxy-bingai-linux-arm64.tar.gz 90 | asset_content_type: application/gzip 91 | 92 | - name: build windows amd64 version 93 | run: | 94 | GOOS=windows GOARCH=arm64 go build -ldflags="-s -w" -tags netgo -trimpath -o go-proxy-bingai.exe main.go 95 | 96 | - name: package windows arm64 97 | run: tar -zcvf go-proxy-bingai-windows-arm64.tar.gz go-proxy-bingai.exe 98 | 99 | - name: upload windows arm64 100 | uses: actions/upload-release-asset@v1 101 | env: 102 | GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} 103 | with: 104 | upload_url: ${{ needs.release.outputs.upload_url }} 105 | asset_path: go-proxy-bingai-windows-arm64.tar.gz 106 | asset_name: go-proxy-bingai-windows-arm64.tar.gz 107 | asset_content_type: application/gzip 108 | 109 | docker-build: 110 | runs-on: ubuntu-latest 111 | 112 | permissions: 113 | packages: write 114 | contents: read 115 | 116 | steps: 117 | - uses: actions/checkout@v3 118 | 119 | - name: Setup timezone 120 | uses: zcong1993/setup-timezone@master 121 | with: 122 | timezone: Asia/Shanghai 123 | 124 | - name: Login to GHCR 125 | uses: docker/login-action@v2 126 | with: 127 | registry: ghcr.io 128 | username: ${{ github.repository_owner }} 129 | password: ${{ secrets.GH_TOKEN }} 130 | 131 | - name: Set up QEMU 132 | uses: docker/setup-qemu-action@v2 133 | 134 | - name: Set up Docker Buildx 135 | uses: docker/setup-buildx-action@v2 136 | 137 | - name: Login to Docker Hub 138 | uses: docker/login-action@v2 139 | with: 140 | username: ${{ secrets.DOCKERHUB_USERNAME }} 141 | password: ${{ secrets.DOCKERHUB_TOKEN }} 142 | 143 | - name: Build and push 144 | uses: docker/build-push-action@v4 145 | with: 146 | platforms: linux/amd64,linux/arm64 147 | context: . 148 | file: ./docker/Dockerfile 149 | push: true 150 | tags: | 151 | ghcr.io/${{ github.repository }}:${{ github.ref_name }} 152 | ghcr.io/${{ github.repository }}:latest 153 | docker.io/${{ github.repository }}:${{ github.ref_name }} 154 | docker.io/${{ github.repository }}:latest 155 | cache-from: type=registry,ref=${{ github.repository }}:cache 156 | cache-to: type=registry,ref=${{ github.repository }}:cache,mode=max -------------------------------------------------------------------------------- /frontend/src/components/ChatNav/ChatNav.vue: -------------------------------------------------------------------------------- 1 | 134 | 135 | 163 | -------------------------------------------------------------------------------- /web/assets/index-29dab197.css: -------------------------------------------------------------------------------- 1 | *,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.fixed{position:fixed}.absolute{position:absolute}.bottom-\[110px\]{bottom:110px}.left-0{left:0px}.right-4{right:1rem}.right-6{right:1.5rem}.top-0{top:0px}.top-6{top:1.5rem}.z-50{z-index:50}.float-right{float:right}.my-4{margin-top:1rem;margin-bottom:1rem}.ml-2{margin-left:.5rem}.mt-4{margin-top:1rem}.box-border{box-sizing:border-box}.block{display:block}.inline-block{display:inline-block}.flex{display:flex}.hidden{display:none}.h-\[130px\]{height:130px}.h-\[350px\]{height:350px}.h-\[40vh\]{height:40vh}.h-screen{height:100vh}.max-h-\[390px\]{max-height:390px}.w-0{width:0px}.w-11\/12{width:91.666667%}.w-\[310px\]{width:310px}.w-\[56px\]{width:56px}.w-full{width:100%}.w-screen{width:100vw}.max-w-\[1060px\]{max-width:1060px}.max-w-\[120px\]{max-width:120px}.max-w-\[310px\]{max-width:310px}.flex-1{flex:1 1 0%}.basis-full{flex-basis:100%}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.cursor-pointer{cursor:pointer}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.justify-start{justify-content:flex-start}.justify-center{justify-content:center}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-5{gap:1.25rem}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.text-ellipsis{text-overflow:ellipsis}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.rounded-xl{border-radius:.75rem}.bg-black\/40{background-color:#0006}.bg-gray-100{--tw-bg-opacity: 1;background-color:rgb(243 244 246 / var(--tw-bg-opacity))}.bg-white{--tw-bg-opacity: 1;background-color:rgb(255 255 255 / var(--tw-bg-opacity))}.p-5{padding:1.25rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-4{padding-left:1rem;padding-right:1rem}.px-5{padding-left:1.25rem;padding-right:1.25rem}.px-8{padding-left:2rem;padding-right:2rem}.px-\[14px\]{padding-left:14px;padding-right:14px}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-4{padding-top:1rem;padding-bottom:1rem}.py-6{padding-top:1.5rem;padding-bottom:1.5rem}.pb-2{padding-bottom:.5rem}.text-left{text-align:left}.text-3xl{font-size:1.875rem;line-height:2.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-\[\#c2c2c2\]{--tw-text-opacity: 1;color:rgb(194 194 194 / var(--tw-text-opacity))}.text-blue-500{--tw-text-opacity: 1;color:rgb(59 130 246 / var(--tw-text-opacity))}.no-underline{text-decoration-line:none}.blur{--tw-blur: blur(8px);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.ease-in-out{transition-timing-function:cubic-bezier(.4,0,.2,1)}.cib-serp-main{overflow:hidden}.hover\:bg-gray-100:hover{--tw-bg-opacity: 1;background-color:rgb(243 244 246 / var(--tw-bg-opacity))}@media (min-width: 768px){.md\:w-\[60px\]{width:60px}.md\:px-\[170px\]{padding-left:170px;padding-right:170px}.md\:px-\[34px\]{padding-left:34px;padding-right:34px}}@media (min-width: 1024px){.lg\:block{display:block}.lg\:table-cell{display:table-cell}.lg\:hidden{display:none}.lg\:w-\[400px\]{width:400px}.lg\:w-\[540px\]{width:540px}.lg\:w-\[900px\]{width:900px}}@media (min-width: 1280px){.xl\:h-\[520px\]{height:520px}.xl\:h-\[60vh\]{height:60vh}.xl\:w-\[475px\]{width:475px}.xl\:w-\[600px\]{width:600px}.xl\:w-\[900px\]{width:900px}.xl\:w-auto{width:auto}.xl\:min-w-\[300px\]{min-width:300px}.xl\:max-w-\[650px\]{max-width:650px}.xl\:basis-0{flex-basis:0px}.xl\:px-10{padding-left:2.5rem;padding-right:2.5rem}.xl\:px-\[220px\]{padding-left:220px;padding-right:220px}} 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # go-proxy-bing 2 | 3 | 基于微软 New Bing 用 Vue3 和 Go 简单定制的微软 New Bing 演示站点,拥有一致的 UI 体验,支持 ChatGPT 提示词,国内可用,基本兼容微软 Bing AI 所有功能,无需登录即可畅聊。 4 | 5 | ⭐ Bing 官方聊天服务器(相对较快和稳定,推荐)不可用时,可参考以下方案 6 | 7 | > 1. 可用 ModHeader 添加 X-Forwarded-For 请求头,对应 URL 是 wss://sydney.bing.com/sydney/ChatHub,具体可参考 [issues #71](https://github.com/adams549659584/go-proxy-bingai/issues/71) 及 https://zhuanlan.zhihu.com/p/606655303 8 | 9 | > 2. 本地部署再部署一份作为聊天中转服务,或下载 Release 直接运行,自定义聊天服务器中填入 http://localhost:8080,并选择。 10 | 11 | ⭐ 聊天服务器 (暂时默认 Cloudflare Workers,请求数每天限额 100,000,撑不了多久 ,推荐自行部署,参考下面 [部署聊天服务器](#部署聊天服务器) ) 可在右上角 设置 => 服务选择 中切换 12 | 13 | ⭐ 国内可用 (部署服务器需要直连 www.bing.com 不重定向 CN ,可配置 socks 连接) 14 | 15 | ⭐ 支持现有开源 ChatGPT 提示词库 16 | 17 | ⭐ 需要画图等高级功能时(需选更有创造力模式或右上角 设置 => 图像创建 ),可登录微软账号设置用户 Cookie 进行体验 18 | 19 | ⭐ 遇到一切问题,先点左下角 ![新主题](./docs/img/bing-clear.png) 试试,不行使用刷新大法(Shift + F5 或 Ctrl + Shift + R 或 右上角设置中的一键重置),最终大招就 清理浏览器缓存 及 Cookie ,比如(24 小时限制、未登录提示等等) 20 | 21 | - [go-proxy-bing](#go-proxy-bing) 22 | - [网页展示](#网页展示) 23 | - [侧边栏](#侧边栏) 24 | - [演示站点](#演示站点) 25 | - [设置用户](#设置用户) 26 | - [环境变量](#环境变量) 27 | - [部署](#部署) 28 | - [Docker](#Docker) 29 | - [Release](#Release) 30 | - [Railway](#Railway) 31 | - [Vercel](#Vercel) 32 | - [Render](#Render) 33 | - [部署聊天服务器](#部署聊天服务器) 34 | - [TODO](#TODO) 35 | 36 | ## 网页展示 37 | 38 | - 电脑端未登录状态 39 | 40 | ![电脑未登录](./docs/img/bing-nologin.png) 41 | 42 | - 电脑端登录 43 | 44 | ![电脑端登录](./docs/img/bing-login-1.png) 45 | ![提示词1](./docs/img/bing-prompt-1.png) 46 | ![提示词2](./docs/img/bing-prompt-2.png) 47 | ![聊天服务器选择](./docs/img/bing-sydney-service-1.png) 48 | 49 | - 电脑端画图 50 | 51 | > ⭐ 需登录,并选择 更有创造力 对话模式 52 | 53 | ![电脑端画图](./docs/img/bing-draw.png) 54 | 55 | - 手机端未登录状态 56 | 57 | ![手机端未登录](./docs/img/bing-m-nologin.png) 58 | 59 | ## 侧边栏 60 | 61 | - 在 Edge 浏览器可把聊天和撰写分别添加侧边栏 62 | 63 | ![添加侧边栏](./docs/img/sidebar-add.png) 64 | 65 | ![聊天](./docs/img/sidebar-chat.png) 66 | 67 | ![撰写](./docs/img/sidebar-compose.png) 68 | 69 | ## 演示站点 70 | 71 | ### 甲骨文小鸡仔,轻虐 72 | 73 | - https://bing.vcanbb.top 74 | 75 | ### Railway 搭建 76 | 77 | - https://bing-railway.vcanbb.top 78 | 79 | - https://go-proxy-bingai-production.up.railway.app 80 | 81 | ### Vercel 搭建 82 | 83 | - https://bing-vercel.vcanbb.top 84 | 85 | - https://go-proxy-bingai-adams549659584.vercel.app 86 | 87 | ### Render 搭建 88 | 89 | - https://bing-render.vcanbb.top 90 | 91 | - https://go-proxy-bingai.onrender.com 92 | 93 | ## 设置用户 94 | 95 | - 访问 https://www.bing.com/ 或 https://cn.bing.com/ ,登录 96 | 97 | - F12 或 Ctrl + Shift + I 打开控制台 98 | 99 | - 拿到 Cookie 中 _U 的值 后,在网站设置 => 设置用户 中填入即可。 100 | 101 | ![获取Cookie](./docs/img/bing-cookie.png) 102 | 103 | ## 环境变量 104 | 105 | ```bash 106 | # 运行端口 默认 8080 可选 107 | PORT=8080 108 | # Socks 环境变量 示例 可选 109 | Go_Proxy_BingAI_SOCKS_URL=192.168.0.88:1070 110 | # Socks 账号、密码 可选 111 | Go_Proxy_BingAI_SOCKS_USER=xxx 112 | Go_Proxy_BingAI_SOCKS_PWD=xxx 113 | # 默认用户 Cookie 设置,可选,不推荐使用,固定前缀 Go_Proxy_BingAI_USER_TOKEN 可设置多个,未登录用户将随机使用,多人共用将很快触发图形验证,并很快达到该账号的24小时限制 114 | Go_Proxy_BingAI_USER_TOKEN_1=xxx 115 | Go_Proxy_BingAI_USER_TOKEN_2=xxx 116 | Go_Proxy_BingAI_USER_TOKEN_3=xxx ... 117 | # 简单授权认证密码,可选 118 | Go_Proxy_BingAI_AUTH_KEY=xxx 119 | ``` 120 | 121 | ## 部署 122 | 123 | > ⭐ 需 https 域名 (自行配置 nginx 等) (前后端都有限制 只有在HTTPS的情况下,浏览器 Accept-Encoding 才会包含 br , localhost 除外) 124 | 125 | > 支持 Linux (amd64 / arm64)、Windows (amd64 / arm64) 126 | 127 | > 国内机器部署可配置 socks 环境变量 128 | 129 | ### Docker 130 | 131 | > 参考 [Dockerfile](./docker/Dockerfile) 、[docker-compose.yml](./docker/docker-compose.yml) 132 | 133 | - docker 示例 134 | 135 | ```bash 136 | # 运行容器 监听8080 端口 137 | docker run -d -p 8080:8080 --name go-proxy-bingai --restart=unless-stopped adams549659584/go-proxy-bingai 138 | 139 | # 配置 socks 环境变量 140 | docker run -e Go_Proxy_BingAI_SOCKS_URL=192.168.0.88:1070 -e Go_Proxy_BingAI_SOCKS_USER=xxx -e Go_Proxy_BingAI_SOCKS_PWD=xxx -d -p 8080:8080 --name go-proxy-bingai --restart=unless-stopped adams549659584/go-proxy-bingai 141 | ``` 142 | 143 | - docker compose 示例 144 | 145 | ```yaml 146 | version: '3' 147 | 148 | services: 149 | go-proxy-bingai: 150 | # 镜像名称 151 | image: adams549659584/go-proxy-bingai 152 | # 容器名称 153 | container_name: go-proxy-bingai 154 | # 自启动 155 | restart: unless-stopped 156 | ports: 157 | - 8080:8080 158 | # environment: 159 | # - Go_Proxy_BingAI_SOCKS_URL=192.168.0.88:1070 160 | # - Go_Proxy_BingAI_SOCKS_USER=xxx 161 | # - Go_Proxy_BingAI_SOCKS_PWD=xxx 162 | # - Go_Proxy_BingAI_USER_TOKEN_1=xxx 163 | # - Go_Proxy_BingAI_USER_TOKEN_2=xxx 164 | ``` 165 | 166 | ### Release 167 | 168 | 在 [GitHub Releases](https://github.com/adams549659584/go-proxy-bingai/releases) 下载适用于对应平台的压缩包,解压后可得到可执行文件 go-proxy-bingai,直接运行即可。 169 | 170 | ### Railway 171 | 172 | > 主要配置 Dockerfile 路径 及 端口就可以 173 | 174 | ```bash 175 | PORT=8080 176 | RAILWAY_DOCKERFILE_PATH=docker/Dockerfile 177 | ``` 178 | 179 | 一键部署,点这里 => [![Deploy on Railway](https://railway.app/button.svg)](https://railway.app/template/uIckWS?referralCode=BBs747) 180 | 181 | ![Railway 模板部署](./docs/img/railway-1.png) 182 | 183 | 自行使用 Railway 部署配置如下 184 | 185 | ![Railway 环境变量](./docs/img/railway-2.png) 186 | 187 | ![Railway 域名](./docs/img/railway-3.png) 188 | 189 | ### Vercel 190 | 191 | > ⭐ Vercel 部署不支持 Websocket ,需选择 官方聊天服务器 或 Cloudflare 192 | 193 | 一键部署,点这里 => [![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https://github.com/adams549659584/go-proxy-bingai&project-name=go-proxy-bingai&repository-name=go-proxy-bingai-vercel) 194 | 195 | ![Vercel 一键部署](./docs/img/vercel-1.png) 196 | 197 | ![Vercel 域名](./docs/img/vercel-2.png) 198 | 199 | ### Render 200 | 201 | 一键部署,点这里 => [![Deploy to Render](https://render.com/images/deploy-to-render-button.svg)](https://render.com/deploy?repo=https://github.com/adams549659584/go-proxy-bingai) 202 | 203 | ![Render 一键部署](./docs/img/render-1.png) 204 | 205 | ![Render 域名](./docs/img/render-2.png) 206 | 207 | ## 部署聊天服务器 208 | 209 | > 核心代码 [worker.js](./cloudflare/worker.js) 210 | 211 | > 具体部署 Cloudflare Workers 教程自行查询,大概如下 212 | 213 | - [注册 Cloudflare 账号](https://dash.cloudflare.com/sign-up) 214 | 215 | - 创建 Worker 服务,复制 [worker.js](./cloudflare/worker.js) 全部代码,粘贴至创建的服务中,保存并部署。 216 | 217 | - 触发器 中自定义访问域名。 218 | 219 | ## TODO 220 | 221 | - [x] 撰写 222 | - [x] Vue3 重构 223 | - [x] 提示词 224 | - [x] 历史聊天 225 | - [x] 导出消息到本地(Markdown、图片、PDF) 226 | - [x] 简单访问权限控制 227 | -------------------------------------------------------------------------------- /Taskfile.yaml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | vars: 4 | BUILD_VERSION: 5 | sh: git describe --tags 6 | BUILD_DATE: 7 | sh: date "+%F %T" 8 | COMMIT_ID: 9 | sh: git rev-parse HEAD 10 | 11 | tasks: 12 | clean: 13 | cmds: 14 | - rm -rf frontend/node_modules 15 | - rm -rf release 16 | # extended globbing is not supported 17 | # - rm -rf web/!(web.go) 18 | - cp web/web.go web.go 19 | - rm -rf web/* 20 | - mv web.go web/web.go 21 | build_web: 22 | dir: frontend 23 | cmds: 24 | # 修订号,例如 0.0.1 25 | - npm version patch 26 | # - pnpm build 27 | - pnpm install && pnpm build 28 | build_web_minor: 29 | dir: frontend 30 | cmds: 31 | # 次版本号,例如 0.1.0 32 | - npm version minor 33 | # - pnpm build 34 | - pnpm install && pnpm build 35 | build_web_major: 36 | dir: frontend 37 | cmds: 38 | # 主版本号,例如 1.0.0 39 | - npm version major 40 | # - pnpm build 41 | - pnpm install && pnpm build 42 | build_tpl: 43 | label: build-{{.TASK}} 44 | cmds: 45 | - | 46 | GOOS={{.GOOS}} GOARCH={{.GOARCH}} GOARM={{.GOARM}} GOMIPS={{.GOMIPS}} GOAMD64={{.GOAMD64}} \ 47 | go build -tags netgo -trimpath -o release/go-proxy-bingai_{{.TASK}} -ldflags \ 48 | "-w -s -X 'main.version={{.BUILD_VERSION}}' -X 'main.buildDate={{.BUILD_DATE}}' -X 'main.commitID={{.COMMIT_ID}}'" 49 | linux_386: 50 | cmds: 51 | - task: build_tpl 52 | vars: { 53 | TASK: "{{.TASK}}", 54 | GOOS: linux, 55 | GOARCH: 386 56 | } 57 | linux_amd64: 58 | cmds: 59 | - task: build_tpl 60 | vars: { 61 | TASK: "{{.TASK}}", 62 | GOOS: linux, 63 | GOARCH: amd64 64 | } 65 | linux_amd64_v2: 66 | cmds: 67 | - task: build_tpl 68 | vars: { 69 | TASK: "{{.TASK}}", 70 | GOOS: linux, 71 | GOARCH: amd64, 72 | GOAMD64: v2 73 | } 74 | linux_amd64_v3: 75 | cmds: 76 | - task: build_tpl 77 | vars: { 78 | TASK: "{{.TASK}}", 79 | GOOS: linux, 80 | GOARCH: amd64, 81 | GOAMD64: v3 82 | } 83 | linux_amd64_v4: 84 | cmds: 85 | - task: build_tpl 86 | vars: { 87 | TASK: "{{.TASK}}", 88 | GOOS: linux, 89 | GOARCH: amd64, 90 | GOAMD64: v4 91 | } 92 | linux_armv5: 93 | cmds: 94 | - task: build_tpl 95 | vars: { 96 | TASK: "{{.TASK}}", 97 | GOOS: linux, 98 | GOARCH: arm, 99 | GOARM: 5 100 | } 101 | linux_armv6: 102 | cmds: 103 | - task: build_tpl 104 | vars: { 105 | TASK: "{{.TASK}}", 106 | GOOS: linux, 107 | GOARCH: arm, 108 | GOARM: 6 109 | } 110 | linux_armv7: 111 | cmds: 112 | - task: build_tpl 113 | vars: { 114 | TASK: "{{.TASK}}", 115 | GOOS: linux, 116 | GOARCH: arm, 117 | GOARM: 7 118 | } 119 | linux_armv8: 120 | cmds: 121 | - task: build_tpl 122 | vars: { 123 | TASK: "{{.TASK}}", 124 | GOOS: linux, 125 | GOARCH: arm64 126 | } 127 | linux_mips_hardfloat: 128 | cmds: 129 | - task: build_tpl 130 | vars: { 131 | TASK: "{{.TASK}}", 132 | GOOS: linux, 133 | GOARCH: mips, 134 | GOMIPS: hardfloat 135 | } 136 | linux_mipsle_softfloat: 137 | cmds: 138 | - task: build_tpl 139 | vars: { 140 | TASK: "{{.TASK}}", 141 | GOOS: linux, 142 | GOARCH: mipsle, 143 | GOMIPS: softfloat 144 | } 145 | linux_mipsle_hardfloat: 146 | cmds: 147 | - task: build_tpl 148 | vars: { 149 | TASK: "{{.TASK}}", 150 | GOOS: linux, 151 | GOARCH: mipsle, 152 | GOMIPS: hardfloat 153 | } 154 | linux_mips64: 155 | cmds: 156 | - task: build_tpl 157 | vars: { 158 | TASK: "{{.TASK}}", 159 | GOOS: linux, 160 | GOARCH: mips64 161 | } 162 | linux_mips64le: 163 | cmds: 164 | - task: build_tpl 165 | vars: { 166 | TASK: "{{.TASK}}", 167 | GOOS: linux, 168 | GOARCH: mips64le 169 | } 170 | windows_386.exe: 171 | cmds: 172 | - task: build_tpl 173 | vars: { 174 | TASK: "{{.TASK}}", 175 | GOOS: windows, 176 | GOARCH: 386 177 | } 178 | windows_amd64.exe: 179 | cmds: 180 | - task: build_tpl 181 | vars: { 182 | TASK: "{{.TASK}}", 183 | GOOS: windows, 184 | GOARCH: amd64 185 | } 186 | windows_amd64_v2.exe: 187 | cmds: 188 | - task: build_tpl 189 | vars: { 190 | TASK: "{{.TASK}}", 191 | GOOS: windows, 192 | GOARCH: amd64, 193 | GOAMD64: v2 194 | } 195 | windows_amd64_v3.exe: 196 | cmds: 197 | - task: build_tpl 198 | vars: { 199 | TASK: "{{.TASK}}", 200 | GOOS: windows, 201 | GOARCH: amd64, 202 | GOAMD64: v3 203 | } 204 | windows_amd64_v4.exe: 205 | cmds: 206 | - task: build_tpl 207 | vars: { 208 | TASK: "{{.TASK}}", 209 | GOOS: windows, 210 | GOARCH: amd64, 211 | GOAMD64: v4 212 | } 213 | darwin_amd64: 214 | cmds: 215 | - task: build_tpl 216 | vars: { 217 | TASK: "{{.TASK}}", 218 | GOOS: darwin, 219 | GOARCH: amd64, 220 | } 221 | darwin_arm64: 222 | cmds: 223 | - task: build_tpl 224 | vars: { 225 | TASK: "{{.TASK}}", 226 | GOOS: darwin, 227 | GOARCH: arm64, 228 | } 229 | docker: 230 | cmds: 231 | - docker build -t adams549659584/go-proxy-bingai:{{.BUILD_VERSION}} -f docker/Dockerfile . 232 | - docker tag adams549659584/go-proxy-bingai:{{.BUILD_VERSION}} adams549659584/go-proxy-bingai 233 | default: 234 | cmds: 235 | - task: clean 236 | - task: build_web 237 | # - task: linux_386 238 | - task: linux_amd64 239 | # - task: linux_amd64_v2 240 | # - task: linux_amd64_v3 241 | # - task: linux_amd64_v4 242 | # - task: linux_armv5 243 | # - task: linux_armv6 244 | # - task: linux_armv7 245 | - task: linux_armv8 246 | # - task: linux_mips_hardfloat 247 | # - task: linux_mipsle_softfloat 248 | # - task: linux_mipsle_hardfloat 249 | # - task: linux_mips64 250 | # - task: linux_mips64le 251 | # - task: windows_386.exe 252 | # - task: windows_amd64.exe 253 | # - task: windows_amd64_v2.exe 254 | # - task: windows_amd64_v3.exe 255 | # - task: windows_amd64_v4.exe 256 | # - task: darwin_amd64 257 | # - task: darwin_arm64 258 | release: 259 | cmds: 260 | - task: default 261 | -------------------------------------------------------------------------------- /frontend/types/bing/index.d.ts: -------------------------------------------------------------------------------- 1 | declare const sj_evt: { 2 | bind: (n: string, t: Function, i: boolean, r?: any) => void; 3 | fire: (n: string) => void; 4 | }; 5 | declare const SydneyFullScreenConv: { 6 | initWithWaitlistUpdate: (n: object, t: number) => void; 7 | }; 8 | 9 | /** 10 | * 更有创造力 | 更平衡 | 更精确 11 | */ 12 | type ToneType = 'Creative' | 'Balanced' | 'Precise'; 13 | 14 | interface BingMessage { 15 | /** 16 | * 消息内容 17 | */ 18 | text: string; 19 | 20 | /** 21 | * 还可以图片链接? 22 | */ 23 | imageUrl?: string; 24 | } 25 | 26 | type BingMessageType = keyof { 27 | ActionRequest: 'ActionRequest'; 28 | Ads: 'Ads'; 29 | AdsQuery: 'AdsQuery'; 30 | CaptchaChallenge: 'CaptchaChallenge'; 31 | Chat: 'Chat'; 32 | Context: 'Context'; 33 | Disengaged: 'Disengaged'; 34 | SearchQuery: 'SearchQuery'; 35 | Internal: 'Internal'; 36 | Suggestion: 'Suggestion'; 37 | InternalSuggestions: 'InternalSuggestions'; 38 | InternalSearchQuery: 'InternalSearchQuery'; 39 | InternalSearchResult: 'InternalSearchResult'; 40 | InternalActionMarker: 'InternalActionMarker'; 41 | InternalStateMarker: 'InternalStateMarker'; 42 | InternalLoaderMessage: 'InternalLoaderMessage'; 43 | Progress: 'Progress'; 44 | GenerateContentQuery: 'GenerateContentQuery'; 45 | RenderCardRequest: 'RenderCardRequest'; 46 | SemanticSerp: 'SemanticSerp'; 47 | Any: 'Any'; 48 | ChatName: 'ChatName'; 49 | }; 50 | 51 | interface SuggestedResponses { 52 | contentOrigin: string; 53 | hiddenText?: string; 54 | messageId: string; 55 | messageType: BingMessageType; 56 | offense: string; 57 | text: string; 58 | } 59 | interface TextMessageModel { 60 | author: string; 61 | messageId: string; 62 | messageType: BingMessageType; 63 | type: string; 64 | contentOrigin: string; 65 | imageUrl: string; 66 | isFinalized: boolean; 67 | text: string; 68 | suggestedResponses: SuggestedResponses[]; 69 | } 70 | 71 | interface BingChat { 72 | // 是否请求响应中 73 | isRequestPending: boolean; 74 | api: { 75 | bing: { 76 | _endpoint: string; 77 | captcha: { 78 | client: { 79 | sendOperationRequest: (operationArguments: object, operationSpec: object) => {}; 80 | }; 81 | }; 82 | conversation: { 83 | /** 84 | * 创建请求 85 | */ 86 | create: (O) => {}; 87 | /** 88 | * 聊天记录 89 | */ 90 | getChats: (O) => {}; 91 | }; 92 | }; 93 | sydney: {}; 94 | }; 95 | requestToken: { 96 | cancel: () => Promise; 97 | /** 98 | * 聊天完成 99 | * @param O 100 | * @returns 101 | */ 102 | complete: (O) => Promise; 103 | error: (O) => Promise; 104 | }; 105 | onConversationExpired: PublicSubscribeEvent; 106 | onLoadConversationInvoked: PublicSubscribeEvent; 107 | onMessage: PublicSubscribeEvent; 108 | onPendingRequestStateChanged: PublicSubscribeEvent; 109 | onRequestGenerated: PublicSubscribeEvent; 110 | onResponseRendered: PublicSubscribeEvent; 111 | onStreamingComplete: PublicSubscribeEvent; 112 | } 113 | 114 | interface BingConversation { 115 | clientId: string; 116 | conversationType: string; 117 | hashedSignature: string; 118 | id: string; 119 | isExpired: boolean; 120 | messages: TextMessageModel[]; 121 | state: string; 122 | suggestions: SuggestedResponses[]; 123 | updateId: (Id: string, expiry: Date, clientId: string, signature: string) => {}; 124 | } 125 | 126 | type PublicSubscribeEvent = (callback: Function, thisArgs, disposables) => {}; 127 | 128 | declare const CIB: { 129 | /** 130 | * 微软 bing 版本信息 131 | */ 132 | version: { 133 | buildTimestamp: string; 134 | commit: string; 135 | version: string; 136 | }; 137 | /** 138 | * 整个必应聊天 cib-serp 139 | */ 140 | vm: { 141 | errorService: { 142 | onAppErrorStateChange: PublicSubscribeEvent; 143 | onChatErrorStateChange: PublicSubscribeEvent; 144 | onChatWarningStateChange: PublicSubscribeEvent; 145 | }; 146 | /** 147 | * 是否手机版 148 | */ 149 | isMobile: boolean; 150 | actionBar: { 151 | /** 152 | * 输入框 153 | */ 154 | input: HTMLTextAreaElement; 155 | /** 156 | * 输入框文本 赋值即输入问题 157 | */ 158 | inputText: string; 159 | /** 160 | * 自动建议的前置文本 161 | */ 162 | autoSuggestPrependedText: string; 163 | /** 164 | * 自动建议附加文本 165 | */ 166 | autoSuggestAppendedText: string; 167 | /** 168 | * 提交当前输入框问题 169 | */ 170 | submitInputText: () => {}; 171 | }; 172 | conversation: BingConversation; 173 | /** 174 | * 历史记录 175 | */ 176 | sidePanel: { 177 | /** 178 | * M 是否显示 179 | */ 180 | isVisibleMobile: boolean; 181 | /** 182 | * PC 是否显示 get shouldShowPanel 183 | */ 184 | isVisibleDesktop: boolean; 185 | }; 186 | /** 187 | * 选择对话样式 188 | */ 189 | toneSelector: { 190 | /** 191 | * 更有创造力 | 更平衡 | 更精确 -- 直接赋值即可切换 set tone(O) 192 | */ 193 | tone: ToneType; 194 | }; 195 | }; 196 | config: { 197 | bing: { 198 | baseUrl: string; 199 | }; 200 | edgeAction: { 201 | /** 202 | * hook 接收消息,需启用 203 | */ 204 | hookIncomingMessage: (message) => boolean; 205 | isEnabled: boolean; 206 | }; 207 | features: { 208 | enableThreads: boolean; 209 | /** 210 | * 获取聊天历史 211 | */ 212 | enableGetChats: boolean; 213 | }; 214 | sydney: { 215 | baseUrl: string; 216 | /** 217 | * 安全域名?移除 localhost,开发即可 create 218 | */ 219 | hostnamesToBypassSecureConnection: string[]; 220 | expiryInMinutes: number; 221 | }; 222 | messaging: { 223 | /** 224 | * 单次最大对话数 225 | */ 226 | maxTurnsPerConversation: number; 227 | /** 228 | * 打字机速度调节,默认 1000 / 15 = 55 (每秒15字?) 229 | */ 230 | messageBufferWorkerStreamDelayMS: number; 231 | }; 232 | }; 233 | manager: { 234 | chat: BingChat; 235 | conversation: BingConversation; 236 | /** 237 | * 重置聊天 238 | */ 239 | resetConversation: () => {}; 240 | 241 | /** 242 | * 发送消息 243 | * @param O 消息内容 244 | * @param B 默认 false ,则发送消息 245 | * @param G 默认 chat 消息类型 246 | * @param U 默认 Keyboard 似乎只是区分输入及语音 247 | * @returns 248 | */ 249 | sendMessage: (O: BingMessage, B?: boolean, G?: BingMessageType, U?: 'Keyboard' | 'Speech') => {}; 250 | 251 | onResetConversationInvoked: PublicSubscribeEvent; 252 | }; 253 | 254 | onConsentGiven: PublicSubscribeEvent; 255 | onConversationExpired: PublicSubscribeEvent; 256 | onConversationRequestStateChange: PublicSubscribeEvent; 257 | onInputMethodChanged: PublicSubscribeEvent; 258 | onMobileUpsellPopupShown: PublicSubscribeEvent; 259 | onModeChanged: PublicSubscribeEvent; 260 | onModeChanging: PublicSubscribeEvent; 261 | onResetConversation: PublicSubscribeEvent; 262 | onResponseRendered: PublicSubscribeEvent; 263 | onResponseToneChanged: PublicSubscribeEvent; 264 | onSerpSlotSuggestionInvoked: PublicSubscribeEvent; 265 | /** 266 | * 接收流完成 267 | */ 268 | onStreamingComplete: PublicSubscribeEvent; 269 | onThreadLoadInvoked(listener); 270 | onThreadLoaded(listener); 271 | onWorkToggleChanged: PublicSubscribeEvent; 272 | 273 | responseTone: ToneType; 274 | }; 275 | -------------------------------------------------------------------------------- /frontend/src/components/ChatPromptStore/ChatPromptStore.vue: -------------------------------------------------------------------------------- 1 | 144 | 145 | 198 | -------------------------------------------------------------------------------- /web/img/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /frontend/public/img/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /web/js/bing/chat/amd.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | var amd,define,require;(function(n){function e(n,i,u){t[n]||(t[n]={dependencies:i,callback:u},r(n))}function r(n){if(n){if(n)return u(n)}else{if(!f){for(var r in t)u(r);f=!0}return i}}function u(n){var s,e;if(i[n])return i[n];if(t.hasOwnProperty(n)){var h=t[n],f=h.dependencies,l=h.callback,a=r,o={},c=[a,o];if(f.length<2)throw"invalid usage";else if(f.length>2)for(s=f.slice(2,f.length),e=0;e0)for(o=!1,f=0;f=0?encodeURIComponent(atob(decodeURIComponent(h[1]))):h[1])):f=encodeURIComponent(n[u]("href")),clc.furl&&!n[u]("data-private")?o+="&url="+f:clc.mfurl&&(o+="&abc="+f));r&&(o+="&source="+r);c="";clc.mc&&(c="&c="+ctcc++);l="&"+o+c;_w.si_sbwu(l)||_w[e]&&_w[e](l,n,i,s);break}if(t)break}}catch(v){_w.SharedLogHelper?SharedLogHelper.LogWarning("clickEX",null,v):(new Image).src=_G.lsUrl+'&Type=Event.ClientInst&DATA=[{"T":"CI.Warning","FID":"CI","Name":"JSWarning","Text":'+v.message+"}]"}return!0};_w.si_sbwu||(_w.si_sbwu=function(){return!1}),function(){_w._G&&(_G.si_ct_e="click")}();var wlc_d = 1500, wlc_t =63820323883;;var perf;(function(n){function f(n){return i.hasOwnProperty(n)?i[n]:n}function e(n){var t="S";return n==0?t="P":n==2&&(t="M"),t}function o(n){for(var c,i=[],t={},r,l=0;l").concat(i,"<\/TS><\/D><\/E>"),s="".concat(h,"<\/Events>").concat(i,"<\/STS><\/ClientInstRequest>"),u=!_w.navigator||!navigator[o];if(!u)try{navigator[o](e,s)}catch(c){u=!0}u&&(r=sj_gx(),r.open("POST",e,!0),r.setRequestHeader("Content-Type","text/xml"),r.send(s))}}(window.perf);var perf;(function(n){function a(){return c(Math.random()*1e4)}function o(){return y?c(f.now())+l:+new Date}function v(n,r,f){t.length===0&&i&&sb_st(u,1e3);t.push({k:n,v:r,t:f})}function p(n){return i||(r=n),!i}function w(n,t){t||(t=o());v(n,t,0)}function b(n,t){v(n,t,1)}function u(){var u,f;if(t.length){for(u=0;u=0){for(r in f)h=f[r],typeof h=="number"&&h>0&&r!=="navigationStart"&&r!==s&&u.mark(r,h);_G.FCT&&u.mark("FN",_G.FCT);_G.BCT&&u.mark("BN",_G.BCT)}u.record("nav",s in f?f[s]:performance[s].type)}e="connection";c="";_w.navigator&&navigator[e]&&(c=',"net":"'.concat(navigator[e].type,'"'),navigator[e].downlinkMax&&(c+=',"dlMax":"'.concat(navigator[e].downlinkMax,'"')));_G.PPImg=new Image;_G.PPImg.src=_G.lsUrl+'&Type=Event.CPT&DATA={"pp":{"S":"'+(t||"L")+'",'+o.join(",")+',"CT":'+(n-_G.ST)+',"IL":'+_d.images.length+"}"+(_G.C1?","+_G.C1:"")+c+"}"+(_G.P?"&P="+_G.P:"")+(_G.DA?"&DA="+_G.DA:"")+(_G.MN?"&MN="+_G.MN:"");_G.PPS=1;sb_st(function(){u&&u.flush();sj_evt.fire("onPP");sj_evt.fire(_w.p1)},1)}};_w.onbeforeunload=function(){si_PP(new Date,"A")};sj_evt.bind("ajax.requestSent",function(){window.perf&&perf.reset()});var sj_log=function(n,t,i){var r=new RegExp('"',"g");(new Image).src=_G.lsUrl+'&Type=Event.ClientInst&DATA=[{"T":"'+n+'","FID":"CI","Name":"'+t+'","Text":"'+escape(i.replace(r,""))+'"}]'};var BM=BM||{},adrule="."+_G.adc+" > ul";BM.rules={".b_scopebar":[0,80,0],".b_logo":[-1,-1,0],".b_searchboxForm":[100,19,0],"#id_h":[-1,-1,0],"#b_tween":[-1,-1,1],"#b_results":[100,-1,1],"#b_context":[710,-1,1],"#b_navheader":[-1,-1,0],"#bfb-answer":[-1,-1,1],".tab-menu > ul":[-1,-1,1],".b_footer":[0,-1,0],"#b_notificationContainer":[-1,-1,0],"#ajaxMaskLayer":[-1,-1,0],"img,div[data-src],.rms_img":[-1,-1,0],iframe:[-1,-1,0]};BM.rules[adrule]=[-1,-1,1];var BM=BM||{};(function(n){function u(n,u){n in t||(t[n]=[]);!u.compute||n in r||(r[n]=u.compute);!u.unload||n in i||(i[n]=u.unload);u.load&&u.load()}function f(n,i){t[n].push({t:s(),i:i})}function e(n){return n in i&&i[n](),n in t?t[n]:void 0}function o(){for(var n in r)r[n]()}function s(){return window.performance&&performance.now?Math.round(performance.now()):new Date-window.si_ST}var t={},i={},r={};n.wireup=u;n.enqueue=f;n.dequeue=e;n.trigger=o})(BM);(function(n){function i(){var i=document.documentElement,r=document.body,u="innerWidth"in window?window.innerWidth:i.clientWidth,f="innerHeight"in window?window.innerHeight:i.clientHeight,e=window.pageXOffset||i.scrollLeft,o=window.pageYOffset||i.scrollTop,s=document.visibilityState||"default";n.enqueue(t,{x:e,y:o,w:u,h:f,dw:r.clientWidth,dh:r.clientHeight,v:s})}var t="V";n.wireup(t,{load:null,compute:i,unload:null})})(BM);(function(n){function i(){var e,o,u,s,f,r;if(document.querySelector&&document.querySelectorAll){e=[];o=n.rules;for(u in o)for(s=o[u],u+=!s[2]?"":" >*",f=document.querySelectorAll(u),r=0;r2)for(s=f.slice(2,f.length),e=0;e0)for(o=!1,f=0;f=0?encodeURIComponent(atob(decodeURIComponent(h[1]))):h[1])):f=encodeURIComponent(n[u]("href")),clc.furl&&!n[u]("data-private")?o+="&url="+f:clc.mfurl&&(o+="&abc="+f));r&&(o+="&source="+r);c="";clc.mc&&(c="&c="+ctcc++);l="&"+o+c;_w.si_sbwu(l)||_w[e]&&_w[e](l,n,i,s);break}if(t)break}}catch(v){_w.SharedLogHelper?SharedLogHelper.LogWarning("clickEX",null,v):(new Image).src=_G.lsUrl+'&Type=Event.ClientInst&DATA=[{"T":"CI.Warning","FID":"CI","Name":"JSWarning","Text":'+v.message+"}]"}return!0};_w.si_sbwu||(_w.si_sbwu=function(){return!1}),function(){_w._G&&(_G.si_ct_e="click")}();var wlc_d = 1500, wlc_t =63820323883;;var perf;(function(n){function f(n){return i.hasOwnProperty(n)?i[n]:n}function e(n){var t="S";return n==0?t="P":n==2&&(t="M"),t}function o(n){for(var c,i=[],t={},r,l=0;l").concat(i,"<\/TS><\/D><\/E>"),s="".concat(h,"<\/Events>").concat(i,"<\/STS><\/ClientInstRequest>"),u=!_w.navigator||!navigator[o];if(!u)try{navigator[o](e,s)}catch(c){u=!0}u&&(r=sj_gx(),r.open("POST",e,!0),r.setRequestHeader("Content-Type","text/xml"),r.send(s))}}(window.perf);var perf;(function(n){function a(){return c(Math.random()*1e4)}function o(){return y?c(f.now())+l:+new Date}function v(n,r,f){t.length===0&&i&&sb_st(u,1e3);t.push({k:n,v:r,t:f})}function p(n){return i||(r=n),!i}function w(n,t){t||(t=o());v(n,t,0)}function b(n,t){v(n,t,1)}function u(){var u,f;if(t.length){for(u=0;u=0){for(r in f)h=f[r],typeof h=="number"&&h>0&&r!=="navigationStart"&&r!==s&&u.mark(r,h);_G.FCT&&u.mark("FN",_G.FCT);_G.BCT&&u.mark("BN",_G.BCT)}u.record("nav",s in f?f[s]:performance[s].type)}e="connection";c="";_w.navigator&&navigator[e]&&(c=',"net":"'.concat(navigator[e].type,'"'),navigator[e].downlinkMax&&(c+=',"dlMax":"'.concat(navigator[e].downlinkMax,'"')));_G.PPImg=new Image;_G.PPImg.src=_G.lsUrl+'&Type=Event.CPT&DATA={"pp":{"S":"'+(t||"L")+'",'+o.join(",")+',"CT":'+(n-_G.ST)+',"IL":'+_d.images.length+"}"+(_G.C1?","+_G.C1:"")+c+"}"+(_G.P?"&P="+_G.P:"")+(_G.DA?"&DA="+_G.DA:"")+(_G.MN?"&MN="+_G.MN:"");_G.PPS=1;sb_st(function(){u&&u.flush();sj_evt.fire("onPP");sj_evt.fire(_w.p1)},1)}};_w.onbeforeunload=function(){si_PP(new Date,"A")};sj_evt.bind("ajax.requestSent",function(){window.perf&&perf.reset()});var sj_log=function(n,t,i){var r=new RegExp('"',"g");(new Image).src=_G.lsUrl+'&Type=Event.ClientInst&DATA=[{"T":"'+n+'","FID":"CI","Name":"'+t+'","Text":"'+escape(i.replace(r,""))+'"}]'};var BM=BM||{},adrule="."+_G.adc+" > ul";BM.rules={".b_scopebar":[0,80,0],".b_logo":[-1,-1,0],".b_searchboxForm":[100,19,0],"#id_h":[-1,-1,0],"#b_tween":[-1,-1,1],"#b_results":[100,-1,1],"#b_context":[710,-1,1],"#b_navheader":[-1,-1,0],"#bfb-answer":[-1,-1,1],".tab-menu > ul":[-1,-1,1],".b_footer":[0,-1,0],"#b_notificationContainer":[-1,-1,0],"#ajaxMaskLayer":[-1,-1,0],"img,div[data-src],.rms_img":[-1,-1,0],iframe:[-1,-1,0]};BM.rules[adrule]=[-1,-1,1];var BM=BM||{};(function(n){function u(n,u){n in t||(t[n]=[]);!u.compute||n in r||(r[n]=u.compute);!u.unload||n in i||(i[n]=u.unload);u.load&&u.load()}function f(n,i){t[n].push({t:s(),i:i})}function e(n){return n in i&&i[n](),n in t?t[n]:void 0}function o(){for(var n in r)r[n]()}function s(){return window.performance&&performance.now?Math.round(performance.now()):new Date-window.si_ST}var t={},i={},r={};n.wireup=u;n.enqueue=f;n.dequeue=e;n.trigger=o})(BM);(function(n){function i(){var i=document.documentElement,r=document.body,u="innerWidth"in window?window.innerWidth:i.clientWidth,f="innerHeight"in window?window.innerHeight:i.clientHeight,e=window.pageXOffset||i.scrollLeft,o=window.pageYOffset||i.scrollTop,s=document.visibilityState||"default";n.enqueue(t,{x:e,y:o,w:u,h:f,dw:r.clientWidth,dh:r.clientHeight,v:s})}var t="V";n.wireup(t,{load:null,compute:i,unload:null})})(BM);(function(n){function i(){var e,o,u,s,f,r;if(document.querySelector&&document.querySelectorAll){e=[];o=n.rules;for(u in o)for(s=o[u],u+=!s[2]?"":" >*",f=document.querySelectorAll(u),r=0;r res.json()) 17 | // .then((res) => { 18 | // const limitCount = 10000; 19 | // return res.data.filter((x) => parseInt(x[2].replace(/,/g,'')) >= limitCount).map((x) => `{"${x[0]}", "${x[1]}"}, //${x[2]}`); 20 | // }); 21 | // console.log(`results : `,results); 22 | // return results.join('\n'); 23 | // } 24 | // 25 | // copy(await getIpRange()); 26 | var IP_RANGE = [][]string{ 27 | {"3.2.50.0", "3.5.31.255"}, //192,000 28 | {"3.5.74.0", "3.5.133.255"}, //15,360 29 | {"3.12.0.0", "3.23.255.255"}, //786,432 30 | {"3.30.0.0", "3.33.34.255"}, //205,568 31 | {"3.33.36.0", "3.33.255.255"}, //56,320 32 | {"3.40.0.0", "3.63.255.255"}, //1,572,864 33 | {"3.80.0.0", "3.95.255.255"}, //1,048,576 34 | {"3.100.0.0", "3.103.255.255"}, //262,144 35 | {"3.116.0.0", "3.119.255.255"}, //262,144 36 | {"3.128.0.0", "3.247.255.255"}, //7,864,320 37 | {"4.0.0.0", "4.1.179.255"}, //111,616 38 | {"4.1.181.0", "4.14.241.255"}, //867,584 39 | {"4.15.21.0", "4.16.47.255"}, //72,448 40 | {"4.16.55.0", "4.18.65.255"}, //133,888 41 | {"4.18.68.0", "4.28.135.255"}, //672,768 42 | {"4.28.139.0", "4.31.207.255"}, //214,272 43 | {"4.31.209.0", "4.33.203.255"}, //129,792 44 | {"4.33.234.0", "4.37.0.255"}, //202,496 45 | {"4.37.2.0", "4.42.31.255"}, //335,360 46 | {"4.42.35.0", "4.55.87.255"}, //865,536 47 | {"4.55.95.0", "4.59.175.255"}, //282,880 48 | {"4.59.179.0", "4.71.152.255"}, //779,776 49 | {"4.71.154.0", "4.143.255.255"}, //4,744,704 50 | {"4.148.0.0", "4.157.255.255"}, //655,360 51 | {"4.184.0.0", "4.184.55.255"}, //14,336 52 | {"4.198.160.0", "4.198.255.255"}, //24,576 53 | {"4.203.96.0", "4.203.255.255"}, //40,960 54 | {"4.227.0.0", "4.227.255.255"}, //65,536 55 | {"4.232.200.0", "4.232.255.255"}, //14,336 56 | {"4.236.0.0", "4.236.255.255"}, //65,536 57 | {"4.242.0.0", "4.242.255.255"}, //65,536 58 | {"4.246.0.0", "4.246.255.255"}, //65,536 59 | {"4.248.128.0", "4.249.255.255"}, //98,304 60 | {"4.255.0.0", "4.255.255.255"}, //65,536 61 | {"5.78.0.0", "5.78.255.255"}, //65,536 62 | {"5.153.23.0", "5.153.63.255"}, //10,496 63 | {"6.0.0.0", "8.3.111.255"}, //33,779,712 64 | {"8.3.128.0", "8.5.250.255"}, //162,560 65 | {"8.5.252.0", "8.7.243.255"}, //129,024 66 | {"8.7.245.0", "8.10.5.255"}, //135,424 67 | {"8.10.8.0", "8.14.121.255"}, //291,328 68 | {"8.14.123.0", "8.14.196.255"}, //18,944 69 | {"8.15.0.0", "8.17.204.255"}, //183,552 70 | {"8.17.207.0", "8.18.49.255"}, //25,344 71 | {"8.18.51.0", "8.18.112.255"}, //15,872 72 | {"8.18.197.0", "8.19.7.255"}, //17,152 73 | {"8.19.9.0", "8.20.99.255"}, //88,832 74 | {"8.20.128.0", "8.20.252.255"}, //32,000 75 | {"8.21.42.0", "8.21.109.255"}, //17,408 76 | {"8.21.112.0", "8.22.175.255"}, //81,920 77 | {"8.22.177.0", "8.23.138.255"}, //55,808 78 | {"8.23.140.0", "8.23.239.255"}, //25,600 79 | {"8.24.16.0", "8.24.241.255"}, //57,856 80 | {"8.24.245.0", "8.25.95.255"}, //27,392 81 | {"8.25.99.0", "8.25.248.255"}, //38,400 82 | {"8.25.250.0", "8.26.93.255"}, //25,600 83 | {"8.26.95.0", "8.26.175.255"}, //20,736 84 | {"8.26.181.0", "8.27.63.255"}, //35,584 85 | {"8.27.80.0", "8.28.3.255"}, //46,080 86 | {"8.28.21.0", "8.28.81.255"}, //15,616 87 | {"8.28.83.0", "8.28.126.255"}, //11,264 88 | {"8.28.128.0", "8.28.200.255"}, //18,688 89 | {"8.28.214.0", "8.29.104.255"}, //37,632 90 | {"8.29.106.0", "8.29.223.255"}, //30,208 91 | {"8.29.225.0", "8.30.207.255"}, //61,184 92 | {"8.30.235.0", "8.33.95.255"}, //161,024 93 | {"8.33.138.0", "8.34.68.255"}, //47,872 94 | {"8.34.72.0", "8.34.145.255"}, //18,944 95 | {"8.34.147.0", "8.34.199.255"}, //13,568 96 | {"8.34.224.0", "8.35.56.255"}, //22,784 97 | {"8.35.60.0", "8.35.148.255"}, //22,784 98 | {"8.35.150.0", "8.35.210.255"}, //15,616 99 | {"8.35.212.0", "8.36.76.255"}, //30,976 100 | {"8.36.78.0", "8.36.129.255"}, //13,312 101 | {"8.36.131.0", "8.36.215.255"}, //21,760 102 | {"8.36.221.0", "8.37.40.255"}, //19,456 103 | {"8.37.44.0", "8.38.146.255"}, //91,904 104 | {"8.38.173.0", "8.39.5.255"}, //22,784 105 | {"8.39.19.0", "8.39.124.255"}, //27,136 106 | {"8.39.145.0", "8.39.200.255"}, //14,336 107 | {"8.39.216.0", "8.40.25.255"}, //16,896 108 | {"8.40.32.0", "8.40.106.255"}, //19,200 109 | {"8.40.141.0", "8.41.4.255"}, //30,720 110 | {"8.41.40.0", "8.42.7.255"}, //57,344 111 | {"8.42.9.0", "8.42.50.255"}, //10,752 112 | {"8.42.56.0", "8.42.160.255"}, //26,880 113 | {"8.42.173.0", "8.42.244.255"}, //18,432 114 | {"8.42.246.0", "8.43.120.255"}, //33,536 115 | {"8.43.124.0", "8.43.223.255"}, //25,600 116 | {"8.44.7.0", "8.44.57.255"}, //13,056 117 | {"8.44.93.0", "8.45.95.255"}, //66,304 118 | {"8.45.97.0", "8.46.112.255"}, //69,632 119 | {"8.46.119.0", "8.47.68.255"}, //52,736 120 | {"8.47.70.0", "8.49.215.255"}, //168,448 121 | {"8.49.217.0", "8.50.11.255"}, //13,056 122 | {"8.50.21.0", "8.51.0.255"}, //60,416 123 | {"8.51.64.0", "8.127.255.255"}, //5,029,888 124 | {"8.192.0.0", "8.192.108.255"}, //27,904 125 | {"8.192.110.0", "8.207.255.255"}, //1,020,416 126 | {"8.221.0.0", "8.221.127.255"}, //32,768 127 | {"8.224.0.0", "8.238.42.255"}, //928,512 128 | {"8.238.53.0", "8.238.142.255"}, //23,040 129 | {"8.238.205.0", "8.241.255.255"}, //209,664 130 | {"8.244.80.0", "8.244.130.255"}, //13,056 131 | {"8.244.132.0", "8.244.255.255"}, //31,744 132 | {"8.245.64.0", "8.245.127.255"}, //16,384 133 | {"8.245.160.0", "8.245.255.255"}, //24,576 134 | {"8.246.139.0", "8.246.191.255"}, //13,568 135 | {"8.246.201.0", "9.9.8.255"}, //1,196,032 136 | {"9.9.10.0", "9.255.255.255"}, //16,184,832 137 | {"11.0.0.0", "11.210.23.255"}, //13,768,704 138 | {"11.210.25.0", "12.5.185.255"}, //3,383,552 139 | {"12.5.188.0", "12.19.87.255"}, //891,904 140 | {"12.19.96.0", "12.24.140.255"}, //339,200 141 | {"12.24.142.0", "12.35.147.255"}, //722,432 142 | {"12.35.149.0", "12.41.127.255"}, //387,840 143 | {"12.41.136.0", "12.46.103.255"}, //319,488 144 | {"12.46.106.0", "12.129.111.255"}, //5,441,024 145 | {"12.129.113.0", "12.139.119.255"}, //657,152 146 | {"12.139.121.0", "12.144.81.255"}, //317,696 147 | {"12.144.88.0", "12.159.147.255"}, //998,400 148 | {"12.159.152.0", "12.174.223.255"}, //1,001,472 149 | {"12.175.0.0", "12.184.30.255"}, //597,760 150 | {"12.184.32.0", "12.192.62.255"}, //532,224 151 | {"12.192.64.0", "12.196.47.255"}, //258,048 152 | {"12.196.63.0", "12.204.9.255"}, //510,720 153 | {"12.204.16.0", "12.206.179.255"}, //173,056 154 | {"12.206.184.0", "12.208.168.255"}, //127,232 155 | {"12.208.172.0", "13.32.0.255"}, //5,199,104 156 | {"13.32.176.0", "13.32.215.255"}, //10,240 157 | {"13.34.93.0", "13.34.255.255"}, //41,728 158 | {"13.35.73.0", "13.35.127.255"}, //14,080 159 | {"13.44.0.0", "13.47.255.255"}, //262,144 160 | {"13.52.0.0", "13.52.255.255"}, //65,536 161 | {"13.56.0.0", "13.66.255.255"}, //720,896 162 | {"13.67.128.0", "13.68.255.255"}, //98,304 163 | {"13.71.192.0", "13.72.191.255"}, //65,536 164 | {"13.73.32.0", "13.73.95.255"}, //16,384 165 | {"13.77.64.0", "13.77.255.255"}, //49,152 166 | {"13.78.128.0", "13.78.255.255"}, //32,768 167 | {"13.82.0.0", "13.86.255.255"}, //327,680 168 | {"13.87.127.0", "13.88.199.255"}, //84,224 169 | {"13.89.0.0", "13.92.255.255"}, //262,144 170 | {"13.93.128.0", "13.93.255.255"}, //32,768 171 | {"13.96.0.0", "13.103.255.255"}, //524,288 172 | {"13.104.1.0", "13.104.41.255"}, //10,496 173 | {"13.105.204.0", "13.105.255.255"}, //13,312 174 | {"13.107.55.0", "13.107.136.255"}, //20,992 175 | {"13.107.141.0", "13.107.197.255"}, //14,592 176 | {"13.107.255.0", "13.111.255.255"}, //262,400 177 | {"13.116.0.0", "13.120.63.255"}, //278,528 178 | {"13.120.128.0", "13.121.64.255"}, //49,408 179 | {"13.121.128.0", "13.122.63.255"}, //49,152 180 | {"13.122.128.0", "13.123.255.255"}, //98,304 181 | {"13.128.0.0", "13.199.255.255"}, //4,718,592 182 | {"13.216.0.0", "13.224.15.255"}, //528,384 183 | {"13.226.9.0", "13.226.56.255"}, //12,288 184 | {"13.226.176.0", "13.226.243.255"}, //17,408 185 | {"13.240.0.0", "13.243.255.255"}, //262,144 186 | {"13.248.128.0", "13.248.255.255"}, //32,768 187 | {"13.249.34.0", "13.249.143.255"}, //28,160 188 | {"13.249.176.0", "13.249.240.255"}, //16,640 189 | {"13.252.0.0", "13.255.255.255"}, //262,144 190 | {"15.0.0.0", "15.106.75.255"}, //6,966,272 191 | {"15.106.77.0", "15.109.211.255"}, //231,168 192 | {"15.109.213.0", "15.113.77.255"}, //227,584 193 | {"15.113.79.0", "15.114.96.255"}, //70,144 194 | {"15.114.98.0", "15.118.101.255"}, //263,168 195 | {"15.118.103.0", "15.119.207.255"}, //92,416 196 | {"15.119.209.0", "15.122.23.255"}, //149,248 197 | {"15.122.25.0", "15.124.124.255"}, //156,672 198 | {"15.124.128.0", "15.127.94.255"}, //188,160 199 | {"15.127.98.0", "15.145.19.255"}, //1,159,680 200 | {"15.145.24.0", "15.151.255.255"}, //452,608 201 | {"15.153.0.0", "15.155.255.255"}, //196,608 202 | {"15.158.192.0", "15.159.255.255"}, //81,920 203 | {"15.162.0.0", "15.163.255.255"}, //131,072 204 | {"15.166.0.0", "15.167.255.255"}, //131,072 205 | {"15.169.0.0", "15.177.23.255"}, //530,432 206 | {"15.177.101.0", "15.183.255.255"}, //432,896 207 | {"15.186.0.0", "15.187.255.255"}, //131,072 208 | {"15.189.0.0", "15.189.255.255"}, //65,536 209 | {"15.190.64.0", "15.192.255.255"}, //180,224 210 | {"15.193.11.0", "15.195.184.255"}, //175,616 211 | {"15.195.186.0", "15.205.255.255"}, //673,280 212 | {"15.208.0.0", "15.219.200.255"}, //772,352 213 | {"15.219.202.0", "15.220.43.255"}, //25,088 214 | {"15.221.54.0", "15.221.127.255"}, //18,944 215 | {"15.221.153.0", "15.221.255.255"}, //26,368 216 | {"15.224.0.0", "15.227.255.255"}, //262,144 217 | {"15.231.0.0", "15.234.255.255"}, //262,144 218 | {"15.238.0.0", "15.247.255.255"}, //655,360 219 | {"15.248.72.0", "16.0.89.255"}, //528,896 220 | } 221 | 222 | // 获取真实有效的随机IP 223 | func GetRandomIP() string { 224 | seed := time.Now().UnixNano() 225 | rng := rand.New(rand.NewSource(seed)) 226 | 227 | // 生成随机索引 228 | randomIndex := rng.Intn(len(IP_RANGE)) 229 | 230 | // 获取随机 IP 地址范围 231 | startIP := IP_RANGE[randomIndex][0] 232 | endIP := IP_RANGE[randomIndex][1] 233 | 234 | // 将起始 IP 地址转换为整数形式 235 | startIPInt := ipToUint32(net.ParseIP(startIP)) 236 | // 将结束 IP 地址转换为整数形式 237 | endIPInt := ipToUint32(net.ParseIP(endIP)) 238 | 239 | // 生成随机 IP 地址 240 | randomIPInt := rng.Uint32()%(endIPInt-startIPInt+1) + startIPInt 241 | randomIP := uint32ToIP(randomIPInt) 242 | 243 | return randomIP 244 | } 245 | 246 | // 将 IP 地址转换为 uint32 247 | func ipToUint32(ip net.IP) uint32 { 248 | ip = ip.To4() 249 | var result uint32 250 | result += uint32(ip[0]) << 24 251 | result += uint32(ip[1]) << 16 252 | result += uint32(ip[2]) << 8 253 | result += uint32(ip[3]) 254 | return result 255 | } 256 | 257 | // 将 uint32 转换为 IP 地址 258 | func uint32ToIP(intIP uint32) string { 259 | ip := fmt.Sprintf("%d.%d.%d.%d", byte(intIP>>24), byte(intIP>>16), byte(intIP>>8), byte(intIP)) 260 | return ip 261 | } 262 | -------------------------------------------------------------------------------- /frontend/src/views/chat/components/Chat/Chat.vue: -------------------------------------------------------------------------------- 1 | 277 | 278 | 321 | -------------------------------------------------------------------------------- /common/proxy.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "bytes" 5 | "compress/gzip" 6 | "fmt" 7 | "io" 8 | "log" 9 | "math/rand" 10 | "net/http" 11 | "net/http/httputil" 12 | "net/url" 13 | "strconv" 14 | "strings" 15 | "time" 16 | 17 | "github.com/andybalholm/brotli" 18 | "golang.org/x/net/proxy" 19 | ) 20 | 21 | var ( 22 | BING_SYDNEY_DOMAIN = "https://sydney.bing.com" 23 | // BING_CHAT_URL, _ = url.Parse(BING_CHAT_DOMAIN + "/sydney/ChatHub") 24 | BING_SYDNEY_URL, _ = url.Parse(BING_SYDNEY_DOMAIN) 25 | BING_URL, _ = url.Parse("https://www.bing.com") 26 | // EDGE_SVC_URL, _ = url.Parse("https://edgeservices.bing.com") 27 | KEEP_REQ_HEADER_MAP = map[string]bool{ 28 | "Accept": true, 29 | "Accept-Encoding": true, 30 | "Accept-Language": true, 31 | "Referer": true, 32 | "Connection": true, 33 | "Cookie": true, 34 | "Upgrade": true, 35 | "User-Agent": true, 36 | "Sec-Websocket-Extensions": true, 37 | "Sec-Websocket-Key": true, 38 | "Sec-Websocket-Version": true, 39 | "X-Request-Id": true, 40 | "X-Forwarded-For": true, 41 | "Content-Length": true, 42 | "Content-Type": true, 43 | "Access-Control-Request-Headers": true, 44 | "Access-Control-Request-Method": true, 45 | } 46 | DEL_LOCATION_DOMAINS = []string{ 47 | "https://cn.bing.com", 48 | "https://www.bing.com", 49 | } 50 | USER_TOKEN_COOKIE_NAME = "_U" 51 | RAND_COOKIE_INDEX_NAME = "BingAI_Rand_CK" 52 | RAND_IP_COOKIE_NAME = "BingAI_Rand_IP" 53 | PROXY_WEB_PREFIX_PATH = "/web/" 54 | PROXY_WEB_PAGE_PATH = PROXY_WEB_PREFIX_PATH + "index.html" 55 | ) 56 | 57 | func NewSingleHostReverseProxy(target *url.URL) *httputil.ReverseProxy { 58 | originalScheme := "http" 59 | httpsSchemeName := "https" 60 | var originalHost string 61 | var originalPath string 62 | // var originalDomain string 63 | var randIP string 64 | var resCKRandIndex string 65 | director := func(req *http.Request) { 66 | if req.URL.Scheme == httpsSchemeName || req.Header.Get("X-Forwarded-Proto") == httpsSchemeName { 67 | originalScheme = httpsSchemeName 68 | } 69 | originalHost = req.Host 70 | originalPath = req.URL.Path 71 | // originalDomain = fmt.Sprintf("%s:%s", originalScheme, originalHost) 72 | 73 | req.URL.Scheme = target.Scheme 74 | req.URL.Host = target.Host 75 | req.Host = target.Host 76 | 77 | // originalRefer := req.Referer() 78 | // if originalRefer != "" && !strings.Contains(originalRefer, originalDomain) { 79 | // req.Header.Set("Referer", strings.ReplaceAll(originalRefer, originalDomain, BING_URL.String())) 80 | // } else { 81 | req.Header.Set("Referer", fmt.Sprintf("%s/search?q=Bing+AI", BING_URL.String())) 82 | // } 83 | 84 | // 同一会话尽量保持相同的随机IP 85 | ckRandIP, _ := req.Cookie(RAND_IP_COOKIE_NAME) 86 | if ckRandIP != nil && ckRandIP.Value != "" { 87 | randIP = ckRandIP.Value 88 | } 89 | if randIP == "" { 90 | randIP = GetRandomIP() 91 | } 92 | req.Header.Set("X-Forwarded-For", randIP) 93 | 94 | // 未登录用户 95 | ckUserToken, _ := req.Cookie(USER_TOKEN_COOKIE_NAME) 96 | if ckUserToken == nil || ckUserToken.Value == "" { 97 | randCKIndex, randCkVal := getRandCookie(req) 98 | if randCkVal != "" { 99 | resCKRandIndex = strconv.Itoa(randCKIndex) 100 | req.AddCookie(&http.Cookie{ 101 | Name: USER_TOKEN_COOKIE_NAME, 102 | Value: randCkVal, 103 | }) 104 | } 105 | // ua := req.UserAgent() 106 | // if !strings.Contains(ua, "iPhone") || !strings.Contains(ua, "Mobile") { 107 | // req.Header.Set("User-Agent", "iPhone Mobile "+ua) 108 | // } 109 | } 110 | 111 | ua := req.UserAgent() 112 | isMobile := strings.Contains(ua, "Mobile") || strings.Contains(ua, "Android") 113 | 114 | // m pc 画图大小不一样 115 | if isMobile { 116 | 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") 117 | } else { 118 | 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") 119 | } 120 | 121 | for hKey, _ := range req.Header { 122 | if _, ok := KEEP_REQ_HEADER_MAP[hKey]; !ok { 123 | req.Header.Del(hKey) 124 | } 125 | } 126 | 127 | // reqHeaderByte, _ := json.Marshal(req.Header) 128 | // log.Println("剩余请求头 : ", string(reqHeaderByte)) 129 | } 130 | //改写返回信息 131 | modifyFunc := func(res *http.Response) error { 132 | contentType := res.Header.Get("Content-Type") 133 | if strings.Contains(contentType, "text/javascript") { 134 | contentEncoding := res.Header.Get("Content-Encoding") 135 | switch contentEncoding { 136 | case "gzip": 137 | // log.Println("ContentEncoding : ", contentEncoding, " Path : ", originalPath) 138 | modifyGzipBody(res, originalScheme, originalHost) 139 | case "br": 140 | // log.Println("ContentEncoding : ", contentEncoding, " Path : ", originalPath) 141 | modifyBrBody(res, originalScheme, originalHost) 142 | default: 143 | log.Println("ContentEncoding default : ", contentEncoding, " Path : ", originalPath) 144 | modifyDefaultBody(res, originalScheme, originalHost) 145 | } 146 | } 147 | 148 | // 修改响应 cookie 域 149 | // resCookies := res.Header.Values("Set-Cookie") 150 | // if len(resCookies) > 0 { 151 | // for i, v := range resCookies { 152 | // resCookies[i] = strings.ReplaceAll(strings.ReplaceAll(v, ".bing.com", originalHost), "bing.com", originalHost) 153 | // } 154 | // } 155 | 156 | // 设置服务器 cookie 对应索引 157 | if resCKRandIndex != "" { 158 | ckRandIndex := &http.Cookie{ 159 | Name: RAND_COOKIE_INDEX_NAME, 160 | Value: resCKRandIndex, 161 | Path: "/", 162 | } 163 | res.Header.Set("Set-Cookie", ckRandIndex.String()) 164 | } 165 | 166 | // 删除 CSP 167 | res.Header.Del("Content-Security-Policy-Report-Only") 168 | res.Header.Del("Report-To") 169 | 170 | // 删除重定向前缀域名 cn.bing.com www.bing.com 等 171 | location := res.Header.Get("Location") 172 | if location != "" { 173 | for _, delLocationDomain := range DEL_LOCATION_DOMAINS { 174 | if strings.HasPrefix(location, delLocationDomain) { 175 | res.Header.Set("Location", location[len(delLocationDomain):]) 176 | log.Println("Del Location Domain :", location) 177 | log.Println("RandIP : ", randIP) 178 | // 换新ip 179 | randIP = GetRandomIP() 180 | } 181 | } 182 | } 183 | 184 | // 设置随机ip cookie 185 | ckRandIP := &http.Cookie{ 186 | Name: RAND_IP_COOKIE_NAME, 187 | Value: randIP, 188 | Path: "/", 189 | } 190 | res.Header.Set("Set-Cookie", ckRandIP.String()) 191 | 192 | // 跨域 193 | // if IS_DEBUG_MODE { 194 | // res.Header.Set("Access-Control-Allow-Origin", "*") 195 | // res.Header.Set("Access-Control-Allow-Methods", "*") 196 | // res.Header.Set("Access-Control-Allow-Headers", "*") 197 | // } 198 | 199 | return nil 200 | } 201 | errorHandler := func(res http.ResponseWriter, req *http.Request, err error) { 202 | log.Println("代理异常 :", err) 203 | res.Write([]byte(err.Error())) 204 | } 205 | 206 | // tr := &http.Transport{ 207 | // TLSClientConfig: &tls.Config{ 208 | // // 如果只设置 InsecureSkipVerify: true对于这个问题不会有任何改变 209 | // InsecureSkipVerify: true, 210 | // ClientAuth: tls.NoClientCert, 211 | // }, 212 | // } 213 | 214 | // 代理请求 请求回来的内容 报错自动调用 215 | reverseProxy := &httputil.ReverseProxy{ 216 | Director: director, 217 | ModifyResponse: modifyFunc, 218 | ErrorHandler: errorHandler, 219 | } 220 | 221 | // socks 222 | if SOCKS_URL != "" { 223 | var socksAuth *proxy.Auth 224 | if SOCKS_USER != "" && SOCKS_PWD != "" { 225 | socksAuth = &proxy.Auth{ 226 | User: SOCKS_USER, 227 | Password: SOCKS_PWD, 228 | } 229 | } 230 | s5Proxy, err := proxy.SOCKS5("tcp", SOCKS_URL, socksAuth, proxy.Direct) 231 | if err != nil { 232 | panic(err) 233 | } 234 | tr := &http.Transport{ 235 | Dial: s5Proxy.Dial, 236 | } 237 | reverseProxy.Transport = tr 238 | } 239 | 240 | return reverseProxy 241 | } 242 | 243 | // return cookie index and cookie 244 | func getRandCookie(req *http.Request) (int, string) { 245 | utLen := len(USER_TOKEN_LIST) 246 | if utLen == 0 { 247 | return 0, "" 248 | } 249 | if utLen == 1 { 250 | return 0, USER_TOKEN_LIST[0] 251 | } 252 | 253 | ckRandIndex, _ := req.Cookie(RAND_COOKIE_INDEX_NAME) 254 | if ckRandIndex != nil && ckRandIndex.Value != "" { 255 | tmpIndex, err := strconv.Atoi(ckRandIndex.Value) 256 | if err != nil { 257 | log.Println("ckRandIndex err :", err) 258 | return 0, "" 259 | } 260 | if tmpIndex < utLen { 261 | return tmpIndex, USER_TOKEN_LIST[tmpIndex] 262 | } 263 | } 264 | seed := time.Now().UnixNano() 265 | rng := rand.New(rand.NewSource(seed)) 266 | randIndex := rng.Intn(len(USER_TOKEN_LIST)) 267 | return randIndex, USER_TOKEN_LIST[randIndex] 268 | } 269 | 270 | func replaceResBody(originalBody string, originalScheme string, originalHost string) string { 271 | modifiedBodyStr := originalBody 272 | 273 | if originalScheme == "https" { 274 | if strings.Contains(modifiedBodyStr, BING_URL.Host) { 275 | modifiedBodyStr = strings.ReplaceAll(modifiedBodyStr, BING_URL.Host, originalHost) 276 | } 277 | } else { 278 | originalDomain := fmt.Sprintf("%s://%s", originalScheme, originalHost) 279 | if strings.Contains(modifiedBodyStr, BING_URL.String()) { 280 | modifiedBodyStr = strings.ReplaceAll(modifiedBodyStr, BING_URL.String(), originalDomain) 281 | } 282 | } 283 | 284 | // 对话暂时支持国内网络,而且 Vercel 还不支持 Websocket ,先不用 285 | // if strings.Contains(modifiedBodyStr, BING_CHAT_DOMAIN) { 286 | // modifiedBodyStr = strings.ReplaceAll(modifiedBodyStr, BING_CHAT_DOMAIN, originalDomain) 287 | // } 288 | 289 | // if strings.Contains(modifiedBodyStr, "https://www.bingapis.com") { 290 | // modifiedBodyStr = strings.ReplaceAll(modifiedBodyStr, "https://www.bingapis.com", "https://bing.vcanbb.top") 291 | // } 292 | return modifiedBodyStr 293 | } 294 | 295 | func modifyGzipBody(res *http.Response, originalScheme string, originalHost string) error { 296 | gz, err := gzip.NewReader(res.Body) 297 | if err != nil { 298 | return err 299 | } 300 | defer gz.Close() 301 | 302 | bodyByte, err := io.ReadAll(gz) 303 | if err != nil { 304 | return err 305 | } 306 | originalBody := string(bodyByte) 307 | modifiedBodyStr := replaceResBody(originalBody, originalScheme, originalHost) 308 | // 修改响应内容 309 | modifiedBody := []byte(modifiedBodyStr) 310 | // gzip 压缩 311 | var buf bytes.Buffer 312 | writer := gzip.NewWriter(&buf) 313 | defer writer.Close() 314 | 315 | _, err = writer.Write(modifiedBody) 316 | if err != nil { 317 | return err 318 | } 319 | err = writer.Flush() 320 | if err != nil { 321 | return err 322 | } 323 | err = writer.Close() 324 | if err != nil { 325 | return err 326 | } 327 | 328 | // 修改 Content-Length 头 329 | res.Header.Set("Content-Length", strconv.Itoa(buf.Len())) 330 | // 修改响应内容 331 | res.Body = io.NopCloser(&buf) 332 | 333 | return nil 334 | } 335 | 336 | func modifyBrBody(res *http.Response, originalScheme string, originalHost string) error { 337 | reader := brotli.NewReader(res.Body) 338 | var uncompressed bytes.Buffer 339 | uncompressed.ReadFrom(reader) 340 | 341 | originalBody := uncompressed.String() 342 | 343 | modifiedBodyStr := replaceResBody(originalBody, originalScheme, originalHost) 344 | 345 | // 修改响应内容 346 | modifiedBody := []byte(modifiedBodyStr) 347 | // br 压缩 348 | var buf bytes.Buffer 349 | writer := brotli.NewWriter(&buf) 350 | writer.Write(modifiedBody) 351 | writer.Close() 352 | 353 | // 修改 Content-Length 头 354 | // res.ContentLength = int64(buf.Len()) 355 | res.Header.Set("Content-Length", strconv.Itoa(buf.Len())) 356 | // 修改响应内容 357 | res.Body = io.NopCloser(bytes.NewReader(buf.Bytes())) 358 | 359 | return nil 360 | } 361 | 362 | func modifyDefaultBody(res *http.Response, originalScheme string, originalHost string) error { 363 | bodyByte, err := io.ReadAll(res.Body) 364 | if err != nil { 365 | return err 366 | } 367 | originalBody := string(bodyByte) 368 | modifiedBodyStr := replaceResBody(originalBody, originalScheme, originalHost) 369 | // 修改响应内容 370 | modifiedBody := []byte(modifiedBodyStr) 371 | 372 | // 修改 Content-Length 头 373 | // res.ContentLength = int64(buf.Len()) 374 | res.Header.Set("Content-Length", strconv.Itoa(len(modifiedBody))) 375 | // 修改响应内容 376 | res.Body = io.NopCloser(bytes.NewReader(modifiedBody)) 377 | 378 | return nil 379 | } 380 | --------------------------------------------------------------------------------