您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Copy cover image of the post you selected in article list view
// ==UserScript== // @name InoReader copy cover image // @namespace http://tampermonkey.net/ // @version 0.0.2 // @description Copy cover image of the post you selected in article list view // @author Kenya-West // @match https://*.inoreader.com/feed* // @match https://*.inoreader.com/article* // @match https://*.inoreader.com/folder* // @match https://*.inoreader.com/starred* // @match https://*.inoreader.com/library* // @match https://*.inoreader.com/dashboard* // @match https://*.inoreader.com/web_pages* // @match https://*.inoreader.com/trending* // @match https://*.inoreader.com/commented* // @match https://*.inoreader.com/recent* // @match https://*.inoreader.com/search* // @match https://*.inoreader.com/channel* // @match https://*.inoreader.com/teams* // @match https://*.inoreader.com/dashboard* // @match https://*.inoreader.com/pocket* // @match https://*.inoreader.com/liked* // @match https://*.inoreader.com/tags* // @icon https://inoreader.com/favicon.ico?v=8 // @license MIT // ==/UserScript== // @ts-check (function () { "use strict"; document.head.insertAdjacentHTML( "beforeend", ` <style> .tm_copy_image_button { display: inline-block; cursor: pointer; position: absolute; right: 0.5rem; top: -2rem; background-color: rgba(0, 0, 0, 0.3); color: white; font-family: 'Inoreader-UI-Icons-Font' !important; font-size: 1.5rem; padding: 0.1rem; border-radius: 50%; margin-left: 0.5rem; transition: background-color 0.3s; } .tm_copy_image_button:hover { background-color: rgba(0, 0, 0, 0.7); transition: background-color 0.3s; } .tm_copy_image_button:active { background-color: rgba(0, 0, 0, 0.9); transition: background-color 0.3s; } .tm_copy_image_button::before { content: "\\ea11"; } .tm_copy_image_button__success::before { content: "\\e976"; } </style>` ); /** * @typedef {Object} appConfig * @property {Array<{ * prefixUrl: string, * corsType: "direct" | "corsSh" | "corsAnywhere" | "corsFlare", * token?: string, * hidden?: boolean * }>} corsProxies */ const appConfig = { }; /** * Represents the application state. * @typedef {Object} AppState * @property {boolean} readerPaneMutationObserverLinked - Indicates whether the reader pane mutation observer is linked. * @property {boolean} articleViewOpened - Indicates whether the article view is opened. * @property {Object} copyBadge - Represents the currently playing video. * @property {HTMLDivElement | null} copyBadge.currentVideoElement - The current video element being played. * @property {function} copyBadge.set - Sets the current video element and pauses the previous one. * @property {function} copyBadge.get - Retrieves the current video element. */ const appState = { readerPaneMutationObserverLinked: false, articleViewOpened: false, copyBadge: { /** * Represents the currently playing video. * @type {HTMLDivElement | null} */ currentCopyBadgeElement: null, /** * * @param {HTMLDivElement | null} badge */ set: (badge) => { const previousCopyBadge = appState.copyBadge.currentCopyBadgeElement; if (previousCopyBadge?.isConnected) { appState.copyBadge.currentCopyBadgeElement?.parentElement?.removeChild(previousCopyBadge); } appState.copyBadge.currentCopyBadgeElement = badge; }, /** * * @returns {HTMLDivElement | null} */ get: () => { return appState.copyBadge.currentCopyBadgeElement; }, disconnect: () => { if (appState.copyBadge.currentCopyBadgeElement?.isConnected) { appState.copyBadge.currentCopyBadgeElement?.parentElement?.removeChild(appState.copyBadge.currentCopyBadgeElement); appState.copyBadge.currentCopyBadgeElement = null; } }, }, }; // Select the node that will be observed for mutations const targetNode = document.body; // Options for the observer (which mutations to observe) const mutationObserverGlobalConfig = { attributes: false, childList: true, subtree: true, }; const querySelectorPathArticleRoot = ".article_full_contents .article_content"; /** * Callback function to execute when mutations are observed * @param {MutationRecord[]} mutationsList - List of mutations observed * @param {MutationObserver} observer - The MutationObserver instance */ const callback = function (mutationsList, observer) { for (let i = 0; i < mutationsList.length; i++) { if (mutationsList[i].type === "childList") { mutationsList[i].addedNodes.forEach(function (node) { if (node.nodeType === Node.ELEMENT_NODE) { setCopyIconInArticleList(node); } }); } } }; // // // FIRST PART - RESTORE IMAGES IN ARTICLE LIST // // // /** * * @param {Node} node * @returns {void} */ function setCopyIconInArticleList(node) { /** * @type {MutationObserver | undefined} */ let tmObserverImageRestoreReaderPane; const readerPane = document.body.querySelector("#reader_pane"); if (readerPane) { if (!appState.readerPaneMutationObserverLinked) { appState.readerPaneMutationObserverLinked = true; /** * Callback function to execute when mutations are observed * @param {MutationRecord[]} mutationsList - List of mutations observed * @param {MutationObserver} observer - The MutationObserver instance */ const callback = function (mutationsList, observer) { // filter mutations by having id on target and to have only unique id attribute values let filteredMutations = mutationsList // @ts-ignore .filter((mutation) => mutation.target?.id.includes("article_")) // @ts-ignore .filter((mutation, index, self) => self.findIndex((t) => t.target?.id === mutation.target?.id) === index); if (filteredMutations.length === 2) { // check to have only two mutations: one that has .article_current class and one should not const firstMutation = filteredMutations[0]; const secondMutation = filteredMutations[1]; // sort by abscence of .article_current class filteredMutations = [firstMutation, secondMutation].sort((a, b) => { // @ts-ignore return a.target?.classList?.contains("article_current") ? 1 : -1; }); // @ts-ignore if (firstMutation.target?.classList?.contains("article_current") && !secondMutation.target?.classList?.contains("article_current")) { filteredMutations = []; } } for (let mutation of filteredMutations) { if (mutation.type === "attributes") { if (mutation.attributeName === "class") { /** * @type {HTMLDivElement} */ // @ts-ignore const target = mutation.target; if ( target.classList.contains("article_current") && target.querySelector(".article_tile_content_wraper .article_tile_picture") ) { // тут const imageElement = getImageElement(target); if (imageElement) { const imageUrl = getImageLink(imageElement); if (imageUrl) { const button = createButtonElement(imageUrl); placeButton(target, button); appState.copyBadge.set(button); } } } else if ( !target.classList.contains("article_current") && target.querySelector(".article_tile_content_wraper .article_tile_picture") ) { // тут если снято выделение } /** * * @param {Node & HTMLDivElement} node * @returns {HTMLDivElement | null} */ function getImageElement(node) { const nodeElement = node; /** * @type {HTMLDivElement | null} */ const divImageElement = nodeElement.querySelector("a[href] > div[style*='background-image']"); return divImageElement ?? null; } /** * * @param {HTMLDivElement} div */ function getImageLink(div) { const backgroundImageUrl = div?.style.backgroundImage; return commonGetUrlFromBackgroundImage(backgroundImageUrl); } /** * * @param {string} imageUrl * @returns {HTMLDivElement} */ function createButtonElement(imageUrl) { const button = document.createElement("div"); button.className = "tm_copy_image_button"; button.title = "Copy image to clipboard"; button.addEventListener("click", () => { copyImage(imageUrl); }); return button; } /** * * @param {HTMLDivElement} article * @param {HTMLDivElement} buttonElement */ function placeButton(article, buttonElement) { if (article) { article.appendChild(buttonElement); } else { console.error("Article was not found. Copy button has not been placed"); } } /** * * @param {string} imageLink */ function copyImage(imageLink) { const img = new Image(); img.crossOrigin = "Anonymous"; // This enables CORS const c = document.createElement("canvas"); const ctx = c.getContext("2d"); /** * @param {string} path * @param {{ (imgBlob: any): void; (arg0: any): void; }} func */ function setCanvasImage(path, func) { img.onload = function () { // @ts-ignore c.width = this.naturalWidth; // @ts-ignore c.height = this.naturalHeight; // @ts-ignore ctx.drawImage(this, 0, 0); c.toBlob((/** @type {any} */ blob) => { func(blob); }, "image/png"); }; img.src = path; } setCanvasImage(imageLink, (/** @type {any} */ imgBlob) => { navigator.clipboard .write([new ClipboardItem({ "image/png": imgBlob })]) .then((e) => { setSuccessIcon(); }) .catch((e) => { console.error(e); alert( `Failed to copy image to clipboard. This feature may not supported in your browser, or something happened with image. Please try to save it manually. Error: ${ e.message ?? e.body ?? e.toString() ?? e.name ?? e.constructor.name ?? e.constructor.toString() }` ); }); }); } function setSuccessIcon() { appState.copyBadge.get()?.classList.add("tm_copy_image_button__success"); } } } } }; // Options for the observer (which mutations to observe) const mutationObserverLocalConfig = { attributes: true, attributeFilter: ["class"], childList: false, subtree: true, }; // Create an observer instance linked to the callback function tmObserverImageRestoreReaderPane = new MutationObserver(callback); // Start observing the target node for configured mutations tmObserverImageRestoreReaderPane.observe(readerPane, mutationObserverLocalConfig); } } else { appState.readerPaneMutationObserverLinked = false; tmObserverImageRestoreReaderPane?.disconnect(); } } /** * * @param {string} backgroundImageUrl * @returns {string | undefined} */ function commonGetUrlFromBackgroundImage(backgroundImageUrl) { /** * @type {string | undefined} */ let imageUrl; try { imageUrl = backgroundImageUrl?.match(/url\("(.*)"\)/)?.[1]; } catch (error) { imageUrl = backgroundImageUrl?.slice(5, -2); } if (!imageUrl || imageUrl == "undefined") { return; } if (!imageUrl?.startsWith("http")) { console.error(`The image could not be parsed. Image URL: ${imageUrl}`); return; } return imageUrl; } // Create an observer instance linked to the callback function const tmObserverImageRestore = new MutationObserver(callback); // Start observing the target node for configured mutations tmObserverImageRestore.observe(targetNode, mutationObserverGlobalConfig); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址