import './styles.scss' import { unmountComponentAtNode } from 'react-dom' import { render } from 'preact' import DecisionCard from '../components/DecisionCard' import { config as siteConfig } from './site-adapters' import { config as toolsConfig } from './selection-tools' import { config as menuConfig } from './menu-tools' import { chatgptWebModelKeys, getPreferredLanguageKey, getUserConfig, setAccessToken, } from '../config/index.mjs' import { createElementAtPosition, cropText, getClientPosition, getPossibleElementByQuerySelector, } from '../utils' import FloatingToolbar from '../components/FloatingToolbar' import Browser from 'webextension-polyfill' import { getPreferredLanguage } from '../config/language.mjs' import '../_locales/i18n-react' import { changeLanguage } from 'i18next' import { initSession } from '../services/init-session.mjs' import { getChatGptAccessToken, registerPortListener } from '../services/wrappers.mjs' import { generateAnswersWithChatgptWebApi } from '../services/apis/chatgpt-web.mjs' import NotificationForChatGPTWeb from '../components/NotificationForChatGPTWeb' /** * @param {SiteConfig} siteConfig * @param {UserConfig} userConfig */ async function mountComponent(siteConfig, userConfig) { const retry = 10 let oldUrl = location.href for (let i = 1; i <= retry; i++) { if (location.href !== oldUrl) { console.log(`SiteAdapters Retry ${i}/${retry}: stop`) return } const e = (siteConfig && (getPossibleElementByQuerySelector(siteConfig.sidebarContainerQuery) || getPossibleElementByQuerySelector(siteConfig.appendContainerQuery) || getPossibleElementByQuerySelector(siteConfig.resultsContainerQuery))) || getPossibleElementByQuerySelector([userConfig.prependQuery]) || getPossibleElementByQuerySelector([userConfig.appendQuery]) if (e) { console.log(`SiteAdapters Retry ${i}/${retry}: found`) console.log(e) break } else { console.log(`SiteAdapters Retry ${i}/${retry}: not found`) if (i === retry) return else await new Promise((r) => setTimeout(r, 500)) } } document.querySelectorAll('.chatgptbox-container,#chatgptbox-container').forEach((e) => { unmountComponentAtNode(e) e.remove() }) let question if (userConfig.inputQuery) question = await getInput([userConfig.inputQuery]) if (!question && siteConfig) question = await getInput(siteConfig.inputQuery) document.querySelectorAll('.chatgptbox-container,#chatgptbox-container').forEach((e) => { unmountComponentAtNode(e) e.remove() }) const container = document.createElement('div') container.id = 'chatgptbox-container' render( , container, ) } /** * @param {string[]|function} inputQuery * @returns {Promise} */ async function getInput(inputQuery) { let input if (typeof inputQuery === 'function') { input = await inputQuery() if (input) return `Reply in ${await getPreferredLanguage()}.\n` + input return input } const searchInput = getPossibleElementByQuerySelector(inputQuery) if (searchInput) { if (searchInput.value) input = searchInput.value else if (searchInput.textContent) input = searchInput.textContent if (input) return ( `Reply in ${await getPreferredLanguage()}.\nThe following is a search input in a search engine, ` + `giving useful content or solutions and as much information as you can related to it, ` + `use markdown syntax to make your answer more readable, such as code blocks, bold, list:\n` + input ) } } let toolbarContainer const deleteToolbar = () => { if (toolbarContainer && toolbarContainer.className === 'chatgptbox-toolbar-container') toolbarContainer.remove() } const createSelectionTools = (toolbarContainer, selection) => { toolbarContainer.className = 'chatgptbox-toolbar-container' render( , toolbarContainer, ) } async function prepareForSelectionTools() { document.addEventListener('mouseup', (e) => { if (toolbarContainer && toolbarContainer.contains(e.target)) return const selectionElement = window.getSelection()?.rangeCount > 0 && window.getSelection()?.getRangeAt(0).endContainer.parentElement if (toolbarContainer && selectionElement && toolbarContainer.contains(selectionElement)) return deleteToolbar() setTimeout(async () => { const selection = window .getSelection() ?.toString() .trim() .replace(/^-+|-+$/g, '') if (selection) { let position const config = await getUserConfig() if (!config.selectionToolsNextToInputBox) position = { x: e.pageX + 20, y: e.pageY + 20 } else { const inputElement = selectionElement.querySelector('input, textarea') if (inputElement) { position = getClientPosition(inputElement) position = { x: position.x + window.scrollX + inputElement.offsetWidth + 50, y: e.pageY + 30, } } else { position = { x: e.pageX + 20, y: e.pageY + 20 } } } toolbarContainer = createElementAtPosition(position.x, position.y) createSelectionTools(toolbarContainer, selection) } }) }) document.addEventListener('mousedown', (e) => { if (toolbarContainer && toolbarContainer.contains(e.target)) return document.querySelectorAll('.chatgptbox-toolbar-container').forEach((e) => e.remove()) }) document.addEventListener('keydown', (e) => { if ( toolbarContainer && !toolbarContainer.contains(e.target) && (e.target.nodeName === 'INPUT' || e.target.nodeName === 'TEXTAREA') ) { setTimeout(() => { if (!window.getSelection()?.toString().trim()) deleteToolbar() }) } }) } async function prepareForSelectionToolsTouch() { document.addEventListener('touchend', (e) => { if (toolbarContainer && toolbarContainer.contains(e.target)) return if ( toolbarContainer && window.getSelection()?.rangeCount > 0 && toolbarContainer.contains(window.getSelection()?.getRangeAt(0).endContainer.parentElement) ) return deleteToolbar() setTimeout(() => { const selection = window .getSelection() ?.toString() .trim() .replace(/^-+|-+$/g, '') if (selection) { toolbarContainer = createElementAtPosition( e.changedTouches[0].pageX + 20, e.changedTouches[0].pageY + 20, ) createSelectionTools(toolbarContainer, selection) } }) }) document.addEventListener('touchstart', (e) => { if (toolbarContainer && toolbarContainer.contains(e.target)) return document.querySelectorAll('.chatgptbox-toolbar-container').forEach((e) => e.remove()) }) } let menuX, menuY async function prepareForRightClickMenu() { document.addEventListener('contextmenu', (e) => { menuX = e.clientX menuY = e.clientY }) Browser.runtime.onMessage.addListener(async (message) => { if (message.type === 'CREATE_CHAT') { const data = message.data let prompt = '' if (data.itemId in toolsConfig) { prompt = await toolsConfig[data.itemId].genPrompt(data.selectionText) } else if (data.itemId in menuConfig) { const menuItem = menuConfig[data.itemId] if (!menuItem.genPrompt) return else prompt = await menuItem.genPrompt() if (prompt) prompt = cropText(`Reply in ${await getPreferredLanguage()}.\n` + prompt) } const position = data.useMenuPosition ? { x: menuX, y: menuY } : { x: window.innerWidth / 2 - 300, y: window.innerHeight / 2 - 200 } const container = createElementAtPosition(position.x, position.y) container.className = 'chatgptbox-toolbar-container-not-queryable' render( , container, ) } }) } async function prepareForStaticCard() { const userConfig = await getUserConfig() let siteRegex if (userConfig.useSiteRegexOnly) siteRegex = userConfig.siteRegex else siteRegex = new RegExp( (userConfig.siteRegex && userConfig.siteRegex + '|') + Object.keys(siteConfig).join('|'), ) const matches = location.hostname.match(siteRegex) if (matches) { const siteName = matches[0] if ( userConfig.siteAdapters.includes(siteName) && !userConfig.activeSiteAdapters.includes(siteName) ) return if (siteName in siteConfig) { const siteAction = siteConfig[siteName].action if (siteAction && siteAction.init) { await siteAction.init(location.hostname, userConfig, getInput, mountComponent) } } mountComponent(siteConfig[siteName], userConfig) } } async function overwriteAccessToken() { if (location.hostname !== 'chat.openai.com') return let data if (location.pathname === '/api/auth/session') { const response = document.querySelector('pre').textContent try { data = JSON.parse(response) } catch (error) { console.error('json error', error) } } else { const resp = await fetch('https://chat.openai.com/api/auth/session') data = await resp.json().catch(() => ({})) } if (data && data.accessToken) { await setAccessToken(data.accessToken) console.log(data.accessToken) } } async function prepareForForegroundRequests() { if (location.hostname !== 'chat.openai.com' || location.pathname === '/auth/login') return const userConfig = await getUserConfig() if (!chatgptWebModelKeys.some((model) => userConfig.activeApiModes.includes(model))) return const div = document.createElement('div') document.body.append(div) render(, div) await Browser.runtime.sendMessage({ type: 'SET_CHATGPT_TAB', data: {}, }) registerPortListener(async (session, port) => { if (chatgptWebModelKeys.includes(session.modelName)) { const accessToken = await getChatGptAccessToken() await generateAnswersWithChatgptWebApi(port, session.question, session, accessToken) } }) } async function run() { await getPreferredLanguageKey().then((lang) => { changeLanguage(lang) }) Browser.runtime.onMessage.addListener(async (message) => { if (message.type === 'CHANGE_LANG') { const data = message.data changeLanguage(data.lang) } }) await overwriteAccessToken() await prepareForForegroundRequests() prepareForSelectionTools() prepareForSelectionToolsTouch() prepareForStaticCard() prepareForRightClickMenu() } run()