(function () {
class CatCatcher {
constructor() {
console.log("catch.js Start");
// 初始化属性
this.enable = true; // 捕获开关
this.language = navigator.language; // 语言设置
this.isComplete = false; // 捕获完成标志
this.catchMedia = []; // 捕获的媒体数据
this.mediaSize = 0; // 捕获的媒体数据大小
this.setFileName = null; // 文件名
this.catCatch = null; // UI元素
// 移动面板相关属性
this.x = 0;
this.y = 0;
// 初始化语言
if (window.CatCatchI18n) {
if (!window.CatCatchI18n.languages.includes(this.language)) {
this.language = this.language.split("-")[0];
if (!window.CatCatchI18n.languages.includes(this.language)) {
this.language = "en";
}
}
}
// 初始化组件
// 删除iframe sandbox属性 避免 issues #576
this.setupIframeProcessing();
// 初始化 Trusted Types
this.initTrustedTypes();
// 创建和设置UI
this.createUI();
// 代理MediaSource方法
this.proxyMediaSourceMethods();
// 自动跳转到缓冲尾
if (localStorage.getItem("CatCatchCatch_autoToBuffered") == "checked") {
const autoToBufferedInterval = setInterval(() => {
const videos = document.querySelectorAll('video');
if (videos.length > 0 && Array.from(videos).some(video => !video.paused && video.readyState > 2)) {
const autoToBufferedElement = this.catCatch.querySelector("#autoToBuffered");
if (autoToBufferedElement) {
autoToBufferedElement.click();
clearInterval(autoToBufferedInterval);
}
}
}, 1000);
}
}
/**
* 设置iframe处理,删除sandbox属性
* 解决 issues #576
*/
setupIframeProcessing() {
document.addEventListener('DOMContentLoaded', () => {
const processIframe = (iframe) => {
if (iframe && iframe.hasAttribute && iframe.hasAttribute('sandbox')) {
const clonedIframe = iframe.cloneNode(true);
clonedIframe.removeAttribute('sandbox');
if (iframe.parentNode) {
iframe.parentNode.replaceChild(clonedIframe, iframe);
}
}
};
document.querySelectorAll('iframe').forEach(processIframe);
const observer = new MutationObserver((mutationsList) => {
for (const mutation of mutationsList) {
if (mutation.type === 'childList') {
mutation.addedNodes.forEach(node => {
if (node.nodeName === 'IFRAME') {
processIframe(node);
} else if (node.querySelectorAll) {
node.querySelectorAll('iframe').forEach(processIframe);
}
});
}
}
});
observer.observe(document.body || document.documentElement, { childList: true, subtree: true });
});
}
/**
* 初始化 Trusted Types
*/
initTrustedTypes() {
let createHTML = (string) => {
try {
const fakeDiv = document.createElement('div');
fakeDiv.innerHTML = string;
createHTML = (string) => string;
} catch (e) {
if (typeof trustedTypes !== 'undefined') {
const policy = trustedTypes.createPolicy('catCatchPolicy', { createHTML: (s) => s });
createHTML = (string) => policy.createHTML(string);
const _innerHTML = Object.getOwnPropertyDescriptor(Element.prototype, 'innerHTML');
Object.defineProperty(Element.prototype, 'innerHTML', {
set: function (value) {
_innerHTML.set.call(this, createHTML(value));
}
});
} else {
console.warn("trustedTypes不可用,跳过安全策略设置");
}
}
};
createHTML("
");
}
/**
* 创建UI元素
*/
createUI() {
const buttonStyle = 'style="border:solid 1px #000;margin:2px;padding:2px;background:#fff;border-radius:4px;border:solid 1px #c7c7c780;color:#000;"';
const checkboxStyle = 'style="-webkit-appearance: auto;"';
this.catCatch = document.createElement("div");
this.catCatch.setAttribute("id", "CatCatchCatch");
const style = `
display: flex;
flex-direction: column;
align-items: flex-start;`;
this.catCatch.innerHTML = `
`;
this.catCatch.style = `
position: fixed;
z-index: 999999;
top: 10%;
left: 90%;
background: rgb(255 255 255 / 85%);
border: solid 1px #c7c7c7;
border-radius: 4px;
color: rgb(26, 115, 232);
padding: 5px 5px 5px 5px;
font-size: 12px;
font-family: "Microsoft YaHei", "Helvetica", "Arial", sans-serif;
user-select: none;`;
// 创建 Shadow DOM
this.createShadowRoot();
// 初始化UI元素引用
this.tips = this.catCatch.querySelector("#tips");
this.fileName = this.catCatch.querySelector("#fileName");
this.selector = this.catCatch.querySelector("#selector");
this.regular = this.catCatch.querySelector("#regular");
if (!this.tips || !this.fileName || !this.selector || !this.regular) {
console.error("UI元素初始化失败,找不到必要的DOM元素");
}
// 初始化显示
this.tips.innerHTML = this.i18n("waiting", "等待视频播放");
this.selector.innerHTML = localStorage.getItem("CatCatchCatch_selector") ?? "Null";
this.regular.innerHTML = localStorage.getItem("CatCatchCatch_regular") ?? "Null";
// 绑定事件
this.bindEvents();
// 自动从头捕获设置
if (localStorage.getItem("CatCatchCatch_restart") == "checked") {
this.setupAutoRestart();
}
}
/**
* 创建 Shadow DOM
* 解决 issues #693 安全使用attachShadow 从iframe中获取原生方法
*/
createShadowRoot() {
try {
// 解决 issues #693 安全使用attachShadow 从iframe中获取原生方法
const createSecureShadowRoot = (element, mode = 'closed') => {
const getPristineAttachShadow = () => {
try {
const iframe = document.createElement('iframe');
const parentNode = document.body || document.documentElement;
parentNode.appendChild(iframe);
const pristineMethod = iframe.contentDocument.createElement('div').attachShadow;
iframe.remove();
if (pristineMethod) return pristineMethod;
} catch (e) {
console.log("获取原生attachShadow方法失败:", e);
}
return Element.prototype.attachShadow;
};
const executor = Element.prototype.attachShadow.toString().includes('[native code]')
? Element.prototype.attachShadow.bind(element)
: getPristineAttachShadow().bind(element);
try {
return executor({ mode });
} catch (e) {
console.error('Shadow DOM 创建失败:', e);
// 应急处理:降级方案
return document.createElement('div');
}
};
// 创建 Shadow DOM 放入CatCatch
const divShadow = document.createElement('div');
const shadowRoot = createSecureShadowRoot(divShadow);
shadowRoot.appendChild(this.catCatch);
// 页面插入Shadow DOM
const htmlElement = document.getElementsByTagName('html')[0];
if (htmlElement) {
htmlElement.appendChild(divShadow);
} else {
document.appendChild(divShadow);
}
} catch (error) {
console.error("创建Shadow DOM失败:", error);
// 降级方案:直接添加到body
try {
const body = document.body || document.documentElement;
body.appendChild(this.catCatch);
} catch (e) {
console.error("降级添加UI也失败:", e);
}
}
}
/**
* 绑定事件处理函数
*/
bindEvents() {
// 移动面板相关事件
this.catCatch.addEventListener('mousedown', this.handleDragStart.bind(this));
// 设置选项相关事件
const autoDown = this.catCatch.querySelector("#autoDown");
if (autoDown) autoDown.addEventListener('change', this.handleAutoDownChange.bind(this));
const ffmpeg = this.catCatch.querySelector("#ffmpeg");
if (ffmpeg) ffmpeg.addEventListener('change', this.handleFfmpegChange.bind(this));
const restartAlways = this.catCatch.querySelector("#restartAlways");
if (restartAlways) restartAlways.addEventListener('change', this.handleRestartAlwaysChange.bind(this));
// 按钮相关事件
const clean = this.catCatch.querySelector("#clean");
if (clean) clean.addEventListener('click', this.handleClean.bind(this));
const download = this.catCatch.querySelector("#download");
if (download) download.addEventListener('click', this.handleDownload.bind(this));
const hide = this.catCatch.querySelector("#hide");
if (hide) hide.addEventListener('click', this.handleHide.bind(this));
const img = this.catCatch.querySelector("img");
if (img) img.addEventListener('click', this.handleHide.bind(this));
const close = this.catCatch.querySelector("#close");
if (close) close.addEventListener('click', this.handleClose.bind(this));
const restart = this.catCatch.querySelector("#restart");
if (restart) restart.addEventListener('click', this.handleRestart.bind(this));
const setFileName = this.catCatch.querySelector("#setFileName");
if (setFileName) setFileName.addEventListener('click', this.handleSetFileName.bind(this));
const test = this.catCatch.querySelector("#test");
if (test) test.addEventListener('click', this.handleTest.bind(this));
const summary = this.catCatch.querySelector("#summary");
if (summary) summary.addEventListener('click', this.getFileName.bind(this));
const completeClearCache = this.catCatch.querySelector("#completeClearCache");
if (completeClearCache) completeClearCache.addEventListener('click', this.handleCompleteClearCache.bind(this));
// 自动跳转到缓冲节点
// this.autoToBufferedFlag = true;
const autoToBuffered = this.catCatch.querySelector("#autoToBuffered");
if (autoToBuffered) autoToBuffered.addEventListener('click', this.handleAutoToBuffered.bind(this));
// 文件名设置相关事件
const setSelector = this.catCatch.querySelector("#setSelector");
if (setSelector) setSelector.addEventListener('click', this.handleSetSelector.bind(this));
const setRegular = this.catCatch.querySelector("#setRegular");
if (setRegular) setRegular.addEventListener('click', this.handleSetRegular.bind(this));
// i18n 处理
this.applyI18n();
}
/**
* 应用国际化文本
*/
applyI18n() {
if (window.CatCatchI18n) {
this.catCatch.querySelectorAll('[data-i18n]').forEach((element) => {
if (element && element.dataset && element.dataset.i18n) {
element.innerHTML = window.CatCatchI18n[element.dataset.i18n][this.language] || element.innerHTML;
}
});
this.catCatch.querySelectorAll('[data-i18n-outer]').forEach((element) => {
if (element && element.dataset && element.dataset.i18nOuter) {
element.outerHTML = window.CatCatchI18n[element.dataset.i18nOuter][this.language] || element.outerHTML;
}
});
}
}
/**
* 翻译函数
* @param {String} key
* @param {String|null} original 原始文本
* @returns 翻译后的文本
*/
i18n(key, original = "") {
if (!window.CatCatchI18n || !key || !window.CatCatchI18n[key]) { return original; }
return window.CatCatchI18n[key][this.language] || original;
}
/**
* 处理面板拖动事件
* @param {MouseEvent} event
*/
handleDragStart(event) {
this.x = event.pageX - this.catCatch.offsetLeft;
this.y = event.pageY - this.catCatch.offsetTop;
const moveHandler = this.handleMove.bind(this);
document.addEventListener('mousemove', moveHandler);
document.addEventListener('mouseup', () => {
document.removeEventListener('mousemove', moveHandler);
}, { once: true });
}
/**
* 处理面板移动事件
* 通过鼠标事件更新面板位置
* @param {MouseEvent} event
*/
handleMove(event) {
if (!this.catCatch) return;
this.catCatch.style.left = (event.pageX - this.x) + 'px';
this.catCatch.style.top = (event.pageY - this.y) + 'px';
}
handleAutoDownChange(event) {
localStorage.setItem("CatCatchCatch_autoDown", event.target.checked ? "checked" : "");
}
handleFfmpegChange(event) {
localStorage.setItem("CatCatchCatch_ffmpeg", event.target.checked ? "checked" : "");
}
handleRestartAlwaysChange(event) {
localStorage.setItem("CatCatchCatch_restart", event.target.checked ? "checked" : "");
}
/**
* 处理清理缓存事件
* @param {MouseEvent} event
*/
handleClean(event) {
if (window.confirm(this.i18n("clearCacheConfirmation", "确认清除缓存?"))) {
this.clearCache();
const $clean = this.catCatch.querySelector("#clean");
if (!$clean) return;
$clean.innerHTML = this.i18n("cleanupCompleted", "清理完成!");
setTimeout(() => {
if ($clean) $clean.innerHTML = this.i18n("clearCache", "清理缓存");
}, 1000);
}
}
/**
* 处理下载事件
* @param {MouseEvent} event
*/
handleDownload(event) {
try {
if (this.isComplete || window.confirm(this.i18n("downloadConfirmation", "提前下载可能会造成数据混乱.确认?"))) {
this.catchDownload();
}
} catch (error) {
console.error("下载处理失败:", error);
alert(this.i18n("downloadError", "下载过程中出错,请查看控制台"));
}
}
handleHide(event) {
const catCatchElement = this.catCatch.querySelector('#catCatch');
if (catCatchElement.style.display === "none") {
catCatchElement.style.display = "flex";
this.catCatch.style.opacity = "";
} else {
catCatchElement.style.display = "none";
this.catCatch.style.opacity = "0.5";
}
}
handleClose(event) {
if (this.isComplete || window.confirm(this.i18n("closeConfirmation", "确认关闭?"))) {
this.clearCache();
this.enable = false;
this.catCatch.style.display = "none";
window.postMessage({ action: "catCatchToBackground", Message: "script", script: "catch.js", refresh: false });
}
}
/**
* 从头捕获
* @param {MouseEvent} event
*/
handleRestart(event) {
const checkHead = this.catCatch.querySelector("#checkHead");
if (checkHead) checkHead.checked = true;
this.clearCache();
document.querySelectorAll("video").forEach((element) => {
element.currentTime = 0;
element.play();
});
}
handleSetFileName(event) {
this.setFileName = window.prompt(this.i18n("fileName", "输入文件名, 不包含扩展名"), this.setFileName ?? "");
this.getFileName();
}
handleTest(event) {
console.log("捕获的媒体数据:", this.catchMedia);
}
handleCompleteClearCache(event) {
localStorage.setItem("CatCatchCatch_completeClearCache", event.target.checked ? "checked" : "");
}
/**
* 自动缓冲尾
* @param {MouseEvent} event
*/
handleAutoToBuffered(event) {
// if (!this.autoToBufferedFlag) return;
// this.autoToBufferedFlag = false;
const $autoToBuffered = this.catCatch.querySelector("#autoToBuffered");
if (!$autoToBuffered) return;
localStorage.setItem("CatCatchCatch_autoToBuffered", event.target.checked ? "checked" : "");
const videos = document.querySelectorAll("video");
for (let video of videos) {
video.addEventListener("progress", (event) => {
try {
if (video.buffered && video.buffered.length > 0) {
const bufferedEnd = video.buffered.end(0);
if ($autoToBuffered.checked && bufferedEnd < video.duration) {
video.currentTime = bufferedEnd - 5;
}
}
} catch (error) {
console.error("处理缓冲进度失败:", error);
}
});
video.addEventListener("ended", () => {
$autoToBuffered.checked = false;
});
}
}
/**
* CSS选择器 提取文件名
* @param {MouseEvent} event
*/
handleSetSelector(event) {
const result = window.prompt("Selector", localStorage.getItem("CatCatchCatch_selector") ?? "");
if (result == null) return;
if (result == "") {
this.clearFileName("selector");
return;
}
let title;
try {
title = document.querySelector(result);
} catch (e) {
this.clearFileName("selector", this.i18n("fileNameError", "选择器语法错误!"));
return;
}
if (title && title.innerHTML) {
this.selector.innerHTML = this.stringModify(result);
localStorage.setItem("CatCatchCatch_selector", result);
this.getFileName();
} else {
this.clearFileName("selector", this.i18n("fileNameError", "表达式错误, 无法获取或内容为空!"));
}
}
/**
* 正则 提取文件名
* @param {MouseEvent} event
*/
handleSetRegular(event) {
let result = window.prompt(this.i18n("regular", "文件名获取正则"), localStorage.getItem("CatCatchCatch_regular") ?? "");
if (result == null) return;
if (result == "") {
this.clearFileName("regular");
return;
}
try {
new RegExp(result);
this.regular.innerHTML = this.stringModify(result);
localStorage.setItem("CatCatchCatch_regular", result);
this.getFileName();
} catch (e) {
this.clearFileName("regular", this.i18n("fileNameError", "正则表达式错误"));
console.log(e);
}
}
/**
* 核心函数 代理MediaSource方法
*/
proxyMediaSourceMethods() {
// 代理 addSourceBuffer 方法
window.MediaSource.prototype.addSourceBuffer = new Proxy(window.MediaSource.prototype.addSourceBuffer, {
apply: (target, thisArg, argumentsList) => {
try {
const result = Reflect.apply(target, thisArg, argumentsList);
// 标题获取
setTimeout(() => { this.getFileName(); }, 2000);
this.tips.innerHTML = this.i18n("capturingData", "捕获数据中...");
this.catchMedia.push({ mimeType: argumentsList[0], bufferList: [] });
const index = this.catchMedia.length - 1;
// 代理 appendBuffer 方法
result.appendBuffer = new Proxy(result.appendBuffer, {
apply: (target, thisArg, argumentsList) => {
Reflect.apply(target, thisArg, argumentsList);
if (this.enable && argumentsList[0]) {
this.mediaSize += argumentsList[0].byteLength || 0;
if (this.tips) {
this.tips.innerHTML = this.i18n("capturingData", "捕获数据中...") + ": " + this.byteToSize(this.mediaSize);
}
this.catchMedia[index].bufferList.push(argumentsList[0]);
}
}
});
return result;
} catch (error) {
console.error("addSourceBuffer 代理错误:", error);
return Reflect.apply(target, thisArg, argumentsList);
}
}
});
// 代理 endOfStream 方法
window.MediaSource.prototype.endOfStream = new Proxy(window.MediaSource.prototype.endOfStream, {
apply: (target, thisArg, argumentsList) => {
try {
Reflect.apply(target, thisArg, argumentsList);
if (this.enable) {
this.isComplete = true;
if (this.tips) {
this.tips.innerHTML = this.i18n("captureCompleted", "捕获完成");
}
if (localStorage.getItem("CatCatchCatch_autoDown") == "checked") {
setTimeout(() => this.catchDownload(), 500);
}
}
} catch (error) {
console.error("endOfStream 代理错误:", error);
return Reflect.apply(target, thisArg, argumentsList);
}
}
});
}
/**
* 自动从头捕获
* 监控DOM变化,自动重置视频播放位置
*/
setupAutoRestart() {
document.addEventListener('DOMContentLoaded', () => {
document.querySelectorAll('video').forEach((video) => this.resetVideoPlayback(video));
// 监控 DOM
const observer = new MutationObserver(mutations => {
mutations.forEach(mutation => {
mutation.addedNodes.forEach(node => {
try {
if (node.tagName === 'VIDEO') {
this.resetVideoPlayback(node);
} else if (node.querySelectorAll) {
node.querySelectorAll('video').forEach(video => this.resetVideoPlayback(video));
}
} catch (error) {
console.error("处理新添加的视频节点失败:", error);
}
});
});
});
observer.observe(document.body || document.documentElement, { childList: true, subtree: true });
});
}
/**
* 重置视频播放位置
* @param {Object} video
*/
resetVideoPlayback(video) {
if (!video) return;
const timer = setInterval(() => {
if (!video.paused) {
video.currentTime = 0;
const checkHead = this.catCatch.querySelector("#checkHead");
if (checkHead) checkHead.checked = true;
this.clearCache();
clearInterval(timer);
}
}, 500);
// 5秒后如果还没有检测到播放,就清除定时器
setTimeout(() => clearInterval(timer), 5000);
video.addEventListener('play', () => {
if (!video.isResetCatCatch) {
video.isResetCatCatch = true;
video.currentTime = 0;
const checkHead = this.catCatch.querySelector("#checkHead");
if (checkHead) checkHead.checked = true;
this.clearCache();
}
}, { once: true });
}
/**
* 下载捕获的数据
*/
catchDownload() {
if (this.catchMedia.length == 0) {
alert(this.i18n("noData", "没抓到有效数据"));
return;
}
let downloadWithFFmpeg = this.catchMedia.length >= 2 && localStorage.getItem("CatCatchCatch_ffmpeg") == "checked";
/**
* 检查文件
* 检查是否有头部文件 没有头部文件则提示 不使用ffmpeg合并
* 检查是否有多个头部文件 根据用户选项 是否清理多于头部数据
*/
const checkHead = this.catCatch.querySelector("#checkHead");
// 仅确认一次是否清除多余头部数据
let userConfirmedHeadChoice = false;
for (let key in this.catchMedia) {
if (!this.catchMedia[key]?.bufferList || this.catchMedia[key].bufferList.length <= 1) continue;
let lastHeaderIndex = -1;
// 遍历所有 buffer 寻找最后一个头部
for (let i = 0; i < this.catchMedia[key].bufferList.length; i++) {
const data = new Uint8Array(this.catchMedia[key].bufferList[i]);
// 检查MP4格式的头部 (ftyp)
if (data.length > 8 &&
data[4] === 0x66 && // 'f'
data[5] === 0x74 && // 't'
data[6] === 0x79 && // 'y'
data[7] === 0x70) // 'p'
{
lastHeaderIndex = i; // 持续更新直到找到最后一个头部
}
// 检查WebM格式的头部 (1A 45 DF A3)
else if (data.length > 4 &&
data[0] === 0x1A &&
data[1] === 0x45 &&
data[2] === 0xDF &&
data[3] === 0xA3) {
lastHeaderIndex = i; // 持续更新直到找到最后一个WebM头部
}
}
if (lastHeaderIndex == -1) {
alert(this.i18n("noHead", "没有检测到视频头部数据, 请使用本地工具处理"));
downloadWithFFmpeg = false; // 没有头部数据则不使用ffmpeg合并
}
if (lastHeaderIndex > 0) {
// 只有第一次遇到多余头部且用户尚未选择时才提示
if (!userConfirmedHeadChoice && !checkHead.checked) {
checkHead.checked = window.confirm(this.i18n("headData", "检测到多余头部数据, 是否清除?"));
userConfirmedHeadChoice = true; // 标记已经询问过用户
}
if (checkHead.checked) {
this.catchMedia[key].bufferList.splice(0, lastHeaderIndex); // 移除最后一个头部之前的所有元素
}
}
}
downloadWithFFmpeg ? this.downloadWithFFmpeg() : this.downloadDirect();
if (this.isComplete) {
if (localStorage.getItem("CatCatchCatch_completeClearCache") == "checked") { this.clearCache(); }
if (this.tips) {
this.tips.innerHTML = this.i18n("downloadCompleted", "下载完毕...");
}
}
}
/**
* 使用FFmpeg合并下载捕获的数据
*/
downloadWithFFmpeg() {
const media = [];
for (let item of this.catchMedia) {
if (!item || !item.bufferList || item.bufferList.length === 0) continue;
const mime = (item.mimeType && item.mimeType.split(';')[0]) || 'video/mp4';
const fileBlob = new Blob(item.bufferList, { type: mime });
const type = mime.split('/')[0] || 'video';
media.push({
data: (typeof chrome == "object") ? URL.createObjectURL(fileBlob) : fileBlob,
type: type
});
}
if (media.length === 0) {
alert(this.i18n("noData", "没有有效数据可下载"));
return;
}
const title = this.fileName ? this.fileName.innerHTML.trim() : document.title;
window.postMessage({
action: "catCatchFFmpeg",
use: "catchMerge",
files: media,
title: title,
output: title,
quantity: media.length
});
}
/**
* 直接下载捕获的数据
*/
downloadDirect() {
const a = document.createElement('a');
let downloadCount = 0;
for (let item of this.catchMedia) {
if (!item || !item.bufferList || item.bufferList.length === 0) continue;
const mime = (item.mimeType && item.mimeType.split(';')[0]) || 'video/mp4';
const type = mime.split('/')[0] == "video" ? "mp4" : "mp3";
const fileBlob = new Blob(item.bufferList, { type: mime });
a.href = URL.createObjectURL(fileBlob);
a.download = `${this.fileName ? this.fileName.innerHTML.trim() : document.title}.${type}`;
a.click();
// 释放URL对象以避免内存泄漏
setTimeout(() => URL.revokeObjectURL(a.href), 100);
downloadCount++;
}
a.remove();
if (downloadCount === 0) {
alert(this.i18n("noData", "没有有效数据可下载"));
}
}
clearFileName(obj = "selector", warning = "") {
localStorage.removeItem("CatCatchCatch_" + obj);
const element = obj == "selector" ? this.selector : this.regular;
if (element) element.innerHTML = this.i18n("notSet", "未设置");
this.getFileName();
if (warning) alert(warning);
}
/**
* 清理缓存
*/
clearCache() {
this.mediaSize = 0;
if (this.isComplete) {
this.catchMedia = [];
this.isComplete = false;
return;
}
for (let key in this.catchMedia) {
const media = this.catchMedia[key];
if (media && media.bufferList && media.bufferList.length > 0) {
// 保留第一个buffer块,清除其余的
const firstBuffer = media.bufferList[0];
media.bufferList = [firstBuffer];
this.mediaSize += firstBuffer ? (firstBuffer.byteLength || 0) : 0;
}
}
}
byteToSize(byte) {
if (!byte || byte < 1024) return "0KB";
if (byte < 1024 * 1024) {
return (byte / 1024).toFixed(1) + "KB";
} else if (byte < 1024 * 1024 * 1024) {
return (byte / 1024 / 1024).toFixed(1) + "MB";
} else {
return (byte / 1024 / 1024 / 1024).toFixed(1) + "GB";
}
}
/**
* 获取文件名
*/
getFileName() {
try {
if (!this.fileName) return;
if (this.setFileName) {
this.fileName.innerHTML = this.stringModify(this.setFileName);
return;
}
let name = "";
const selectorKey = localStorage.getItem("CatCatchCatch_selector");
if (selectorKey) {
const title = document.querySelector(selectorKey);
if (title && title.innerHTML) {
name = title.innerHTML;
}
}
const regularKey = localStorage.getItem("CatCatchCatch_regular");
if (regularKey) {
const str = name == "" ? document.documentElement.outerHTML : name;
const reg = new RegExp(regularKey, "g");
let result = str.match(reg);
if (result) {
result = result.filter((item) => item !== "");
name = result.join("_");
}
}
this.fileName.innerHTML = name ? this.stringModify(name) : this.stringModify(document.title);
} catch (error) {
console.error("获取文件名失败:", error);
if (this.fileName) this.fileName.innerHTML = this.stringModify(document.title);
}
}
stringModify(str) {
if (!str) return "untitled";
return str.replace(/['\\:\*\?"<\/>\|~]/g, function (m) {
return {
"'": ''',
'\\': '\',
'/': '/',
':': ':',
'*': '*',
'?': '?',
'"': '"',
'<': '<',
'>': '>',
'|': '|',
'~': '_'
}[m];
});
}
}
// 创建并启动CatCatcher实例
const catCatcher = new CatCatcher();
})();