This commit is contained in:
Gabe Yuan
2023-08-30 18:05:37 +08:00
parent d7cee8cca6
commit c46fe7d1c6
33 changed files with 770 additions and 559 deletions

View File

@@ -1,4 +1,4 @@
import { CLIENT_EXTS, CLIENT_USERSCRIPT, CLIENT_WEB } from "../config";
// import { CLIENT_EXTS, CLIENT_USERSCRIPT, CLIENT_WEB } from "../config";
/**
* 浏览器兼容插件,另可用于判断是插件模式还是网页模式,方便开发
@@ -13,7 +13,21 @@ function _browser() {
}
export const browser = _browser();
export const client = process.env.REACT_APP_CLIENT;
export const isExt = CLIENT_EXTS.includes(client);
export const isGm = client === CLIENT_USERSCRIPT;
export const isWeb = client === CLIENT_WEB;
// export const client = process.env.REACT_APP_CLIENT;
// export const isExt = CLIENT_EXTS.includes(client);
// export const isGm = client === CLIENT_USERSCRIPT;
// export const isWeb = client === CLIENT_WEB;
/**
* 本地语言识别
* @param {*} q
* @returns
*/
export const detectLang = async (q) => {
try {
const res = await browser?.i18n?.detectLanguage(q);
return res?.languages?.[0]?.language;
} catch (err) {
console.log("[detect lang]", err);
}
};

6
src/libs/client.js Normal file
View File

@@ -0,0 +1,6 @@
import { CLIENT_EXTS, CLIENT_USERSCRIPT, CLIENT_WEB } from "../config";
export const client = process.env.REACT_APP_CLIENT;
export const isExt = CLIENT_EXTS.includes(client);
export const isGm = client === CLIENT_USERSCRIPT;
export const isWeb = client === CLIENT_WEB;

View File

@@ -1,4 +1,4 @@
import { isExt, isGm } from "./browser";
import { isExt, isGm } from "./client";
import { sendMsg } from "./msg";
import { taskPool } from "./pool";
import {

View File

@@ -1,108 +1,12 @@
import storage from "./storage";
import {
DEFAULT_SETTING,
STOKEY_SETTING,
STOKEY_RULES,
STOKEY_FAB,
GLOBLA_RULE,
GLOBAL_KEY,
DEFAULT_SUBRULES_LIST,
} from "../config";
import { browser } from "./browser";
import { isMatch } from "./utils";
import { loadSubRules } from "./rules";
import { CACHE_NAME } from "../config";
/**
* 查询storage中的设置
* @returns
* 清除缓存数据
*/
export const getSetting = async () => ({
...DEFAULT_SETTING,
...((await storage.getObj(STOKEY_SETTING)) || {}),
});
/**
* 查询规则列表
* @returns
*/
export const getRules = async () => (await storage.getObj(STOKEY_RULES)) || [];
/**
* 查询fab位置信息
* @returns
*/
export const getFab = async () => (await storage.getObj(STOKEY_FAB)) || {};
/**
* 设置fab位置信息
* @returns
*/
export const setFab = async (obj) => await storage.setObj(STOKEY_FAB, obj);
/**
* 根据href匹配规则
* @param {*} rules
* @param {string} href
* @returns
*/
export const matchRule = async (
rules,
href,
{ injectRules = true, subrulesList = DEFAULT_SUBRULES_LIST }
) => {
rules = [...rules];
if (injectRules) {
try {
const selectedSub = subrulesList.find((item) => item.selected);
if (selectedSub?.url) {
const subRules = await loadSubRules(selectedSub.url);
rules.splice(-1, 0, ...subRules);
}
} catch (err) {
console.log("[load injectRules]", err);
}
}
const rule = rules.find((r) =>
r.pattern.split(",").some((p) => isMatch(href, p.trim()))
);
const globalRule =
rules.find((r) => r.pattern.split(",").some((p) => p.trim() === "*")) ||
GLOBLA_RULE;
if (!rule) {
return globalRule;
}
rule.selector =
rule?.selector?.trim() ||
globalRule?.selector?.trim() ||
GLOBLA_RULE.selector;
rule.bgColor = rule?.bgColor?.trim() || globalRule?.bgColor?.trim();
["translator", "fromLang", "toLang", "textStyle", "transOpen"].forEach(
(key) => {
if (rule[key] === GLOBAL_KEY) {
rule[key] = globalRule[key];
}
}
);
return rule;
};
/**
* 本地语言识别
* @param {*} q
* @returns
*/
export const detectLang = async (q) => {
export const tryClearCaches = async () => {
try {
const res = await browser?.i18n?.detectLanguage(q);
return res?.languages?.[0]?.language;
caches.delete(CACHE_NAME);
} catch (err) {
console.log("[detect lang]", err);
console.log("[clean caches]", err.message);
}
};

View File

@@ -1,18 +1,78 @@
import storage from "./storage";
import { fetchPolyfill } from "./fetch";
import { matchValue, type } from "./utils";
import {
STOKEY_RULESCACHE_PREFIX,
getSyncWithDefault,
updateSync,
getSubRulesWithDefault,
getSubRules,
delSubRules,
setSubRules,
} from "./storage";
import { fetchPolyfill } from "./fetch";
import { matchValue, type, isMatch } from "./utils";
import {
GLOBAL_KEY,
OPT_TRANS_ALL,
OPT_STYLE_ALL,
OPT_LANGS_FROM,
OPT_LANGS_TO,
GLOBLA_RULE,
DEFAULT_SUBRULES_LIST,
} from "../config";
import { syncOpt } from "./sync";
const fromLangs = OPT_LANGS_FROM.map((item) => item[0]);
const toLangs = OPT_LANGS_TO.map((item) => item[0]);
// import { syncOpt } from "./sync";
/**
* 根据href匹配规则
* @param {*} rules
* @param {string} href
* @returns
*/
export const matchRule = async (
rules,
href,
{ injectRules = true, subrulesList = DEFAULT_SUBRULES_LIST }
) => {
rules = [...rules];
if (injectRules) {
try {
const selectedSub = subrulesList.find((item) => item.selected);
if (selectedSub?.url) {
const subRules = await loadSubRules(selectedSub.url);
rules.splice(-1, 0, ...subRules);
}
} catch (err) {
console.log("[load injectRules]", err);
}
}
const rule = rules.find((r) =>
r.pattern.split(",").some((p) => isMatch(href, p.trim()))
);
const globalRule =
rules.find((r) => r.pattern.split(",").some((p) => p.trim() === "*")) ||
GLOBLA_RULE;
if (!rule) {
return globalRule;
}
rule.selector =
rule?.selector?.trim() ||
globalRule?.selector?.trim() ||
GLOBLA_RULE.selector;
rule.bgColor = rule?.bgColor?.trim() || globalRule?.bgColor?.trim();
["translator", "fromLang", "toLang", "textStyle", "transOpen"].forEach(
(key) => {
if (rule[key] === GLOBAL_KEY) {
rule[key] = globalRule[key];
}
}
);
return rule;
};
/**
* 检查过滤rules
@@ -27,6 +87,8 @@ export const checkRules = (rules) => {
throw new Error("data error");
}
const fromLangs = OPT_LANGS_FROM.map((item) => item[0]);
const toLangs = OPT_LANGS_TO.map((item) => item[0]);
const patternSet = new Set();
rules = rules
.filter((rule) => type(rule) === "object")
@@ -62,84 +124,18 @@ export const checkRules = (rules) => {
return rules;
};
/**
* 订阅规则的本地缓存
*/
export const rulesCache = {
fetch: async (url, isBg = false) => {
const res = await fetchPolyfill(url, { isBg });
const rules = checkRules(res).filter(
(rule) => rule.pattern.replaceAll(GLOBAL_KEY, "") !== ""
);
return rules;
},
set: async (url, rules) => {
await storage.setObj(`${STOKEY_RULESCACHE_PREFIX}${url}`, rules);
},
get: async (url) => {
return await storage.getObj(`${STOKEY_RULESCACHE_PREFIX}${url}`);
},
del: async (url) => {
await storage.del(`${STOKEY_RULESCACHE_PREFIX}${url}`);
},
};
/**
* 同步订阅规则
* @param {*} url
* @returns
*/
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;
}
return await syncSubRules(url);
};
// /**
// * 订阅规则的本地缓存
// */
// export const rulesCache = {
// fetch: async (url, isBg = false) => {
// const res = await fetchPolyfill(url, { isBg });
// const rules = checkRules(res).filter(
// (rule) => rule.pattern.replaceAll(GLOBAL_KEY, "") !== ""
// );
// return rules;
// },
// set: (url, rules) => setSubRules(url, rules),
// get: (url) => getSubRulesWithDefault(url),
// del: (url) => delSubRules(url),
// };

View File

@@ -1,28 +1,41 @@
import { browser, isExt, isGm } from "./browser";
import {
STOKEY_SETTING,
STOKEY_RULES,
STOKEY_FAB,
STOKEY_SYNC,
STOKEY_MSAUTH,
STOKEY_RULESCACHE_PREFIX,
DEFAULT_SETTING,
DEFAULT_RULES,
DEFAULT_SYNC,
BUILTIN_RULES,
} from "../config";
import { browser, isExt, isGm } from "./client";
// import { APP_NAME } from "../config/app";
async function set(key, val) {
if (isExt) {
await browser.storage.local.set({ [key]: val });
} else if (isGm) {
const oldValue = await (window.KISS_GM || GM).getValue(key);
// const oldValue = await (window.KISS_GM || GM).getValue(key);
await (window.KISS_GM || GM).setValue(key, val);
window.dispatchEvent(
new StorageEvent("storage", {
key,
oldValue,
newValue: val,
})
);
// window.dispatchEvent(
// new StorageEvent("storage", {
// key,
// oldValue,
// newValue: val,
// })
// );
} else {
const oldValue = window.localStorage.getItem(key);
// const oldValue = window.localStorage.getItem(key);
window.localStorage.setItem(key, val);
window.dispatchEvent(
new StorageEvent("storage", {
key,
oldValue,
newValue: val,
})
);
// window.dispatchEvent(
// new StorageEvent("storage", {
// key,
// oldValue,
// newValue: val,
// })
// );
}
}
@@ -41,25 +54,25 @@ async function del(key) {
if (isExt) {
await browser.storage.local.remove([key]);
} else if (isGm) {
const oldValue = await (window.KISS_GM || GM).getValue(key);
// const oldValue = await (window.KISS_GM || GM).getValue(key);
await (window.KISS_GM || GM).deleteValue(key);
window.dispatchEvent(
new StorageEvent("storage", {
key,
oldValue,
newValue: null,
})
);
// window.dispatchEvent(
// new StorageEvent("storage", {
// key,
// oldValue,
// newValue: null,
// })
// );
} else {
const oldValue = window.localStorage.getItem(key);
// const oldValue = window.localStorage.getItem(key);
window.localStorage.removeItem(key);
window.dispatchEvent(
new StorageEvent("storage", {
key,
oldValue,
newValue: null,
})
);
// window.dispatchEvent(
// new StorageEvent("storage", {
// key,
// oldValue,
// newValue: null,
// })
// );
}
}
@@ -83,22 +96,22 @@ async function putObj(key, obj) {
await setObj(key, { ...cur, ...obj });
}
/**
* 监听storage事件
* @param {*} handleChanged
*/
function onChanged(handleChanged) {
if (isExt) {
browser.storage.onChanged.addListener(handleChanged);
} else {
window.addEventListener("storage", handleChanged);
}
}
// /**
// * 监听storage事件
// * @param {*} handleChanged
// */
// function onChanged(handleChanged) {
// if (isExt) {
// browser.storage.onChanged.addListener(handleChanged);
// } else {
// window.addEventListener("storage", handleChanged);
// }
// }
/**
* 对storage的封装
*/
const storage = {
export const storage = {
get,
set,
del,
@@ -106,7 +119,70 @@ const storage = {
trySetObj,
getObj,
putObj,
onChanged,
// onChanged,
};
export default storage;
/**
* 设置信息
*/
export const getSetting = () => getObj(STOKEY_SETTING);
export const getSettingWithDefault = async () => ({
...DEFAULT_SETTING,
...((await getSetting()) || {}),
});
export const setSetting = (val) => setObj(STOKEY_SETTING, val);
export const updateSetting = (obj) => putObj(STOKEY_SETTING, obj);
/**
* 规则列表
*/
export const getRules = () => getObj(STOKEY_RULES);
export const getRulesWithDefault = async () =>
(await getRules()) || DEFAULT_RULES;
export const setRules = (val) => setObj(STOKEY_RULES, val);
/**
* 订阅规则
*/
export const getSubRules = (url) => getObj(STOKEY_RULESCACHE_PREFIX + url);
export const getSubRulesWithDefault = async () => (await getSubRules()) || [];
export const delSubRules = (url) => del(STOKEY_RULESCACHE_PREFIX + url);
export const setSubRules = (url, val) =>
setObj(STOKEY_RULESCACHE_PREFIX + url, val);
/**
* fab位置
*/
export const getFab = () => getObj(STOKEY_FAB);
export const getFabWithDefault = async () => (await getFab()) || {};
export const setFab = (obj) => setObj(STOKEY_FAB, obj);
/**
* 数据同步
*/
export const getSync = () => getObj(STOKEY_SYNC);
export const getSyncWithDefault = async () => (await getSync()) || DEFAULT_SYNC;
export const updateSync = (obj) => putObj(STOKEY_SYNC, obj);
/**
* ms auth
*/
export const getMsauth = () => getObj(STOKEY_MSAUTH);
export const setMsauth = (val) => setObj(STOKEY_MSAUTH, val);
/**
* 存入默认数据
*/
export const tryInitDefaultData = async () => {
try {
await trySetObj(STOKEY_SETTING, DEFAULT_SETTING);
await trySetObj(STOKEY_RULES, DEFAULT_RULES);
await trySetObj(STOKEY_SYNC, DEFAULT_SYNC);
await trySetObj(
`${STOKEY_RULESCACHE_PREFIX}${process.env.REACT_APP_RULESURL}`,
BUILTIN_RULES
);
} catch (err) {
console.log("[init default]", err);
}
};

62
src/libs/subRules.js Normal file
View File

@@ -0,0 +1,62 @@
import { getSyncWithDefault, updateSync } from "./storage";
import { apiFetchRules } from "../apis";
/**
* 同步订阅规则
* @param {*} url
* @returns
*/
export const syncSubRules = async (url, isBg = false) => {
const rules = await apiFetchRules(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 getSyncWithDefault();
const now = Date.now();
const interval = 24 * 60 * 60 * 1000; // 间隔一天
if (now - subRulesSyncAt > interval) {
await syncAllSubRules(subrulesList, isBg);
await updateSync({ subRulesSyncAt: now });
}
} catch (err) {
console.log("[try sync all subrules]", err);
}
};
/**
* 从缓存或远程加载订阅规则
* @param {*} url
* @returns
*/
export const loadOrFetchSubRules = async (url) => {
const rules = await apiFetchRules(url);
if (rules?.length) {
return rules;
}
return await syncSubRules(url);
};

View File

@@ -8,27 +8,27 @@ import {
STOKEY_RULES,
KV_SALT_SHARE,
} from "../config";
import storage from "../libs/storage";
import { storage, getSyncWithDefault, updateSync } from "../libs/storage";
import { getSetting, getRules } from ".";
import { apiSyncData } from "../apis";
import { sha256 } from "./utils";
/**
* 同步相关数据
*/
export const syncOpt = {
load: async () => (await storage.getObj(STOKEY_SYNC)) || DEFAULT_SYNC,
update: async (obj) => {
await storage.putObj(STOKEY_SYNC, obj);
},
};
// /**
// * 同步相关数据
// */
// export const syncOpt = {
// load: async () => (await storage.getObj(STOKEY_SYNC)) || DEFAULT_SYNC,
// update: async (obj) => {
// await storage.putObj(STOKEY_SYNC, obj);
// },
// };
/**
* 同步设置
* @returns
*/
export const syncSetting = async (isBg = false) => {
const { syncUrl, syncKey, settingUpdateAt } = await syncOpt.load();
const syncSetting = async (isBg = false) => {
const { syncUrl, syncKey, settingUpdateAt } = await getSyncWithDefault();
if (!syncUrl || !syncKey) {
return;
}
@@ -46,13 +46,13 @@ export const syncSetting = async (isBg = false) => {
);
if (res && res.updateAt > settingUpdateAt) {
await syncOpt.update({
await updateSync({
settingUpdateAt: res.updateAt,
settingSyncAt: res.updateAt,
});
await storage.setObj(STOKEY_SETTING, res.value);
} else {
await syncOpt.update({ settingSyncAt: res.updateAt });
await updateSync({ settingSyncAt: res.updateAt });
}
};
@@ -68,8 +68,8 @@ export const trySyncSetting = async (isBg = false) => {
* 同步规则
* @returns
*/
export const syncRules = async (isBg = false) => {
const { syncUrl, syncKey, rulesUpdateAt } = await syncOpt.load();
const syncRules = async (isBg = false) => {
const { syncUrl, syncKey, rulesUpdateAt } = await getSyncWithDefault();
if (!syncUrl || !syncKey) {
return;
}
@@ -87,13 +87,13 @@ export const syncRules = async (isBg = false) => {
);
if (res && res.updateAt > rulesUpdateAt) {
await syncOpt.update({
await updateSync({
rulesUpdateAt: res.updateAt,
rulesSyncAt: res.updateAt,
});
await storage.setObj(STOKEY_RULES, res.value);
} else {
await syncOpt.update({ rulesSyncAt: res.updateAt });
await updateSync({ rulesSyncAt: res.updateAt });
}
};

View File

@@ -12,14 +12,16 @@ import {
import Content from "../views/Content";
import { fetchUpdate, fetchClear } from "./fetch";
import { debounce } from "./utils";
import { isExt } from "./client";
/**
* 翻译类
*/
export class Translator {
_rule = {};
_minLength = 0;
_maxLength = 0;
_setting = {};
_rootNodes = new Set();
_tranNodes = new Map();
_skipNodeNames = [
APP_LCNAME,
"style",
@@ -36,8 +38,6 @@ export class Translator {
"script",
"iframe",
];
_rootNodes = new Set();
_tranNodes = new Map();
// 显示
_interseObserver = new IntersectionObserver(
@@ -89,12 +89,14 @@ export class Translator {
};
};
constructor(rule, { fetchInterval, fetchLimit, minLength, maxLength }) {
constructor(rule, setting) {
const { fetchInterval, fetchLimit } = setting;
fetchUpdate(fetchInterval, fetchLimit);
this._overrideAttachShadow();
this._minLength = minLength ?? TRANS_MIN_LENGTH;
this._maxLength = maxLength ?? TRANS_MAX_LENGTH;
this.rule = rule;
this._setting = setting;
this._rule = rule;
if (rule.transOpen === "true") {
this._register();
}
@@ -268,7 +270,11 @@ export class Translator {
this._tranNodes.set(el, q);
// 太长或太短
if (!q || q.length < this._minLength || q.length > this._maxLength) {
if (
!q ||
q.length < (this._setting.minLength ?? TRANS_MIN_LENGTH) ||
q.length > (this._setting.maxLength ?? TRANS_MAX_LENGTH)
) {
return;
}