patch: fix(upgrade) bing client (#505)

This commit is contained in:
josc146
2023-09-06 23:52:28 +08:00
parent 97c04c2464
commit e71ee74c6d
9 changed files with 288 additions and 123 deletions
+1
View File
@@ -249,6 +249,7 @@ async function copyFiles(entryPoints, targetDir) {
async function finishOutput(outputDirSuffix) {
const commonFiles = [
{ src: 'src/logo.png', dst: 'logo.png' },
{ src: 'src/rules.json', dst: 'rules.json' },
{ src: 'build/shared.js', dst: 'shared.js' },
{ src: 'build/content-script.css', dst: 'content-script.css' }, // shared
+41 -1
View File
@@ -83,7 +83,7 @@ async function executeApi(session, port, config) {
const accessToken = await getChatGptAccessToken()
await generateAnswersWithChatgptWebApi(port, session.question, session, accessToken)
}
} else if (bingWebModelKeys.some((n) => session.modelName.includes(n))) {
} else if (bingWebModelKeys.includes(session.modelName)) {
const accessToken = await getBingAccessToken()
if (session.modelName.includes('bingFreeSydney'))
await generateAnswersWithBingWebApi(port, session.question, session, accessToken, true)
@@ -192,9 +192,49 @@ Browser.runtime.onMessage.addListener(async (message, sender) => {
}
break
}
case 'FETCH': {
if (message.data.input.includes('bing.com')) {
const accessToken = await getBingAccessToken()
await setUserConfig({ bingAccessToken: accessToken })
}
try {
const response = await fetch(message.data.input, message.data.init)
const text = await response.text()
return [
{
body: text,
status: response.status,
statusText: response.statusText,
},
null,
]
} catch (error) {
return [null, error]
}
}
}
})
Browser.webRequest.onBeforeSendHeaders.addListener(
(details) => {
const headers = details.requestHeaders
for (let i = 0; i < headers.length; i++) {
if (headers[i].name === 'Origin') {
headers[i].value = 'https://www.bing.com'
} else if (headers[i].name === 'Referer') {
headers[i].value = 'https://www.bing.com/search?q=Bing+AI&showconv=1&FORM=hpcodx'
}
}
return { requestHeaders: headers }
},
{
urls: ['wss://sydney.bing.com/*', 'https://www.bing.com/*'],
types: ['xmlhttprequest', 'websocket'],
},
['requestHeaders'],
)
registerPortListener(async (session, port, config) => await executeApi(session, port, config))
registerCommands()
refreshMenu()
+114 -69
View File
@@ -5,17 +5,17 @@ import InputBox from '../InputBox'
import ConversationItem from '../ConversationItem'
import { createElementAtPosition, isFirefox, isMobile, isSafari } from '../../utils'
import {
LinkExternalIcon,
ArchiveIcon,
DesktopDownloadIcon,
LinkExternalIcon,
MoveToBottomIcon,
} from '@primer/octicons-react'
import { WindowDesktop, XLg, Pin } from 'react-bootstrap-icons'
import { Pin, WindowDesktop, XLg } from 'react-bootstrap-icons'
import FileSaver from 'file-saver'
import { render } from 'preact'
import FloatingToolbar from '../FloatingToolbar'
import { useClampWindowSize } from '../../hooks/use-clamp-window-size'
import { ModelMode, Models } from '../../config/index.mjs'
import { bingWebModelKeys, getUserConfig, ModelMode, Models } from '../../config/index.mjs'
import { useTranslation } from 'react-i18next'
import DeleteButton from '../DeleteButton'
import { useConfig } from '../../hooks/use-config.mjs'
@@ -23,6 +23,7 @@ import { createSession } from '../../services/local-session.mjs'
import { v4 as uuidv4 } from 'uuid'
import { initSession } from '../../services/init-session.mjs'
import { findLastIndex } from 'lodash-es'
import { generateAnswersWithBingWebApi } from '../../services/apis/bing-web.mjs'
const logo = Browser.runtime.getURL('logo.png')
@@ -48,6 +49,7 @@ function ConversationCard(props) {
const windowSize = useClampWindowSize([750, 1500], [250, 1100])
const bodyRef = useRef(null)
const [completeDraggable, setCompleteDraggable] = useState(false)
const useForegroundFetch = bingWebModelKeys.includes(session.modelName)
/**
* @type {[ConversationItemData[], (conversationItemData: ConversationItemData[]) => void]}
@@ -96,12 +98,12 @@ function ConversationCard(props) {
}
}, [conversationItemData])
useEffect(() => {
useEffect(async () => {
// when the page is responsive, session may accumulate redundant data and needs to be cleared after remounting and before making a new request
if (props.question) {
const newSession = initSession({ question: props.question })
setSession(newSession)
port.postMessage({ session: newSession })
await postMessage({ session: newSession })
}
}, [props.question]) // usually only triggered once
@@ -125,6 +127,100 @@ function ConversationCard(props) {
})
}
const portMessageListener = (msg) => {
if (msg.answer) {
updateAnswer(msg.answer, false, 'answer')
}
if (msg.session) {
if (msg.done) msg.session = { ...msg.session, isRetry: false }
setSession(msg.session)
}
if (msg.done) {
updateAnswer('', true, 'answer', true)
setIsReady(true)
}
if (msg.error) {
switch (msg.error) {
case 'UNAUTHORIZED':
updateAnswer(
`${t('UNAUTHORIZED')}<br>${t('Please login at https://chat.openai.com first')}${
isSafari() ? `<br>${t('Then open https://chat.openai.com/api/auth/session')}` : ''
}<br>${t('And refresh this page or type you question again')}` +
`<br><br>${t(
'Consider creating an api key at https://platform.openai.com/account/api-keys',
)}`,
false,
'error',
)
break
case 'CLOUDFLARE':
updateAnswer(
`${t('OpenAI Security Check Required')}<br>${
isSafari()
? t('Please open https://chat.openai.com/api/auth/session')
: t('Please open https://chat.openai.com')
}<br>${t('And refresh this page or type you question again')}` +
`<br><br>${t(
'Consider creating an api key at https://platform.openai.com/account/api-keys',
)}`,
false,
'error',
)
break
default:
if (conversationItemData[conversationItemData.length - 1].content.includes('gpt-loading'))
updateAnswer(msg.error, false, 'error')
else
setConversationItemData([
...conversationItemData,
new ConversationItemData('error', msg.error),
])
break
}
setIsReady(true)
}
}
const foregroundMessageListeners = useRef([])
/**
* @param {Session|undefined} session
* @param {boolean|undefined} stop
*/
const postMessage = async ({ session, stop }) => {
if (useForegroundFetch) {
foregroundMessageListeners.current.forEach((listener) => listener({ session, stop }))
if (session) {
const fakePort = {
postMessage: (msg) => {
portMessageListener(msg)
},
onMessage: {
addListener: (listener) => {
foregroundMessageListeners.current.push(listener)
},
removeListener: (listener) => {
foregroundMessageListeners.current.splice(
foregroundMessageListeners.current.indexOf(listener),
1,
)
},
},
onDisconnect: {
addListener: () => {},
removeListener: () => {},
},
}
const bingToken = (await getUserConfig()).bingAccessToken
if (session.modelName.includes('bingFreeSydney'))
await generateAnswersWithBingWebApi(fakePort, session.question, session, bingToken, true)
else await generateAnswersWithBingWebApi(fakePort, session.question, session, bingToken)
}
} else {
port.postMessage({ session, stop })
}
}
useEffect(() => {
const portListener = () => {
setPort(Browser.runtime.connect())
@@ -146,68 +242,17 @@ function ConversationCard(props) {
}
}, [port])
useEffect(() => {
const listener = (msg) => {
if (msg.answer) {
updateAnswer(msg.answer, false, 'answer')
if (useForegroundFetch) {
return () => {}
} else {
port.onMessage.addListener(portMessageListener)
return () => {
port.onMessage.removeListener(portMessageListener)
}
if (msg.session) {
if (msg.done) msg.session = { ...msg.session, isRetry: false }
setSession(msg.session)
}
if (msg.done) {
updateAnswer('', true, 'answer', true)
setIsReady(true)
}
if (msg.error) {
switch (msg.error) {
case 'UNAUTHORIZED':
updateAnswer(
`${t('UNAUTHORIZED')}<br>${t('Please login at https://chat.openai.com first')}${
isSafari() ? `<br>${t('Then open https://chat.openai.com/api/auth/session')}` : ''
}<br>${t('And refresh this page or type you question again')}` +
`<br><br>${t(
'Consider creating an api key at https://platform.openai.com/account/api-keys',
)}`,
false,
'error',
)
break
case 'CLOUDFLARE':
updateAnswer(
`${t('OpenAI Security Check Required')}<br>${
isSafari()
? t('Please open https://chat.openai.com/api/auth/session')
: t('Please open https://chat.openai.com')
}<br>${t('And refresh this page or type you question again')}` +
`<br><br>${t(
'Consider creating an api key at https://platform.openai.com/account/api-keys',
)}`,
false,
'error',
)
break
default:
if (
conversationItemData[conversationItemData.length - 1].content.includes('gpt-loading')
)
updateAnswer(msg.error, false, 'error')
else
setConversationItemData([
...conversationItemData,
new ConversationItemData('error', msg.error),
])
break
}
setIsReady(true)
}
}
port.onMessage.addListener(listener)
return () => {
port.onMessage.removeListener(listener)
}
}, [conversationItemData])
const getRetryFn = (session) => () => {
const getRetryFn = (session) => async () => {
updateAnswer(`<p class="gpt-loading">${t('Waiting for response...')}</p>`, false, 'answer')
setIsReady(false)
@@ -223,8 +268,8 @@ function ConversationCard(props) {
const newSession = { ...session, isRetry: true }
setSession(newSession)
try {
port.postMessage({ stop: true })
port.postMessage({ session: newSession })
await postMessage({ stop: true })
await postMessage({ session: newSession })
} catch (e) {
updateAnswer(e, false, 'error')
}
@@ -348,8 +393,8 @@ function ConversationCard(props) {
<DeleteButton
size={16}
text={t('Clear Conversation')}
onConfirm={() => {
port.postMessage({ stop: true })
onConfirm={async () => {
await postMessage({ stop: true })
Browser.runtime.sendMessage({
type: 'DELETE_CONVERSATION',
data: {
@@ -449,7 +494,7 @@ function ConversationCard(props) {
enabled={isReady}
port={port}
reverseResizeDir={props.pageMode}
onSubmit={(question) => {
onSubmit={async (question) => {
const newQuestion = new ConversationItemData('question', question)
const newAnswer = new ConversationItemData(
'answer',
@@ -461,7 +506,7 @@ function ConversationCard(props) {
const newSession = { ...session, question, isRetry: false }
setSession(newSession)
try {
port.postMessage({ session: newSession })
await postMessage({ session: newSession })
} catch (e) {
updateAnswer(e, false, 'error')
}
+1
View File
@@ -207,6 +207,7 @@ export const defaultConfig = {
],
accessToken: '',
tokenSavedOn: 0,
bingAccessToken: '',
chatgptJumpBackTabId: 0,
chatgptTabId: 0,
+19 -5
View File
@@ -10,10 +10,13 @@
"128": "logo.png"
},
"host_permissions": [
"https://*.openai.com/",
"https://*.bing.com/",
"https://*.poe.com/",
"https://*.google.com/"
"https://*.openai.com/*",
"https://*.bing.com/*",
"wss://*.bing.com/*",
"https://*.poe.com/*",
"https://*.google.com/*",
"https://claude.ai/*",
"<all_urls>"
],
"permissions": [
"commands",
@@ -21,7 +24,9 @@
"storage",
"contextMenus",
"unlimitedStorage",
"tabs"
"tabs",
"webRequest",
"declarativeNetRequestWithHostAccess"
],
"optional_permissions": [
"background"
@@ -32,6 +37,15 @@
"action": {
"default_popup": "popup.html"
},
"declarative_net_request": {
"rule_resources": [
{
"id": "ruleset",
"enabled": true,
"path": "rules.json"
}
]
},
"options_ui": {
"page": "popup.html",
"open_in_tab": true
+5 -1
View File
@@ -16,10 +16,14 @@
"contextMenus",
"unlimitedStorage",
"tabs",
"webRequest",
"https://*.openai.com/",
"https://*.bing.com/",
"wss://*.bing.com/*",
"https://*.poe.com/",
"https://*.google.com/"
"https://*.google.com/",
"https://claude.ai/",
"<all_urls>"
],
"background": {
"scripts": [
+24
View File
@@ -0,0 +1,24 @@
[
{
"id": 1,
"action": {
"type": "modifyHeaders",
"requestHeaders": [
{
"operation": "set",
"header": "origin",
"value": "https://www.bing.com"
},
{
"operation": "set",
"header": "referer",
"value": "https://www.bing.com/search?q=Bing+AI&showconv=1&FORM=hpcodx"
}
]
},
"condition": {
"requestDomains": ["sydney.bing.com", "www.bing.com"],
"resourceTypes": ["xmlhttprequest", "websocket"]
}
}
]
+53 -47
View File
@@ -2,6 +2,7 @@
import { v4 as uuidv4 } from 'uuid'
import BingImageCreator from './BingImageCreator'
import { fetchBg } from '../../../utils/fetch-bg.mjs'
/**
* https://stackoverflow.com/a/58326357
@@ -71,51 +72,53 @@ export default class BingAIClient {
}
async createNewConversation() {
this.headers = {
accept: 'application/json',
'accept-language': 'en-US,en;q=0.9',
'content-type': 'application/json',
'sec-ch-ua': '"Microsoft Edge";v="113", "Chromium";v="113", "Not-A.Brand";v="24"',
'sec-ch-ua-arch': '"x86"',
'sec-ch-ua-bitness': '"64"',
'sec-ch-ua-full-version': '"113.0.1774.50"',
'sec-ch-ua-full-version-list':
'"Microsoft Edge";v="113.0.1774.50", "Chromium";v="113.0.5672.127", "Not-A.Brand";v="24.0.0.0"',
'sec-ch-ua-mobile': '?0',
'sec-ch-ua-model': '""',
'sec-ch-ua-platform': '"Windows"',
'sec-ch-ua-platform-version': '"15.0.0"',
'sec-fetch-dest': 'empty',
'sec-fetch-mode': 'cors',
'sec-fetch-site': 'same-origin',
'sec-ms-gec': genRanHex(64).toUpperCase(),
'sec-ms-gec-version': '1-115.0.1866.1',
'x-ms-client-request-id': uuidv4(),
'x-ms-useragent':
'azsdk-js-api-client-factory/1.0.0-beta.1 core-rest-pipeline/1.10.0 OS/Win32',
'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.50',
cookie:
this.options.cookies ||
(this.options.userToken ? `_U=${this.options.userToken}` : undefined),
Referer: 'https://www.bing.com/search?q=Bing+AI&showconv=1',
'Referrer-Policy': 'origin-when-cross-origin',
// Workaround for request being blocked due to geolocation
// 'x-forwarded-for': '1.1.1.1', // 1.1.1.1 seems to no longer work.
...(this.options.xForwardedFor ? { 'x-forwarded-for': this.options.xForwardedFor } : {}),
}
// filter undefined values
this.headers = Object.fromEntries(
Object.entries(this.headers).filter(([, value]) => value !== undefined),
)
const fetchOptions = {
headers: {
accept: 'application/json',
'accept-language': 'en-US,en;q=0.9',
'content-type': 'application/json',
'sec-ch-ua': '"Microsoft Edge";v="113", "Chromium";v="113", "Not-A.Brand";v="24"',
'sec-ch-ua-arch': '"x86"',
'sec-ch-ua-bitness': '"64"',
'sec-ch-ua-full-version': '"113.0.1774.50"',
'sec-ch-ua-full-version-list':
'"Microsoft Edge";v="113.0.1774.50", "Chromium";v="113.0.5672.127", "Not-A.Brand";v="24.0.0.0"',
'sec-ch-ua-mobile': '?0',
'sec-ch-ua-model': '""',
'sec-ch-ua-platform': '"Windows"',
'sec-ch-ua-platform-version': '"15.0.0"',
'sec-fetch-dest': 'empty',
'sec-fetch-mode': 'cors',
'sec-fetch-site': 'same-origin',
'sec-ms-gec': genRanHex(64).toUpperCase(),
'sec-ms-gec-version': '1-115.0.1866.1',
'x-ms-client-request-id': uuidv4(),
'x-ms-useragent':
'azsdk-js-api-client-factory/1.0.0-beta.1 core-rest-pipeline/1.10.0 OS/Win32',
'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.50',
cookie:
this.options.cookies ||
(this.options.userToken ? `_U=${this.options.userToken}` : undefined),
Referer: 'https://www.bing.com/search?q=Bing+AI&showconv=1',
'Referrer-Policy': 'origin-when-cross-origin',
// Workaround for request being blocked due to geolocation
// 'x-forwarded-for': '1.1.1.1', // 1.1.1.1 seems to no longer work.
...(this.options.xForwardedFor ? { 'x-forwarded-for': this.options.xForwardedFor } : {}),
},
headers: this.headers,
}
if (this.options.proxy) {
// fetchOptions.dispatcher = new ProxyAgent(this.options.proxy);
}
const response = await fetch(`${this.options.host}/turing/conversation/create`, fetchOptions)
const { status, headers } = response
if (status === 200 && +headers.get('content-length') < 5) {
throw new Error('/turing/conversation/create: Your IP is blocked by BingAI.')
}
// if (this.options.proxy) {
// fetchOptions.dispatcher = new ProxyAgent(this.options.proxy)
// } else {
// fetchOptions.dispatcher = new Agent({ connect: { timeout: 20_000 } })
// }
const response = await fetchBg(`${this.options.host}/turing/conversation/create`, fetchOptions)
const body = await response.text()
try {
return JSON.parse(body)
@@ -126,10 +129,10 @@ export default class BingAIClient {
async createWebSocketConnection() {
return new Promise((resolve, reject) => {
// let agent;
if (this.options.proxy) {
// agent = new HttpsProxyAgent(this.options.proxy);
}
// let agent
// if (this.options.proxy) {
// agent = new HttpsProxyAgent(this.options.proxy)
// }
const ws = new WebSocket('wss://sydney.bing.com/sydney/ChatHub')
@@ -449,6 +452,9 @@ export default class BingAIClient {
if (!messages?.length || messages[0].author !== 'bot') {
return
}
if (messages[0].contentOrigin === 'Apology') {
return
}
if (messages[0]?.contentType === 'IMAGE') {
// You will never get a message of this type without 'gencontentv3' being on.
bicIframe = this.bic
+30
View File
@@ -0,0 +1,30 @@
import Browser from 'webextension-polyfill'
/**
* @param {RequestInfo|URL} input
* @param {RequestInit=} init
* @returns {Promise<Response>}
*/
export function fetchBg(input, init) {
return new Promise((resolve, reject) => {
Browser.runtime
.sendMessage({
type: 'FETCH',
data: { input, init },
})
.then((messageResponse) => {
const [response, error] = messageResponse
if (response === null) {
reject(error)
} else {
const body = response.body ? new Blob([response.body]) : undefined
resolve(
new Response(body, {
status: response.status,
statusText: response.statusText,
}),
)
}
})
})
}