1
This commit is contained in:
395
js/init.js
Normal file
395
js/init.js
Normal file
@@ -0,0 +1,395 @@
|
||||
// 低版本chrome manifest v3协议 会有 getMessage 函数不存在的bug
|
||||
if (chrome.i18n.getMessage === undefined) {
|
||||
chrome.i18n.getMessage = (key) => key;
|
||||
fetch(chrome.runtime.getURL("_locales/zh_CN/messages.json")).then(res => res.json()).then(data => {
|
||||
chrome.i18n.getMessage = (key) => data[key].messages;
|
||||
}).catch((e) => { console.error(e); });
|
||||
}
|
||||
/**
|
||||
* 部分修改版chrome 不存在 chrome.downloads API
|
||||
* 例如 夸克浏览器
|
||||
* 使用传统下载方式下载 但无法监听 无法另存为 无法判断下载是否失败 唉~
|
||||
*/
|
||||
if (!chrome.downloads) {
|
||||
chrome.downloads = {
|
||||
download: function (options, callback) {
|
||||
let a = document.createElement('a');
|
||||
a.href = options.url;
|
||||
a.download = options.filename;
|
||||
a.click();
|
||||
a = null;
|
||||
callback && callback();
|
||||
},
|
||||
onChanged: { addListener: function () { } },
|
||||
showDefaultFolder: function () { },
|
||||
show: function () { },
|
||||
}
|
||||
}
|
||||
// 兼容 114版本以下没有chrome.sidePanel
|
||||
if (!chrome.sidePanel || !chrome.sidePanel.setPanelBehavior) {
|
||||
chrome.sidePanel = {
|
||||
setOptions: function (options) { },
|
||||
setPanelBehavior: function (options) { },
|
||||
}
|
||||
}
|
||||
|
||||
// 简写翻译函数
|
||||
const i18n = new Proxy(chrome.i18n.getMessage, {
|
||||
get: function (target, key) {
|
||||
return chrome.i18n.getMessage(key);
|
||||
}
|
||||
});
|
||||
// 全局变量
|
||||
var G = {};
|
||||
G.initSyncComplete = false;
|
||||
G.initLocalComplete = false;
|
||||
// 缓存数据
|
||||
var cacheData = { init: true };
|
||||
G.blackList = new Set(); // 正则屏蔽资源列表
|
||||
G.blockUrlSet = new Set(); // 屏蔽网址列表
|
||||
G.requestHeaders = new Map(); // 临时储存请求头
|
||||
G.urlMap = new Map(); // url查重map
|
||||
G.deepSearchTemporarilyClose = null; // 深度搜索临时变量
|
||||
|
||||
// 初始化当前tabId
|
||||
chrome.tabs.query({ active: true, currentWindow: true }, function (tabs) {
|
||||
if (tabs[0] && tabs[0].id) {
|
||||
G.tabId = tabs[0].id;
|
||||
} else {
|
||||
G.tabId = -1;
|
||||
}
|
||||
});
|
||||
|
||||
// 手机浏览器
|
||||
G.isMobile = /Mobile|Android|iPhone|iPad/i.test(navigator.userAgent);
|
||||
|
||||
// 所有设置变量 默认值
|
||||
G.OptionLists = {
|
||||
Ext: [
|
||||
{ "ext": "flv", "size": 0, "state": true },
|
||||
{ "ext": "hlv", "size": 0, "state": true },
|
||||
{ "ext": "f4v", "size": 0, "state": true },
|
||||
{ "ext": "mp4", "size": 0, "state": true },
|
||||
{ "ext": "mp3", "size": 0, "state": true },
|
||||
{ "ext": "wma", "size": 0, "state": true },
|
||||
{ "ext": "wav", "size": 0, "state": true },
|
||||
{ "ext": "m4a", "size": 0, "state": true },
|
||||
{ "ext": "ts", "size": 0, "state": false },
|
||||
{ "ext": "webm", "size": 0, "state": true },
|
||||
{ "ext": "ogg", "size": 0, "state": true },
|
||||
{ "ext": "ogv", "size": 0, "state": true },
|
||||
{ "ext": "acc", "size": 0, "state": true },
|
||||
{ "ext": "mov", "size": 0, "state": true },
|
||||
{ "ext": "mkv", "size": 0, "state": true },
|
||||
{ "ext": "m4s", "size": 0, "state": true },
|
||||
{ "ext": "m3u8", "size": 0, "state": true },
|
||||
{ "ext": "m3u", "size": 0, "state": true },
|
||||
{ "ext": "mpeg", "size": 0, "state": true },
|
||||
{ "ext": "avi", "size": 0, "state": true },
|
||||
{ "ext": "wmv", "size": 0, "state": true },
|
||||
{ "ext": "asf", "size": 0, "state": true },
|
||||
{ "ext": "movie", "size": 0, "state": true },
|
||||
{ "ext": "divx", "size": 0, "state": true },
|
||||
{ "ext": "mpeg4", "size": 0, "state": true },
|
||||
{ "ext": "vid", "size": 0, "state": true },
|
||||
{ "ext": "aac", "size": 0, "state": true },
|
||||
{ "ext": "mpd", "size": 0, "state": true },
|
||||
{ "ext": "weba", "size": 0, "state": true },
|
||||
{ "ext": "opus", "size": 0, "state": true },
|
||||
{ "ext": "srt", "size": 0, "state": false },
|
||||
{ "ext": "vtt", "size": 0, "state": false },
|
||||
],
|
||||
Type: [
|
||||
{ "type": "audio/*", "size": 0, "state": true },
|
||||
{ "type": "video/*", "size": 0, "state": true },
|
||||
{ "type": "application/ogg", "size": 0, "state": true },
|
||||
{ "type": "application/vnd.apple.mpegurl", "size": 0, "state": true },
|
||||
{ "type": "application/x-mpegurl", "size": 0, "state": true },
|
||||
{ "type": "application/mpegurl", "size": 0, "state": true },
|
||||
{ "type": "application/octet-stream-m3u8", "size": 0, "state": true },
|
||||
{ "type": "application/dash+xml", "size": 0, "state": true },
|
||||
{ "type": "application/m4s", "size": 0, "state": true },
|
||||
],
|
||||
Regex: [
|
||||
{ "type": "ig", "regex": "https://cache\\.video\\.[a-z]*\\.com/dash\\?tvid=.*", "ext": "json", "state": false },
|
||||
{ "type": "ig", "regex": ".*\\.bilivideo\\.(com|cn).*\\/live-bvc\\/.*m4s", "ext": "", "blackList": true, "state": false },
|
||||
],
|
||||
TitleName: false,
|
||||
Player: "",
|
||||
ShowWebIco: !G.isMobile,
|
||||
MobileUserAgent: "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1",
|
||||
m3u8dl: 0,
|
||||
m3u8dlArg: `"\${url}" --save-dir "%USERPROFILE%\\Downloads\\m3u8dl" --save-name "\${title}_\${now}" \${referer|exists:'-H "Referer:*"'} \${cookie|exists:'-H "Cookie:*"'} --no-log`,
|
||||
m3u8dlConfirm: false,
|
||||
playbackRate: 2,
|
||||
copyM3U8: "${url}",
|
||||
copyMPD: "${url}",
|
||||
copyOther: "${url}",
|
||||
autoClearMode: 1,
|
||||
catDownload: false,
|
||||
saveAs: false,
|
||||
userAgent: "",
|
||||
downFileName: "${title}.${ext}",
|
||||
css: "",
|
||||
checkDuplicates: true,
|
||||
enable: true,
|
||||
downActive: !G.isMobile, // 手机端默认不启用 后台下载
|
||||
downAutoClose: true,
|
||||
downStream: false,
|
||||
aria2Rpc: "http://localhost:6800/jsonrpc",
|
||||
enableAria2Rpc: false,
|
||||
enableAria2RpcReferer: true,
|
||||
aria2RpcToken: "",
|
||||
m3u8AutoDown: true,
|
||||
badgeNumber: true,
|
||||
send2local: false,
|
||||
send2localManual: false,
|
||||
send2localURL: "http://127.0.0.1:8000/",
|
||||
send2localMethod: 'POST',
|
||||
send2localBody: '{"action": "${action}", "data": ${data}, "tabId": "${tabId}"}',
|
||||
send2localType: 0,
|
||||
popup: false,
|
||||
popupMode: 0, // 0:preview.html 1:popup.html 2:window preview.html 3: window popup.html
|
||||
invoke: false,
|
||||
invokeText: `m3u8dlre:"\${url}" --save-dir "%USERPROFILE%\\Downloads" --del-after-done --save-name "\${title}_\${now}" --auto-select \${referer|exists:'-H "Referer: *"'}`,
|
||||
invokeConfirm: false,
|
||||
// m3u8解析器默认参数
|
||||
M3u8Thread: 6,
|
||||
M3u8Mp4: false,
|
||||
M3u8OnlyAudio: false,
|
||||
M3u8SkipDecrypt: false,
|
||||
M3u8StreamSaver: false,
|
||||
M3u8Ffmpeg: true,
|
||||
M3u8AutoClose: false,
|
||||
// 第三方服务地址
|
||||
onlineServiceAddress: 0,
|
||||
chromeLimitSize: 1.8 * 1024 * 1024 * 1024,
|
||||
blockUrl: [],
|
||||
blockUrlWhite: false,
|
||||
maxLength: G.isMobile ? 999 : 9999,
|
||||
sidePanel: false, // 侧边栏
|
||||
deepSearch: false, // 常开深度搜索
|
||||
// MQTT 配置
|
||||
send2MQTT: false,
|
||||
mqttEnable: false,
|
||||
mqttBroker: "test.mosquitto.org",
|
||||
mqttPort: 8081,
|
||||
mqttPath: "/mqtt",
|
||||
mqttProtocol: "wss",
|
||||
mqttClientId: "cat-catch-client",
|
||||
mqttUser: "",
|
||||
mqttPassword: "",
|
||||
mqttTopic: "cat-catch/media",
|
||||
mqttQos: 0,
|
||||
mqttTitleLength: 100,
|
||||
mqttDataFormat: "",
|
||||
getHtmlDOM: false,
|
||||
};
|
||||
// 本地储存的配置
|
||||
G.LocalVar = {
|
||||
featMobileTabId: [],
|
||||
featAutoDownTabId: [],
|
||||
mediaControl: { tabid: 0, index: -1 },
|
||||
previewShowTitle: false, // 是否显示标题
|
||||
previewDeleteDuplicateFilenames: false, // 是否删除重复文件名
|
||||
};
|
||||
|
||||
// 102版本以上 非Firefox 开启更多功能
|
||||
G.isFirefox = (typeof browser == "object");
|
||||
G.version = navigator.userAgent.match(/(Chrome|Firefox)\/([\d]+)/);
|
||||
G.version = G.version && G.version[2] ? parseInt(G.version[2]) : 93;
|
||||
|
||||
// 脚本列表
|
||||
G.scriptList = new Map();
|
||||
G.scriptList.set("search.js", { key: "search", refresh: true, allFrames: true, world: "MAIN", name: i18n.deepSearch, off: i18n.closeSearch, i18n: false, tabId: new Set() });
|
||||
G.scriptList.set("catch.js", { key: "catch", refresh: true, allFrames: true, world: "MAIN", name: i18n.cacheCapture, off: i18n.closeCapture, i18n: true, tabId: new Set() });
|
||||
G.scriptList.set("recorder.js", { key: "recorder", refresh: false, allFrames: true, world: "MAIN", name: i18n.videoRecording, off: i18n.closeRecording, i18n: true, tabId: new Set() });
|
||||
G.scriptList.set("recorder2.js", { key: "recorder2", refresh: false, allFrames: false, world: "ISOLATED", name: i18n.screenCapture, off: i18n.closeCapture, i18n: true, tabId: new Set() });
|
||||
G.scriptList.set("webrtc.js", { key: "webrtc", refresh: true, allFrames: true, world: "MAIN", name: i18n.recordWebRTC, off: i18n.closeRecording, i18n: true, tabId: new Set() });
|
||||
|
||||
// ffmpeg
|
||||
G.ffmpegConfig = {
|
||||
tab: 0,
|
||||
cacheData: [],
|
||||
version: 1,
|
||||
get url() {
|
||||
return G.onlineServiceAddress == 0 ? "https://ffmpeg.bmmmd.com/" : "https://ffmpeg2.bmmmd.com/";
|
||||
}
|
||||
}
|
||||
// streamSaver 边下边存
|
||||
G.streamSaverConfig = {
|
||||
get url() {
|
||||
return G.onlineServiceAddress == 0 ? "https://stream.bmmmd.com/mitm.html" : "https://stream2.bmmmd.com/mitm.html";
|
||||
}
|
||||
}
|
||||
|
||||
// 正则预编译
|
||||
const reFilename = /filename="?([^"]+)"?/;
|
||||
const reStringModify = /[<>:"\/\\|?*~]/g;
|
||||
const reFilterFileName = /[<>:"|?*~]/g;
|
||||
const reTemplates = /\${([^}|]+)(?:\|([^}]+))?}/g;
|
||||
const reJSONparse = /([{,]\s*)([\w-]+)(\s*:)/g;
|
||||
|
||||
// 防抖
|
||||
let debounce = undefined;
|
||||
let debounceCount = 0;
|
||||
let debounceTime = 0;
|
||||
|
||||
// Init
|
||||
InitOptions();
|
||||
|
||||
// 初始变量
|
||||
function InitOptions() {
|
||||
// 断开重新连接后 立刻把local里MediaData数据交给cacheData
|
||||
(chrome.storage.session ?? chrome.storage.local).get({ MediaData: {} }, function (items) {
|
||||
if (items.MediaData.init) {
|
||||
cacheData = {};
|
||||
return;
|
||||
}
|
||||
cacheData = items.MediaData;
|
||||
});
|
||||
// 读取sync配置数据 交给全局变量G
|
||||
chrome.storage.sync.get(G.OptionLists, function (items) {
|
||||
if (chrome.runtime.lastError) {
|
||||
items = G.OptionLists;
|
||||
}
|
||||
// 确保有默认值
|
||||
for (let key in G.OptionLists) {
|
||||
if (items[key] === undefined || items[key] === null) {
|
||||
items[key] = G.OptionLists[key];
|
||||
}
|
||||
}
|
||||
// Ext的Array转为Map类型
|
||||
items.Ext = new Map(items.Ext.map(item => [item.ext, item]));
|
||||
// Type的Array转为Map类型
|
||||
items.Type = new Map(items.Type.map(item => [item.type, { size: item.size, state: item.state }]));
|
||||
// 预编译正则匹配
|
||||
items.Regex = items.Regex.map(item => {
|
||||
let reg = undefined;
|
||||
try { reg = new RegExp(item.regex, item.type) } catch (e) { item.state = false; }
|
||||
return { regex: reg, ext: item.ext, blackList: item.blackList, state: item.state }
|
||||
});
|
||||
// 预编译屏蔽通配符
|
||||
items.blockUrl = items.blockUrl.map(item => {
|
||||
return { url: wildcardToRegex(item.url), state: item.state }
|
||||
});
|
||||
|
||||
// 兼容旧配置
|
||||
if (items.copyM3U8.includes('$url$')) {
|
||||
items.copyM3U8 = items.copyM3U8.replaceAll('$url$', '${url}').replaceAll('$referer$', '${referer}').replaceAll('$title$', '${title}');
|
||||
chrome.storage.sync.set({ copyM3U8: items.copyM3U8 });
|
||||
}
|
||||
if (items.copyMPD.includes('$url$')) {
|
||||
items.copyMPD = items.copyMPD.replaceAll('$url$', '${url}').replaceAll('$referer$', '${referer}').replaceAll('$title$', '${title}');
|
||||
chrome.storage.sync.set({ copyMPD: items.copyMPD });
|
||||
}
|
||||
if (items.copyOther.includes('$url$')) {
|
||||
items.copyOther = items.copyOther.replaceAll('$url$', '${url}').replaceAll('$referer$', '${referer}').replaceAll('$title$', '${title}');
|
||||
chrome.storage.sync.set({ copyOther: items.copyOther });
|
||||
}
|
||||
if (typeof items.m3u8dl == 'boolean') {
|
||||
items.m3u8dl = items.m3u8dl ? 1 : 0;
|
||||
chrome.storage.sync.set({ m3u8dl: items.m3u8dl });
|
||||
}
|
||||
|
||||
// 侧边栏
|
||||
chrome.sidePanel.setPanelBehavior({ openPanelOnActionClick: items.sidePanel });
|
||||
|
||||
G = { ...items, ...G };
|
||||
|
||||
// 初始化 G.blockUrlSet
|
||||
(typeof isLockUrl == 'function') && chrome.tabs.query({}, function (tabs) {
|
||||
for (const tab of tabs) {
|
||||
if (tab.url && isLockUrl(tab.url)) {
|
||||
G.blockUrlSet.add(tab.id);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
chrome.action.setIcon({ path: G.enable ? "/img/icon.png" : "/img/icon-disable.png" });
|
||||
G.initSyncComplete = true;
|
||||
});
|
||||
// 读取local配置数据 交给全局变量G
|
||||
(chrome.storage.session ?? chrome.storage.local).get(G.LocalVar, function (items) {
|
||||
items.featMobileTabId = new Set(items.featMobileTabId);
|
||||
items.featAutoDownTabId = new Set(items.featAutoDownTabId);
|
||||
G = { ...items, ...G };
|
||||
G.initLocalComplete = true;
|
||||
});
|
||||
}
|
||||
// 监听变化,新值给全局变量
|
||||
chrome.storage.onChanged.addListener(function (changes, namespace) {
|
||||
if (changes.MediaData) {
|
||||
if (changes.MediaData.newValue?.init) { cacheData = {}; }
|
||||
return;
|
||||
}
|
||||
for (let [key, { oldValue, newValue }] of Object.entries(changes)) {
|
||||
newValue ??= G.OptionLists[key];
|
||||
if (key == "Ext") {
|
||||
G.Ext = new Map(newValue.map(item => [item.ext, item]));
|
||||
continue;
|
||||
}
|
||||
if (key == "Type") {
|
||||
G.Type = new Map(newValue.map(item => [item.type, { size: item.size, state: item.state }]));
|
||||
continue;
|
||||
}
|
||||
if (key == "Regex") {
|
||||
G.Regex = newValue.map(item => {
|
||||
let reg = undefined;
|
||||
try { reg = new RegExp(item.regex, item.type) } catch (e) { item.state = false; }
|
||||
return { regex: reg, ext: item.ext, blackList: item.blackList, state: item.state }
|
||||
});
|
||||
continue;
|
||||
}
|
||||
if (key == "blockUrl") {
|
||||
G.blockUrl = newValue.map(item => {
|
||||
return { url: wildcardToRegex(item.url), state: item.state }
|
||||
});
|
||||
continue;
|
||||
}
|
||||
if (key == "featMobileTabId" || key == "featAutoDownTabId") {
|
||||
G[key] = new Set(newValue);
|
||||
continue;
|
||||
}
|
||||
if (key == "sidePanel" && !G.isFirefox) {
|
||||
chrome.sidePanel.setPanelBehavior({ openPanelOnActionClick: newValue });
|
||||
continue;
|
||||
}
|
||||
G[key] = newValue;
|
||||
}
|
||||
});
|
||||
|
||||
// 扩展升级,清空本地储存
|
||||
chrome.runtime.onInstalled.addListener(function (details) {
|
||||
if (details.reason == "update") {
|
||||
chrome.storage.local.clear(function () {
|
||||
if (chrome.storage.session) {
|
||||
chrome.storage.session.clear(InitOptions);
|
||||
} else {
|
||||
InitOptions();
|
||||
}
|
||||
});
|
||||
chrome.alarms.create("nowClear", { when: Date.now() + 3000 });
|
||||
}
|
||||
if (details.reason == "install") {
|
||||
chrome.tabs.create({ url: "install.html" });
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 将用户输入的URL(可能包含通配符)转换为正则表达式
|
||||
* @param {string} urlPattern - 用户输入的URL,可能包含通配符
|
||||
* @returns {RegExp} - 转换后的正则表达式
|
||||
*/
|
||||
function wildcardToRegex(urlPattern) {
|
||||
// 将通配符 * 转换为正则表达式的 .*
|
||||
// 将通配符 ? 转换为正则表达式的 .
|
||||
// 同时转义其他正则表达式特殊字符
|
||||
const regexPattern = urlPattern
|
||||
.replace(/[.+^${}()|[\]\\]/g, '\\$&') // 转义正则表达式特殊字符
|
||||
.replace(/\*/g, '.*') // 将 * 替换为 .*
|
||||
.replace(/\?/g, '.'); // 将 ? 替换为 .
|
||||
|
||||
// 创建正则表达式,确保匹配整个URL
|
||||
return new RegExp(`^${regexPattern}$`, 'i'); // 忽略大小写
|
||||
}
|
||||
Reference in New Issue
Block a user