From 6d10253430b4ceb3b012a09b06ceb2f729da3671 Mon Sep 17 00:00:00 2001 From: Yun Shan Date: Fri, 14 Jun 2024 22:29:03 +0800 Subject: [PATCH] =?UTF-8?q?=E9=80=82=E9=85=8Dv3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chainWebpack.config.js | 2 + package-lock.json | 88 ++-- package.json | 6 +- src/background/CookieHandler.js | 16 +- src/background/countdown.js | 97 ++++ src/background/fetcher.js | 89 ++++ .../fetcher/impl/CeobeCanteenCookieFetcher.js | 1 + .../fetcher/impl/CustomLocalCookieFetcher.js | 58 +-- .../local/ArknightsOfficialWebDataSource.js | 27 -- .../impl/local/GameBulletinListDataSource.js | 4 +- src/background/heartbeat.js | 39 ++ src/background/index.js | 422 +++++++----------- src/background/request_interceptor.js | 56 ++- src/common/platform/AbstractPlatform.js | 63 ++- src/common/platform/PlatformHelper.js | 76 ++-- src/common/platform/impl/BrowserPlatform.js | 9 +- src/common/platform/impl/ChromePlatform.js | 86 ++-- src/common/platform/impl/FirefoxPlatform.js | 32 +- src/common/sync/SanInfo.js | 49 +- src/common/sync/SyncData.js | 7 + src/components/DataSourceSelect.vue | 33 +- src/manifest.json | 21 +- src/offscreen/html/img2blob.html | 10 + src/offscreen/js/img2blob.js | 18 + src/options/App.vue | 4 +- 25 files changed, 787 insertions(+), 526 deletions(-) create mode 100644 src/background/countdown.js create mode 100644 src/background/fetcher.js delete mode 100644 src/background/fetcher/impl/local/ArknightsOfficialWebDataSource.js create mode 100644 src/background/heartbeat.js create mode 100644 src/offscreen/html/img2blob.html create mode 100644 src/offscreen/js/img2blob.js diff --git a/chainWebpack.config.js b/chainWebpack.config.js index b74ebfa2..00ddb559 100644 --- a/chainWebpack.config.js +++ b/chainWebpack.config.js @@ -62,6 +62,7 @@ const chainWebpack = (config) => { } config.entry('background').add(path.resolve(__dirname, './src/background/index.js')); config.entry('contentScripts').add(path.resolve(__dirname, './src/contentScripts/index.js')); + config.entry('offscreen/img2blob').add(path.resolve(__dirname, './src/offscreen/js/img2blob.js')); config.output.filename('[name].js'); config.plugin('copy').tap((_args) => { @@ -83,6 +84,7 @@ const chainWebpack = (config) => { }, { from: 'src/Dun-Cookies-Info.json', to: '[name][ext]' }, { from: 'node_modules/element-ui/lib/theme-chalk/fonts/', to: 'css/fonts/[name][ext]' }, + { from: 'src/offscreen/html', to: 'offscreen' }, ], }, ]; diff --git a/package-lock.json b/package-lock.json index 0e95f612..f9970168 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,18 +1,18 @@ { "name": "ceobe-canteen-browser-extension", - "version": "4.0.5", + "version": "5.0.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "ceobe-canteen-browser-extension", - "version": "4.0.5", + "version": "5.0.0", "dependencies": { "@enraged-dun-cookie-development-team/common": "^0.0.1-alpha.28", "@enraged-dun-cookie-development-team/cookie-fetcher-core": "^0.0.1-alpha.16", "animate.css": "^4.1.1", "core-js": "^3.25.1", - "crypto-js": "^4.1.1", + "crypto-js": "^4.2.0", "element-ui": "^2.15.9", "fastest-levenshtein": "^1.0.16", "html2canvas": "^1.4.1", @@ -42,7 +42,7 @@ "less": "^4.1.3", "less-loader": "^8.1.1", "lint-staged": "^13.0.3", - "postcss": "^8.4.16", + "postcss": "^8.4.31", "postcss-html": "^1.5.0", "postcss-less": "6.0.0", "prettier": "^2.7.1", @@ -8372,9 +8372,10 @@ } }, "node_modules/crypto-js": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.1.1.tgz", - "integrity": "sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw==" + "version": "4.2.0", + "resolved": "https://registry.npmmirror.com/crypto-js/-/crypto-js-4.2.0.tgz", + "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==", + "license": "MIT" }, "node_modules/css-functions-list": { "version": "3.1.0", @@ -12612,9 +12613,16 @@ "dev": true }, "node_modules/nanoid": { - "version": "3.3.6", - "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.6.tgz", - "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", + "version": "3.3.7", + "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", "bin": { "nanoid": "bin/nanoid.cjs" }, @@ -13238,13 +13246,28 @@ } }, "node_modules/postcss": { - "version": "8.4.27", - "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.4.27.tgz", - "integrity": "sha512-gY/ACJtJPSmUFPDCHtX78+01fHa64FaU4zaaWfuh1MhGJISufJAH4cun6k/8fwsHYeK4UQmENQK+tRLCFJE8JQ==", + "version": "8.4.38", + "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.4.38.tgz", + "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", "dependencies": { - "nanoid": "^3.3.6", + "nanoid": "^3.3.7", "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" + "source-map-js": "^1.2.0" }, "engines": { "node": "^10 || ^12 || >=14" @@ -14530,9 +14553,10 @@ } }, "node_modules/source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } @@ -22510,9 +22534,9 @@ } }, "crypto-js": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.1.1.tgz", - "integrity": "sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw==" + "version": "4.2.0", + "resolved": "https://registry.npmmirror.com/crypto-js/-/crypto-js-4.2.0.tgz", + "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==" }, "css-functions-list": { "version": "3.1.0", @@ -25635,9 +25659,9 @@ } }, "nanoid": { - "version": "3.3.6", - "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.6.tgz", - "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==" + "version": "3.3.7", + "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==" }, "natural-compare": { "version": "1.4.0", @@ -26116,13 +26140,13 @@ "integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==" }, "postcss": { - "version": "8.4.27", - "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.4.27.tgz", - "integrity": "sha512-gY/ACJtJPSmUFPDCHtX78+01fHa64FaU4zaaWfuh1MhGJISufJAH4cun6k/8fwsHYeK4UQmENQK+tRLCFJE8JQ==", + "version": "8.4.38", + "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.4.38.tgz", + "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", "requires": { - "nanoid": "^3.3.6", + "nanoid": "^3.3.7", "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" + "source-map-js": "^1.2.0" } }, "postcss-html": { @@ -27061,9 +27085,9 @@ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" }, "source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==" + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==" }, "source-map-support": { "version": "0.5.21", diff --git a/package.json b/package.json index 400832bf..c51b152f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ceobe-canteen-browser-extension", - "version": "4.0.5", + "version": "5.0.0", "private": true, "scripts": { "serve": "vue-cli-service serve", @@ -15,7 +15,7 @@ "@enraged-dun-cookie-development-team/cookie-fetcher-core": "^0.0.1-alpha.16", "animate.css": "^4.1.1", "core-js": "^3.25.1", - "crypto-js": "^4.1.1", + "crypto-js": "^4.2.0", "element-ui": "^2.15.9", "fastest-levenshtein": "^1.0.16", "html2canvas": "^1.4.1", @@ -45,7 +45,7 @@ "less": "^4.1.3", "less-loader": "^8.1.1", "lint-staged": "^13.0.3", - "postcss": "^8.4.16", + "postcss": "^8.4.31", "postcss-html": "^1.5.0", "postcss-less": "6.0.0", "prettier": "^2.7.1", diff --git a/src/background/CookieHandler.js b/src/background/CookieHandler.js index de101041..66beb127 100644 --- a/src/background/CookieHandler.js +++ b/src/background/CookieHandler.js @@ -9,7 +9,6 @@ import { WeiboDataSource } from './fetcher/impl/local/WeiboDataSource'; import { NeteaseCloudMusicDataSource } from './fetcher/impl/local/NeteaseCloudMusicDataSource'; import { GameBulletinListDataSource } from './fetcher/impl/local/GameBulletinListDataSource'; import { MonsterSirenDataSource } from './fetcher/impl/local/MonsterSirenDataSource'; -import { ArknightsOfficialWebDataSource } from './fetcher/impl/local/ArknightsOfficialWebDataSource'; import { TerraHistoricusDataSource } from './fetcher/impl/local/TerraHistoricusDataSource'; import AvailableDataSourceMeta from '../common/sync/AvailableDataSourceMeta'; import { CookieItem } from '../common/CookieItem'; @@ -22,6 +21,16 @@ import { registerUrlToAddReferer } from './request_interceptor'; * @type {[string, string][]} */ const lastCookiesCache = []; +const lastCookiesCacheStorageKey = 'cache:lastCookies'; +PlatformHelper.Storage.getLocalStorage(lastCookiesCacheStorageKey).then((data) => { + if (typeof data === 'string' && data.length > 0) { + const cache = JSON.parse(data); + if (Array.isArray(cache) && cache.length > 0) { + lastCookiesCache.push(...cache); + } + } +}); + /** * 只缓存指定数量的饼用于检测重复 * @type {number} @@ -79,6 +88,7 @@ function tryNotice(source, newCookieList) { lastCookiesCache.shift(); } } + void PlatformHelper.Storage.saveLocalStorage(lastCookiesCacheStorageKey, JSON.stringify(lastCookiesCache)); } const LocalCardMap = {}; @@ -117,12 +127,10 @@ class CookieHandler { return GameBulletinListDataSource.processData(it.rawContent, sourceId); case 'arknights-website:monster-siren': return MonsterSirenDataSource.processData(it.rawContent, sourceId); - case 'arknights-website:official-website': - return ArknightsOfficialWebDataSource.processData(it.rawContent, sourceId); case 'arknights-website:terra-historicus': return TerraHistoricusDataSource.processData(it.rawContent, sourceId); default: - console.warn('未知数据源类型:' + it.dataSourceId.typeId); + console.warn('不支持的数据源类型:' + it.dataSourceId.typeId); } } }) diff --git a/src/background/countdown.js b/src/background/countdown.js new file mode 100644 index 00000000..162f6705 --- /dev/null +++ b/src/background/countdown.js @@ -0,0 +1,97 @@ +import DebugUtil from '../common/util/DebugUtil'; +import CountDown from '../common/sync/CountDownInfo'; +import PlatformHelper from '../common/platform/PlatformHelper'; +import NotificationUtil from '../common/util/NotificationUtil'; +import TimeUtil from '../common/util/TimeUtil'; + +function countDownDebugLog(...data) { + DebugUtil.debugConsoleOutput(0, 'debug', '%c 倒计时 ', 'color: white; background: #DA70D6', ...data); +} + +const countDownThreshold = 5 * 60 * 1000; +let countDownFlag = false; +const countDown = { + sendNoticeList: [], + countDownList: [], + Start() { + CountDown.getCountDownLocalStorage().then((data) => { + data = JSON.parse(data); + if (data) { + this.countDownList = []; + data + .map((x) => x.data) + .forEach((item) => { + this.countDownList.push(item); + const endTime = new Date(item.stopTime).getTime(); + const delayTime = endTime - new Date().getTime(); + if (delayTime >= countDownThreshold) { + countDownDebugLog(`设置alarm[${item.name}]-指定时间:${new Date(endTime).toLocaleString()}`); + const uniqueName = 'countdown_' + item.name + '|' + Math.random().toFixed(3).substring(2, 5); + PlatformHelper.Alarms.create(uniqueName, { when: endTime }); + } else { + countDownDebugLog(`设置setTimeout[${item.name}]-延时:${delayTime}`); + this.sendNoticeList.push( + setTimeout((_) => { + NotificationUtil.SendNotice( + `倒计时完毕`, + `${item.name} 到点了!`, + null, + 'countdown_' + new Date().getTime() + ); + // 有过通知后从内存中删除计时器数据 + CountDown.removeCountDown(item); + }, delayTime) + ); + } + }); + } + }); + }, + Change() { + if (countDownFlag) { + return; + } + countDownFlag = true; + countDownDebugLog('清空setTimeout'); + this.sendNoticeList.forEach((id) => { + clearTimeout(id); + }); + this.sendNoticeList = []; + countDownDebugLog('清空alarms'); + // TODO 简单粗暴全清了显然不行 + // PlatformHelper.Alarms.clearAll().finally(() => { + // this.Start(); + // countDownFlag = false; + // }); + }, + GetAllCountDown() { + let list = []; + this.countDownList.forEach((item) => { + let value = TimeUtil.calcDiff(new Date(item.stopTime), new Date()); + if (value != '') { + list.push({ ...item, timeStr: value, stopTime: TimeUtil.format(item.stopTime, 'yyyy-MM-dd hh:mm:ss') }); + } + }); + return list; + }, +}; + +function countdownAlarmHandler(alarm) { + if (!alarm || !alarm.name) return; + if (alarm.name.startsWith('countdown_')) { + const countDownName = name.split('|')[0].substring('countdown_'.length); + NotificationUtil.SendNotice(`倒计时完毕`, `${countDownName} 到点了!`, null, 'countdown_' + new Date().getTime()); + void CountDown.removeCountDownByName(countDownName); + } +} + +// TODO 暂时没有启用倒计时 +export function CountDownInit() { + countDown.Start(); + PlatformHelper.osIsMac().then((isMac) => { + if (isMac) { + setInterval(() => countDown.Change(), 10 * 60 * 1000); + } + }); + PlatformHelper.Alarms.addListener(countdownAlarmHandler); +} diff --git a/src/background/fetcher.js b/src/background/fetcher.js new file mode 100644 index 00000000..01b9f1a2 --- /dev/null +++ b/src/background/fetcher.js @@ -0,0 +1,89 @@ +import { CookieFetchManager, registerFetcher } from './fetcher/CookieFetcherManager'; +import { CeobeCanteenCookieFetcher } from './fetcher/impl/CeobeCanteenCookieFetcher'; +import { FallbackLocalCookieFetcher } from './fetcher/impl/FallbackLocalCookieFetcher'; +import { CustomLocalCookieFetcher } from './fetcher/impl/CustomLocalCookieFetcher'; +import { FetchConfig, FetcherStrategy } from './fetcher/FetchConfig'; +import Settings from '../common/Settings'; + +const cookieFetcherManager = new CookieFetchManager(); + +registerFetcher('server', CeobeCanteenCookieFetcher); +/* IFTRUE_feature__local_fetch */ +registerFetcher('local-fallback', FallbackLocalCookieFetcher); +/* FITRUE_feature__local_fetch */ +/* IFTRUE_feature__custom_datasource */ +registerFetcher('local-custom', CustomLocalCookieFetcher); +/* FITRUE_feature__custom_datasource */ + +/** + * TODO 之后这个要改成能够自定义的 + * + * @return {FetchConfig} + */ +function buildMainCookieFetchConfig(enable = true) { + return new FetchConfig( + MAIN_FETCH_CONFIG_KEY, + enable, + Settings.enableDataSources, + Settings.dun.intervalTime, + Settings.dun.autoLowFrequency + ? Settings.dun.lowFrequencyTime.map((it) => { + let realHour; + if (it < 12) realHour = it + 12; + else realHour = it - 12; + return realHour; + }) + : undefined, + Settings.dun.autoLowFrequency ? Settings.dun.timeOfLowFrequency : 1, + [ + new FetcherStrategy('default', 'server'), + /* IFTRUE_feature__local_fetch */ + new FetcherStrategy('default', 'local-fallback'), + /* FITRUE_feature__local_fetch */ + ] + ); +} + +/* IFTRUE_feature__custom_datasource */ +/** + * @return {FetchConfig} + */ +function buildCustomCookieFetchConfig(enable = true) { + return new FetchConfig( + CUSTOM_FETCH_CONFIG_KEY, + enable, + Settings.extraFeature.enableCustomDataSources, + Settings.dun.intervalTime, + Settings.dun.autoLowFrequency + ? Settings.dun.lowFrequencyTime.map((it) => { + let realHour; + if (it < 12) realHour = it + 12; + else realHour = it - 12; + return realHour; + }) + : undefined, + Settings.dun.autoLowFrequency ? Settings.dun.timeOfLowFrequency : 1, + [new FetcherStrategy('default', 'local-custom')] + ); +} +/* FITRUE_feature__custom_datasource */ + +const MAIN_FETCH_CONFIG_KEY = 'main'; +const CUSTOM_FETCH_CONFIG_KEY = 'custom'; + +export function updateFetch() { + // 主配置在配置页面保证了不可能为空,自定义配置可能为空 + cookieFetcherManager.updateFetchConfig(MAIN_FETCH_CONFIG_KEY, buildMainCookieFetchConfig(true)); + /* IFTRUE_feature__custom_datasource */ + if (Settings.extraFeature.enableCustomDataSources?.length > 0) { + cookieFetcherManager.updateFetchConfig(CUSTOM_FETCH_CONFIG_KEY, buildCustomCookieFetchConfig(true)); + } else { + cookieFetcherManager.removeFetchConfig(CUSTOM_FETCH_CONFIG_KEY); + } + /* FITRUE_feature__custom_datasource */ +} + +export function stopFetch() { + cookieFetcherManager.removeFetchConfig(MAIN_FETCH_CONFIG_KEY); + cookieFetcherManager.removeFetchConfig(CUSTOM_FETCH_CONFIG_KEY); +} diff --git a/src/background/fetcher/impl/CeobeCanteenCookieFetcher.js b/src/background/fetcher/impl/CeobeCanteenCookieFetcher.js index 33891777..7f2bd5a2 100644 --- a/src/background/fetcher/impl/CeobeCanteenCookieFetcher.js +++ b/src/background/fetcher/impl/CeobeCanteenCookieFetcher.js @@ -2,6 +2,7 @@ import { AbstractCookieFetcher } from '../AbstractCookieFetcher'; import ServerUtil from '../../../common/util/ServerUtil'; import { CookieHandler } from '../../CookieHandler'; import DebugUtil from '../../../common/util/DebugUtil'; +import PlatformHelper from '../../../common/platform/PlatformHelper'; export class CeobeCanteenCookieFetcher extends AbstractCookieFetcher { /** diff --git a/src/background/fetcher/impl/CustomLocalCookieFetcher.js b/src/background/fetcher/impl/CustomLocalCookieFetcher.js index 94123cc5..22b9e032 100644 --- a/src/background/fetcher/impl/CustomLocalCookieFetcher.js +++ b/src/background/fetcher/impl/CustomLocalCookieFetcher.js @@ -12,33 +12,6 @@ registerDefaultDataSourceTypes(); const pad = (num) => (num > 9 ? `${num}` : `0${num}`); -/** - * - * @param fetchConfig {FetchConfig} - * @return {FetchControllerConfig} - */ -function _buildConfig(fetchConfig) { - const config = { - default_interval: fetchConfig.globalInterval * 1000, - groups: [], - }; - const customGroups = {}; - const commonGroupConfig = {}; - if (fetchConfig.lowFrequencyTimeRange && fetchConfig.lowFrequencyMultiple > 1) { - this.updateGroupIntervalByTimeRange(config.default_interval, fetchConfig, commonGroupConfig); - } - for (const { type, dataId } of fetchConfig.enableDataSourceList) { - if (!customGroups[type]) customGroups[type] = { type: type, datasource: [], ...commonGroupConfig }; - const source = {}; - if (this.dataIdKeyInConfig[type]) { - source[this.dataIdKeyInConfig[type]] = dataId; - } - customGroups[type].datasource.push(source); - } - config.groups.push(...Object.values(customGroups).filter((group) => group.datasource.length > 0)); - return config; -} - export class CustomLocalCookieFetcher extends AbstractCookieFetcher { dataIdKeyInConfig = { 'bilibili:dynamic-by-uid': 'uid', @@ -119,7 +92,7 @@ export class CustomLocalCookieFetcher extends AbstractCookieFetcher { async start(fetchConfig) { if (this.runningFlag) return; - const config = _buildConfig(fetchConfig); + const config = this._buildConfig(fetchConfig); this.startWithFetcherControllerConfig(config, fetchConfig); } @@ -131,7 +104,7 @@ export class CustomLocalCookieFetcher extends AbstractCookieFetcher { async _checkAvailable(fetchConfig) { if (fetchConfig) { - const config = _buildConfig(fetchConfig); + const config = this._buildConfig(fetchConfig); try { FetchController.validateConfig(config); } catch (e) { @@ -143,4 +116,31 @@ export class CustomLocalCookieFetcher extends AbstractCookieFetcher { await fetch('https://www.baidu.com/', { mode: 'no-cors' }); return true; } + + /** + * + * @param fetchConfig {FetchConfig} + * @return {FetchControllerConfig} + */ + _buildConfig(fetchConfig) { + const config = { + default_interval: fetchConfig.globalInterval * 1000, + groups: [], + }; + const customGroups = {}; + const commonGroupConfig = {}; + if (fetchConfig.lowFrequencyTimeRange && fetchConfig.lowFrequencyMultiple > 1) { + this.updateGroupIntervalByTimeRange(config.default_interval, fetchConfig, commonGroupConfig); + } + for (const { type, dataId } of fetchConfig.enableDataSourceList) { + if (!customGroups[type]) customGroups[type] = { type: type, datasource: [], ...commonGroupConfig }; + const source = {}; + if (this.dataIdKeyInConfig[type]) { + source[this.dataIdKeyInConfig[type]] = dataId; + } + customGroups[type].datasource.push(source); + } + config.groups.push(...Object.values(customGroups).filter((group) => group.datasource.length > 0)); + return config; + } } diff --git a/src/background/fetcher/impl/local/ArknightsOfficialWebDataSource.js b/src/background/fetcher/impl/local/ArknightsOfficialWebDataSource.js deleted file mode 100644 index 00cf89f0..00000000 --- a/src/background/fetcher/impl/local/ArknightsOfficialWebDataSource.js +++ /dev/null @@ -1,27 +0,0 @@ -import Settings from '../../../../common/Settings'; -import TimeUtil from '../../../../common/util/TimeUtil'; -import { CookieItem } from '../../../../common/CookieItem'; -import PlatformHelper from '../../../../common/platform/PlatformHelper'; - -/** - * 明日方舟官网数据源。 - *

- */ -export class ArknightsOfficialWebDataSource { - static async processData(rawDataText, sourceId) { - let $ = PlatformHelper.HtmlParser; - const $item = $(rawDataText); - let date = $('.articleItemDate', $item).text(); - let title = $('.articleItemTitle', $item).text(); - let url = $('.articleItemLink', $item).attr('href'); - let time = new Date(`${date} ${Settings.getTimeBySortMode()}`); - let judgment = url.match(/\d+/g); - return CookieItem.builder(sourceId) - .id(parseInt(judgment[0])) - .timeForSort(time.getTime()) - .timeForDisplay(TimeUtil.format(time, 'yyyy-MM-dd')) - .content(title) - .jumpUrl(`https://ak.hypergryph.com${url}`) - .build(); - } -} diff --git a/src/background/fetcher/impl/local/GameBulletinListDataSource.js b/src/background/fetcher/impl/local/GameBulletinListDataSource.js index ae0a1d65..529b65e6 100644 --- a/src/background/fetcher/impl/local/GameBulletinListDataSource.js +++ b/src/background/fetcher/impl/local/GameBulletinListDataSource.js @@ -1,8 +1,6 @@ -import { DataSourceMeta, DataSourceTypeInfo } from '../../../../common/datasource/DataSourceMeta'; import Settings from '../../../../common/Settings'; import TimeUtil from '../../../../common/util/TimeUtil'; import { CookieItem } from '../../../../common/CookieItem'; -import PlatformHelper from '../../../../common/platform/PlatformHelper'; /** * 游戏公告数据源。 @@ -17,7 +15,7 @@ export class GameBulletinListDataSource { .timeForSort(time.getTime()) .timeForDisplay(TimeUtil.format(time, 'yyyy-MM-dd')) .content(data.title.replace('\\n', '\n')) - .jumpUrl(`https://terra-historicus.hypergryph.com/comic/${data.comicCid}/episode/${data.episodeCid}`) + .jumpUrl(`https://ak-webview.hypergryph.com/api/game/bulletin/${data.cid}`) .build(); } } diff --git a/src/background/heartbeat.js b/src/background/heartbeat.js new file mode 100644 index 00000000..e4534035 --- /dev/null +++ b/src/background/heartbeat.js @@ -0,0 +1,39 @@ +// 复制自:https://developer.chrome.com/docs/extensions/develop/migrate/to-service-workers?hl=zh-cn#keep-sw-alive +// TODO 该文件暂未用到,Chrome官方不喜欢这样的,之后看看正常蹲饼能不能一直活,能的话就不要这个了,不能的话就还是用这个保活 +/** + * Tracks when a service worker was last alive and extends the service worker + * lifetime by writing the current time to extension storage every 20 seconds. + * You should still prepare for unexpected termination - for example, if the + * extension process crashes or your extension is manually stopped at + * chrome://serviceworker-internals. + */ +let heartbeatInterval; + +async function runHeartbeat() { + await PlatformHelper.Storage.saveLocalStorage('last-heartbeat', new Date().getTime()); +} + +/** + * Starts the heartbeat interval which keeps the service worker alive. Call + * this sparingly when you are doing work which requires persistence, and call + * stopHeartbeat once that work is complete. + */ +export async function startHeartbeat() { + // Run the heartbeat once at service worker startup. + runHeartbeat().then(() => { + // Then again every 20 seconds. + heartbeatInterval = setInterval(runHeartbeat, 20 * 1000); + }); +} + +export async function stopHeartbeat() { + clearInterval(heartbeatInterval); +} + +/** + * Returns the last heartbeat stored in extension storage, or undefined if + * the heartbeat has never run before. + */ +export async function getLastHeartbeat() { + return await PlatformHelper.Storage.getLocalStorage('last-heartbeat'); +} diff --git a/src/background/index.js b/src/background/index.js index 63b06fad..a5eb3660 100644 --- a/src/background/index.js +++ b/src/background/index.js @@ -1,9 +1,6 @@ -import Settings from '../common/Settings'; -import NotificationUtil from '../common/util/NotificationUtil'; -import SanInfo from '../common/sync/SanInfo'; +import DebugUtil from '../common/util/DebugUtil'; import { ENABLE_FEATURES, - MESSAGE_CHANGE_COUNTDOWN, MESSAGE_GET_COUNTDOWN, MESSAGE_SAN_GET, MESSAGE_WEIBO_ADD_REFERER, @@ -12,110 +9,157 @@ import { PAGE_WELCOME, PLATFORM_FIREFOX, } from '../common/Constants'; +import Settings from '../common/Settings'; import PlatformHelper from '../common/platform/PlatformHelper'; import ServerUtil from '../common/util/ServerUtil'; -import CountDown from '../common/sync/CountDownInfo'; -import TimeUtil from '../common/util/TimeUtil'; -import PenguinStatistics from '../common/sync/PenguinStatisticsInfo'; +import { stopFetch, updateFetch } from './fetcher'; +import SanInfo from '../common/sync/SanInfo'; +import { registerUrlToAddReferer } from './request_interceptor'; import CardList from '../common/sync/CardList'; -import { CookieFetchManager, registerFetcher } from './fetcher/CookieFetcherManager'; -import { FetchConfig, FetcherStrategy } from './fetcher/FetchConfig'; -import { CeobeCanteenCookieFetcher } from './fetcher/impl/CeobeCanteenCookieFetcher'; -import { CustomLocalCookieFetcher } from './fetcher/impl/CustomLocalCookieFetcher'; -import DebugUtil from '../common/util/DebugUtil'; -import { interceptBeforeSendHeaders, registerUrlToAddReferer } from './request_interceptor'; -import { FallbackLocalCookieFetcher } from './fetcher/impl/FallbackLocalCookieFetcher'; +import NotificationUtil from '../common/util/NotificationUtil'; +import CountDown from '../common/sync/CountDownInfo'; import { UserUtil } from '../common/util/UserUtil'; +import PenguinStatistics from '../common/sync/PenguinStatisticsInfo'; +import { JsonValidator } from '@enraged-dun-cookie-development-team/common/json'; // 第一时间调用以生成uuid void UserUtil.getClientId(); - -// 开启弹出菜单窗口化时的窗口ID -let popupWindowId = null; - -const cookieFetcherManager = new CookieFetchManager(); - -registerFetcher('server', CeobeCanteenCookieFetcher); -/* IFTRUE_feature__local_fetch */ -registerFetcher('local-fallback', FallbackLocalCookieFetcher); -/* FITRUE_feature__local_fetch */ -/* IFTRUE_feature__custom_datasource */ -registerFetcher('local-custom', CustomLocalCookieFetcher); -/* FITRUE_feature__custom_datasource */ - -/** - * TODO 之后这个要改成能够自定义的 - * - * @return {FetchConfig} - */ -function buildMainCookieFetchConfig(enable = true) { - return new FetchConfig( - MAIN_FETCH_CONFIG_KEY, - enable, - Settings.enableDataSources, - Settings.dun.intervalTime, - Settings.dun.autoLowFrequency - ? Settings.dun.lowFrequencyTime.map((it) => { - let realHour; - if (it < 12) realHour = it + 12; - else realHour = it - 12; - return realHour; - }) - : undefined, - Settings.dun.autoLowFrequency ? Settings.dun.timeOfLowFrequency : 1, - [ - new FetcherStrategy('default', 'server'), - /* IFTRUE_feature__local_fetch */ - new FetcherStrategy('default', 'local-fallback'), - /* FITRUE_feature__local_fetch */ - ] - ); +// TODO 因为ajv用到了eval,所以只能全局禁止验证 +JsonValidator.validate = () => true; + +const alarmKeyCheckUpdate = 'core:check-extension-update'; +const alarmKeyCheckServerInfoCache = 'core:check-serverinfo-cache'; +const alarmKeyAliveCheck = 'core:alive-check'; + +// 记录启动时间 +const startTime = new Date().getTime(); + +function alarmHandler(alarm) { + if (!alarm || !alarm.name) return; + switch (alarm.name) { + case alarmKeyCheckUpdate: { + void checkExtensionUpdate(); + break; + } + case alarmKeyCheckServerInfoCache: { + void ServerUtil.checkServerDataSourceInfoCache(); + break; + } + case alarmKeyAliveCheck: { + // 3秒内视为这次闹钟触发的时候已经死了 + if (Math.abs(new Date().getTime() - startTime) < 3000) { + DebugUtil.debugLog(0, '恢复蹲饼'); + updateFetch(); + } + break; + } + default: { + if (alarm.name.startsWith('countdown_')) { + const countDownName = name.split('|')[0].substring('countdown_'.length); + NotificationUtil.SendNotice( + `倒计时完毕`, + `${countDownName} 到点了!`, + null, + 'countdown_' + new Date().getTime() + ); + void CountDown.removeCountDownByName(countDownName); + } + break; + } + } } -/* IFTRUE_feature__custom_datasource */ -/** - * @return {FetchConfig} - */ -function buildCustomCookieFetchConfig(enable = true) { - return new FetchConfig( - CUSTOM_FETCH_CONFIG_KEY, - enable, - Settings.extraFeature.enableCustomDataSources, - Settings.dun.intervalTime, - Settings.dun.autoLowFrequency - ? Settings.dun.lowFrequencyTime.map((it) => { - let realHour; - if (it < 12) realHour = it + 12; - else realHour = it - 12; - return realHour; - }) - : undefined, - Settings.dun.autoLowFrequency ? Settings.dun.timeOfLowFrequency : 1, - [new FetcherStrategy('default', 'local-custom')] - ); +async function checkExtensionUpdate() { + const extensionInfo = await PlatformHelper.Extension.getExtensionInfo(); + // 商店安装的不检查更新 + if (extensionInfo.installType !== 'normal') { + void ServerUtil.getVersionInfo(); + } + void ServerUtil.getAnnouncementInfo(true); } -/* FITRUE_feature__custom_datasource */ -const MAIN_FETCH_CONFIG_KEY = 'main'; -const CUSTOM_FETCH_CONFIG_KEY = 'custom'; +PlatformHelper.Alarms.addListener(alarmHandler); + +// 监听前台事件 +PlatformHelper.Message.registerListener('background', null, (message) => { + if (message.type) { + const data = message.data; + switch (message.type) { + case MESSAGE_SAN_GET: + return SanInfo; + // TODO 暂未启用Countdown + // case MESSAGE_CHANGE_COUNTDOWN: + // countDown.Change(); + // return; + case MESSAGE_GET_COUNTDOWN: + return []; + // return countDown.GetAllCountDown(); + case MESSAGE_WEIBO_ADD_REFERER: + if (data.urls && data.urls.length > 0) { + data.urls.forEach((src) => registerUrlToAddReferer(src, 'https://m.weibo.cn/')); + } + return; + default: + return; + } + } +}); -function updateFetch() { - // 主配置在配置页面保证了不可能为空,自定义配置可能为空 - cookieFetcherManager.updateFetchConfig(MAIN_FETCH_CONFIG_KEY, buildMainCookieFetchConfig(true)); - /* IFTRUE_feature__custom_datasource */ - if (Settings.extraFeature.enableCustomDataSources?.length > 0) { - cookieFetcherManager.updateFetchConfig(CUSTOM_FETCH_CONFIG_KEY, buildCustomCookieFetchConfig(true)); +// 监听标签 +PlatformHelper.Notification.addClickListener((id) => { + let item = CardList.getFirstPageList().find((x) => x.id === id); + if (item) { + void PlatformHelper.Tabs.create(item.jumpUrl); + } else if (id === 'update') { + void PlatformHelper.Tabs.createWithExtensionFile(PAGE_UPDATE); + } else if (id.slice(0, 12) === 'announcement') { + alert('博士,你点下去的是条重要公告噢,打开列表就可以看到啦'); } else { - cookieFetcherManager.removeFetchConfig(CUSTOM_FETCH_CONFIG_KEY); + alert('o(╥﹏╥)o 时间过于久远...最近列表内没有找到该网站'); } - /* FITRUE_feature__custom_datasource */ -} +}); + +// 监听安装更新 +PlatformHelper.Lifecycle.addInstalledListener((details) => { + Settings.doAfterInit(() => { + if (details.reason === 'install' || !Settings.agreeLicense) { + void PlatformHelper.Tabs.createWithExtensionFile(PAGE_WELCOME); + } + if (details.reason === 'update' || details.reason === 'install') { + void PlatformHelper.Storage.saveLocalStorage('version-notice', false); + } + }); +}); + +// 监听扩展图标被点击,用于打开窗口化的弹出页面 +PlatformHelper.BrowserAction.addIconClickListener(async () => { + const popupWindowIdStorageKey = 'runtime.popupWindowId'; + if (Settings.display.windowMode) { + const popupWindowId = await PlatformHelper.Storage.getLocalStorage(popupWindowIdStorageKey); + if (popupWindowId) { + PlatformHelper.Windows.getAllWindow().then((allWindow) => { + if (allWindow.findIndex((x) => x.id == popupWindowId) > 0) { + PlatformHelper.Windows.remove(popupWindowId); + } + }); + } + const width = 800; + let height = 950; + if (PlatformHelper.PlatformType === PLATFORM_FIREFOX) { + height = 850; + } + PlatformHelper.Windows.createPopupWindow(PlatformHelper.Extension.getURL(PAGE_POPUP_WINDOW), width, height).then( + (tab) => PlatformHelper.Storage.saveLocalStorage(popupWindowIdStorageKey, tab.id) + ); + } +}); function ExtensionInit() { DebugUtil.debugLog(0, '插件启动...'); if (ENABLE_FEATURES.length > 0) { DebugUtil.debugLog(0, '已启用特性:', ENABLE_FEATURES); } + // 开始蹲饼! Settings.doAfterInit((initSettings) => { if (initSettings.open) { @@ -124,14 +168,13 @@ function ExtensionInit() { } else { DebugUtil.debugLog(0, '蹲饼开关已关闭'); } - setTimeout(async () => { - const extensionInfo = await PlatformHelper.Extension.getExtensionInfo(); - // 商店安装的不检查更新 - if (extensionInfo.installType !== 'normal') { - void ServerUtil.getVersionInfo(); - } - void ServerUtil.getAnnouncementInfo(true); - }, 600000); + }); + + Settings.doAfterInit((initSettings) => { + void PlatformHelper.Alarms.createIfNotExists(alarmKeyCheckUpdate, { + delayInMinutes: 60, + periodInMinutes: 60, + }); }); Settings.doAfterUpdate((settings, changed) => { @@ -151,188 +194,27 @@ function ExtensionInit() { DebugUtil.debugLog(0, '开始蹲饼'); updateFetch(); } else { - cookieFetcherManager.removeFetchConfig(MAIN_FETCH_CONFIG_KEY); - cookieFetcherManager.removeFetchConfig(CUSTOM_FETCH_CONFIG_KEY); + stopFetch(); + // 都不蹲饼了还保什么活( + void PlatformHelper.Alarms.clear(alarmKeyAliveCheck); } }); // 启动时的缓存检查在AvailableDataSourceMeta // 每隔6小时检查一遍缓存 - setInterval(() => { - void ServerUtil.checkServerDataSourceInfoCache(); - }, 6 * 60 * 60 * 1000); - - // 监听前台事件 - PlatformHelper.Message.registerListener('background', null, (message) => { - if (message.type) { - const data = message.data; - switch (message.type) { - case MESSAGE_SAN_GET: - return SanInfo; - case MESSAGE_CHANGE_COUNTDOWN: - countDown.Change(); - return; - case MESSAGE_GET_COUNTDOWN: - return countDown.GetAllCountDown(); - case MESSAGE_WEIBO_ADD_REFERER: - if (data.urls && data.urls.length > 0) { - data.urls.forEach((src) => registerUrlToAddReferer(src, 'https://m.weibo.cn/')); - } - return; - default: - return; - } - } - }); - - // 监听标签 - PlatformHelper.Notification.addClickListener((id) => { - let item = CardList.getFirstPageList().find((x) => x.id === id); - if (item) { - PlatformHelper.Tabs.create(item.jumpUrl); - } else if (id === 'update') { - PlatformHelper.Tabs.createWithExtensionFile(PAGE_UPDATE); - } else if (id.slice(0, 12) === 'announcement') { - alert('博士,你点下去的是条重要公告噢,打开列表就可以看到啦'); - } else { - alert('o(╥﹏╥)o 时间过于久远...最近列表内没有找到该网站'); - } - }); - - // 监听安装更新 - PlatformHelper.Lifecycle.addInstalledListener((details) => { - Settings.doAfterInit(() => { - if (details.reason === 'install' || !Settings.agreeLicense) { - PlatformHelper.Tabs.createWithExtensionFile(PAGE_WELCOME); - } - if (details.reason === 'update' || details.reason === 'install') { - PlatformHelper.Storage.saveLocalStorage('version-notice', false); - } - }); + void PlatformHelper.Alarms.createIfNotExists(alarmKeyCheckServerInfoCache, { + delayInMinutes: 6 * 60, + periodInMinutes: 6 * 60, }); - // 监听扩展图标被点击,用于打开窗口化的弹出页面 - PlatformHelper.BrowserAction.addIconClickListener(() => { - if (Settings.display.windowMode) { - if (popupWindowId != null) { - PlatformHelper.Windows.getAllWindow().then((allWindow) => { - if (allWindow.findIndex((x) => x.id == popupWindowId) > 0) { - PlatformHelper.Windows.remove(popupWindowId); - } - }); - } - const width = 800; - let height = 950; - if (PlatformHelper.PlatformType === PLATFORM_FIREFOX) { - height = 850; - } - PlatformHelper.Windows.createPopupWindow(PlatformHelper.Extension.getURL(PAGE_POPUP_WINDOW), width, height).then( - (tab) => (popupWindowId = tab.id) - ); - } - }); - - PlatformHelper.Alarms.addListener((alarm) => { - /** - * @type {string|undefined} - */ - const name = alarm.name; - countDownDebugLog(`alarm监听器触发[${name}]:`, alarm); - if (name && name.startsWith('countdown_')) { - const countDownName = name.split('|')[0].substring('countdown_'.length); - NotificationUtil.SendNotice(`倒计时完毕`, `${countDownName} 到点了!`, null, 'countdown_' + new Date().getTime()); - CountDown.removeCountDownByName(countDownName); - } - }); - - PlatformHelper.Http.onBeforeSendHeaders(interceptBeforeSendHeaders, { urls: ['*://*.sinaimg.cn/*'] }, [ - 'blocking', - 'requestHeaders', - ]); + // 拿一次企鹅物流数据 + PenguinStatistics.GetNewItems(); } -function countDownDebugLog(...data) { - DebugUtil.debugConsoleOutput(0, 'debug', '%c 倒计时 ', 'color: white; background: #DA70D6', ...data); -} - -const countDownThreshold = 5 * 60 * 1000; -let countDownFlag = false; -const countDown = { - sendNoticeList: [], - countDownList: [], - Start() { - CountDown.getCountDownLocalStorage().then((data) => { - data = JSON.parse(data); - if (data) { - this.countDownList = []; - data - .map((x) => x.data) - .forEach((item) => { - this.countDownList.push(item); - const endTime = new Date(item.stopTime).getTime(); - const delayTime = endTime - new Date().getTime(); - if (delayTime >= countDownThreshold) { - countDownDebugLog(`设置alarm[${item.name}]-指定时间:${new Date(endTime).toLocaleString()}`); - const uniqueName = 'countdown_' + item.name + '|' + Math.random().toFixed(3).substring(2, 5); - PlatformHelper.Alarms.create(uniqueName, { when: endTime }); - } else { - countDownDebugLog(`设置setTimeout[${item.name}]-延时:${delayTime}`); - this.sendNoticeList.push( - setTimeout((_) => { - NotificationUtil.SendNotice( - `倒计时完毕`, - `${item.name} 到点了!`, - null, - 'countdown_' + new Date().getTime() - ); - // 有过通知后从内存中删除计时器数据 - CountDown.removeCountDown(item); - }, delayTime) - ); - } - }); - } - }); - }, - Change() { - if (countDownFlag) { - return; - } - countDownFlag = true; - countDownDebugLog('清空setTimeout'); - this.sendNoticeList.forEach((id) => { - clearTimeout(id); - }); - this.sendNoticeList = []; - countDownDebugLog('清空alarms'); - PlatformHelper.Alarms.clearAll().finally(() => { - this.Start(); - countDownFlag = false; - }); - }, - GetAllCountDown() { - let list = []; - this.countDownList.forEach((item) => { - let value = TimeUtil.calcDiff(new Date(item.stopTime), new Date()); - if (value != '') { - list.push({ ...item, timeStr: value, stopTime: TimeUtil.format(item.stopTime, 'yyyy-MM-dd hh:mm:ss') }); - } - }); - return list; - }, -}; - -const penguinStatistics = { - Init() { - PenguinStatistics.GetNewItems(); - }, -}; - ExtensionInit(); -countDown.Start(); -PlatformHelper.osIsMac().then((isMac) => { - if (isMac) { - setInterval(() => countDown.Change(), 10 * 60 * 1000); - } + +// 每半分钟进行一次保活检查 +void PlatformHelper.Alarms.create(alarmKeyAliveCheck, { + delayInMinutes: 0.5, + periodInMinutes: 0.5, }); -penguinStatistics.Init(); diff --git a/src/background/request_interceptor.js b/src/background/request_interceptor.js index 68961544..78779470 100644 --- a/src/background/request_interceptor.js +++ b/src/background/request_interceptor.js @@ -1,26 +1,48 @@ -const refererMap = new Map(); +import PlatformHelper from '../common/platform/PlatformHelper'; /** - * - * @param url {string} - * @param referer {string} + * Returns a hash code from a string + * @param {String} str The string to hash. + * @return {Number} A 32bit integer + * @see http://werxltd.com/wp/2010/05/13/javascript-implementation-of-javas-string-hashcode-method/ */ -export function registerUrlToAddReferer(url, referer) { - refererMap.set(url, referer); +function hashCode(str) { + let hash = 0; + for (let i = 0, len = str.length; i < len; i++) { + let chr = str.charCodeAt(i); + hash = (hash << 5) - hash + chr; + hash |= 0; // Convert to 32bit integer + } + return hash; } /** * - * @param details {{url: string, requestHeaders: HttpHeaders}} + * @param url {string} + * @param referer {string} */ -export function interceptBeforeSendHeaders(details) { - if (!refererMap.has(details.url)) return {}; - for (let i = 0; i < details.requestHeaders.length; i++) { - const header = details.requestHeaders[i]; - if (header.name.toLowerCase() === 'referer') { - details.requestHeaders.splice(i, 1); - } - } - details.requestHeaders.push({ name: 'Referer', value: refererMap.get(details.url) }); - return { requestHeaders: details.requestHeaders }; +export function registerUrlToAddReferer(url, referer) { + const id = Math.abs(hashCode(url)); + void PlatformHelper.Http.updateSessionRules({ + removeRuleIds: [id], + addRules: [ + { + id: id, + action: { + type: 'modifyHeaders', + requestHeaders: [ + { + header: 'Referer', + operation: 'set', + value: referer, + }, + ], + }, + condition: { + domainType: 'thirdParty', + urlFilter: url, + }, + }, + ], + }); } diff --git a/src/common/platform/AbstractPlatform.js b/src/common/platform/AbstractPlatform.js index cc41063f..3fd56e3c 100644 --- a/src/common/platform/AbstractPlatform.js +++ b/src/common/platform/AbstractPlatform.js @@ -80,6 +80,15 @@ export class AbstractPlatform { throw new Error(unsupportedTip); } + /** + * 从本地移除一项数据 + * @param {string|string[]} keys + * @return {Promise} + */ + removeLocalStorage(keys) { + throw new Error(unsupportedTip); + } + /** * 向其它页面发送消息 * @param type 消息类型,只有监听指定类型的监听器才能接收到消息 @@ -272,17 +281,35 @@ export class AbstractPlatform { } /** - * 创建定时警报 + * 创建定时闹钟 * @param name {string | undefined} * @param alarmInfo {{when?: number, delayInMinutes?: number, periodInMinutes?: number} | undefined} - * @return {void} + * @return {Promise} */ createAlarm(name, alarmInfo) { throw new Error(unsupportedTip); } /** - * 清除全部警报 + * 获取定时闹钟 + * @param name {string} + * @return {Promise<{name: string, scheduledTime: number, periodInMinutes?: number} | undefined>} + */ + getAlarm(name) { + throw new Error(unsupportedTip); + } + + /** + * 清除指定闹钟 + * @param name {string} + * @return {Promise} + */ + clearAlarm(name) { + throw new Error(unsupportedTip); + } + + /** + * 清除全部闹钟 * @return {Promise} */ clearAllAlarms() { @@ -299,13 +326,33 @@ export class AbstractPlatform { } /** - * 增加请求监听器 + * 定义请求过滤操作的动态会话规则 + *

+ * @param options {{addRules: any[]}} 规则参数 + * @return {Promise} + */ + declarativeNetRequestUpdateSessionRules(options) { + throw new Error(unsupportedTip); + } + + /** + * 创建屏幕外文档 + *
+ * NOTE: 注意!同时只能存在一个屏幕外文档,所以创建完只能临时用,必须马上关闭。如果要长期用需要专门做一套逻辑。 + *

+ * @param parameters {{url: string, reasons: string[], justification: string}} 创建参数 + * @return {Promise} + */ + offscreenCreateDocument(parameters) { + throw new Error(unsupportedTip); + } + + /** + * 关闭屏幕外文档 *

- * @param listener 监听器 - * @param filter URL匹配列表,指定要监听的请求URL - * @param extraInfoSpec 额外参数 + * @return {Promise} */ - onBeforeSendHeaders(listener, filter, extraInfoSpec) { + offscreenCloseDocument() { throw new Error(unsupportedTip); } diff --git a/src/common/platform/PlatformHelper.js b/src/common/platform/PlatformHelper.js index 07678adc..96d66e67 100644 --- a/src/common/platform/PlatformHelper.js +++ b/src/common/platform/PlatformHelper.js @@ -117,6 +117,10 @@ export default class PlatformHelper { return httpHelper; } + static get Offscreen() { + return offscreenHelper; + } + static get HtmlParser() { return currentPlatform.getHtmlParser(); } @@ -152,6 +156,10 @@ class StorageHelper { saveLocalStorage(name, data) { return currentPlatform.saveLocalStorage(name, data); } + + removeLocalStorage(keys) { + return currentPlatform.removeLocalStorage(keys); + } } class BrowserActionHelper { @@ -216,22 +224,15 @@ class NotificationHelper { */ async createWithSpecialIcon(id, iconUrl, title, message, imageUrl) { let objectUrl; - let canvas; - if (typeof imageUrl === 'string' && imageUrl.startsWith('http')) { + const img2blobFlag = typeof imageUrl === 'string' && imageUrl.startsWith('http'); + if (img2blobFlag) { try { - const blob = await Http.get(imageUrl, { responseTransformer: (r) => r.blob() }); - canvas = document.createElement('canvas'); - canvas.height = 200; - canvas.width = 400; - const ctx = canvas.getContext('2d'); - const bitmap = await createImageBitmap(blob, { resizeWidth: canvas.width }); - ctx.drawImage(bitmap, 0, 0); - bitmap.close(); - /** - * @type {unknown} - */ - const newBlob = await new Promise((r) => canvas.toBlob(r)); - objectUrl = URL.createObjectURL(newBlob); + await currentPlatform.offscreenCreateDocument({ + url: '/offscreen/img2blob.html', + reasons: ['BLOBS'], + justification: 'for notification image', + }); + objectUrl = await PlatformHelper.Message.send('offscreen:img2blob', { imageUrl: imageUrl }); DebugUtil.debugConsoleOutput( 0, 'debug', @@ -252,16 +253,11 @@ class NotificationHelper { imageUrl = undefined; } } - return await currentPlatform - .createNotifications(id, iconUrl, title, message, objectUrl || imageUrl) - .finally((_) => { - if (objectUrl) { - URL.revokeObjectURL(objectUrl); - } - if (canvas) { - canvas.remove(); - } - }); + return await currentPlatform.createNotifications(id, iconUrl, title, message, objectUrl || imageUrl).finally(() => { + if (img2blobFlag) { + currentPlatform.offscreenCloseDocument(); + } + }); } addClickListener(listener) { @@ -322,6 +318,21 @@ class AlarmHelper { return currentPlatform.createAlarm(name, alarmInfo); } + async createIfNotExists(name, alarmInfo) { + const alarm = await currentPlatform.getAlarm(name); + if (!alarm) { + return currentPlatform.createAlarm(name, alarmInfo); + } + } + + get(name) { + return currentPlatform.getAlarm(name); + } + + clear(name) { + return currentPlatform.clearAlarm(name); + } + clearAll() { return currentPlatform.clearAllAlarms(); } @@ -337,8 +348,18 @@ class HttpHelper { return currentPlatform.sendHttpRequest(url, 'GET', timeout); } - onBeforeSendHeaders(listener, filter, extraInfoSpec) { - return currentPlatform.onBeforeSendHeaders(listener, filter, extraInfoSpec); + updateSessionRules(options) { + return currentPlatform.declarativeNetRequestUpdateSessionRules(options); + } +} + +class OffscreenHelper { + create(parameters) { + return currentPlatform.offscreenCreateDocument(parameters); + } + + close() { + return currentPlatform.offscreenCloseDocument(); } } @@ -359,5 +380,6 @@ const downloadsHelper = new DownloadsHelper(); const lifecycleHelper = new LifecycleHelper(); const alarmHelper = new AlarmHelper(); const httpHelper = new HttpHelper(); +const offscreenHelper = new OffscreenHelper(); const imgHelper = new ImgHelper(); globalThis.PlatformHelper = PlatformHelper; diff --git a/src/common/platform/impl/BrowserPlatform.js b/src/common/platform/impl/BrowserPlatform.js index 15aca0bd..af3cbee1 100644 --- a/src/common/platform/impl/BrowserPlatform.js +++ b/src/common/platform/impl/BrowserPlatform.js @@ -7,14 +7,15 @@ import html2canvas from 'html2canvas'; const IGNORE_MESSAGE_ERROR_1 = 'Could not establish connection. Receiving end does not exist.'; const IGNORE_MESSAGE_ERROR_2 = 'The message port closed before a response was received.'; -let _isBackground; let _isMobile; +const _isBackground = typeof globalThis == 'object' && typeof globalThis.serviceWorker == 'object'; +console.log(`Current isBackground: ${_isBackground}`); const imageCache = {}; const qrcodeCache = {}; const CORS_AVAILABLE_DOMAINS = { 'penguin-stats.io': true, 'penguin-stats.cn': true }; -// 正常浏览器在给权限后跨域视为basic请求 无视cors相关设定,脑子有毛病的QQ浏览器在mode: no-cors跨域时直接用CORB策略拒绝读取响应(正常浏览器好像只会在contentScript里有这种设定) +// 正常浏览器在给权限后跨域视为basic请求 无视cors相关设定,脑子有毛病的QQ浏览器在mode: no-cors跨域时直接用CORS策略拒绝读取响应(正常浏览器好像只会在contentScript里有这种设定) // 事实上正常浏览器和QQ浏览器在加权限后mode: no-cors跨域的Response.type都是basic,但是QQ浏览器就是不让你读取 诶就是玩 const ALWAYS_ENABLE_CORS = typeof navigator != 'undefined' && navigator.userAgent.includes('QQBrowser'); @@ -24,10 +25,6 @@ const ALWAYS_ENABLE_CORS = typeof navigator != 'undefined' && navigator.userAgen export default class BrowserPlatform extends AbstractPlatform { constructor() { super(); - // 这部分放在类里面的原因是放在外面会被意外执行导致报错 - // 判断当前url中是否包含background(已知的其它方法都是Promise,都不能保证在isBackground被使用之前完成判断) - _isBackground = window.document.URL.indexOf('background') !== -1; - console.log(`Current isBackground: ${_isBackground}`); const head = navigator.userAgent; _isMobile = head.indexOf('Android') > 1 || head.indexOf('iPhone') > 1; diff --git a/src/common/platform/impl/ChromePlatform.js b/src/common/platform/impl/ChromePlatform.js index e32228a0..fae4f456 100644 --- a/src/common/platform/impl/ChromePlatform.js +++ b/src/common/platform/impl/ChromePlatform.js @@ -1,6 +1,7 @@ import { PLATFORM_CHROME } from '../../Constants'; import BrowserPlatform from './BrowserPlatform'; import DebugUtil from '../../util/DebugUtil'; +import { name } from 'jsdom/lib/jsdom/living/helpers/validate-names'; // noinspection JSUnresolvedVariable export default class ChromePlatform extends BrowserPlatform { @@ -32,34 +33,23 @@ export default class ChromePlatform extends BrowserPlatform { }); } - getLocalStorage(name) { - return new Promise((resolve, reject) => { - chrome.storage.local.get(name, (result) => { - if (chrome.runtime.lastError) { - reject(chrome.runtime.lastError); - return; - } - if (typeof name === 'string') { - resolve(result[name]); - } else { - resolve(result); - } - }); - }); + async getLocalStorage(name) { + const result = await chrome.storage.local.get(name); + if (typeof name === 'string') { + return result[name]; + } else { + return result; + } } saveLocalStorage(name, data) { const val = {}; val[name] = data; - return new Promise((resolve, reject) => { - chrome.storage.local.set(val, () => { - if (chrome.runtime.lastError) { - reject(chrome.runtime.lastError); - return; - } - resolve(); - }); - }); + return chrome.storage.local.set(val); + } + + removeLocalStorage(keys) { + return chrome.storage.local.remove(keys); } sendMessage(type, data) { @@ -85,11 +75,13 @@ export default class ChromePlatform extends BrowserPlatform { return chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { const value = this.__handleReceiverMessage(id, type, message, listener); if (value !== undefined) { - // Chromium内核中必须用return true的方式进行异步返回,不支持直接返回Promise - // 参考兼容性表格:https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/runtime/onMessage - sendResponse(value); if (value.constructor === Promise) { + // Chromium内核中必须用return true的方式进行异步返回,不支持直接返回Promise + // 参考兼容性表格:https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/runtime/onMessage + value.then((result) => sendResponse(result)).catch((reason) => sendResponse(reason)); return true; + } else { + sendResponse(value); } } }); @@ -97,7 +89,7 @@ export default class ChromePlatform extends BrowserPlatform { setPopup(url) { return new Promise((resolve, reject) => { - chrome.browserAction.setPopup({ popup: url }, () => { + chrome.action.setPopup({ popup: url }, () => { if (chrome.runtime.lastError) { reject(chrome.runtime.lastError); return; @@ -138,7 +130,7 @@ export default class ChromePlatform extends BrowserPlatform { } addIconClickListener(listener) { - return chrome.browserAction.onClicked.addListener(listener); + return chrome.action.onClicked.addListener(listener); } createTab(url) { @@ -220,7 +212,7 @@ export default class ChromePlatform extends BrowserPlatform { setBadgeText(text) { return new Promise((resolve, reject) => { - chrome.browserAction.setBadgeText({ text: text }, () => { + chrome.action.setBadgeText({ text: text }, () => { if (chrome.runtime.lastError) { reject(chrome.runtime.lastError); return; @@ -232,7 +224,7 @@ export default class ChromePlatform extends BrowserPlatform { setBadgeBackgroundColor(color) { return new Promise((resolve, reject) => { - chrome.browserAction.setBadgeBackgroundColor({ color: color }, () => { + chrome.action.setBadgeBackgroundColor({ color: color }, () => { if (chrome.runtime.lastError) { reject(chrome.runtime.lastError); return; @@ -243,28 +235,34 @@ export default class ChromePlatform extends BrowserPlatform { } createAlarm(name, alarmInfo) { - chrome.alarms.create(name, alarmInfo); + return chrome.alarms.create(name, alarmInfo); + } + + getAlarm(name) { + return chrome.alarms.get(name); + } + + clearAlarm(name) { + return chrome.alarms.clear(name); } clearAllAlarms() { - return new Promise((resolve, reject) => { - chrome.alarms.clearAll((result) => { - if (chrome.runtime.lastError) { - reject(chrome.runtime.lastError); - return; - } - resolve(result); - }); - }); + return chrome.alarms.clearAll(); } addAlarmsListener(listener) { chrome.alarms.onAlarm.addListener(listener); } - onBeforeSendHeaders(listener, filter, extraInfoSpec) { - if (!extraInfoSpec) extraInfoSpec = []; - if (!extraInfoSpec.includes('extraHeaders')) extraInfoSpec.push('extraHeaders'); - chrome.webRequest.onBeforeSendHeaders.addListener(listener, filter, extraInfoSpec); + declarativeNetRequestUpdateSessionRules(options) { + return chrome.declarativeNetRequest.updateSessionRules(options); + } + + offscreenCreateDocument(parameters) { + return chrome.offscreen.createDocument(parameters); + } + + offscreenCloseDocument() { + return chrome.offscreen.closeDocument(); } } diff --git a/src/common/platform/impl/FirefoxPlatform.js b/src/common/platform/impl/FirefoxPlatform.js index 1c31b7c5..1a148bb4 100644 --- a/src/common/platform/impl/FirefoxPlatform.js +++ b/src/common/platform/impl/FirefoxPlatform.js @@ -59,6 +59,10 @@ export default class FirefoxPlatform extends BrowserPlatform { return browser.storage.local.set(val); } + removeLocalStorage(keys) { + return browser.storage.local.remove(keys); + } + sendMessage(type, data) { const message = this.__buildMessageToSend(type, data); @@ -94,7 +98,7 @@ export default class FirefoxPlatform extends BrowserPlatform { setPopup(url) { return new Promise((resolve, reject) => { // 虽然不知道为啥Firefox这个不返回Promise,但是Firefox文档里这个确实没写返回值 - browser.browserAction.setPopup({ popup: url }); + browser.action.setPopup({ popup: url }); resolve(); }); } @@ -119,7 +123,7 @@ export default class FirefoxPlatform extends BrowserPlatform { } addIconClickListener(listener) { - return browser.browserAction.onClicked.addListener((tab, OnClickData) => { + return browser.action.onClicked.addListener((tab, OnClickData) => { // 忽略OnClickData是为了和Chrome的行为一致(准确来讲应该是和AbstractPlatform中注释规定的行为一致) return listener(tab); }); @@ -168,17 +172,25 @@ export default class FirefoxPlatform extends BrowserPlatform { } setBadgeText(text) { - return browser.browserAction.setBadgeText({ text: text }); + return browser.action.setBadgeText({ text: text }); } setBadgeBackgroundColor(color) { - return browser.browserAction.setBadgeBackgroundColor({ color: color }); + return browser.action.setBadgeBackgroundColor({ color: color }); } createAlarm(name, alarmInfo) { browser.alarms.create(name, alarmInfo); } + getAlarm(name) { + return browser.alarms.get(name); + } + + clearAlarm(name) { + return browser.alarms.clear(name); + } + clearAllAlarms() { return browser.alarms.clearAll(); } @@ -187,7 +199,15 @@ export default class FirefoxPlatform extends BrowserPlatform { browser.alarms.onAlarm.addListener(listener); } - onBeforeSendHeaders(listener, filter, extraInfoSpec) { - browser.webRequest.onBeforeSendHeaders.addListener(listener, filter, extraInfoSpec); + declarativeNetRequestUpdateSessionRules(options) { + return browser.declarativeNetRequest.updateSessionRules(options); + } + + offscreenCreateDocument(parameters) { + return browser.offscreen.createDocument(parameters); + } + + offscreenCloseDocument() { + return browser.offscreen.closeDocument(); } } diff --git a/src/common/sync/SanInfo.js b/src/common/sync/SanInfo.js index 98e29ff6..7be2acd2 100644 --- a/src/common/sync/SanInfo.js +++ b/src/common/sync/SanInfo.js @@ -12,8 +12,8 @@ import NotificationUtil from '../util/NotificationUtil'; import PlatformHelper from '../platform/PlatformHelper'; // region 理智计算(自动提醒) +const alarmKeySanRecovery = 'tools:san-recovery'; -let sanTimerId = null; // 理智计算流程: // 在弹出页面点击"开始计算"会启动理智计算,当理智满了之后才会停止 // 在设置页面重新设置理智最大值后,会停止理智计算 @@ -22,7 +22,7 @@ let sanTimerId = null; function sanRecovery(san) { const timeElapsed = new Date().getTime() - san.updateTime; - // 时间超长时可能是休眠导致setTimeout延时了,用经过时间除以6分钟来判断理智恢复数量以避免出问题 + // 时间超长时可能是休眠导致延时了,用经过时间除以6分钟来判断理智恢复数量以避免出问题 if (timeElapsed > SAN_RECOVERY_SPEED) { const recovery = Math.floor(timeElapsed / SAN_RECOVERY_SPEED); san.currentSan += recovery; @@ -35,8 +35,7 @@ function sanRecovery(san) { if (san.currentSan >= Settings.san.maxValue) { san.currentSan = Settings.san.maxValue; noticeSan(`理智已满`, `理智已经满了!!请博士赶快上线清理智,不要浪费啦!`); - clearTimeout(sanTimerId); - sanTimerId = null; + void PlatformHelper.Alarms.clear(alarmKeySanRecovery); } san.saveUpdate(); if (DEBUG_LOG) { @@ -61,28 +60,23 @@ function startSanRecovery(san, delay) { } } if (remainTimeIntervalId === 0) san.startReloadTime(); - sanTimerId = setTimeout(() => { - // 将实际恢复逻辑放进setTimeout中,如果放在外面就会出现第一次会立刻恢复1理智(而没有等待恢复时间)的问题 - sanRecovery(san); - if (san.currentSan < Settings.san.maxValue) { - startSanRecovery(san); - } - }, delay); + // 将实际恢复逻辑放进alarm中,如果放在外面就会出现第一次会立刻恢复1理智(而没有等待恢复时间)的问题 + void PlatformHelper.Alarms.create(alarmKeySanRecovery, { + delayInMinutes: delay / (60 * 1000), + }); } function handleSanUpdate(san, settings) { // 先停止旧的计时器再判断是否需要启动新计时器 - if (sanTimerId) { - clearTimeout(sanTimerId); - sanTimerId = null; - } - if (san.currentSan < settings.san.maxValue) { - if (settings.feature.san) { - startSanRecovery(san); + PlatformHelper.Alarms.clear(alarmKeySanRecovery).finally(() => { + if (san.currentSan < settings.san.maxValue) { + if (settings.feature.san) { + startSanRecovery(san); + } + } else { + noticeSan(`哼哼!理智已满!`, `理智已经满了,请博士不要再逗我玩了`); } - } else { - noticeSan(`哼哼!理智已满!`, `理智已经满了,请博士不要再逗我玩了`); - } + }); } // 由于现在Settings的更新message不能识别更新了哪些内容,故此处加一个变量来识别 @@ -92,10 +86,7 @@ function handleSettingsUpdate(san, settings) { // 如果功能被禁用或者理智最大值有更新,则停止计时器并将当前理智设置为最大值 if (!settings.feature.san || oldMaxSan !== settings.san.maxValue) { oldMaxSan = settings.san.maxValue; - if (sanTimerId) { - clearTimeout(sanTimerId); - sanTimerId = null; - } + void PlatformHelper.Alarms.clear(alarmKeySanRecovery); san.currentSan = settings.san.maxValue; san.saveUpdate(); } @@ -237,4 +228,12 @@ class SanInfo { } const instance = new SanInfo(); +PlatformHelper.Alarms.addListener((alarm) => { + if (alarm.name !== alarmKeySanRecovery) return; + sanRecovery(instance); + if (instance.currentSan < Settings.san.maxValue) { + startSanRecovery(instance); + } +}); + export default instance; diff --git a/src/common/sync/SyncData.js b/src/common/sync/SyncData.js index 80dfe11a..de5e4ef4 100644 --- a/src/common/sync/SyncData.js +++ b/src/common/sync/SyncData.js @@ -113,6 +113,9 @@ class DataSynchronizer { if (!data || typeof dataInitFn === 'function') { await PlatformHelper.Storage.saveLocalStorage(keyPersist(this.key), this.target); } + } else if (data) { + // 这种情况属于之前持久化了后来代码改成不需要持久化,就删除旧数据 + await PlatformHelper.Storage.removeLocalStorage(keyPersist(this.key)); } this.__setInited(); }; @@ -318,6 +321,9 @@ class DataSynchronizerOnlyBackgroundWritable extends DataSynchronizer { // 用于储存已注册的key,避免重复注册相同的key const keyMap = {}; +// TODO 由于不清楚具体会被什么时候强行停止插件,暂时全量持久化,之后看看有什么不需要持久化的再说 +const FORCE_PERSIST_ALL_DATA = true; + /** * 创建同步数据 * @param target 目标源对象 @@ -332,6 +338,7 @@ function createSyncData(target, key, mode, shouldPersist = false, updateHandler if (keyMap[key]) { throw new Error('duplicate sync key: ' + key); } + if (FORCE_PERSIST_ALL_DATA) shouldPersist = true; let synchronizer; switch (mode) { case DataSyncMode.ALL_WRITABLE: diff --git a/src/components/DataSourceSelect.vue b/src/components/DataSourceSelect.vue index 278014e1..1c878974 100644 --- a/src/components/DataSourceSelect.vue +++ b/src/components/DataSourceSelect.vue @@ -128,6 +128,7 @@ import Settings from '../common/Settings'; import { DataSourceMeta } from '../common/datasource/DataSourceMeta'; import PlatformHelper from '../common/platform/PlatformHelper'; import { MESSAGE_WEIBO_ADD_REFERER } from '../common/Constants'; +import { JsonValidator } from '@enraged-dun-cookie-development-team/common/json'; export default { name: 'DataSourceSelect', @@ -152,6 +153,8 @@ export default { mounted() { /* IFTRUE_feature__custom_datasource */ this.enableCustom = true; + // TODO 因为ajv用到了eval,所以只能全局禁止验证 + JsonValidator.validate = () => true; /* FITRUE_feature__custom_datasource */ Settings.doAfterInit(() => { AvailableDataSourceMeta.doAfterInit(() => this.buildDataSourceTree()); @@ -293,17 +296,15 @@ export default { /** * @type {DataSourceMeta} */ - let sourceMeta = { + const sourceMeta = { type: this.addCustomDataSourceDialogFormData.type, dataId: this.addCustomDataSourceDialogFormData.dataId, }; try { - let source; - let info; let success = false; - switch (this.addCustomDataSourceDialogFormData.type) { - case 'bilibili:dynamic-by-uid': - if (!/^[0-9]{1,16}$/.test(this.addCustomDataSourceDialogFormData.dataId)) { + switch (sourceMeta.type) { + case 'bilibili:dynamic-by-uid': { + if (!/^[0-9]{1,16}$/.test(sourceMeta.dataId)) { this.$message({ center: true, message: '数据源ID不合法!', @@ -311,17 +312,18 @@ export default { }); break; } - source = new DefaultDataSources.BilibiliDataSource({ - uid: this.addCustomDataSourceDialogFormData.dataId, + const source = new DefaultDataSources.BilibiliDataSource({ + uid: sourceMeta.dataId, logger: DefaultLogger, }); - info = await source.createDisplayInfo(); + const info = await source.createDisplayInfo(); sourceMeta.icon = info.icon; sourceMeta.name = '自定义-' + info.name + '-B站'; success = true; break; - case 'weibo:dynamic-by-uid': - if (!/^[0-9]{1,16}$/.test(this.addCustomDataSourceDialogFormData.dataId)) { + } + case 'weibo:dynamic-by-uid': { + if (!/^[0-9]{1,16}$/.test(sourceMeta.dataId)) { this.$message({ center: true, message: '数据源ID不合法!', @@ -329,20 +331,21 @@ export default { }); break; } - source = new DefaultDataSources.WeiboDataSource({ - uid: this.addCustomDataSourceDialogFormData.dataId, + const source = new DefaultDataSources.WeiboDataSource({ + uid: sourceMeta.dataId, logger: DefaultLogger, }); - info = await source.createDisplayInfo(); + const info = await source.createDisplayInfo(); sourceMeta.icon = info.icon; sourceMeta.name = '自定义-' + info.name + '-微博'; PlatformHelper.Message.send(MESSAGE_WEIBO_ADD_REFERER, { urls: [info.icon] }); success = true; break; + } default: this.$message({ center: true, - message: `未知的数据源类型:${this.addCustomDataSourceDialogFormData.type}!`, + message: `未知的数据源类型:${sourceMeta.type}!`, type: 'error', }); break; diff --git a/src/manifest.json b/src/manifest.json index f15b3151..8ed6a1bd 100644 --- a/src/manifest.json +++ b/src/manifest.json @@ -1,5 +1,5 @@ { - "manifest_version": 2, + "manifest_version": 3, "name": "小刻食堂 - 明日方舟蹲饼器 - Arknights-Ceobe-Canteen", "version": "dev", "description": "自由的兔兔把烙好的饼到处藏,就由小刻把它们都找出来吧!", @@ -8,13 +8,14 @@ "48": "assets/image/icon.png", "128": "assets/image/icon.png" }, - "browser_action": { - "default_icon": "assets/image/icon.png", + "action": { + "default_icon": { + "32": "assets/image/icon.png" + }, "default_title": "小刻食堂" }, "background": { - "scripts": ["background.js"], - "persistent": true + "service_worker": "background.js" }, "permissions": [ "clipboardWrite", @@ -24,8 +25,10 @@ "notifications", "downloads", "alarms", - "webRequest", - "webRequestBlocking", + "declarativeNetRequest", + "offscreen" + ], + "host_permissions": [ "https://*.bilibili.com/*", "https://*.weibo.cn/*", "https://*.hypergryph.com/*", @@ -42,5 +45,7 @@ "page": "options.html", "open_in_tab": true }, - "content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'" + "content_security_policy": { + "extension_pages": "script-src 'self' 'wasm-unsafe-eval'; object-src 'self'" + } } diff --git a/src/offscreen/html/img2blob.html b/src/offscreen/html/img2blob.html new file mode 100644 index 00000000..e4de7f3c --- /dev/null +++ b/src/offscreen/html/img2blob.html @@ -0,0 +1,10 @@ + + + + + img2blob + + + + + diff --git a/src/offscreen/js/img2blob.js b/src/offscreen/js/img2blob.js new file mode 100644 index 00000000..df95b0c9 --- /dev/null +++ b/src/offscreen/js/img2blob.js @@ -0,0 +1,18 @@ +import { Http } from '@enraged-dun-cookie-development-team/common/request'; +import PlatformHelper from '../../common/platform/PlatformHelper'; + +PlatformHelper.Message.registerListener('img2blob', 'offscreen:img2blob', async (data) => { + const blob = await Http.get(data.imageUrl, { responseTransformer: (r) => r.blob() }); + const canvas = document.createElement('canvas'); + canvas.height = 200; + canvas.width = 400; + const ctx = canvas.getContext('2d'); + const bitmap = await createImageBitmap(blob, { resizeWidth: canvas.width }); + ctx.drawImage(bitmap, 0, 0); + bitmap.close(); + /** + * @type {unknown} + */ + const newBlob = await new Promise((r) => canvas.toBlob(r)); + return URL.createObjectURL(newBlob); +}); diff --git a/src/options/App.vue b/src/options/App.vue index e0b66e00..9b500b46 100644 --- a/src/options/App.vue +++ b/src/options/App.vue @@ -195,7 +195,7 @@ - +