您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
將 Pixiv 作品圖片與動圖直接存入 Eagle
当前为
// ==UserScript== // @name Pixiv Save to Eagle // @name:zh-TW Pixiv 圖片儲存至 Eagle // @name:ja Pixivの畫像を直接Eagleに儲存 // @name:en Pixiv Save to Eagle // @name:de Pixiv-Bilder direkt in Eagle speichern // @name:es Guardar imágenes de Pixiv directamente en Eagle // @description 將 Pixiv 作品圖片與動圖直接存入 Eagle // @description:zh-TW 直接將 Pixiv 上的圖片與動圖儲存到 Eagle // @description:ja Pixivの作品畫像とアニメーションを直接Eagleに儲存します // @description:en Save Pixiv images & animations directly into Eagle // @description:de Speichert Pixiv-Bilder und Animationen direkt in Eagle // @description:es Guarda imágenes y animaciones de Pixiv directamente en Eagle // // @version 1.1.5 // @match https://www.pixiv.net/artworks/* // @icon https://www.google.com/s2/favicons?sz=64&domain=pixiv.net // @grant GM_xmlhttpRequest // @grant GM.getValue // @grant GM.setValue // @require https://gf.qytechs.cn/scripts/2963-gif-js/code/gifjs.js?version=8596 // @run-at document-end // // @author Max // @namespace https://github.com/Max46656 // @license MPL2.0 // ==/UserScript== class EagleClient { async save(urlOrBase64, name, folderId = []) { return new Promise(resolve => { const data = { url: urlOrBase64, name, folderId: Array.isArray(folderId) ? folderId : [folderId], tags: [], website: location.href, headers: { referer: "https://www.pixiv.net/" } } GM_xmlhttpRequest({ url: "http://localhost:41595/api/item/addFromURL", method: "POST", headers: { "Content-Type": "application/json" }, data: JSON.stringify(data), onload: r => { if (r.status >= 200 && r.status < 300) { console.log("✅ Added:", name) } else { console.error("Failed:", r) } resolve() }, onerror: e => { console.error(e) resolve() }, ontimeout: e => { console.error(e) resolve() } }) }) } async getFolderList() { return new Promise(resolve => { GM_xmlhttpRequest({ url: "http://localhost:41595/api/folder/list", method: "GET", onload: res => { try { const folders = JSON.parse(res.responseText).data || [] const list = [] const appendFolder = (f, prefix = "") => { list.push({ id: f.id, name: prefix + f.name }) if (f.children && f.children.length) { f.children.forEach(c => appendFolder(c, "└── " + prefix)) } } folders.forEach(f => appendFolder(f)) resolve(list) } catch (e) { console.error("解析資料夾列表失敗", e) resolve([]) } }, onerror: err => { console.error(err) resolve([]) } }) }) } } class PixivIllust { constructor(eagleClient) { this.eagle = eagleClient this.illust = this.fetchIllust() } fetchIllust() { const illustId = location.href.match(/artworks\/(\d+)/)?.[1] if (!illustId) return null if (!this.illust || this.illust.illustId != illustId) { const xhr = new XMLHttpRequest() xhr.open("GET", `/ajax/illust/${illustId}`, false) xhr.send() if (xhr.status === 200) { this.illust = JSON.parse(xhr.responseText).body } } return this.illust } isSingle() { return (this.illust.illustType === 0 || this.illust.illustType === 1) && this.illust.pageCount === 1 } isSet() { return this.illust.pageCount > 1 } isGif() { return this.illust.illustType === 2 } async handleSingle(folderId) { const illust = this.illust const url = illust.urls.original const name = `Pixiv @${illust.userName} ${illust.title}(${illust.illustId})` await this.eagle.save(url, name, folderId) console.log("已送到 Eagle:", name) } async handleSet(folderId) { const illust = this.illust const url = illust.urls.original const urls = Array.from({ length: illust.pageCount }, (_, i) => url.replace(/_p\d\./, `_p${i}.`)) for (const [i, u] of urls.entries()) { const name = `Pixiv @${illust.userName} ${illust.title}(${illust.illustId})_p${i}` await this.eagle.save(u, name, folderId) } console.log(`已送 ${illust.pageCount} 張到 Eagle`) } async handleGif(folderId) { try { const illust = this.illust const xhr = new XMLHttpRequest() xhr.open("GET", `/ajax/illust/${illust.illustId}/ugoira_meta`, false) xhr.send() const frames = JSON.parse(xhr.responseText).body.frames const gif = new GIF({ workers: 1, quality: 10, workerScript: GIF_worker_URL }) const gifFrames = new Array(frames.length) await Promise.all(frames.map((frame, idx) => new Promise((resolve, reject) => { const url = illust.urls.original.replace("ugoira0.", `ugoira${idx}.`) GM_xmlhttpRequest({ method: "GET", url, headers: { referer: "https://www.pixiv.net/" }, responseType: "arraybuffer", onload: res => { if (res.status >= 200 && res.status < 300) { const suffix = url.split(".").pop() const mime = { png: "image/png", jpg: "image/jpeg", jpeg: "image/jpeg" }[suffix] const blob = new Blob([res.response], { type: mime }) const img = document.createElement("img") const reader = new FileReader() reader.onload = () => { img.src = reader.result img.onload = () => { gifFrames[idx] = { frame: img, option: { delay: frame.delay } } resolve() } img.onerror = () => reject(new Error("圖片載入失敗:" + url)) } reader.readAsDataURL(blob) } else { reject(new Error(`下載失敗 ${res.status}: ${url}`)) } }, onerror: reject, ontimeout: reject }) }))) gifFrames.forEach(f => gif.addFrame(f.frame, f.option)) gif.on("finished", async blob => { const reader = new FileReader() reader.onload = async () => { const base64 = reader.result const name = `Pixiv @${illust.userName} ${illust.title}(${illust.illustId}).gif` await this.eagle.save(base64, name, folderId) console.log("已送動圖到 Eagle:", name) } reader.readAsDataURL(blob) }) gif.render() } catch (e) { console.error("handleGif error:", e) } } } class PixivEagleUI { constructor() { this.eagle = new EagleClient() this.illust = new PixivIllust(this.eagle) this.buttonContainerSelector = "section.kDUrpE" this.init() } init() { this.addButton() this.observeUrlChange(() => { this.addButton() this.illust.illust = this.illust.fetchIllust() }) } async waitForElement(selector, timeout = 10000) { return new Promise((resolve, reject) => { const el = document.querySelector(selector) if (el) return resolve(el) const obs = new MutationObserver(() => { const e = document.querySelector(selector) if (e) { obs.disconnect() resolve(e) } }) obs.observe(document.body, { childList: true, subtree: true }) if (timeout) { setTimeout(() => { obs.disconnect() reject(new Error("Timeout:" + selector)) }, timeout) } }) } async addButton() { try { const section = await this.waitForElement(this.buttonContainerSelector) if (document.getElementById("save-to-eagle-btn")) return const container = document.createElement("div") container.classList.add("cNcUof") const btn = document.createElement("button") btn.id = "save-to-eagle-btn" btn.textContent = "Save to Eagle" btn.className = "charcoal-button" btn.dataset.variant = "Primary" const select = document.createElement("select") select.id = "eagle-folder-select" select.style.marginLeft = "8px" const lastFolderId = await GM.getValue("eagle_last_folder") const folders = await this.eagle.getFolderList() folders.forEach(f => { const option = document.createElement("option") option.value = f.id option.textContent = f.name if (f.id === lastFolderId) option.selected = true select.appendChild(option) }) btn.onclick = async () => { const folderId = select.value await GM.setValue("eagle_last_folder", folderId) this.illust.fetchIllust() if (this.illust.isSingle()) { await this.illust.handleSingle(folderId) } else if (this.illust.isSet()) { await this.illust.handleSet(folderId) } else if (this.illust.isGif()) { await this.illust.handleGif(folderId) } else { console.log("不支援此作品類型") } } container.appendChild(btn) container.appendChild(select) section.appendChild(container) } catch (e) { console.error(e) } } observeUrlChange(callback) { let oldHref = location.href const title = document.querySelector("title") const observer = new MutationObserver(() => { if (oldHref !== location.href) { oldHref = location.href callback() } }) observer.observe(title, { childList: true }) } } new PixivEagleUI()
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址