Last active
April 30, 2025 10:05
-
-
Save zhanhongtao/198e58075d4b3415c2f0f5e394204c6a to your computer and use it in GitHub Desktop.
auto-run-script-for-task
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==UserScript== | |
// @name Task | |
// @namespace https://x181.cn/ | |
// @version 0.41.2 | |
// @author TT | |
// @description Hello world! | |
// @icon https://img.artproglobal.com/c/21736720685ad57d4fb8362b70f62f8d?width=512&height=512&hTag=d7a495871ba01409369e6c2231ad49f0 | |
// @homepage https://x181.cn | |
// @downloadURL https://gist.github.com/zhanhongtao/198e58075d4b3415c2f0f5e394204c6a/raw/task.user.js | |
// @updateURL https://gist.github.com/zhanhongtao/198e58075d4b3415c2f0f5e394204c6a/raw/task.user.js | |
// @match *://*/* | |
// @connect localhost | |
// @connect x181.cn | |
// @connect api.telegram.org | |
// @connect * | |
// @grant GM.xmlHttpRequest | |
// @grant GM_getValue | |
// @grant GM_info | |
// @grant GM_openInTab | |
// @grant GM_registerMenuCommand | |
// @grant GM_setValue | |
// @grant GM_xmlhttpRequest | |
// @grant unsafeWindow | |
// @run-at document-idle | |
// ==/UserScript== | |
(function () { | |
'use strict'; | |
const Log = (flag = false) => { | |
return function log2(...rest) { | |
if (flag === false) | |
return; | |
let list2 = rest; | |
if (typeof window !== "undefined" && typeof top !== "undefined") { | |
list2 = [ | |
`%cisTop: ${top === window}. url = ${location.href}.`, | |
"color: gray; font-size: 1.2em;", | |
...rest | |
]; | |
} | |
console.log(...list2); | |
}; | |
}; | |
const log$2 = Log(false); | |
const defaultMeta = { pause: false }; | |
function match(rule, value) { | |
if (Array.isArray(rule)) | |
return rule.some((rule2) => match(rule2, value)); | |
if (typeof rule === "boolean") | |
return rule; | |
if (typeof rule === "string") | |
return value.includes(rule); | |
if (rule instanceof RegExp) | |
return rule.test(value); | |
if (typeof rule === "function") | |
return rule(value); | |
if ("exec" in rule) | |
return rule.test(value); | |
let result = true; | |
if (result && rule.basic) | |
result = match(rule.basic, value); | |
if (result && rule.or) | |
result = rule.or.some((rule2) => match(rule2, value)); | |
if (result && rule.not) | |
result = match(rule.not, value) == false; | |
return result; | |
} | |
function filter(list2, value) { | |
return list2.filter((task2) => match(task2.match, value)); | |
} | |
async function run(task2) { | |
if (task2.top && top !== window) | |
return; | |
log$2("start run task = ", task2); | |
const scripts2 = Array.isArray(task2.script) ? task2.script : [task2.script]; | |
scripts2.forEach((script) => { | |
try { | |
script(task2); | |
} catch (e) { | |
console.log(`run script failed`, e); | |
} | |
}); | |
} | |
function core(scripts2, url) { | |
filter(scripts2, url).forEach((task2) => { | |
log$2("match.task", task2); | |
run({ ...task2, meta: task2.meta || { ...defaultMeta } }); | |
}); | |
} | |
function waitForSelector(selector, options) { | |
let node = null; | |
return waitForFunction(() => { | |
const selectors2 = Array.isArray(selector) ? selector : [selector]; | |
const callback = (selector2) => { | |
node = document.querySelector(selector2); | |
return node != null; | |
}; | |
return options ? options.type === "some" ? selectors2.some(callback) : selectors2.every(callback) : selectors2.some(callback); | |
}, { ...options, polling: 100 }).then(() => node); | |
} | |
const defaultWaitForFunctionOptions = { | |
timeout: 3e3, | |
polling: "raf" | |
}; | |
function waitForFunction(fn, options) { | |
let t = Date.now(); | |
const opt = { ...defaultWaitForFunctionOptions, ...options }; | |
return new Promise((resolve, reject) => { | |
let f = () => { | |
const has = fn(); | |
if (has) | |
resolve(true); | |
else if (typeof opt.polling === "number") { | |
if (opt.timeout === 0 || Date.now() - t < opt.timeout) { | |
setTimeout(f, opt.polling); | |
} else { | |
reject("timeout"); | |
} | |
} else if (opt.polling === "raf") { | |
requestAnimationFrame(f); | |
} else if (opt.polling === "mutation") { | |
let timer = 0; | |
const observer = new MutationObserver(function() { | |
const has2 = fn(); | |
if (has2 == false) | |
return; | |
if (timer) | |
clearTimeout(timer); | |
observer.disconnect(); | |
resolve(true); | |
}); | |
let node = document.querySelector("html"); | |
if (node == null) { | |
observer.disconnect(); | |
reject("no root node"); | |
} else { | |
observer.observe(node, { childList: true }); | |
timer = setTimeout(() => { | |
observer.disconnect(); | |
reject("timeout"); | |
}, opt.timeout); | |
} | |
} else { | |
reject("not support"); | |
} | |
}; | |
f(); | |
}); | |
} | |
function useBlockSelector(selector) { | |
return () => { | |
waitForSelector(selector).then(() => { | |
(Array.isArray(selector) ? selector : [selector]).forEach((selector2) => { | |
const node = document.querySelector(selector2); | |
if (node == null) | |
return; | |
const next = node.nextElementSibling; | |
if (next) | |
next.remove(); | |
}); | |
}); | |
}; | |
} | |
function useRedirect() { | |
return function() { | |
if (top !== window) | |
return; | |
let search = new URLSearchParams(location.search); | |
for (let value of search.values()) { | |
if (URL.canParse(value)) { | |
location.href = value; | |
} | |
} | |
}; | |
} | |
function useRebuildUrl(fn) { | |
return () => { | |
const next = fn(location.href); | |
if (next) | |
location.href = next; | |
}; | |
} | |
const task$4 = [ | |
{ | |
match: new URLPattern({ hostname: `outlook.live.com` }), | |
script: useBlockSelector(`[data-max-width="2400"]`) | |
}, | |
{ | |
match: [ | |
"https://cdn.jellow.site/", | |
"https://cdnv2.ruguoapp.com/" | |
], | |
script: useRebuildUrl((url) => url.includes("?") ? url.replace(/\?.+$/, "") : "") | |
}, | |
{ | |
match: { | |
or: [ | |
(url) => { | |
const u = new URL(url); | |
const pathname = u.pathname.toLowerCase(); | |
const keywords = ["goto", "link", "redirect", "jump"]; | |
return keywords.some((key) => pathname.includes(key)); | |
}, | |
new URLPattern({ "hostname": "(link|go).*" }) | |
] | |
}, | |
// https://github.com/einaregilsson/Redirector | |
script: useRedirect() | |
}, | |
{ | |
match: "http://uz.yurixu.com/", | |
script: () => { | |
const useHost = "www.newsmth.net"; | |
const hosts = [ | |
"www.newsmth.net", | |
"www.mysmth.net" | |
]; | |
document.addEventListener("click", (e) => { | |
const target = e.target; | |
if (target == null) | |
return; | |
const link = target.closest("a"); | |
if (link == null) | |
return; | |
if (hosts.some((host) => link.host === host)) { | |
link.href = link.href.replace(/^https?:\/\/(.+?)\//, () => `https://${useHost}/`); | |
} | |
}, true); | |
} | |
} | |
]; | |
const bingTask = [ | |
{ | |
match: new URLPattern({ hostname: "cn.bing.com" }), | |
script: task$3, | |
top: true | |
} | |
]; | |
const selectors = [ | |
"#bgDiv", | |
".hp_top_cover", | |
".img_cont" | |
]; | |
function task$3() { | |
let nodes = document.querySelectorAll(selectors.join(",")); | |
if (nodes.length === 0) | |
return; | |
Array.from(nodes).some((node) => { | |
let style = window.getComputedStyle(node); | |
let backgroundImage = style.backgroundImage; | |
let res = ""; | |
backgroundImage.replace(/url\(('|")?(.*?)\1\)/, (_, __, src) => { | |
res = src; | |
return ""; | |
}); | |
return res ? (console.log("%c wallpaper:", `color: red`, res), true) : false; | |
}); | |
} | |
const doubanTask = [ | |
{ | |
match: "book.douban.com", | |
script: bookTask, | |
top: true | |
}, | |
// 配置豆瓣读书... | |
{ | |
// https://freembook.com/? | |
match: "https://zebra.9farm.com/", | |
script: bookBeforeSearchTask, | |
top: true | |
}, | |
{ | |
match: "movie.douban.com", | |
script: movieTask, | |
top: true | |
}, | |
{ | |
match: "fm.douban.com", | |
script: musicTask, | |
top: true | |
} | |
]; | |
function createLinkElement(template, keyword) { | |
let a = document.createElement("a"); | |
a.href = template.replaceAll("%s", encodeURIComponent(keyword)); | |
a.target = "_blank"; | |
a.setAttribute("target", "_blank"); | |
a.setAttribute("rel", "noopener"); | |
a.setAttribute("referrerpolicy", "no-referrer"); | |
return a; | |
} | |
function createElement(source, title, target, keyword) { | |
let span = document.createElement("span"); | |
span.className = "pl"; | |
span.append(document.createTextNode(source)); | |
const a = createLinkElement(target, keyword ? keyword : title); | |
a.append(document.createTextNode(title)); | |
let fragment = document.createDocumentFragment(); | |
fragment.appendChild(span); | |
fragment.appendChild(a); | |
let br = document.createElement("br"); | |
fragment.appendChild(br); | |
return fragment; | |
} | |
function createElements(list2) { | |
let fragment = document.createDocumentFragment(); | |
for (let i = 0; i < list2.length; ++i) { | |
let item = list2[i]; | |
let element = createElement( | |
item.description, | |
item.title, | |
item.template, | |
item.keyword | |
); | |
fragment.appendChild(element); | |
} | |
return fragment; | |
} | |
function movieTask() { | |
let node = document.querySelector("#content > h1 span:first-child"); | |
if (!node) | |
return; | |
let title = node.innerText.trim(); | |
let infoNode = document.querySelector("#info"); | |
if (!infoNode) | |
return; | |
let list2 = [ | |
{ | |
description: "g(ddz):", | |
title, | |
keyword: title, | |
template: "https://www.google.com/search?newwindow=1&safe=strict&source=hp&ei=--05W5X6CoqD8wX_zZmwCg&btnG=Google+%E6%90%9C%E7%B4%A2&q=%s&oq=beego&gs_l=psy-ab.3..0j0i203k1l2j0j0i203k1j0i10i203k1j0i203k1l4.6311.7597.0.7829.9.6.0.0.0.0.132.522.2j3.6.0....0...1c.1.64.psy-ab..3.6.609.6..35i39k1j0i131k1j0i67k1j0i10k1.87.jNu-vssuoKQ".replace("%s", `%s ${encodeURIComponent("site:dandanzan.club")}`) | |
} | |
]; | |
let fragment = createElements(list2); | |
infoNode.appendChild(fragment); | |
} | |
function findCodeText(node) { | |
const pl = node.querySelectorAll("span.pl"); | |
const list2 = Array.from(pl); | |
for (let i = 0; i < list2.length; ++i) { | |
const item = list2[i]; | |
const text = item.innerHTML; | |
if (text.includes("ISBN")) { | |
let code = item.nextSibling; | |
if (code) { | |
const value = code.nodeValue; | |
if (value) { | |
return value.trim(); | |
} | |
} | |
} | |
} | |
return ""; | |
} | |
function bookTask() { | |
let node = document.querySelector("#wrapper > h1"); | |
if (!node) | |
return; | |
let infoNode = document.querySelector("#info"); | |
if (!infoNode) | |
return; | |
let title = node.innerText.trim(); | |
let code = findCodeText(infoNode); | |
let list2 = [ | |
// { description: 'z-lib:', title, template: 'https://zh.singlelogin.re/s/%s', keyword: title }, | |
{ description: "Annas-Archive:", title, template: "https://annas-archive.org/search?q=%s", keyword: code } | |
]; | |
let fragment = createElements(list2); | |
infoNode.appendChild(fragment); | |
} | |
function bookBeforeSearchTask() { | |
const qs = new URLSearchParams(location.search); | |
const q = qs.get("q"); | |
if (q == null) | |
return; | |
const key = "session"; | |
const value = JSON.stringify({ | |
name: q, | |
author: "", | |
lang: "", | |
publisher: "", | |
ext: "", | |
isbn: "", | |
id: "" | |
}); | |
const old = localStorage.getItem(key); | |
localStorage.setItem(key, value); | |
if (old != value) { | |
location.reload(); | |
} | |
} | |
function musicTask() { | |
const _send = XMLHttpRequest.prototype.send; | |
const _open = XMLHttpRequest.prototype.open; | |
XMLHttpRequest.prototype.open = function(_, url) { | |
_open.apply(this, arguments); | |
this.__url = url; | |
}; | |
XMLHttpRequest.prototype.send = function() { | |
let t = this.onreadystatechange; | |
this.onreadystatechange = function(e) { | |
if (t) | |
t.call(this, e); | |
const readyState = this.readyState; | |
if (readyState === 4) { | |
const type = this.responseType || "text"; | |
if (type === "text" && this.response) { | |
let data; | |
try { | |
data = JSON.parse(this.response); | |
} catch (e2) { | |
console.log("douban.fm.parse.response.failed"); | |
} | |
if (data && data.song) { | |
const songs = data.song; | |
songs.forEach((song) => { | |
console.log(`%c${song.title}`, "color: red", song.url); | |
}); | |
} | |
} | |
} | |
return this; | |
}; | |
_send.apply(this, arguments); | |
return this; | |
}; | |
} | |
function sleep(n) { | |
return new Promise(function(resolve) { | |
setTimeout(resolve, n * 1e3); | |
}); | |
} | |
const log$1 = Log(false); | |
const dubokuTask = { | |
match: [ | |
"k.jhooslea.com", | |
"k.rbaa.top", | |
/\w+\.duboku\.(?:io|fun|com|tv|top)/, | |
/duboku\.(?:io|fun|com|tv|top)/ | |
], | |
script: task$2 | |
}; | |
let entered = false; | |
let pageButtonInit = false; | |
function insertMovie() { | |
let selector = ".myui-header__menu"; | |
let links = document.querySelectorAll(`${selector} > li > a`); | |
let is = Array.from(links).some((link) => link.getAttribute("href") == "/vodtype/1.html"); | |
if (is) | |
return; | |
let box = document.querySelector(selector); | |
if (box == null) | |
return; | |
let children = box.children; | |
let newNode = document.createElement("li"); | |
newNode.classList.add("hidden-sm", "hidden-xs"); | |
if (/(?:vodtype|vodshow)\/1(-.*)?\.html/i.test(location.href)) | |
newNode.classList.add("active"); | |
newNode.innerHTML = `<a href="/vodtype/1.html">电影</a>`; | |
if (children.length > 1) | |
box.insertBefore(newNode, children[1]); | |
} | |
function task$2() { | |
if (top == null) | |
return; | |
insertMovie(); | |
let path = top.location.href; | |
if (/voddetail/i.test(path)) { | |
if (pageButtonInit) | |
return; | |
let doc = top.document; | |
let latest = doc.querySelector("#playlist1 li:last-child a"); | |
let posNode = doc.querySelector(".myui-content__operate"); | |
if (posNode && latest) { | |
let playNode = posNode.firstElementChild; | |
if (playNode == null) | |
return; | |
let newNode = playNode.cloneNode(true); | |
newNode.href = latest.href; | |
newNode.innerHTML = `<i class="fa fa-play"></i> 播放最新一集`; | |
newNode.className = `btn btn-primary`; | |
posNode.appendChild(newNode); | |
pageButtonInit = true; | |
} | |
return; | |
} | |
let sidebar = document.querySelector(".myui-sidebar"); | |
if (sidebar) { | |
sidebar.style.display = "none"; | |
let previous = sidebar.previousElementSibling; | |
if (previous) { | |
previous.style.cssText = "width: 100% !important"; | |
} | |
} | |
let videos = document.querySelectorAll("video"); | |
if (videos.length === 0) { | |
log$1("No video tag. nextTick"); | |
if (top !== window) { | |
sleep(1).then(task$2); | |
} | |
return; | |
} | |
let video = Array.from(videos).find((video2) => video2.id === "playerCnt_html5_api"); | |
if (video == null) { | |
log$1("No video tag matched. nextTick"); | |
if (top !== window) | |
sleep(1).then(task$2); | |
return; | |
} | |
if (entered) | |
return; | |
entered = true; | |
let promise = video.play(); | |
if (promise) { | |
promise.catch(() => { | |
let playButton = document.querySelector(".vjs-big-play-button"); | |
if (playButton) { | |
playButton.click(); | |
} | |
}); | |
} | |
video.addEventListener("ended", function() { | |
log$1("enter video ended handler."); | |
log$1("去 top 页面查找下一集"); | |
if (top == null) | |
return; | |
let links = top.document.links; | |
for (let i = 0; i < links.length; ++i) { | |
let link = links[i]; | |
let text = link.innerText; | |
if (text.indexOf("下集") > -1) { | |
log$1(`next url: ${link.href}`); | |
if (link.href !== top.location.href) { | |
top.location.href = link.href; | |
break; | |
} | |
} | |
} | |
}); | |
} | |
function stopImmediatePropagation() { | |
["click"].forEach((name) => { | |
document.addEventListener(name, function(e) { | |
e.stopImmediatePropagation(); | |
}, true); | |
}); | |
} | |
function createHiddenNodesBySelectorsTask(selectors2) { | |
return () => { | |
selectors2.forEach((selector) => { | |
let nodes = document.querySelectorAll(selector); | |
nodes.forEach((node) => { | |
if (node) | |
node.style.display = "none"; | |
}); | |
}); | |
}; | |
} | |
function removeRemovedAdBlockerModal() { | |
let elements = document.querySelectorAll("cloudflare-app"); | |
Array.from(elements).forEach((element) => { | |
let style = window.getComputedStyle(element, null); | |
let zIndex = style.getPropertyValue("z-index"); | |
if (+zIndex >= 400) { | |
element.style.display = "none"; | |
} | |
}); | |
} | |
function remove(node) { | |
let previous; | |
while (node && (previous = node.previousSibling)) { | |
previous.remove(); | |
} | |
} | |
function useObserver(selector, options = { childList: true }) { | |
let observer; | |
return (handler2) => { | |
return (task2) => { | |
if (observer) | |
observer.disconnect(); | |
observer = new MutationObserver(() => { | |
const scripts2 = Array.isArray(handler2) ? handler2 : [handler2]; | |
scripts2.forEach((script) => script(task2)); | |
}); | |
let node = document.querySelector(selector); | |
if (node) | |
observer.observe(node, options); | |
}; | |
}; | |
} | |
let fetchInit = false; | |
function fetchLocationPerMinute() { | |
fetch(location.href).finally(() => { | |
sleep(60).then(fetchLocationPerMinute); | |
}); | |
} | |
function addEventListener(task2) { | |
let box = document.querySelector(".board-list"); | |
if (box) { | |
let links = box.querySelectorAll("a"); | |
Array.from(links).forEach((link) => { | |
link.setAttribute("target", "_blank"); | |
}); | |
const f = () => handler(); | |
window.removeEventListener("hashchange", f); | |
window.addEventListener("hashchange", f); | |
} else { | |
sleep(0.2).then(() => addEventListener()); | |
} | |
} | |
function handler(task2) { | |
addEventListener(); | |
let body = document.body; | |
if (body) | |
body.style.cssText = "max-width: 1280px; margin: 0 auto;"; | |
createHiddenNodesBySelectorsTask(["#ban_ner", "#banner_slider"])(); | |
if (fetchInit == false) { | |
fetchInit = true; | |
fetchLocationPerMinute(); | |
} | |
let style = document.createElement("style"); | |
style.innerText = ".board-list tr { line-height: 2 } .board-list td { padding: 4px 0 } .title_9 a { font-size: 1.2em } .title_10 { width: 120px !important }"; | |
let head = document.querySelector("head"); | |
if (head) | |
head.appendChild(style); | |
} | |
const mysmthTask = { | |
match: [ | |
"https://www.newsmth.net/", | |
"https://www.mysmth.net/" | |
], | |
script: useObserver("#body")(handler) | |
}; | |
function nextTick(fn) { | |
return Promise.resolve().then(fn); | |
} | |
const log = Log(false); | |
const weChatTask = [ | |
{ | |
match: /https:\/\/mp\.weixin\.qq\.com\/s.*/, | |
script: task$1 | |
}, | |
{ | |
match: /weixin110\.qq.com/, | |
script: linkTask | |
} | |
]; | |
function linkTask() { | |
let node = document.querySelector("p"); | |
if (node == null) | |
return; | |
let text = node.innerText.trim(); | |
if (/https?:\/\//i.test(text)) { | |
location.href = text; | |
return; | |
} | |
} | |
function task$1() { | |
stopImmediatePropagation(); | |
weChatTextToLink(); | |
weChatQRCodeToLink(); | |
logAudioLink(); | |
} | |
function logAudioLink() { | |
let template = `https://res.wx.qq.com/voice/getvoice?mediaid=`; | |
sleep(1).then(() => { | |
if (typeof window == "undefined") | |
return; | |
if (typeof window.voiceList !== "undefined") { | |
let list2 = window.voiceList.voice_in_appmsg; | |
if (Array.isArray(list2)) { | |
list2.forEach((item, index) => { | |
let voice_id = item.voice_id; | |
let url = `${template}${voice_id}`; | |
log(`音频(${index + 1})地址: ${url}`); | |
}); | |
} | |
} | |
}); | |
} | |
let weChatQRCodeToLinkDone = false; | |
function weChatQRCodeToLink() { | |
if (weChatQRCodeToLinkDone) | |
return; | |
let nodes = document.querySelectorAll("span[data-src] img"); | |
Array.from(nodes).forEach((node) => { | |
let a = document.createElement("a"); | |
let p = node.parentNode; | |
let href = p.dataset.src; | |
if (href == null) | |
return; | |
a.href = href; | |
a.target = "_blank"; | |
a.style.display = "block"; | |
a.appendChild(document.createTextNode(href)); | |
p.appendChild(a); | |
}); | |
if (nodes.length) { | |
weChatQRCodeToLinkDone = true; | |
} | |
} | |
function weChatTextToLink() { | |
if (document.body == null) { | |
return sleep(0.2).then(() => { | |
nextTick(weChatTextToLink); | |
}); | |
} | |
log("enter wechat textToLink func"); | |
let walker = document.createTreeWalker( | |
document.body, | |
NodeFilter.SHOW_TEXT, | |
{ | |
acceptNode: (node) => { | |
let p = node.parentNode; | |
while (p && ["A", "SCRIPT", "STYLE"].indexOf(p.nodeName) === -1) { | |
p = p.parentNode; | |
} | |
if (p) | |
return NodeFilter.FILTER_SKIP; | |
let text = node.nodeValue; | |
if (text && /https?:\/\//i.test(text)) { | |
return NodeFilter.FILTER_ACCEPT; | |
} | |
return NodeFilter.FILTER_REJECT; | |
} | |
} | |
); | |
let links = []; | |
while (walker.nextNode()) { | |
let node = walker.currentNode; | |
let text = node.nodeValue.toLowerCase(); | |
let offset = text.indexOf("http"); | |
let linkTextNode = node.splitText(offset); | |
if (linkTextNode == null) | |
continue; | |
if (linkTextNode.nodeValue == null) | |
continue; | |
let spaceOffset = linkTextNode.nodeValue.search(/\s|[))]/); | |
let linkNode = linkTextNode; | |
if (spaceOffset > -1) { | |
let t = linkTextNode.splitText(spaceOffset).previousSibling; | |
if (t) | |
linkNode = t; | |
} | |
let a = document.createElement("a"); | |
if (linkNode.nodeValue) | |
a.href = linkNode.nodeValue; | |
a.setAttribute("target", "_blank"); | |
linkNode.parentNode.insertBefore(a, linkNode); | |
a.appendChild(linkNode); | |
links.push(a); | |
} | |
if (links.length) { | |
suptolink(links); | |
} | |
return; | |
} | |
function suptolink(links) { | |
let sups = document.querySelectorAll("sup"); | |
let lastFindedNode; | |
Array.from(sups).reverse().forEach((sup) => { | |
let text = sup.innerText.trim(); | |
if (text === "") | |
return; | |
let link = findLinkByText(text, lastFindedNode || links[links.length - 1]); | |
if (link == null) | |
return; | |
lastFindedNode = link; | |
let a = document.createElement("a"); | |
a.href = link.href; | |
a.setAttribute("target", "_blank"); | |
sup.parentNode.insertBefore(a, sup); | |
a.appendChild(sup); | |
}); | |
} | |
function findLinkByText(text, link) { | |
let find; | |
let node = link.previousSibling || link.parentNode; | |
while (node && (node.nodeType == 1 || node.nodeType === 3)) { | |
let t = node.innerText || node.nodeValue || ""; | |
if (node.nodeType === 1) { | |
if (node.nodeName === "A") | |
link = node; | |
else { | |
let a = node.querySelector("a"); | |
if (a) | |
link = a; | |
} | |
} | |
if (t.indexOf(text) > -1) { | |
find = link; | |
break; | |
} | |
node = node.previousSibling || node.parentNode; | |
} | |
return find; | |
} | |
function mutationObserver(meta, callback, selector, options = { childList: true }) { | |
if (meta == null) | |
return; | |
if (meta.observer) { | |
meta.observer.disconnect(); | |
} | |
meta.observer = new MutationObserver(function() { | |
callback(); | |
}); | |
let node = document.querySelector(selector); | |
if (node) { | |
meta.observer.observe(node, options); | |
} | |
} | |
const zhihuTask = [ | |
{ | |
match: /^https?:\/\/.*?\.zhihu\.com/, | |
script: [ | |
replaceExternalLinkTask, | |
replaceToRawImage | |
], | |
exclude: "link.zhihu.com" | |
}, | |
{ | |
// https://github.com/pillarjs/path-to-regexp | |
// https://developer.mozilla.org/en-US/docs/Web/API/URL_Pattern_API | |
// https://developer.mozilla.org/en-US/docs/Web/API/URLPattern | |
// new URLPattern(input, baseURL, options) | |
// options: { ignoreCase: false[default] }? | |
// baseURL: string? | |
// input: string, { protocol, username, password, hostname, port, pathname, search, hash, baseURL } | |
// Note: Omitted parts of the object will be treated as wildcards(*) | |
// example: | |
// new URLPattern({ hostname: ':sub.zhihu.com' }) | |
// new URLPattern("/books/:id(\\d+)", "https://example.com") | |
// 语法 | |
// a) 普通字符,严格匹配 | |
// b) * 通配符匹配任意字符,贪婪性 | |
// c) 命名 group - example: /book/:id 另外可以给 group 添加 (Regexp) 规则限制 | |
// d) 可选/多次匹配 - {any}? - example: {www.}?zhihu.com 匹配 www.zhihu.com 和 zhihu.com; 期望匹配任意子域名,可以使用 {*.}?zhihu.com | |
// e) 正则表达式,使用括弧包裹 - example: /book/:id(\\d+). 注意:最外层括弧是 URLPattern 语法,不是正则的 group; 另外还有一些使用限制 | |
// https://developer.mozilla.org/en-US/docs/Web/API/URL_Pattern_API#regex_matchers_limitations | |
// 正则表达式中的 ^ 字符只用在 protocol 字段中,$ 字符只用在 hash 字段中; | |
// example: new URLPattern({ protocol: "(^https?)" }) | |
// 不支持 (?=) 和 (?!) 语法 - 前后环视 | |
// 正则区间表达式 [] 中的括弧/特殊符号,同样需要做转义 | |
// group 可以匿名,example: { pathname: '/book/(\\d+)' } | |
// group 修饰符有三种:?, +, *; | |
// 限制在 pathname 中: | |
// Automatic group prefixing in pathnames | |
// example: | |
// { pathname: '/book/:id?' } 会自动把 group name 前的 / 归到组定义中,这种行为便于使用修饰符 | |
// 匹配 /book/123, /book 但不匹配 /book/ | |
// 如果不期望把 / 归到组定义中,可以使用 {} 包裹。example: | |
// { pathname: '/book/{:id}?' } | |
// 匹配 /book/123, /book/ 但不匹配 /book | |
// pathname 会先 normalization 处理 | |
// example: | |
// /foo/.bar/ -> /foot/bar/ | |
match: new URLPattern("https://{*.}?zhihu.com"), | |
script: () => { | |
function updateTitle() { | |
const title = document.title; | |
document.title = title.replace(/^\(.*?\)/, ""); | |
requestAnimationFrame(updateTitle); | |
} | |
updateTitle(); | |
} | |
}, | |
{ | |
match: new URLPattern("/people/*", "https://www.zhihu.com/"), | |
script: removeReadQuestionsTask | |
}, | |
{ | |
match: new URLPattern("/question/*", "https://www.zhihu.com/"), | |
script: removeFixedHeader | |
} | |
]; | |
function removeFixedHeader() { | |
function fixed() { | |
const t = document.querySelector(".Sticky.is-fixed"); | |
if (t) | |
t.style.position = "relative"; | |
requestAnimationFrame(fixed); | |
} | |
fixed(); | |
} | |
function removeReadQuestionsTask() { | |
function handler2() { | |
let selector = "#Profile-activities .List-item"; | |
let list2 = document.querySelectorAll(selector); | |
let max = 40; | |
if (list2.length > max) { | |
let index = list2.length - max; | |
let node = list2[index]; | |
remove(node); | |
} | |
} | |
window.addEventListener("scroll", handler2); | |
} | |
function replaceExternalLinkTask({ meta }) { | |
function handler2() { | |
let selector = 'a[href^="https://link.zhihu.com/"]'; | |
let links = document.querySelectorAll(selector); | |
Array.from(links).forEach((node) => { | |
let href = node.href; | |
let url = new URL(href); | |
let search = new URLSearchParams(url.search); | |
let target = search.get("target"); | |
if (target) { | |
node.href = decodeURIComponent(target); | |
} | |
}); | |
} | |
mutationObserver(meta, handler2, "body"); | |
handler2(); | |
} | |
function replaceToRawImage() { | |
document.addEventListener("click", (e) => { | |
const target = e.target; | |
if (target == null) | |
return; | |
const node = target; | |
if (node.nodeType != 1) | |
return; | |
if (node.nodeName !== "IMG") | |
return; | |
const token = node.dataset.originalToken; | |
if (token == null) | |
return; | |
const src = node.getAttribute("src"); | |
if (src == null) | |
return; | |
const original = src.replace(/v2-.*?(?=\.)/, token); | |
node.setAttribute("src", original); | |
}); | |
} | |
const weiboTask = [ | |
{ | |
match: /(?:t|weibo)\.cn/i, | |
script: task | |
}, | |
{ | |
match: /wx\d+\.sinaimg\.cn/i, | |
script: navigateToRawImageTask | |
} | |
]; | |
function task() { | |
let node = document.querySelector(".link,.desc,.navigate_link"); | |
if (node == null) | |
return; | |
if (node.tagName === "A") { | |
location.href = node.getAttribute("href"); | |
return; | |
} | |
let text = node.innerText.trim(); | |
if (/https?:\/\//i.test(text)) { | |
location.href = text; | |
return; | |
} | |
} | |
function navigateToRawImageTask() { | |
let url = location.href; | |
if (url.includes("/oslarge/")) | |
return; | |
let i = new URL(url); | |
i.pathname = `/oslarge` + i.pathname.replace(/\/[^\/]*/, ""); | |
let newUrl = i.toString(); | |
location.href = newUrl; | |
} | |
function toRedirectUrl(raw, source, dest) { | |
const p = new URLPattern(source); | |
const result = p.exec(raw); | |
if (!result) | |
return ""; | |
const pathname = result.pathname; | |
const groups = pathname.groups; | |
let url = dest; | |
Object.keys(groups).forEach((key) => { | |
url = url.replace(`:${key}`, groups[key]); | |
}); | |
return url; | |
} | |
function initialize(url, list2) { | |
for (let i = 0; i < list2.length; ++i) { | |
const config = list2[i]; | |
if (config.skip) | |
continue; | |
let destination = url; | |
if (config.pattern) { | |
destination = toRedirectUrl(url, config.source, config.destination || config.source); | |
} else if (url == config.source && config.destination) { | |
destination = config.destination; | |
} | |
if (url !== destination) { | |
return destination; | |
} | |
} | |
return url; | |
} | |
const list = [ | |
{ source: "https://store.pailixiang.com/album/:album/:name.jpg" }, | |
{ source: "https://www.baidu.com/", destination: "https://google.com/", pattern: false, skip: true } | |
]; | |
const last = initialize(location.href, list); | |
const redirectTask = { | |
match: (url) => last !== url, | |
script: () => { | |
location.href = last; | |
} | |
}; | |
var _GM = /* @__PURE__ */ (() => typeof GM != "undefined" ? GM : void 0)(); | |
var _GM_getValue = /* @__PURE__ */ (() => typeof GM_getValue != "undefined" ? GM_getValue : void 0)(); | |
var _GM_info = /* @__PURE__ */ (() => typeof GM_info != "undefined" ? GM_info : void 0)(); | |
var _GM_openInTab = /* @__PURE__ */ (() => typeof GM_openInTab != "undefined" ? GM_openInTab : void 0)(); | |
var _GM_registerMenuCommand = /* @__PURE__ */ (() => typeof GM_registerMenuCommand != "undefined" ? GM_registerMenuCommand : void 0)(); | |
var _GM_setValue = /* @__PURE__ */ (() => typeof GM_setValue != "undefined" ? GM_setValue : void 0)(); | |
var _GM_xmlhttpRequest = /* @__PURE__ */ (() => typeof GM_xmlhttpRequest != "undefined" ? GM_xmlhttpRequest : void 0)(); | |
var _unsafeWindow = /* @__PURE__ */ (() => typeof unsafeWindow != "undefined" ? unsafeWindow : void 0)(); | |
function promisifyRequest(request) { | |
return new Promise((resolve, reject) => { | |
request.oncomplete = request.onsuccess = () => resolve(request.result); | |
request.onabort = request.onerror = () => reject(request.error); | |
}); | |
} | |
function createStore(dbName, storeName) { | |
const request = indexedDB.open(dbName); | |
request.onupgradeneeded = () => request.result.createObjectStore(storeName); | |
const dbp = promisifyRequest(request); | |
return (txMode, callback) => dbp.then((db) => callback(db.transaction(storeName, txMode).objectStore(storeName))); | |
} | |
let defaultGetStoreFunc; | |
function defaultGetStore() { | |
if (!defaultGetStoreFunc) { | |
defaultGetStoreFunc = createStore("keyval-store", "keyval"); | |
} | |
return defaultGetStoreFunc; | |
} | |
function get(key, customStore = defaultGetStore()) { | |
return customStore("readonly", (store) => promisifyRequest(store.get(key))); | |
} | |
function set(key, value, customStore = defaultGetStore()) { | |
return customStore("readwrite", (store) => { | |
store.put(value, key); | |
return promisifyRequest(store.transaction); | |
}); | |
} | |
function del(key, customStore = defaultGetStore()) { | |
return customStore("readwrite", (store) => { | |
store.delete(key); | |
return promisifyRequest(store.transaction); | |
}); | |
} | |
function requestNotificationPermission() { | |
const p = Notification.permission; | |
if (p === "granted") | |
return Promise.resolve(true); | |
if (p === "denied") | |
return Promise.resolve(false); | |
return Notification.requestPermission().then((p2) => { | |
return Promise.resolve(p2 == "granted"); | |
}); | |
} | |
async function notify(message, options) { | |
const permission = await requestNotificationPermission(); | |
if (!permission) | |
return; | |
if (!message) | |
return; | |
new Notification(message, options); | |
} | |
function post(path, data) { | |
let token = _GM_getValue("telegram-token"); | |
if (!token) { | |
token = prompt(`Please input telegram token:`); | |
if (token) | |
_GM_setValue("telegram-token", token); | |
else | |
return Promise.reject("token is empty"); | |
} | |
data.append("chat_id", "@connect2026"); | |
const url = `https://api.telegram.org/bot${token}`; | |
return _GM.xmlHttpRequest({ | |
method: "POST", | |
url: url + path, | |
data | |
}); | |
} | |
function sendMessage(text, parse_mode = "") { | |
const data = new FormData(); | |
data.append("text", text); | |
data.append("parse_mode", parse_mode); | |
return post("/sendMessage", data); | |
} | |
function sendPhoto(photo, caption = "") { | |
const data = new FormData(); | |
data.append("photo", photo); | |
data.append("caption", caption); | |
return post("/sendPhoto", data); | |
} | |
function sendMediaGroup(group) { | |
const data = new FormData(); | |
data.append("media", JSON.stringify(group)); | |
return post("/sendMediaGroup", data); | |
} | |
const idbKey = "directory"; | |
const id = "photo-viewer"; | |
const userSaveButtonClass = "user-save-btn"; | |
function ping(image) { | |
const domain = "https://x181.cn"; | |
const url = `${domain}/api/us/save?url=${encodeURIComponent(image)}`; | |
return _GM_xmlhttpRequest({ | |
method: "GET", | |
url, | |
headers: { | |
"Content-Type": "application/json" | |
} | |
}); | |
} | |
function createTwitterSaveImageOption() { | |
function imageUrlMatcher(node) { | |
const urlRegexp = /\/media\//; | |
return urlRegexp.test(node.src); | |
} | |
function siblingsMatcher(node, debug = false) { | |
const p = node.previousElementSibling; | |
if (p == null) | |
return false; | |
const style = p.style.backgroundImage; | |
const url = style.replace(/url\("(.*?)"\)/, "$1"); | |
const src = node.src; | |
const match2 = url == src; | |
if (match2 && debug) | |
console.log("url: ", url); | |
return match2; | |
} | |
function toImageUrl(rawImageUrl, debug = false) { | |
const src = rawImageUrl; | |
debug && console.log("url.raw", src); | |
const tmp = src.replace(/&name=.*$/, ""); | |
debug && console.log("url.name.remove", tmp); | |
const url = tmp + "&name=4096x4096"; | |
debug && console.log("url.name.fix", url); | |
return url; | |
} | |
function getImageUrls(node, debug = false) { | |
const images = node.querySelectorAll("img"); | |
const matchUrlList = Array.from(images).filter((node2) => imageUrlMatcher(node2)); | |
const checkedUrlList = matchUrlList.filter((node2) => siblingsMatcher(node2, debug)); | |
return checkedUrlList.map((node2) => toImageUrl(node2.src, debug)); | |
} | |
function getSingleImageUrl(node, debug = false) { | |
const x = window.innerWidth / 2; | |
const y = window.innerHeight / 2; | |
const elements = document.elementsFromPoint(x, y); | |
for (let i = 0; i < elements.length; ++i) { | |
const element = elements[i]; | |
if (element.nodeType !== 1) | |
continue; | |
if (element.nodeName != "IMG") | |
continue; | |
const pos = node.compareDocumentPosition(element); | |
if (!(pos & Node.DOCUMENT_POSITION_CONTAINED_BY)) | |
continue; | |
const el = element; | |
const m1 = imageUrlMatcher(el); | |
if (!m1) | |
continue; | |
const m2 = siblingsMatcher(el, debug); | |
if (!m2) | |
continue; | |
return toImageUrl(el.src, debug); | |
} | |
return null; | |
} | |
const tweetDialogSelector = '[role="dialog"]'; | |
const tweetSelector = 'article[data-testid="tweet"]'; | |
function injectSaveButton(group) { | |
const is = group.querySelector("[data-save]"); | |
if (is) | |
return; | |
if (group == null) | |
return; | |
group.append(createSaveButtonNode("save")); | |
group.append(createSaveButtonNode("telegram")); | |
} | |
let cancelInjectAction; | |
function doInjectAction(target) { | |
if (cancelInjectAction) | |
cancelInjectAction(); | |
const dialog = target.querySelector(tweetDialogSelector); | |
if (dialog) { | |
const s = () => { | |
const group = dialog.querySelectorAll(`[role="group"] [role="group"]`); | |
if (group.length) { | |
const last2 = group[group.length - 1]; | |
injectSaveButton(last2); | |
cancelInjectAction(); | |
} | |
}; | |
cancelInjectAction = action(s); | |
} | |
const list2 = target.querySelectorAll(tweetSelector); | |
Array.from(list2).forEach((node) => { | |
const urls = getImageUrls(node); | |
if (urls.length === 0) | |
return; | |
const group = node.querySelector('[role="group"]'); | |
injectSaveButton(group); | |
}); | |
} | |
function tryInjectSaveButton(node) { | |
const cancel = action(() => { | |
doInjectAction(node); | |
}); | |
const observer = new MutationObserver(() => { | |
doInjectAction(node); | |
cancel(); | |
}); | |
observer.observe(node, { childList: true, subtree: true }); | |
} | |
return { | |
inject() { | |
const selectors2 = ["#react-root"]; | |
selectors2.forEach((selector) => { | |
waitForSelector(selector).then(() => { | |
const node = document.querySelector(selector); | |
if (node == null) | |
return; | |
tryInjectSaveButton(node); | |
}); | |
}); | |
}, | |
query(element) { | |
const selectors2 = ['[role="dialog"]', tweetSelector]; | |
for (let i = 0; i < selectors2.length; ++i) { | |
const node = element.closest(selectors2[i]); | |
if (node == null) | |
continue; | |
let urls = []; | |
const debug = true; | |
const is = node.closest(tweetSelector); | |
if (is) | |
urls = getImageUrls(node, debug); | |
else { | |
const single = getSingleImageUrl(node, debug); | |
if (single) | |
urls = [single]; | |
} | |
if (urls.length === 0) | |
continue; | |
return urls.map((url) => { | |
return { | |
url, | |
filename: url.replace(/.*?\/media\/(.*?)\?.*/, "$1") | |
}; | |
}); | |
} | |
return []; | |
} | |
}; | |
} | |
function createXHSSaveImageOption() { | |
function doInjectAction() { | |
const selector = ".bottom-container"; | |
waitForSelector(selector).then(() => { | |
const node = document.querySelector(selector); | |
if (node == null) | |
return; | |
const is = node.querySelector("[data-save]"); | |
if (is) | |
return; | |
node.append( | |
createSaveButtonNode() | |
); | |
}); | |
} | |
return { | |
query() { | |
const selector = ".swiper-wrapper"; | |
const node = document.querySelector(selector); | |
if (node == null) | |
return []; | |
const images = node.querySelectorAll("img"); | |
if (images.length == 0) | |
return []; | |
return Array.from(images).map((img) => { | |
const url = img.src; | |
return { | |
url, | |
filename: url.replace(/^.*\//, "") | |
}; | |
}); | |
}, | |
inject() { | |
const observer = new MutationObserver(() => { | |
doInjectAction(); | |
}); | |
observer.observe(document.body, { childList: true, subtree: true }); | |
} | |
}; | |
} | |
const twitterTask = [ | |
{ | |
match: /(?:x|twitter)\.com/, | |
script: createSaveImageScript( | |
createTwitterSaveImageOption() | |
) | |
}, | |
{ | |
match: new URLPattern("/explore/:id?", "https://www.xiaohongshu.com"), | |
script: createSaveImageScript( | |
createXHSSaveImageOption() | |
) | |
} | |
]; | |
function createSaveImageScript(option) { | |
return () => saveImageScript(option); | |
} | |
function trySendToTelegram(images) { | |
const mediaList = images.map((item) => { | |
return { | |
type: "photo", | |
media: item.url, | |
caption: item.url, | |
// https://core.telegram.org/bots/api#formatting-options | |
// parse_mode: '', // Markdown, MarkdownV2, HTML | |
show_caption_above_media: false | |
}; | |
}); | |
return sendMediaGroup(mediaList); | |
} | |
async function saveImageScript(option) { | |
document.addEventListener("click", (e) => { | |
const target = e.target; | |
if (target == null) | |
return; | |
const can = target.closest(`.${userSaveButtonClass}`); | |
if (!can) | |
return; | |
const images = option.query(target); | |
if (images.length === 0) | |
return; | |
const type = can.dataset.save; | |
let task2 = void 0; | |
if (type == "save") | |
task2 = trySaveImagesToLocale(images); | |
if (type == "telegram") | |
task2 = trySendToTelegram(images); | |
if (task2) { | |
can.style.opacity = "0.4"; | |
task2.finally(() => { | |
can.style.opacity = ""; | |
}); | |
} | |
e.preventDefault(); | |
e.stopImmediatePropagation(); | |
}, true); | |
option.inject(); | |
} | |
function createSaveButtonNode(type = "save", cssText = "") { | |
const div = document.createElement("div"); | |
div.dataset.save = type; | |
div.classList.add(userSaveButtonClass); | |
div.style.cssText = `cursor: pointer; display: flex;` + (cssText ? cssText : `align-self: center;`); | |
const telegram = `<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools --><svg xmlns="http://www.w3.org/2000/svg" aria-label="Telegram" role="img" viewBox="0 0 512 512" width="24px" height="24px"><rect width="512" height="512" rx="15%" fill="#37aee2"/><path fill="#c8daea" d="M199 404c-11 0-10-4-13-14l-32-105 245-144"/><path fill="#a9c9dd" d="M199 404c7 0 11-4 16-8l45-43-56-34"/><path fill="#f6fbfe" d="M204 319l135 99c14 9 26 4 30-14l55-258c5-22-9-32-24-25L79 245c-21 8-21 21-4 26l83 26 190-121c9-5 17-3 11 4"/></svg>`; | |
const save = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 451.296 451.296" xml:space="preserve" width="24px" height="24px"><circle style="fill:#45b39c" cx="225.648" cy="225.638" r="225.638"/><path style="opacity:.1;enable-background:new" d="m450.855 239.322-99.451-99.451c-.076-.08-.13-.176-.209-.255l-39.537-39.491a12.776 12.776 0 0 0-9.026-3.76H109.178c-7.052 0-12.834 5.735-12.834 12.834v232.898c0 3.672 1.566 6.958 4.035 9.295l98.236 98.236c8.87 1.06 17.878 1.668 27.034 1.668 120.028 0 218.126-93.724 225.206-211.974z"/><path style="fill:#64798a" d="m351.176 139.612-39.501-39.501a12.823 12.823 0 0 0-9.066-3.755H109.186c-7.081 0-12.821 5.74-12.821 12.821V342.1c0 7.082 5.74 12.822 12.821 12.822H342.11c7.081 0 12.821-5.74 12.821-12.822V148.678c0-3.401-1.351-6.662-3.755-9.066z"/><path style="fill:#fff" d="M310.065 212.47H141.231c-6.129 0-11.098 4.969-11.098 11.098v88.637c0 6.129 4.969 11.098 11.098 11.098h168.833c6.129 0 11.098-4.969 11.098-11.098v-88.637c0-6.129-4.968-11.098-11.097-11.098z"/><path style="fill:#ebf0f3" d="M149.565 233.626h152.177v9.4H149.565zM149.565 263.168h152.177v9.4H149.565zM149.565 292.762h152.177v9.4H149.565zM156.714 96.355v58.059c0 5.443 4.413 9.856 9.856 9.856h118.156c5.443 0 9.856-4.413 9.856-9.856V96.355H156.714z"/><path style="fill:#3a556a" d="M259.491 107.622h15.698v44.022h-15.698z"/></svg>`; | |
div.innerHTML = type === "save" ? save : telegram; | |
return div; | |
} | |
async function onSelectFolder() { | |
const dirHandle = await window.showDirectoryPicker({ id, mode: "readwrite" }); | |
await set(idbKey, dirHandle); | |
return dirHandle; | |
} | |
async function writeImage(item, dirHandler) { | |
const fileHandle = await get(item.url); | |
if (fileHandle) { | |
try { | |
const file = await fileHandle.getFile(); | |
if (file) | |
return console.log(`url=${item.url} exists`); | |
} catch (e) { | |
if (e.code == DOMException.NOT_FOUND_ERR) { | |
console.log(`url=${item.url} file has removed, pls download regain`); | |
} | |
} | |
} | |
let filetype = ""; | |
const image = await fetch(item.url).then((res) => { | |
const headers = res.headers; | |
const contentType = headers.get("Content-Type"); | |
if (contentType) | |
filetype = contentType.replace(/image\//, ""); | |
return res; | |
}).then((res) => res.blob()); | |
const filename = item.filename + "." + filetype; | |
for await (let name of dirHandler.keys()) | |
if (name == filename) | |
return; | |
const fileHandler = await dirHandler.getFileHandle(filename, { create: true }); | |
const w = await fileHandler.createWritable(); | |
await w.write(image); | |
await w.close(); | |
await set(item.url, fileHandler); | |
} | |
async function onWriteImage(item, dirHandler) { | |
if (!item.url) | |
return; | |
ping(item.url); | |
await writeImage(item, dirHandler); | |
} | |
function onSaveImagesToLocal(list2, folder) { | |
return new Promise((resolve) => { | |
function save(list22, index = 0) { | |
if (list22.length === index) { | |
resolve(true); | |
return; | |
} | |
const p = onWriteImage(list22[index], folder); | |
p.finally(() => { | |
save(list22, index + 1); | |
}); | |
} | |
save(list2); | |
}); | |
} | |
async function trySaveImagesToLocale(images) { | |
let dirHandle = await get(idbKey); | |
if (!dirHandle) | |
dirHandle = await onSelectFolder(); | |
const date = /* @__PURE__ */ new Date(); | |
const dirname = `${date.getFullYear()}${String(date.getMonth() + 1).padStart(2, "0")}${String(date.getDate()).padStart(2, "0")}`; | |
let permission = true; | |
let folder = null; | |
try { | |
folder = await dirHandle.getDirectoryHandle(dirname, { create: true }); | |
} catch (e) { | |
permission = false; | |
} | |
if (permission === false) { | |
await del(idbKey); | |
return trySaveImagesToLocale(images); | |
} | |
if (folder == null) { | |
console.log("no folder selected."); | |
return; | |
} | |
try { | |
await onSaveImagesToLocal(images, folder); | |
notify("Saved!"); | |
} catch (e) { | |
console.log("save directory handle failed"); | |
} | |
} | |
function action(fn) { | |
let timer = 0; | |
function run2() { | |
if (typeof fn === "function") { | |
try { | |
fn(); | |
} catch (e) { | |
} | |
} | |
timer = setTimeout(() => { | |
run2(); | |
}, 60); | |
} | |
run2(); | |
return function cancel() { | |
if (timer) { | |
clearTimeout(timer); | |
timer = 0; | |
} | |
}; | |
} | |
function* treeWalker(root = document.body, filter2 = () => NodeFilter.FILTER_ACCEPT) { | |
const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT, filter2); | |
do { | |
yield walker.currentNode; | |
const shadowRoot = walker.currentNode.shadowRoot; | |
for (let child = shadowRoot ? shadowRoot.firstElementChild : walker.currentNode.firstElementChild; child; child = child.nextElementSibling) { | |
yield* treeWalker(child, filter2); | |
} | |
} while (walker.nextNode()); | |
} | |
function useInterval(n, handler2) { | |
return (task2) => { | |
const inner = () => { | |
const scripts2 = Array.isArray(handler2) ? handler2 : [handler2]; | |
scripts2.forEach((script) => script(task2)); | |
setTimeout(() => { | |
var _a; | |
if (((_a = task2.meta) == null ? void 0 : _a.pause) == false) { | |
inner(); | |
} | |
}, n * 1e3); | |
}; | |
inner(); | |
}; | |
} | |
const otherTask = [ | |
{ | |
match: "https://www.cguardian.com/auctions/live-bidding", | |
meta: { pause: false }, | |
script: useInterval(0.1, (task2) => { | |
for (let node of treeWalker()) { | |
if (node.nodeName == "VIDEO") { | |
node.setAttribute("controls", ""); | |
task2.meta.pause = true; | |
break; | |
} | |
} | |
}) | |
}, | |
{ | |
match: "https://helloacm.com/", | |
script: removeRemovedAdBlockerModal | |
}, | |
{ | |
match: "https://www.flickr.com/photos/", | |
script: createHiddenNodesBySelectorsTask([ | |
".facade-of-protection-zoom" | |
]) | |
}, | |
{ | |
match: true, | |
script: () => { | |
const tags = [ | |
"html", | |
"body", | |
".grayTheme" | |
]; | |
tags.forEach((tag) => { | |
const nodeList = document.querySelectorAll(tag); | |
nodeList.forEach((node) => { | |
node.style.filter = "initial"; | |
}); | |
}); | |
} | |
}, | |
{ | |
match: new URLPattern({ protocol: "https", hostname: "paulgraham.com" }), | |
script: function() { | |
const selectors2 = ["table", "td"]; | |
selectors2.forEach((selector) => { | |
const elements = document.querySelectorAll(selector); | |
Array.from(elements).forEach((element) => { | |
if (element.hasAttribute("width")) { | |
element.removeAttribute("width"); | |
} | |
}); | |
}); | |
} | |
}, | |
{ | |
match: true, | |
script: () => { | |
let contextmenuTarget = { | |
type: "", | |
payload: null | |
}; | |
function clear() { | |
contextmenuTarget = { | |
type: "", | |
payload: null | |
}; | |
} | |
function setValue(type, payload) { | |
contextmenuTarget = { type, payload }; | |
} | |
window.addEventListener("contextmenu", (e) => { | |
const target = e.target; | |
if (target == null) | |
return; | |
const node = target; | |
let type = "url"; | |
let payload = `${document.title} | |
${location.href}`; | |
if (node.nodeName === "IMG") { | |
let src = node.getAttribute("src"); | |
if (src) { | |
const pattern = new URLPattern({ hostname: "{*.}?zhihu.com" }); | |
if (pattern.test(location.href)) { | |
const token = node.getAttribute("data-original-token"); | |
if (token) | |
src = src.replace(/v2-.*?(?=\.)/, token); | |
} | |
type = "image"; | |
payload = src; | |
} | |
} else { | |
const selection = document.getSelection(); | |
if (selection) { | |
const st = selection.type; | |
if (st === "Range") { | |
const raw = selection.toString(); | |
const text = raw.trim(); | |
if (text) { | |
type = "selection"; | |
payload = text + ` | |
Ref: [${document.title.trim() || location.href}](${location.href})`; | |
} | |
} | |
} | |
} | |
setValue(type, payload); | |
}); | |
window.addEventListener("scroll", clear); | |
window.addEventListener("pointerdown", () => { | |
if (contextmenuTarget.type !== "") | |
clear(); | |
}); | |
_GM_registerMenuCommand("Telegram Channel[039]", () => { | |
switch (contextmenuTarget.type) { | |
case "image": | |
sendPhoto(contextmenuTarget.payload); | |
break; | |
case "selection": | |
sendMessage(contextmenuTarget.payload, "MarkdownV2"); | |
break; | |
case "url": | |
sendMessage(contextmenuTarget.payload); | |
break; | |
default: | |
sendMessage(`${document.title} | |
${location.href}`); | |
break; | |
} | |
clear(); | |
}); | |
} | |
}, | |
{ | |
match: true, | |
script: () => { | |
_GM_registerMenuCommand("分享到 Telegram", function() { | |
const url = `https://t.me/share/url?url=${encodeURIComponent(top.location.href)}&text=${encodeURIComponent(top.document.title)}`; | |
_GM_openInTab(url, { active: true }); | |
}); | |
_GM_registerMenuCommand("更新脚本", function() { | |
const url = _GM_info.scriptUpdateURL; | |
if (url) | |
_GM_openInTab(url, { active: true }); | |
}); | |
} | |
} | |
]; | |
_unsafeWindow.telegram = { | |
sendPhoto, | |
sendMediaGroup, | |
sendMessage | |
}; | |
function toResponse(res) { | |
const headersText = (res.responseHeaders || "").trim(); | |
const headers = headersText.split("\r\n").map((line) => line.split(":")).reduce((headers2, item) => { | |
const [key, value] = item; | |
if (key) | |
headers2.append(key, value); | |
return headers2; | |
}, new Headers()); | |
return new Response(res.response, { | |
status: res.status, | |
statusText: res.statusText, | |
headers | |
}); | |
} | |
function toRequest(req) { | |
const method = (req.method || "GET").toUpperCase(); | |
const headers = {}; | |
const rawHeaders = req.headers ? req.headers : {}; | |
if (Array.isArray(rawHeaders)) { | |
rawHeaders.forEach(([key, value]) => { | |
headers[key] = value; | |
}); | |
} else if (rawHeaders instanceof Headers) { | |
for (let key of rawHeaders.keys()) { | |
const value = rawHeaders.get(key); | |
if (value) | |
headers[key] = value; | |
} | |
} | |
const gmXhrRequest = { | |
// 补上... | |
url: "", | |
// string, usually one of GET, HEAD, POST, PUT, DELETE, ... | |
method, | |
// 特殊处理 [done] | |
headers, | |
redirect: req.redirect, | |
// default `follow` | |
// 特殊处理 | |
// cookie, | |
// send the data string in binary mode | |
// binary, | |
// boolean | |
// nocache: req.cache, | |
// revalidate maybe cached content | |
// revalidate | |
// context, | |
// @NOTE: 让用户选择 - Response | |
// responseType, | |
// a MIME type for the request | |
// overrideMimeType | |
// don't send cookies with the request | |
anonymous: req.credentials == void 0 || req.credentials == "omit" ? true : false, | |
// user, password, | |
responseType: "blob" | |
}; | |
const body = req.body; | |
if (body && method === "GET") { | |
gmXhrRequest.data = body; | |
} | |
return gmXhrRequest; | |
} | |
function _xmlHttpRequest(req) { | |
const raw = _GM.xmlHttpRequest(req); | |
const promise = raw.then(toResponse); | |
promise.abort = raw.abort; | |
return promise; | |
} | |
_unsafeWindow.x181 = { | |
post(url, options = {}) { | |
const req = toRequest(options); | |
return _xmlHttpRequest({ ...req, method: "POST", url }); | |
}, | |
get(url, options = {}) { | |
const req = toRequest(options); | |
return _xmlHttpRequest({ ...req, method: "GET", url }); | |
}, | |
fetch(url, options = {}) { | |
const method = String(options && options.method || "GET"); | |
return method.toUpperCase() === "POST" ? this.post(url, options) : this.get(url, options); | |
}, | |
async saveFile(dirHandler, filename, data) { | |
const fileHandler = await dirHandler.getFileHandle(filename, { create: true }); | |
const w = await fileHandler.createWritable(); | |
await w.write(data); | |
await w.close(); | |
}, | |
selectFolder() { | |
return window.showDirectoryPicker({ id: String(Date.now()), mode: "readwrite" }); | |
}, | |
async save(map) { | |
const folder = await this.selectFolder(); | |
if (folder == null) | |
return; | |
for (let [filename, url] of Object.entries(map)) { | |
url = url.startsWith("//") ? "https:" + url : url.startsWith("/") ? location.origin + url : url; | |
const blob = await this.fetch(url).then((res) => res.blob()); | |
await this.saveFile(folder, filename, blob); | |
} | |
} | |
}; | |
const scripts = [ | |
task$4, | |
bingTask, | |
doubanTask, | |
dubokuTask, | |
mysmthTask, | |
otherTask, | |
weiboTask, | |
weChatTask, | |
zhihuTask, | |
redirectTask, | |
twitterTask | |
//windTask, | |
].flat(Infinity); | |
core(scripts, location.href); | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment