sync subscribe rules when browser start or userscript run

This commit is contained in:
Gabe Yuan
2023-08-22 16:27:09 +08:00
parent 2224455a7f
commit ca1e1148d6
10 changed files with 193 additions and 108 deletions

1
.env
View File

@@ -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

View File

@@ -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,
{
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,
{
return fetchPolyfill(input, {
headers: {
"Content-type": "application/json",
},
method: "POST",
body: JSON.stringify([{ Text: text }]),
},
{ useCache: true, usePool: true, translator }
);
useCache: true,
usePool: true,
translator,
});
};
/**
@@ -100,9 +99,7 @@ const apiOpenaiTranslate = async (translator, text, to, from) => {
let prompt = openaiPrompt
.replaceAll(PROMPT_PLACE_FROM, from)
.replaceAll(PROMPT_PLACE_TO, to);
return fetchPolyfill(
openaiUrl,
{
return fetchPolyfill(openaiUrl, {
headers: {
"Content-type": "application/json",
},
@@ -122,9 +119,11 @@ const apiOpenaiTranslate = async (translator, text, to, from) => {
temperature: 0,
max_tokens: 256,
}),
},
{ useCache: true, usePool: true, translator, token: openaiKey }
);
useCache: true,
usePool: true,
translator,
token: openaiKey,
});
};
/**

View File

@@ -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 });
})

View File

@@ -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, // 订阅规则同步时间
};

View File

@@ -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);
};
/**

View File

@@ -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) {

View File

@@ -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);
};

View File

@@ -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, {
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, {
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);
};

View File

@@ -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);
})();

View File

@@ -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);