Last active
November 13, 2024 07:51
-
-
Save zhanhongtao/198e58075d4b3415c2f0f5e394204c6a to your computer and use it in GitHub Desktop.
auto-run-script-for-task
This file contains 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 auto-run-script-for-every-task | |
// @namespace https://x181.cn/ | |
// @version 0.35.16 | |
// @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 | |
// @grant GM_openInTab | |
// @grant GM_registerMenuCommand | |
// @grant GM_xmlhttpRequest | |
// @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); | |
task2.script(task2); | |
} | |
function core(scripts2, url) { | |
filter(scripts2, url).forEach((task2) => { | |
log$2("match.task", task2); | |
run({ ...task2, meta: task2.meta || { ...defaultMeta } }); | |
}); | |
} | |
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; | |
}); | |
} | |
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(); | |
}); | |
} | |
const doubanTask = [ | |
{ | |
match: "book.douban.com", | |
script: bookTask, | |
top: true | |
}, | |
{ | |
match: [ | |
"read.douban.com", | |
"https://www.ituring.com.cn/book/" | |
], | |
script: readTask, | |
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 readTask() { | |
waitForSelector(".article-profile-primary h1, .book-name").then((node) => { | |
waitForFunction( | |
() => node.innerText.trim() != "" | |
).then(() => updateTitle(node)); | |
}); | |
} | |
function updateTitle(node) { | |
const title = node.innerText.trim(); | |
const search = { description: "z-lib:", template: "https://zh.singlelogin.re/s/%s" }; | |
const a = createLinkElement(search.template, title); | |
const parentNode = node.parentNode; | |
if (parentNode == null) | |
return; | |
parentNode.insertBefore(a, node); | |
a.appendChild(node); | |
} | |
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 createRedirectTask() { | |
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; | |
} | |
} | |
}; | |
} | |
const linkTask$1 = { | |
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).*" }), | |
new URLPattern({ "pathname": "/link*" }) | |
] | |
}, | |
script: createRedirectTask() | |
}; | |
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(() => handler2(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$1(); | |
window.removeEventListener("hashchange", f); | |
window.addEventListener("hashchange", f); | |
} else { | |
sleep(0.2).then(() => addEventListener()); | |
} | |
} | |
function handler$1(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$1) | |
}; | |
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, | |
exclude: "link.zhihu.com" | |
}, | |
{ | |
match: (url) => { | |
if ("URLPattern" in window) { | |
let p = new URLPattern("/people/*", "https://www.zhihu.com/"); | |
return p.test(url); | |
} | |
return false; | |
}, | |
script: removeReadQuestionsTask | |
} | |
]; | |
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(); | |
} | |
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 useInterval(n, handler2) { | |
return (task2) => { | |
const inner = () => { | |
handler2(task2); | |
setTimeout(() => { | |
var _a; | |
if (((_a = task2.meta) == null ? void 0 : _a.pause) == false) { | |
inner(); | |
} | |
}, n * 1e3); | |
}; | |
inner(); | |
}; | |
} | |
const hosts = [ | |
"www.newsmth.net", | |
"www.mysmth.net" | |
]; | |
function handler() { | |
const links = document.querySelectorAll("a"); | |
Array.from(links).forEach((link) => { | |
if (link.protocol === "http:" && hosts.some((host) => link.host === host)) { | |
link.href = link.href.replace(/^http:/i, "https:"); | |
} | |
}); | |
} | |
const yurixuTask = { | |
match: "http://uz.yurixu.com/", | |
script: useInterval(0.2, handler) | |
}; | |
const okJiKeTask = { | |
match: [ | |
"https://cdn.jellow.site/", | |
"https://cdnv2.ruguoapp.com/" | |
], | |
script: function() { | |
let href = location.href; | |
if (href.includes("?")) { | |
location.href = href.replace(/\?.+$/, ""); | |
} | |
} | |
}; | |
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_openInTab = /* @__PURE__ */ (() => typeof GM_openInTab != "undefined" ? GM_openInTab : void 0)(); | |
var _GM_registerMenuCommand = /* @__PURE__ */ (() => typeof GM_registerMenuCommand != "undefined" ? GM_registerMenuCommand : void 0)(); | |
var _GM_xmlhttpRequest = /* @__PURE__ */ (() => typeof GM_xmlhttpRequest != "undefined" ? GM_xmlhttpRequest : 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); | |
} | |
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() | |
); | |
} | |
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); | |
} | |
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; | |
trySaveImagesToLocale(images); | |
e.preventDefault(); | |
e.stopImmediatePropagation(); | |
}, true); | |
option.inject(); | |
} | |
function createSaveButtonNode(cssText = "") { | |
const div = document.createElement("div"); | |
div.dataset.save = "1"; | |
div.classList.add(userSaveButtonClass); | |
div.style.cssText = `cursor: pointer;` + (cssText ? cssText : `align-self: center;`); | |
div.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" style="display: block; color: lightskyblue" width="28" height="28" viewBox="0 0 24 24"><path fill="currentColor" d="M20 7.423v10.962q0 .69-.462 1.153T18.384 20H5.616q-.691 0-1.153-.462T4 18.384V5.616q0-.691.463-1.153T5.616 4h10.961zm-8.004 9.115q.831 0 1.417-.582T14 14.543t-.582-1.418t-1.413-.586t-1.419.581T10 14.535t.582 1.418t1.414.587M6.769 9.77h7.423v-3H6.77z"/></svg>`; | |
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()); | |
} | |
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: true, | |
script: () => { | |
_GM_registerMenuCommand("telegram.share", function() { | |
const url = `https://t.me/share/url?url=${encodeURIComponent(top.location.href)}&text=${encodeURIComponent(top.document.title)}`; | |
_GM_openInTab(url, { active: true }); | |
}); | |
} | |
}, | |
{ | |
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"); | |
} | |
}); | |
}); | |
} | |
} | |
]; | |
const scripts = [ | |
bingTask, | |
...doubanTask, | |
dubokuTask, | |
linkTask$1, | |
mysmthTask, | |
...otherTask, | |
...weiboTask, | |
...weChatTask, | |
yurixuTask, | |
...zhihuTask, | |
okJiKeTask, | |
redirectTask, | |
...twitterTask | |
// ...windTask, | |
]; | |
core(scripts, location.href); | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment