diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8f15c0d..d138096 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -7,28 +7,28 @@ on: jobs: build: - runs-on: ubuntu-22.04 + runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: pnpm/action-setup@v2 + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v4 with: - version: 8.7.6 - - uses: actions/setup-node@v3 + version: latest + - uses: actions/setup-node@v4 with: - node-version: "18.17.0" + node-version: latest cache: "pnpm" - run: pnpm install - run: pnpm build - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: build-artifacts path: build deploy-web: needs: build - runs-on: ubuntu-22.04 + runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions/download-artifact@v3 + - uses: actions/checkout@v4 + - uses: actions/download-artifact@v4 with: name: build-artifacts path: build @@ -37,7 +37,8 @@ jobs: with: folder: build/web create-release: - runs-on: ubuntu-22.04 + needs: build + runs-on: ubuntu-latest outputs: upload_url: ${{ steps.create-release.outputs.upload_url }} steps: @@ -55,10 +56,10 @@ jobs: strategy: matrix: client: ["chrome", "edge", "firefox", "userscript"] - runs-on: ubuntu-22.04 + runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions/download-artifact@v3 + - uses: actions/checkout@v4 + - uses: actions/download-artifact@v4 with: name: build-artifacts path: build diff --git a/package.json b/package.json index 901efe1..664602a 100644 --- a/package.json +++ b/package.json @@ -26,13 +26,14 @@ "start:userscript": "REACT_APP_CLIENT=userscript react-app-rewired start", "build:chrome": "rm -rf build/chrome && BUILD_PATH=./build/chrome REACT_APP_CLIENT=chrome react-app-rewired build && rm build/chrome/content.html", "build:edge": "rm -rf build/edge && cp -r build/chrome build/edge", + "build:thunderbird": "rm -rf build/thunderbird && BUILD_PATH=./build/thunderbird REACT_APP_CLIENT=thunderbird react-app-rewired build && rm build/thunderbird/content.html && cp ./build/thunderbird/manifest.thunderbird.json ./build/thunderbird/manifest.json && rm build/*/manifest.thunderbird.json", "build:firefox": "rm -rf build/firefox && cp -r build/chrome build/firefox && cat ./build/firefox/manifest.firefox.json > ./build/firefox/manifest.json && rm build/*/manifest.firefox.json", "build:web": "rm -rf build/web && BUILD_PATH=./build/web REACT_APP_CLIENT=userscript react-app-rewired build", "build:userscript-ios": "file1=build/web/kiss-translator.user.js file2=build/web/kiss-translator-ios-safari.user.js && cp $file1 $file2 && sed -i 's|// @grant unsafeWindow|// @inject-into content|g' $file2", "build:userscript": "rm -rf build/userscript && mkdir build/userscript && cp build/web/*.user.js build/userscript/", "build:rules": "babel-node src/rules.js", - "build": "pnpm build:chrome && pnpm build:edge && pnpm build:firefox && pnpm build:web && pnpm build:userscript-ios && pnpm build:userscript && pnpm build:rules", - "zip": "cd build && zip -r chrome.zip chrome && zip -r edge.zip edge && cd firefox && zip -r ../firefox.zip .", + "build": "pnpm build:chrome && pnpm build:edge && pnpm build:thunderbird && pnpm build:firefox && pnpm build:web && pnpm build:userscript-ios && pnpm build:userscript && pnpm build:rules", + "zip": "cd build && zip -r chrome.zip chrome && zip -r edge.zip edge && (cd firefox && zip -r ../firefox.zip .) && (cd thunderbird && zip -r ../thunderbird.zip .)", "test": "react-app-rewired test", "eject": "react-scripts eject" }, diff --git a/public/manifest.thunderbird.json b/public/manifest.thunderbird.json new file mode 100644 index 0000000..2aa3459 --- /dev/null +++ b/public/manifest.thunderbird.json @@ -0,0 +1,78 @@ +{ + "manifest_version": 2, + "name": "__MSG_app_name__", + "description": "__MSG_app_description__", + "version": "1.8.11", + "default_locale": "en", + "author": "Gabe", + "homepage_url": "https://github.com/fishjar/kiss-translator", + "browser_specific_settings": { + "gecko": { + "id": "yugang2002@gmail.com", + "strict_min_version": "78.0" + } + }, + "background": { + "scripts": ["background.js"] + }, + "content_scripts": [ + { + "js": ["content.js"], + "matches": [""], + "all_frames": true + } + ], + "commands": { + "_execute_browser_action": { + "suggested_key": { + "default": "Alt+K" + } + }, + "toggleTranslate": { + "suggested_key": { + "default": "Alt+Q" + }, + "description": "__MSG_toggle_translate__" + }, + "openTranbox": { + "suggested_key": { + "default": "Alt+S" + }, + "description": "__MSG_open_tranbox__" + }, + "toggleStyle": { + "suggested_key": { + "default": "Alt+C" + }, + "description": "__MSG_toggle_style__" + }, + "openOptions": { + "description": "__MSG_open_options__" + } + }, + "permissions": [ + "", + "storage", + "menus", + "messagesModify", + "scripting", + "declarativeNetRequest" + ], + "icons": { + "16": "images/logo16.png", + "32": "images/logo32.png", + "48": "images/logo48.png", + "128": "images/logo128.png" + }, + "browser_action": { + "default_icon": { + "128": "images/logo128.png" + }, + "default_title": "__MSG_app_name__", + "default_popup": "popup.html" + }, + "options_ui": { + "page": "options.html", + "open_in_tab": true + } +} diff --git a/src/apis/index.js b/src/apis/index.js index f95bd09..ce025d9 100644 --- a/src/apis/index.js +++ b/src/apis/index.js @@ -39,6 +39,7 @@ import { import { sha256 } from "../libs/utils"; import interpreter from "../libs/interpreter"; import { msAuth } from "../libs/auth"; +import {getSettingWithDefault } from "../libs/storage"; /** * 同步数据 @@ -255,10 +256,19 @@ export const apiTranslate = async ({ ); switch (translator) { - case OPT_TRANS_GOOGLE: - trText = res.sentences.map((item) => item.trans).join(" "); - isSame = to === res.src; + case OPT_TRANS_GOOGLE: { + if (!res || !Array.isArray(res) || res.length < 2) { + console.error("Unexpected response structure:", res); + trText = "Error: Invalid response structure"; + isSame = false; + } else { + const translatedText = Array.isArray(res[0]) ? res[0].join(" ") : "Translation unavailable"; + const isTranslationComplete = to === (Array.isArray(res[1]) ? res[1][0] : undefined); + trText = translatedText; + isSame = isTranslationComplete; + } break; + } case OPT_TRANS_MICROSOFT: trText = res .map((item) => item.translations.map((item) => item.text).join(" ")) @@ -323,7 +333,13 @@ export const apiTranslate = async ({ case OPT_TRANS_OLLAMA: case OPT_TRANS_OLLAMA_2: case OPT_TRANS_OLLAMA_3: - trText = res?.response; + let deepModels = (await getSettingWithDefault()).transApis[translator]?.thinkIgnore || ''; + deepModels = deepModels.split(',').filter(model => model.trim() !== ''); + if (deepModels.some(model => res?.model?.startsWith(model))) { + trText = res?.response.replace(/[\s\S]*<\/think>/i, ''); + }else{ + trText = res?.response; + } isSame = text === trText; break; case OPT_TRANS_CUSTOMIZE: diff --git a/src/apis/trans.js b/src/apis/trans.js index f3ec376..b833d07 100644 --- a/src/apis/trans.js +++ b/src/apis/trans.js @@ -58,26 +58,16 @@ const keyPick = (translator, key = "", cacheMap) => { }; const genGoogle = ({ text, from, to, url, key }) => { - const params = { - client: "gtx", - dt: "t", - dj: 1, - ie: "UTF-8", - sl: from, - tl: to, - q: text, - }; - const input = `${url}?${queryString.stringify(params)}`; + const body = JSON.stringify([[ [text], from, to ], "wt_lib"]); const init = { + method: "POST", headers: { - "Content-type": "application/json", + "Content-Type": "application/json+protobuf", + "X-Goog-API-Key": key, }, + body, }; - if (key) { - init.headers.Authorization = `Bearer ${key}`; - } - - return [input, init]; + return [url, init]; }; const genMicrosoft = async ({ text, from, to }) => { diff --git a/src/background.js b/src/background.js index 0e62e7f..779faad 100644 --- a/src/background.js +++ b/src/background.js @@ -1,3 +1,4 @@ +/* global messenger */ import browser from "webextension-polyfill"; import { MSG_FETCH, @@ -44,41 +45,41 @@ const REMOVE_HEADERS = [ async function addContextMenus(contextMenuType = 1) { // 添加前先删除,避免重复ID的错误 try { - await browser.contextMenus.removeAll(); + await browser.menus.removeAll(); } catch (err) { // } switch (contextMenuType) { case 1: - browser.contextMenus.create({ + browser.menus.create({ id: CMD_TOGGLE_TRANSLATE, title: browser.i18n.getMessage("app_name"), contexts: ["page", "selection"], }); break; case 2: - browser.contextMenus.create({ + browser.menus.create({ id: CMD_TOGGLE_TRANSLATE, title: browser.i18n.getMessage("toggle_translate"), contexts: ["page", "selection"], }); - browser.contextMenus.create({ + browser.menus.create({ id: CMD_TOGGLE_STYLE, title: browser.i18n.getMessage("toggle_style"), contexts: ["page", "selection"], }); - browser.contextMenus.create({ + browser.menus.create({ id: CMD_OPEN_TRANBOX, title: browser.i18n.getMessage("open_tranbox"), contexts: ["page", "selection"], }); - browser.contextMenus.create({ + browser.menus.create({ id: "options_separator", type: "separator", contexts: ["page", "selection"], }); - browser.contextMenus.create({ + browser.menus.create({ id: CMD_OPEN_OPTIONS, title: browser.i18n.getMessage("open_options"), contexts: ["page", "selection"], @@ -123,12 +124,26 @@ async function updateCspRules(csplist = DEFAULT_CSPLIST.join(",\n")) { } } +/** + * 注册邮件显示脚本 + */ +async function registerMsgDisplayScript() { + await messenger.messageDisplayScripts.register({ + js: [{file: "/content.js"}] + }); + } + /** * 插件安装 */ browser.runtime.onInstalled.addListener(() => { tryInitDefaultData(); + //在thunderbird中注册脚本 + if (process.env.REACT_APP_CLIENT === "thunderbird") { + registerMsgDisplayScript(); + } + // 右键菜单 addContextMenus(); @@ -151,6 +166,11 @@ browser.runtime.onStartup.addListener(async () => { tryClearCaches(); } + //在thunderbird中注册脚本 + if (process.env.REACT_APP_CLIENT === "thunderbird") { + registerMsgDisplayScript(); + } + // 右键菜单 // firefox重启后菜单会消失,故重复添加 addContextMenus(contextMenuType); diff --git a/src/config/i18n.js b/src/config/i18n.js index 03ba335..f69ae44 100644 --- a/src/config/i18n.js +++ b/src/config/i18n.js @@ -186,6 +186,10 @@ export const I18N = { zh: `最大并发请求数量 (1-100)`, en: `Maximum Number Of Concurrent Requests (1-100)`, }, + think_ignore: { + zh: `忽略以下模型的输出,逗号(,)分割`, + en: `Ignore the block for the following models, comma (,) separated`, + }, fetch_interval: { zh: `每次请求间隔时间 (0-5000ms)`, en: `Time Between Requests (0-5000ms)`, @@ -194,6 +198,10 @@ export const I18N = { zh: `重新翻译间隔时间 (100-5000ms)`, en: `Retranslation Interval (100-5000ms)`, }, + http_timeout: { + zh: `请求超时时间 (5000-30000ms)`, + en: `Request Timeout Time (5000-30000ms)`, + }, min_translate_length: { zh: `最小翻译字符数 (1-100)`, en: `Minimum number Of Translated Characters (1-100)`, diff --git a/src/config/index.js b/src/config/index.js index f8ad82b..5aa6db9 100644 --- a/src/config/index.js +++ b/src/config/index.js @@ -39,7 +39,8 @@ export const CLIENT_CHROME = "chrome"; export const CLIENT_EDGE = "edge"; export const CLIENT_FIREFOX = "firefox"; export const CLIENT_USERSCRIPT = "userscript"; -export const CLIENT_EXTS = [CLIENT_CHROME, CLIENT_EDGE, CLIENT_FIREFOX]; +export const CLIENT_THUNDERBIRD = "thunderbird"; +export const CLIENT_EXTS = [CLIENT_CHROME, CLIENT_EDGE, CLIENT_FIREFOX, CLIENT_THUNDERBIRD]; export const KV_RULES_KEY = "kiss-rules.json"; export const KV_WORDS_KEY = "kiss-words.json"; @@ -86,8 +87,10 @@ export const URL_MICROSOFT_AUTH = "https://edge.microsoft.com/translate/auth"; export const URL_MICROSOFT_LANGDETECT = "https://api-edge.cognitive.microsofttranslator.com/detect?api-version=3.0"; -export const URL_GOOGLE_TRAN = - "https://translate.googleapis.com/translate_a/single"; +export const URL_GOOGLE_TRAN = "https://translate-pa.googleapis.com/v1/translateHtml"; + +export const DEFAULT_GOOGLE_API_KEY = "AIzaSyATBXajvzQLTDHEQbcpq0Ihe0vWDHmO520"; + export const URL_BAIDU_LANGDETECT = "https://fanyi.baidu.com/langdetect"; export const URL_BAIDU_SUGGEST = "https://fanyi.baidu.com/sug"; export const URL_BAIDU_TTS = "https://fanyi.baidu.com/gettts"; @@ -540,13 +543,14 @@ const defaultOllamaApi = { model: "llama3.1", systemPrompt: `You are a professional, authentic machine translation engine.`, userPrompt: `Translate the following source text from ${INPUT_PLACE_FROM} to ${INPUT_PLACE_TO}. Output translation directly without any additional text.\n\nSource Text: ${INPUT_PLACE_TEXT}\n\nTranslated Text:`, + thinkIgnore:`qwen3,deepseek-r1`, fetchLimit: 1, fetchInterval: 500, }; export const DEFAULT_TRANS_APIS = { [OPT_TRANS_GOOGLE]: { url: URL_GOOGLE_TRAN, - key: "", + key: DEFAULT_GOOGLE_API_KEY, fetchLimit: DEFAULT_FETCH_LIMIT, // 最大任务数量 fetchInterval: DEFAULT_FETCH_INTERVAL, // 任务间隔时间 }, @@ -640,6 +644,7 @@ export const DEFAULT_SHORTCUTS = { export const TRANS_MIN_LENGTH = 5; // 最短翻译长度 export const TRANS_MAX_LENGTH = 5000; // 最长翻译长度 export const TRANS_NEWLINE_LENGTH = 20; // 换行字符数 +export const HTTP_TIMEOUT = 5000; // 调用超时时间 export const DEFAULT_BLACKLIST = [ "https://fishjar.github.io/kiss-translator/options.html", "https://translate.google.com", @@ -657,6 +662,7 @@ export const DEFAULT_SETTING = { minLength: TRANS_MIN_LENGTH, maxLength: TRANS_MAX_LENGTH, newlineLength: TRANS_NEWLINE_LENGTH, + httpTimeout: HTTP_TIMEOUT, clearCache: false, // 是否在浏览器下次启动时清除缓存 injectRules: true, // 是否注入订阅规则 // injectWebfix: true, // 是否注入修复补丁(作废) diff --git a/src/libs/fetch.js b/src/libs/fetch.js index 148be3f..e5b5b83 100644 --- a/src/libs/fetch.js +++ b/src/libs/fetch.js @@ -1,19 +1,23 @@ import { isExt, isGm } from "./client"; import { sendBgMsg } from "./msg"; import { taskPool } from "./pool"; +import { storage,getSettingWithDefault } from "./storage"; + + import { MSG_FETCH, MSG_GET_HTTPCACHE, CACHE_NAME, DEFAULT_FETCH_INTERVAL, DEFAULT_FETCH_LIMIT, + HTTP_TIMEOUT, } from "../config"; import { isBg } from "./browser"; import { genTransReq } from "../apis/trans"; import { kissLog } from "./log"; import { blobToBase64 } from "./utils"; -const TIMEOUT = 5000; +let TIMEOUT = HTTP_TIMEOUT; /** * 构造缓存 request @@ -39,7 +43,8 @@ const newCacheReq = async (input, init) => { * @param {*} init * @returns */ -export const fetchGM = async (input, { method = "GET", headers, body } = {}) => +export const fetchGM = async (input, { method = "GET", headers, body } = {}) =>{ + TIMEOUT = (await getSettingWithDefault()).httpTimeout; new Promise((resolve, reject) => { GM.xmlHttpRequest({ method, @@ -66,7 +71,7 @@ export const fetchGM = async (input, { method = "GET", headers, body } = {}) => onerror: reject, }); }); - +} /** * 发起请求 * @param {*} param0 @@ -108,7 +113,11 @@ export const fetchPatcher = async (input, init, transOpts, apiSetting) => { } } - if (AbortSignal?.timeout) { + //const kiss_setting = await storage.get("KISS-Translator_setting"); + //TIMEOUT = JSON.parse(kiss_setting)?.httpTimeout || TIMEOUT; + TIMEOUT = (await getSettingWithDefault()).httpTimeout; + console.log("TIMEOUT", TIMEOUT); + if (AbortSignal?.timeout && !init.signal) { Object.assign(init, { signal: AbortSignal.timeout(TIMEOUT) }); } diff --git a/src/views/Options/Apis.js b/src/views/Options/Apis.js index 55f17fc..af66062 100644 --- a/src/views/Options/Apis.js +++ b/src/views/Options/Apis.js @@ -117,6 +117,7 @@ function ApiFields({ translator }) { model = "", systemPrompt = "", userPrompt = "", + thinkIgnore = "", fetchLimit = DEFAULT_FETCH_LIMIT, fetchInterval = DEFAULT_FETCH_INTERVAL, dictNo = "", @@ -246,6 +247,18 @@ function ApiFields({ translator }) { )} + {(translator.startsWith(OPT_TRANS_OLLAMA)) && ( + <> + + + )} + {(translator.startsWith(OPT_TRANS_OPENAI) || translator === OPT_TRANS_CLAUDE) && ( <> diff --git a/src/views/Options/Setting.js b/src/views/Options/Setting.js index b916e4c..440f405 100644 --- a/src/views/Options/Setting.js +++ b/src/views/Options/Setting.js @@ -27,6 +27,7 @@ import { DEFAULT_CSPLIST, MSG_CONTEXT_MENUS, MSG_UPDATE_CSP, + HTTP_TIMEOUT, } from "../../config"; import { useShortcut } from "../../hooks/Shortcut"; import ShortcutInput from "./ShortcutInput"; @@ -71,6 +72,9 @@ export default function Settings() { case "newlineLength": value = limitNumber(value, 1, 1000); break; + case "httpTimeout": + value = limitNumber(value, 5000, 30000); + break; case "touchTranslate": value = limitNumber(value, 0, 4); break; @@ -110,6 +114,7 @@ export default function Settings() { maxLength, clearCache, newlineLength = TRANS_NEWLINE_LENGTH, + httpTimeout = HTTP_TIMEOUT, contextMenuType = 1, touchTranslate = 2, blacklist = DEFAULT_BLACKLIST.join(",\n"), @@ -188,7 +193,14 @@ export default function Settings() { defaultValue={transInterval} onChange={handleChange} /> - + {i18n("touch_translate_shortcut")}