diff --git a/.env b/.env index a71b188..0da7640 100644 --- a/.env +++ b/.env @@ -8,5 +8,6 @@ REACT_APP_OPTIONSPAGE=https://kiss-translator.rayjar.com/options REACT_APP_OPTIONSPAGE2=https://fishjar.github.io/kiss-translator/options.html REACT_APP_OPTIONSPAGE_DEV=http://localhost:3000/options.html REACT_APP_LOGOURL=https://kiss-translator.rayjar.com/images/logo192.png +REACT_APP_RULESURL=https://kiss-translator.rayjar.com/kiss-translator-rules.json REACT_APP_USERSCRIPT_DOWNLOADURL=https://kiss-translator.rayjar.com/kiss-translator.user.js REACT_APP_USERSCRIPT_DOWNLOADURL2=https://fishjar.github.io/kiss-translator/kiss-translator.user.js diff --git a/src/apis/index.js b/src/apis/index.js index c2c8bb7..3142a3a 100644 --- a/src/apis/index.js +++ b/src/apis/index.js @@ -20,7 +20,7 @@ import { sha256 } from "../libs/utils"; * @param {*} data * @returns */ -export const apiSyncData = async (url, key, data) => +export const apiSyncData = async (url, key, data, isBg = false) => fetchPolyfill(url, { headers: { "Content-type": "application/json", @@ -28,6 +28,7 @@ export const apiSyncData = async (url, key, data) => }, method: "POST", body: JSON.stringify(data), + isBg, }); /** @@ -49,15 +50,14 @@ const apiGoogleTranslate = async (translator, text, to, from) => { }; const { googleUrl } = await getSetting(); const input = `${googleUrl}?${queryString.stringify(params)}`; - return fetchPolyfill( - input, - { - headers: { - "Content-type": "application/json", - }, + return fetchPolyfill(input, { + headers: { + "Content-type": "application/json", }, - { useCache: true, usePool: true, translator } - ); + useCache: true, + usePool: true, + translator, + }); }; /** @@ -74,17 +74,16 @@ const apiMicrosoftTranslate = (translator, text, to, from) => { "api-version": "3.0", }; const input = `${URL_MICROSOFT_TRANS}?${queryString.stringify(params)}`; - return fetchPolyfill( - input, - { - headers: { - "Content-type": "application/json", - }, - method: "POST", - body: JSON.stringify([{ Text: text }]), + return fetchPolyfill(input, { + headers: { + "Content-type": "application/json", }, - { useCache: true, usePool: true, translator } - ); + method: "POST", + body: JSON.stringify([{ Text: text }]), + useCache: true, + usePool: true, + translator, + }); }; /** @@ -100,31 +99,31 @@ const apiOpenaiTranslate = async (translator, text, to, from) => { let prompt = openaiPrompt .replaceAll(PROMPT_PLACE_FROM, from) .replaceAll(PROMPT_PLACE_TO, to); - return fetchPolyfill( - openaiUrl, - { - headers: { - "Content-type": "application/json", - }, - method: "POST", - body: JSON.stringify({ - model: openaiModel, - messages: [ - { - role: "system", - content: prompt, - }, - { - role: "user", - content: text, - }, - ], - temperature: 0, - max_tokens: 256, - }), + return fetchPolyfill(openaiUrl, { + headers: { + "Content-type": "application/json", }, - { useCache: true, usePool: true, translator, token: openaiKey } - ); + method: "POST", + body: JSON.stringify({ + model: openaiModel, + messages: [ + { + role: "system", + content: prompt, + }, + { + role: "user", + content: text, + }, + ], + temperature: 0, + max_tokens: 256, + }), + useCache: true, + usePool: true, + translator, + token: openaiKey, + }); }; /** diff --git a/src/background.js b/src/background.js index 8b59eac..c930ea6 100644 --- a/src/background.js +++ b/src/background.js @@ -14,12 +14,15 @@ import { STOKEY_RULES, STOKEY_SYNC, CACHE_NAME, + STOKEY_RULESCACHE_PREFIX, + BUILTIN_RULES, } from "./config"; import storage from "./libs/storage"; import { getSetting } from "./libs"; import { syncAll } from "./libs/sync"; import { fetchData, fetchPool } from "./libs/fetch"; import { sendTabMsg } from "./libs/msg"; +import { trySyncAllSubRules } from "./libs/rules"; /** * 插件安装 @@ -29,7 +32,10 @@ browser.runtime.onInstalled.addListener(() => { storage.trySetObj(STOKEY_SETTING, DEFAULT_SETTING); storage.trySetObj(STOKEY_RULES, DEFAULT_RULES); storage.trySetObj(STOKEY_SYNC, DEFAULT_SYNC); - // todo:缓存内置rules + storage.trySetObj( + `${STOKEY_RULESCACHE_PREFIX}${process.env.REACT_APP_RULESURL}`, + BUILTIN_RULES + ); }); /** @@ -39,13 +45,16 @@ browser.runtime.onStartup.addListener(async () => { console.log("browser onStartup"); // 同步数据 - await syncAll(); + await syncAll(true); // 清除缓存 - const { clearCache } = await getSetting(); - if (clearCache) { + const setting = await getSetting(); + if (setting.clearCache) { caches.delete(CACHE_NAME); } + + // 同步订阅规则 + trySyncAllSubRules(setting, true); }); /** @@ -55,8 +64,8 @@ browser.runtime.onMessage.addListener( ({ action, args }, sender, sendResponse) => { switch (action) { case MSG_FETCH: - const { input, init, opts } = args; - fetchData(input, init, opts) + const { input, opts } = args; + fetchData(input, opts) .then((data) => { sendResponse({ data }); }) diff --git a/src/config/index.js b/src/config/index.js index f6a3874..4375f90 100644 --- a/src/config/index.js +++ b/src/config/index.js @@ -157,7 +157,7 @@ export const GLOBLA_RULE = { // 订阅列表 export const DEFAULT_SUBRULES_LIST = [ { - url: "https://kiss-translator.rayjar.com/kiss-translator-rules.json", + url: process.env.REACT_APP_RULESURL, selected: true, }, { @@ -192,4 +192,5 @@ export const DEFAULT_SYNC = { settingSyncAt: 0, rulesUpdateAt: 0, rulesSyncAt: 0, + subRulesSyncAt: 0, // 订阅规则同步时间 }; diff --git a/src/libs/fetch.js b/src/libs/fetch.js index c1945e8..dc15e40 100644 --- a/src/libs/fetch.js +++ b/src/libs/fetch.js @@ -65,7 +65,7 @@ const newCacheReq = async (request) => { * @param {*} param0 * @returns */ -const fetchApi = async ({ input, init, translator, token }) => { +const fetchApi = async ({ input, init = {}, translator, token }) => { if (translator === OPT_TRANS_MICROSOFT) { init.headers["Authorization"] = `Bearer ${token}`; } else if (translator === OPT_TRANS_OPENAI) { @@ -103,14 +103,12 @@ export const fetchPool = taskPool( /** * 请求数据统一接口 * @param {*} input - * @param {*} init * @param {*} opts * @returns */ export const fetchData = async ( input, - init, - { useCache, usePool, translator, token } = {} + { useCache, usePool, translator, token, ...init } = {} ) => { const cacheReq = await newCacheReq(new Request(input, init)); const cache = await caches.open(CACHE_NAME); @@ -157,22 +155,21 @@ export const fetchData = async ( /** * fetch 兼容性封装 * @param {*} input - * @param {*} init * @param {*} opts * @returns */ -export const fetchPolyfill = async (input, init, opts) => { +export const fetchPolyfill = async (input, { isBg = false, ...opts } = {}) => { // 插件 - if (isExt) { - const res = await sendMsg(MSG_FETCH, { input, init, opts }); + if (isExt && !isBg) { + const res = await sendMsg(MSG_FETCH, { input, opts }); if (res.error) { throw new Error(res.error); } return res.data; } - // 油猴/网页 - return await fetchData(input, init, opts); + // 油猴/网页/BackgroundPage + return await fetchData(input, opts); }; /** diff --git a/src/libs/index.js b/src/libs/index.js index 7f56962..34ef87e 100644 --- a/src/libs/index.js +++ b/src/libs/index.js @@ -10,7 +10,7 @@ import { } from "../config"; import { browser } from "./browser"; import { isMatch } from "./utils"; -import { tryLoadRules } from "./rules"; +import { loadSubRules } from "./rules"; /** * 获取节点列表并转为数组 @@ -63,7 +63,7 @@ export const matchRule = async ( try { const selectedSub = subrulesList.find((item) => item.selected); if (selectedSub?.url) { - const subRules = await tryLoadRules(selectedSub.url); + const subRules = await loadSubRules(selectedSub.url); rules.splice(-1, 0, ...subRules); } } catch (err) { diff --git a/src/libs/rules.js b/src/libs/rules.js index c3611bf..447019f 100644 --- a/src/libs/rules.js +++ b/src/libs/rules.js @@ -9,6 +9,7 @@ import { OPT_LANGS_FROM, OPT_LANGS_TO, } from "../config"; +import { syncOpt } from "./sync"; const fromLangs = OPT_LANGS_FROM.map((item) => item[0]); const toLangs = OPT_LANGS_TO.map((item) => item[0]); @@ -62,11 +63,11 @@ export const checkRules = (rules) => { }; /** - * 本地rules缓存 + * 订阅规则的本地缓存 */ export const rulesCache = { - fetch: async (url) => { - const res = await fetchPolyfill(url); + fetch: async (url, isBg = false) => { + const res = await fetchPolyfill(url, { isBg }); const rules = checkRules(res).filter( (rule) => rule.pattern.replaceAll(GLOBAL_KEY, "") !== "" ); @@ -84,16 +85,61 @@ export const rulesCache = { }; /** - * 从缓存或远程加载订阅的rules + * 同步订阅规则 * @param {*} url * @returns */ -export const tryLoadRules = async (url) => { - let rules = await rulesCache.get(url); +export const syncSubRules = async (url, isBg = false) => { + const rules = await rulesCache.fetch(url, isBg); + if (rules.length > 0) { + await rulesCache.set(url, rules); + } + return rules; +}; + +/** + * 同步所有订阅规则 + * @param {*} url + * @returns + */ +export const syncAllSubRules = async (subrulesList, isBg = false) => { + for (let subrules of subrulesList) { + try { + await syncSubRules(subrules.url, isBg); + } catch (err) { + console.log(`[sync subrule error]: ${subrules.url}`, err); + } + } +}; + +/** + * 根据时间同步所有订阅规则 + * @param {*} url + * @returns + */ +export const trySyncAllSubRules = async ({ subrulesList }, isBg = false) => { + try { + const { subRulesSyncAt } = await syncOpt.load(); + const now = Date.now(); + const interval = 24 * 60 * 60 * 1000; // 间隔一天 + if (now - subRulesSyncAt > interval) { + await syncAllSubRules(subrulesList, isBg); + await syncOpt.update({ subRulesSyncAt: now }); + } + } catch (err) { + console.log("[try sync all subrules]", err); + } +}; + +/** + * 从缓存或远程加载订阅规则 + * @param {*} url + * @returns + */ +export const loadSubRules = async (url) => { + const rules = await rulesCache.get(url); if (rules?.length) { return rules; } - rules = await rulesCache.fetch(url); - await rulesCache.set(url, rules); - return rules; + return await syncSubRules(url); }; diff --git a/src/libs/sync.js b/src/libs/sync.js index 8fe53c4..6cc95fb 100644 --- a/src/libs/sync.js +++ b/src/libs/sync.js @@ -13,69 +13,95 @@ import { getSetting, getRules } from "."; import { apiSyncData } from "../apis"; import { sha256 } from "./utils"; -export const loadSyncOpt = async () => - (await storage.getObj(STOKEY_SYNC)) || DEFAULT_SYNC; +/** + * 同步相关数据 + */ +export const syncOpt = { + load: async () => (await storage.getObj(STOKEY_SYNC)) || DEFAULT_SYNC, + update: async (obj) => { + await storage.putObj(STOKEY_SYNC, obj); + }, +}; -export const syncSetting = async () => { +/** + * 同步设置 + * @returns + */ +export const syncSetting = async (isBg = false) => { try { - const { syncUrl, syncKey, settingUpdateAt } = await loadSyncOpt(); + const { syncUrl, syncKey, settingUpdateAt } = await syncOpt.load(); if (!syncUrl || !syncKey) { return; } const setting = await getSetting(); - const res = await apiSyncData(syncUrl, syncKey, { - key: KV_SETTING_KEY, - value: setting, - updateAt: settingUpdateAt, - }); + const res = await apiSyncData( + syncUrl, + syncKey, + { + key: KV_SETTING_KEY, + value: setting, + updateAt: settingUpdateAt, + }, + isBg + ); if (res && res.updateAt > settingUpdateAt) { - await storage.putObj(STOKEY_SYNC, { + await syncOpt.update({ settingUpdateAt: res.updateAt, settingSyncAt: res.updateAt, }); await storage.setObj(STOKEY_SETTING, res.value); } else { - await storage.putObj(STOKEY_SYNC, { - settingSyncAt: res.updateAt, - }); + await syncOpt.update({ settingSyncAt: res.updateAt }); } } catch (err) { console.log("[sync setting]", err); } }; -export const syncRules = async () => { +/** + * 同步规则 + * @returns + */ +export const syncRules = async (isBg = false) => { try { - const { syncUrl, syncKey, rulesUpdateAt } = await loadSyncOpt(); + const { syncUrl, syncKey, rulesUpdateAt } = await syncOpt.load(); if (!syncUrl || !syncKey) { return; } const rules = await getRules(); - const res = await apiSyncData(syncUrl, syncKey, { - key: KV_RULES_KEY, - value: rules, - updateAt: rulesUpdateAt, - }); + const res = await apiSyncData( + syncUrl, + syncKey, + { + key: KV_RULES_KEY, + value: rules, + updateAt: rulesUpdateAt, + }, + isBg + ); if (res && res.updateAt > rulesUpdateAt) { - await storage.putObj(STOKEY_SYNC, { + await syncOpt.update({ rulesUpdateAt: res.updateAt, rulesSyncAt: res.updateAt, }); await storage.setObj(STOKEY_RULES, res.value); } else { - await storage.putObj(STOKEY_SYNC, { - rulesSyncAt: res.updateAt, - }); + await syncOpt.update({ rulesSyncAt: res.updateAt }); } } catch (err) { console.log("[sync user rules]", err); } }; +/** + * 同步分享规则 + * @param {*} param0 + * @returns + */ export const syncShareRules = async ({ rules, syncUrl, syncKey }) => { await apiSyncData(syncUrl, syncKey, { key: KV_RULES_SHARE_KEY, @@ -87,7 +113,11 @@ export const syncShareRules = async ({ rules, syncUrl, syncKey }) => { return shareUrl; }; -export const syncAll = async () => { - await syncSetting(); - await syncRules(); +/** + * 同步个人设置和规则 + * @returns + */ +export const syncAll = async (isBg = false) => { + await syncSetting(isBg); + await syncRules(isBg); }; diff --git a/src/userscript.js b/src/userscript.js index 7916654..41e081f 100644 --- a/src/userscript.js +++ b/src/userscript.js @@ -5,6 +5,7 @@ import createCache from "@emotion/cache"; import { CacheProvider } from "@emotion/react"; import { getSetting, getRules, matchRule, getFab } from "./libs"; import { Translator } from "./libs/translator"; +import { trySyncAllSubRules } from "./libs/rules"; /** * 入口函数 @@ -70,4 +71,7 @@ import { Translator } from "./libs/translator"; }, "C" ); + + // 同步订阅规则 + trySyncAllSubRules(setting); })(); diff --git a/src/views/Options/Rules.js b/src/views/Options/Rules.js index d319fd3..9111e39 100644 --- a/src/views/Options/Rules.js +++ b/src/views/Options/Rules.js @@ -36,9 +36,9 @@ import IconButton from "@mui/material/IconButton"; import ShareIcon from "@mui/icons-material/Share"; import SyncIcon from "@mui/icons-material/Sync"; import { useSubrules } from "../../hooks/Rules"; -import { rulesCache, tryLoadRules } from "../../libs/rules"; +import { rulesCache, loadSubRules, syncSubRules } from "../../libs/rules"; import { useAlert } from "../../hooks/Alert"; -import { loadSyncOpt, syncShareRules } from "../../libs/sync"; +import { syncOpt, syncShareRules } from "../../libs/sync"; function RuleFields({ rule, rules, setShow }) { const initFormValues = rule || { ...DEFAULT_RULE, transOpen: "true" }; @@ -402,7 +402,7 @@ function ShareButton({ rules, injectRules, selectedSub }) { const i18n = useI18n(); const handleClick = async () => { try { - const { syncUrl, syncKey } = await loadSyncOpt(); + const { syncUrl, syncKey } = await syncOpt.load(); if (!syncUrl || !syncKey) { alert.warning(i18n("error_sync_setting")); return; @@ -410,7 +410,7 @@ function ShareButton({ rules, injectRules, selectedSub }) { const shareRules = [...rules.list]; if (injectRules) { - const subRules = await tryLoadRules(selectedSub?.url); + const subRules = await loadSubRules(selectedSub?.url); shareRules.splice(-1, 0, ...subRules); } @@ -543,9 +543,8 @@ function SubRulesItem({ index, url, selectedUrl, subrules, setRules }) { const handleSync = async () => { try { setLoading(true); - const rules = await rulesCache.fetch(url); - await rulesCache.set(url, rules); - if (url === selectedUrl) { + const rules = await syncSubRules(url); + if (rules.length > 0 && url === selectedUrl) { setRules(rules); } } catch (err) { @@ -604,11 +603,10 @@ function SubRulesEdit({ subrules }) { } try { - const rules = await rulesCache.fetch(url); + const rules = await syncSubRules(url); if (rules.length === 0) { throw new Error("empty rules"); } - await rulesCache.set(url, rules); await subrules.add(url); setShowInput(false); setInputText(""); @@ -687,7 +685,7 @@ function SubRules() { try { setLoading(true); - const rules = await tryLoadRules(selectedSub?.url); + const rules = await loadSubRules(selectedSub?.url); setRules(rules); } catch (err) { console.log("[load rules]", err);