diff --git a/.husky/pre-push b/.husky/pre-push
index 06c7151c4..12257e946 100644
--- a/.husky/pre-push
+++ b/.husky/pre-push
@@ -13,7 +13,7 @@ need_check=0
while read local_ref local_sha remote_ref remote_sha; do
branch=$(echo "$remote_ref" | sed 's|refs/heads/||')
- if [ "$branch" = "main" ] || echo "$branch" | grep -q "^release"; then
+ if [ "$branch" = "main" ] || echo "$branch" | grep -q "^release/"; then
need_check=1
echo "🔍 Detected push target: $branch"
fi
diff --git a/src/locales/de-DE/translation.json b/src/locales/de-DE/translation.json
index 38f643d8d..fbe32f243 100644
--- a/src/locales/de-DE/translation.json
+++ b/src/locales/de-DE/translation.json
@@ -580,6 +580,11 @@
"maybe_later": "Vielleicht später",
"settings_hint": "Sie können diese Option jederzeit in den Einstellungen ändern."
},
+ "favicon_service": "Favicon-Dienst",
+ "favicon_service_desc": "Dienst zum Abrufen von Website-Symbolen auswählen",
+ "favicon_service_scriptcat": "ScriptCat",
+ "favicon_service_local": "Lokal abrufen",
+ "favicon_service_google": "Google",
"editor": {
"show_script_list": "Skriptliste anzeigen",
"hide_script_list": "Skriptliste ausblenden"
diff --git a/src/locales/en-US/translation.json b/src/locales/en-US/translation.json
index 69a4cf668..309fb2b5f 100644
--- a/src/locales/en-US/translation.json
+++ b/src/locales/en-US/translation.json
@@ -580,6 +580,11 @@
"maybe_later": "Maybe Later",
"settings_hint": "You can change this option in settings at any time."
},
+ "favicon_service": "Favicon Service",
+ "favicon_service_desc": "Choose the service for fetching website icons",
+ "favicon_service_scriptcat": "ScriptCat",
+ "favicon_service_local": "Local Fetch",
+ "favicon_service_google": "Google",
"editor": {
"show_script_list": "Show Script List",
"hide_script_list": "Hide Script List"
diff --git a/src/locales/ja-JP/translation.json b/src/locales/ja-JP/translation.json
index c51ec0285..da291da2a 100644
--- a/src/locales/ja-JP/translation.json
+++ b/src/locales/ja-JP/translation.json
@@ -580,6 +580,11 @@
"maybe_later": "後で",
"settings_hint": "設定ページでいつでも変更できます。"
},
+ "favicon_service": "Favicon サービス",
+ "favicon_service_desc": "ウェブサイトアイコンの取得サービスを選択",
+ "favicon_service_scriptcat": "ScriptCat",
+ "favicon_service_local": "ローカル取得",
+ "favicon_service_google": "Google",
"editor": {
"show_script_list": "スクリプトリストを表示",
"hide_script_list": "スクリプトリストを非表示"
diff --git a/src/locales/ru-RU/translation.json b/src/locales/ru-RU/translation.json
index 553d1e33d..53b912313 100644
--- a/src/locales/ru-RU/translation.json
+++ b/src/locales/ru-RU/translation.json
@@ -580,6 +580,11 @@
"maybe_later": "Может быть позже",
"settings_hint": "Вы можете изменить эту опцию в настройках в любое время."
},
+ "favicon_service": "Сервис Favicon",
+ "favicon_service_desc": "Выберите сервис для получения значков сайтов",
+ "favicon_service_scriptcat": "ScriptCat",
+ "favicon_service_local": "Локальное получение",
+ "favicon_service_google": "Google",
"editor": {
"show_script_list": "Показать список скриптов",
"hide_script_list": "Скрыть список скриптов"
diff --git a/src/locales/vi-VN/translation.json b/src/locales/vi-VN/translation.json
index d9363752c..bf34700de 100644
--- a/src/locales/vi-VN/translation.json
+++ b/src/locales/vi-VN/translation.json
@@ -580,6 +580,11 @@
"maybe_later": "Để sau",
"settings_hint": "Bạn có thể thay đổi tùy chọn này trong cài đặt bất kỳ lúc nào."
},
+ "favicon_service": "Dịch vụ Favicon",
+ "favicon_service_desc": "Chọn dịch vụ để lấy biểu tượng trang web",
+ "favicon_service_scriptcat": "ScriptCat",
+ "favicon_service_local": "Lấy cục bộ",
+ "favicon_service_google": "Google",
"editor": {
"show_script_list": "Hiển thị danh sách script",
"hide_script_list": "Ẩn danh sách script"
diff --git a/src/locales/zh-CN/translation.json b/src/locales/zh-CN/translation.json
index 7d7eca279..800109e87 100644
--- a/src/locales/zh-CN/translation.json
+++ b/src/locales/zh-CN/translation.json
@@ -580,6 +580,11 @@
"maybe_later": "暂不启用",
"settings_hint": "你可以随时在设置中修改此选项。"
},
+ "favicon_service": "图标服务",
+ "favicon_service_desc": "选择获取网站图标的服务",
+ "favicon_service_scriptcat": "ScriptCat",
+ "favicon_service_local": "本地获取",
+ "favicon_service_google": "Google",
"editor": {
"show_script_list": "显示脚本列表",
"hide_script_list": "隐藏脚本列表"
diff --git a/src/locales/zh-TW/translation.json b/src/locales/zh-TW/translation.json
index 6f740789d..de3d49ee1 100644
--- a/src/locales/zh-TW/translation.json
+++ b/src/locales/zh-TW/translation.json
@@ -580,6 +580,11 @@
"maybe_later": "暫不啟用",
"settings_hint": "你可以隨時在設定中修改此選項。"
},
+ "favicon_service": "圖示服務",
+ "favicon_service_desc": "選擇取得網站圖示的服務",
+ "favicon_service_scriptcat": "ScriptCat",
+ "favicon_service_local": "本地取得",
+ "favicon_service_google": "Google",
"editor": {
"show_script_list": "顯示腳本列表",
"hide_script_list": "隱藏腳本列表"
diff --git a/src/pages/options/routes/ScriptList/hooks.tsx b/src/pages/options/routes/ScriptList/hooks.tsx
index 425082748..6cb0909ee 100644
--- a/src/pages/options/routes/ScriptList/hooks.tsx
+++ b/src/pages/options/routes/ScriptList/hooks.tsx
@@ -10,6 +10,7 @@ import {
} from "@App/app/repo/scripts";
import { fetchScript, fetchScriptList } from "@App/pages/store/features/script";
import { loadScriptFavicons } from "@App/pages/store/favicons";
+import { systemConfig } from "@App/pages/store/global";
import { parseTags } from "@App/app/repo/metadata";
import { getCombinedMeta } from "@App/app/service/service_worker/utils";
import { cacheInstance } from "@App/app/cache";
@@ -76,7 +77,8 @@ export function useScriptDataManagement() {
setLoadingList(false);
cacheInstance.tx("faviconOPFSControl", async () => {
if (!mounted) return;
- for await (const { chunkResults } of loadScriptFavicons(list)) {
+ const faviconService = await systemConfig.getFaviconService();
+ for await (const { chunkResults } of loadScriptFavicons(list, faviconService)) {
if (!mounted) return;
setScriptList((prev) => {
const favMap = new Map(chunkResults.map((r) => [r.uuid, r]));
diff --git a/src/pages/options/routes/Setting.tsx b/src/pages/options/routes/Setting.tsx
index afb779499..0ac87c935 100644
--- a/src/pages/options/routes/Setting.tsx
+++ b/src/pages/options/routes/Setting.tsx
@@ -15,6 +15,8 @@ import CustomTrans from "@App/pages/components/CustomTrans";
import { useSystemConfig } from "./utils";
import { subscribeMessage } from "@App/pages/store/global";
import { SystemConfigChange, type SystemConfigKey } from "@App/pkg/config/config";
+import { FaviconDAO } from "@App/app/repo/favicon";
+import { clearFaviconMemoryCache } from "@App/pages/store/favicons";
import { type TKeyValue } from "@Packages/message/message_queue";
import { useEffect, useMemo } from "react";
import { systemConfig } from "@App/pages/store/global";
@@ -41,6 +43,7 @@ function Setting() {
const [badgeTextColor, setBadgeTextColor, submitBadgeTextColor] = useSystemConfig("badge_text_color");
const [scriptMenuDisplayType, setScriptMenuDisplayType, submitScriptMenuDisplayType] =
useSystemConfig("script_menu_display_type");
+ const [faviconService, setFaviconService, submitFaviconService] = useSystemConfig("favicon_service");
const [editorTypeDefinition, setEditorTypeDefinition, submitEditorTypeDefinition] =
useSystemConfig("editor_type_definition");
@@ -81,6 +84,7 @@ function Setting() {
badge_background_color: setBadgeBackgroundColor,
badge_text_color: setBadgeTextColor,
script_menu_display_type: setScriptMenuDisplayType,
+ favicon_service: setFaviconService,
editor_type_definition: setEditorTypeDefinition,
} as const;
const hookMgr = new HookManager();
@@ -306,6 +310,39 @@ function Setting() {
+
+ {/* Favicon 服务 */}
+
+
+ {t("favicon_service")}
+
+
+
{t("favicon_service_desc")}
+
diff --git a/src/pages/store/favicons.test.ts b/src/pages/store/favicons.test.ts
index 6f78f81b0..d3a9f023f 100644
--- a/src/pages/store/favicons.test.ts
+++ b/src/pages/store/favicons.test.ts
@@ -1,5 +1,12 @@
-import { extractFaviconsDomain } from "@App/pages/store/favicons";
-import { describe, it, expect } from "vitest";
+import {
+ extractFaviconsDomain,
+ extractDomainFromPattern,
+ parseFaviconsNew,
+ fetchIconByService,
+ fetchIconByDomain,
+ timeoutAbortSignal,
+} from "@App/pages/store/favicons";
+import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
describe("extractFaviconsDomain", () => {
it("应该正确提取各种URL模式的域名", () => {
@@ -41,4 +48,285 @@ describe("extractFaviconsDomain", () => {
expect(extractFaviconsDomain([], [])).toEqual([]);
expect(extractFaviconsDomain()).toEqual([]);
});
+
+ it("相同域名应该去重", () => {
+ const result = extractFaviconsDomain(["https://example.com/page1", "https://example.com/page2"], []);
+ // 两个pattern提取出相同域名 example.com,应去重
+ expect(result).toHaveLength(1);
+ expect(result[0].domain).toBe("example.com");
+ });
+});
+
+describe("extractDomainFromPattern", () => {
+ // 基础场景已在 extractFaviconsDomain 中覆盖,这里只测试额外边界情况
+ it("应该从带query参数的URL中提取域名", () => {
+ expect(extractDomainFromPattern("https://www.google.com/search?q=test")).toBe("www.google.com");
+ });
+
+ it("空字符串应返回null", () => {
+ expect(extractDomainFromPattern("")).toBe(null);
+ });
+});
+
+describe("parseFaviconsNew", () => {
+ it("应该解析标准favicon link标签", () => {
+ const hrefs: string[] = [];
+ const html = '';
+ parseFaviconsNew(html, (href) => hrefs.push(href));
+ expect(hrefs).toEqual(["/favicon.ico"]);
+ });
+
+ it("应该解析apple-touch-icon", () => {
+ const hrefs: string[] = [];
+ const html = '';
+ parseFaviconsNew(html, (href) => hrefs.push(href));
+ expect(hrefs).toEqual(["/apple-icon.png"]);
+ });
+
+ it("应该解析apple-touch-icon-precomposed", () => {
+ const hrefs: string[] = [];
+ const html = '';
+ parseFaviconsNew(html, (href) => hrefs.push(href));
+ expect(hrefs).toEqual(["/precomposed.png"]);
+ });
+
+ it("应该解析多个favicon link标签", () => {
+ const hrefs: string[] = [];
+ const html = `
+
+
+
+ `;
+ parseFaviconsNew(html, (href) => hrefs.push(href));
+ expect(hrefs).toHaveLength(3);
+ expect(hrefs).toEqual(["/icon1.png", "/icon2.png", "/icon3.png"]);
+ });
+
+ it("没有link标签时不应调用回调", () => {
+ const callback = vi.fn();
+ parseFaviconsNew("hello", callback);
+ expect(callback).not.toHaveBeenCalled();
+ });
+
+ it("应该忽略非favicon的link标签", () => {
+ const hrefs: string[] = [];
+ const html = '';
+ parseFaviconsNew(html, (href) => hrefs.push(href));
+ expect(hrefs).toEqual(["/icon.png"]);
+ });
+
+ it("应该处理单引号和双引号", () => {
+ const hrefs: string[] = [];
+ const html = ``;
+ parseFaviconsNew(html, (href) => hrefs.push(href));
+ expect(hrefs).toEqual(["/icon1.png", "/icon2.png"]);
+ });
+
+ it("应该处理大小写混合的标签", () => {
+ const hrefs: string[] = [];
+ const html = '';
+ parseFaviconsNew(html, (href) => hrefs.push(href));
+ expect(hrefs).toEqual(["/icon.png"]);
+ });
+});
+
+// 创建模拟HTML Response的辅助函数
+const mockHtmlResponse = (url: string, html: string) => ({
+ ok: true,
+ url,
+ headers: new Headers({ "content-type": "text/html; charset=utf-8" }),
+ arrayBuffer: () => Promise.resolve(new TextEncoder().encode(html).buffer),
+});
+
+describe("fetchIconByService", () => {
+ beforeEach(() => {
+ vi.stubGlobal(
+ "fetch",
+ vi.fn().mockResolvedValue({
+ ok: true,
+ url: "https://example.com/favicon.ico",
+ text: () => Promise.resolve(""),
+ blob: () => Promise.resolve(new Blob()),
+ headers: new Headers({ "content-type": "text/html" }),
+ })
+ );
+ });
+
+ afterEach(() => {
+ vi.restoreAllMocks();
+ });
+
+ it("scriptcat服务应返回scriptcat API URL", async () => {
+ const result = await fetchIconByService("example.com", "scriptcat");
+ expect(result).toEqual(["https://ext.scriptcat.org/api/v1/open/favicons?domain=example.com&sz=64"]);
+ });
+
+ it("google服务应返回Google favicon URL", async () => {
+ const result = await fetchIconByService("example.com", "google");
+ expect(result).toEqual(["https://www.google.com/s2/favicons?domain=example.com&sz=64"]);
+ });
+
+ it("应该对域名进行URL编码", async () => {
+ const result = await fetchIconByService("例え.jp", "scriptcat");
+ expect(result).toEqual([
+ `https://ext.scriptcat.org/api/v1/open/favicons?domain=${encodeURIComponent("例え.jp")}&sz=64`,
+ ]);
+ });
+
+ // local 服务的具体行为已在 fetchIconByDomain 测试中充分覆盖
+});
+
+describe("fetchIconByDomain", () => {
+ afterEach(() => {
+ vi.restoreAllMocks();
+ });
+
+ it("应该从HTML中解析favicon并验证", async () => {
+ vi.stubGlobal(
+ "fetch",
+ vi.fn().mockImplementation((url: string | URL) => {
+ const urlStr = url.toString();
+ if (urlStr === "https://example.com") {
+ return Promise.resolve(
+ mockHtmlResponse(
+ "https://example.com/",
+ ''
+ )
+ );
+ }
+ return Promise.resolve({ ok: true, url: "https://example.com/static/favicon.ico" });
+ })
+ );
+
+ const icons = await fetchIconByDomain("example.com");
+ expect(icons).toEqual(["https://example.com/static/favicon.ico"]);
+ });
+
+ it("没有link标签时应回退到/favicon.ico", async () => {
+ vi.stubGlobal(
+ "fetch",
+ vi.fn().mockImplementation((url: string | URL) => {
+ const urlStr = url.toString();
+ if (urlStr === "https://noicon.com") {
+ return Promise.resolve(mockHtmlResponse("https://noicon.com/", ""));
+ }
+ return Promise.resolve({ ok: true, url: "https://noicon.com/favicon.ico" });
+ })
+ );
+
+ const icons = await fetchIconByDomain("noicon.com");
+ expect(icons).toEqual(["https://noicon.com/favicon.ico"]);
+ });
+
+ it("HEAD请求失败时应过滤掉该icon", async () => {
+ vi.stubGlobal(
+ "fetch",
+ vi.fn().mockImplementation((url: string | URL) => {
+ const urlStr = url.toString();
+ if (urlStr === "https://fail.com") {
+ return Promise.resolve(
+ mockHtmlResponse("https://fail.com/", '')
+ );
+ }
+ return Promise.reject(new Error("Not found"));
+ })
+ );
+
+ const icons = await fetchIconByDomain("fail.com");
+ expect(icons).toEqual([]);
+ });
+
+ it("HEAD请求返回非OK状态时应过滤掉该icon", async () => {
+ vi.stubGlobal(
+ "fetch",
+ vi.fn().mockImplementation((url: string | URL) => {
+ const urlStr = url.toString();
+ if (urlStr === "https://badstatus.com") {
+ return Promise.resolve(
+ mockHtmlResponse("https://badstatus.com/", '')
+ );
+ }
+ return Promise.resolve({ ok: false, url: "https://badstatus.com/icon.png" });
+ })
+ );
+
+ const icons = await fetchIconByDomain("badstatus.com");
+ expect(icons).toEqual([]);
+ });
+
+ it("HEAD请求重定向到不同文件名时应过滤", async () => {
+ vi.stubGlobal(
+ "fetch",
+ vi.fn().mockImplementation((url: string | URL) => {
+ const urlStr = url.toString();
+ if (urlStr === "https://redirect.com") {
+ return Promise.resolve(
+ mockHtmlResponse("https://redirect.com/", '')
+ );
+ }
+ return Promise.resolve({ ok: true, url: "https://redirect.com/404.html" });
+ })
+ );
+
+ const icons = await fetchIconByDomain("redirect.com");
+ expect(icons).toEqual([]);
+ });
+
+ it("应该正确解析相对URL为绝对URL", async () => {
+ vi.stubGlobal(
+ "fetch",
+ vi.fn().mockImplementation((url: string | URL) => {
+ const urlStr = url.toString();
+ if (urlStr === "https://cdn.example.com") {
+ // 页面重定向到了不同的URL
+ return Promise.resolve(
+ mockHtmlResponse(
+ "https://www.example.com/home",
+ ''
+ )
+ );
+ }
+ return Promise.resolve({ ok: true, url: "https://www.example.com/assets/icon.png" });
+ })
+ );
+
+ const icons = await fetchIconByDomain("cdn.example.com");
+ expect(icons).toEqual(["https://www.example.com/assets/icon.png"]);
+ });
+
+ it("应该处理多个favicon并全部验证", async () => {
+ vi.stubGlobal(
+ "fetch",
+ vi.fn().mockImplementation((url: string | URL) => {
+ const urlStr = url.toString();
+ if (urlStr === "https://multi.com") {
+ return Promise.resolve(
+ mockHtmlResponse(
+ "https://multi.com/",
+ `
+
+
+
+ `
+ )
+ );
+ }
+ // 所有HEAD请求都成功
+ return Promise.resolve({ ok: true, url: urlStr });
+ })
+ );
+
+ const icons = await fetchIconByDomain("multi.com");
+ expect(icons).toHaveLength(3);
+ expect(icons).toContain("https://multi.com/icon16.png");
+ expect(icons).toContain("https://multi.com/icon32.png");
+ expect(icons).toContain("https://multi.com/apple.png");
+ });
+});
+
+describe("timeoutAbortSignal", () => {
+ it("应该返回AbortSignal", () => {
+ const signal = timeoutAbortSignal(5000);
+ expect(signal).toBeInstanceOf(AbortSignal);
+ });
});
diff --git a/src/pages/store/favicons.ts b/src/pages/store/favicons.ts
index 0ed61ef6f..feb2da4df 100644
--- a/src/pages/store/favicons.ts
+++ b/src/pages/store/favicons.ts
@@ -3,11 +3,26 @@ import { FaviconDAO, type FaviconFile, type FaviconRecord } from "@App/app/repo/
import { v5 as uuidv5 } from "uuid";
import { getFaviconRootFolder } from "@App/app/service/service_worker/utils";
import { readBlobContent } from "@App/pkg/utils/encoding";
+import type { FaviconService } from "@App/pkg/config/config";
let scriptDAO: ScriptDAO | null = null;
let faviconDAO: FaviconDAO | null = null;
const loadFaviconPromises = new Map(); // 关联 iconUrl 和 blobUrl
+// 清除内存中的 favicon 缓存,切换服务时调用
+export const clearFaviconMemoryCache = () => {
+ loadFaviconPromises.forEach((promise) => {
+ Promise.resolve(promise)
+ .then((blobUrl) => {
+ if (typeof blobUrl === "string" && blobUrl.startsWith("blob:")) {
+ URL.revokeObjectURL(blobUrl);
+ }
+ })
+ .catch(() => {});
+ });
+ loadFaviconPromises.clear();
+};
+
/**
* 从URL模式中提取域名
*/
@@ -179,8 +194,26 @@ export async function fetchIconByDomain(domain: string): Promise {
return urls.filter((url) => !!url) as string[];
}
+/**
+ * 根据服务类型获取favicon URL列表
+ */
+export async function fetchIconByService(domain: string, service: FaviconService): Promise {
+ switch (service) {
+ case "scriptcat":
+ return [`https://ext.scriptcat.org/api/v1/open/favicons?domain=${encodeURIComponent(domain)}&sz=64`];
+ case "google":
+ return [`https://www.google.com/s2/favicons?domain=${encodeURIComponent(domain)}&sz=64`];
+ case "local":
+ default:
+ return await fetchIconByDomain(domain);
+ }
+}
+
// 获取脚本的favicon
-export const getScriptFavicon = async (uuid: string): Promise => {
+export const getScriptFavicon = async (
+ uuid: string,
+ service: FaviconService = "scriptcat"
+): Promise => {
scriptDAO ||= new ScriptDAO();
faviconDAO ||= new FaviconDAO();
const script = await scriptDAO.get(uuid);
@@ -199,7 +232,7 @@ export const getScriptFavicon = async (uuid: string): Promise =
domains.map(async (domain) => {
try {
if (domain.domain) {
- const icons = await fetchIconByDomain(domain.domain);
+ const icons = await fetchIconByService(domain.domain, service);
const icon = icons.length > 0 ? icons[0] : "";
return { match: domain.match, website: "http://" + domain.domain, icon };
}
@@ -233,6 +266,11 @@ export const loadFavicon = async (iconUrl: string): Promise => {
// 文件不存在,下载并保存
const newFileHandle = await directoryHandle.getFileHandle(filename, { create: true });
const response = await fetch(iconUrl);
+ if (response.status >= 300) {
+ // 状态码异常,删除创建的空文件并抛出错误
+ await directoryHandle.removeEntry(filename).catch(() => {});
+ throw new Error(`Favicon fetch failed with status ${response.status}`);
+ }
const blob = await response.blob();
const writable = await newFileHandle.createWritable();
await writable.write(blob);
@@ -256,9 +294,9 @@ const getFileFromOPFS = async (opfsRet: FaviconFile): Promise => {
};
// 处理单个脚本的favicon
-const processScriptFavicon = async (script: Script) => {
+const processScriptFavicon = async (script: Script, service: FaviconService = "scriptcat") => {
const favFnAsync = async () => {
- const icons = await getScriptFavicon(script.uuid); // 恒久。不会因SW重启而失效
+ const icons = await getScriptFavicon(script.uuid, service); // 恒久。不会因SW重启而失效
if (icons.length === 0) return [];
const newIcons = await Promise.all(
icons.map(async (icon) => {
@@ -305,7 +343,7 @@ type FavIconResult = {
type TFaviconStack = { chunkResults: FavIconResult[]; pendingCount: number };
// 处理favicon加载,以批次方式处理
-export const loadScriptFavicons = async function* (scripts: Script[]) {
+export const loadScriptFavicons = async function* (scripts: Script[], service: FaviconService = "scriptcat") {
const stack: TFaviconStack[] = [];
const asyncWaiter: { promise?: any; resolve?: any } = {};
const createPromise = () => {
@@ -319,7 +357,7 @@ export const loadScriptFavicons = async function* (scripts: Script[]) {
const results: FavIconResult[] = [];
let waiting = false;
for (const script of scripts) {
- processScriptFavicon(script).then((result: FavIconResult) => {
+ processScriptFavicon(script, service).then((result: FavIconResult) => {
results.push(result);
// 下一个 MacroTask 执行。
// 使用 requestAnimationFrame 而非setTimeout 是因为前台才要显示。而且网页绘画中时会延后这个
diff --git a/src/pkg/config/config.ts b/src/pkg/config/config.ts
index 4215fb516..109d334fa 100644
--- a/src/pkg/config/config.ts
+++ b/src/pkg/config/config.ts
@@ -19,6 +19,8 @@ export type CloudSyncConfig = {
params: { [key: string]: any };
};
+export type FaviconService = "scriptcat" | "local" | "google";
+
export type CATFileStorage = {
filesystem: FileSystemType;
params: { [key: string]: any };
@@ -474,6 +476,14 @@ export class SystemConfig {
getScriptMenuDisplayType(): Promise<"no_browser" | "all"> {
return this._get("script_menu_display_type", "all");
}
+
+ getFaviconService() {
+ return this._get("favicon_service", "scriptcat");
+ }
+
+ setFaviconService(val: FaviconService) {
+ return this._set("favicon_service", val);
+ }
}
let lazyScriptNamePrefix: string = "";