- // ==UserScript==
- // @name Civitai Direct Link Helper
- // @name:zh-CN Civitai 下载助手
- // @namespace http://tampermonkey.net/
- // @version 1.21
- // @description Adds a convenient copy button next to download buttons on Civitai to easily get direct download links for models
- // @description:zh-CN 在 Civitai 的下载按钮旁添加复制按钮,轻松复制直链地址 还有去广告
- // @author hua
- // @match https://civitai.com/*
- // @match https://civitai.green/*
- // @grant unsafeWindow
- // @grant GM_xmlhttpRequest
- // @run-at document-start
- // @license MIT
- // ==/UserScript==
-
-
-
-
- (function () {
- 'use strict';
- unsafeWindow.setInterval = function (fn, time) {
- };
- const origin_setTimeout = unsafeWindow.setTimeout;
- unsafeWindow.setTimeout = function (fn, time) {
- const tags = ['schedule', 'coreAdServerStart', 'exited', 'maybeFetchNotificationAndTrackCurrentUrl', 'googletagservices', '/api/internal/activity', 'iframe_api'];
- if (tags.some(tag => fn.toString().includes(tag))) {
- return;
- }
- function fn_() {
- fn();
- }
- origin_setTimeout(fn_, time);
- };
-
- hookCreateElement();
- changeInfo();
- modifywebpack();
- function modifywebpack() {
- let webpackChunk_N_E;
- const hookPush = () => {
- const originPush = webpackChunk_N_E.push;
- webpackChunk_N_E.push = function (chunk) {
- const funs = chunk?.[1];
- if (funs?.['68714'] && !funs['68714'].inject) {
- let funStr = funs['68714'].toString();
- // funStr = funStr.replace('function(e,i,t){', 'function(e,i,t){debugger;');
- // funStr = funStr.replace('function(e,t,n){"use strict";', 'function(e,t,n){"use strict";debugger;');
- let match_tag = funStr.match(/return (.{1,5})\.length\?\(0,/);
- if (match_tag) {
- const tag = match_tag[1];
- funStr = funStr.replace(`return ${tag}.length?(0,`, `${tag}=${tag}.filter(item => item.type !== "ad");return ${tag}.length?(0,`);
- }
- funs['68714'] = new Function('return ' + funStr)();
- funs['68714'].inject = true;
- }
- if (funs?.['56053'] && !funs['56053'].inject) {
- let funStr = funs['56053'].toString();
- // funStr = funStr.replace('function(e,t,i){"use strict";', 'function(e,t,i){"use strict";debugger;');
- let match_tag = funStr.match(/children\:(.{1,5})\.map\(\(/);
- if (match_tag) {
- const tag = match_tag[1];
- const re_match = funStr.match(/return\(0,(.{1,5}).jsx\)\("div"/);
- if (re_match) {
- const re_str = re_match[0];
- funStr = funStr.replace(re_str, `${tag}.forEach((item,i)=>{ ${tag}[i] = item.filter(ite => ite.data.type !== "ad")});${re_str}`);
- }
- }
- funs['56053'] = new Function('return ' + funStr)();
- funs['56053'].inject = true;
- }
- originPush.call(this, chunk);
- };
- };
- Object.defineProperty(unsafeWindow, 'webpackChunk_N_E', {
- get: function () {
- return webpackChunk_N_E;
- },
- set: function (value) {
- webpackChunk_N_E = value;
- hookPush();
- }
- });
- }
-
- function changeInfo() {
- const originFetch = unsafeWindow.fetch;
- unsafeWindow.fetch = function (url, options) {
- async function fetch_request(response) {
- if (url.includes('/announcement.getAnnouncements')) {
- try {
- const data = await response.json();
- if (data.result?.data?.json) data.result.data.json = [];
- console.log('modify announcement.getAnnouncements');
- response = new Response(JSON.stringify(data), response);
- } catch (e) {
- console.log('fetch_request error', e);
- }
- }
- if (url.includes('auth/session')) {
- try {
- const data = await response.json();
- if (data.user) {
- const user = data.user;
- user.allowAds = false;
- console.log('modify auth/session');
- }
- response = new Response(JSON.stringify(data), response);
- } catch (e) {
- console.log('fetch_request error', e);
- }
- }
- return response;
- }
- return originFetch(url, options).then(fetch_request);
- };
-
- let monitorcount = 1;
- const observer = new MutationObserver((mutations) => {
- for (const mutation of mutations) {
- for (const node of mutation.addedNodes) {
- if (node.tagName === 'SCRIPT' && node.id === '__NEXT_DATA__') {
- monitorcount--;
- console.log('modify __NEXT_DATA__');
- if (monitorcount <= 0) {
- observer.disconnect();
- }
- modify(node);
- }
-
- }
- }
- });
-
- observer.observe(document.documentElement, {
- childList: true,
- subtree: true
- });
- function modify(node) {
- const initalData = JSON.parse(node.textContent);
- const trpcData = initalData.props?.pageProps?.trpcState?.json;
- if (trpcData) {
- const queries = trpcData.queries || [];
- if (queries.length > 0) {
- const query = queries[0];
- const data = query.state?.data || [];
- const remveIndex = [];
- data.forEach((item, index) => {
- const ignoreFlags = ['Announcement', 'Event', 'CosmeticShop'];
- if (ignoreFlags.includes(item.type)) {
- remveIndex.push(index);
- }
- });
- remveIndex.reverse();
- remveIndex.forEach(index => {
- data.splice(index, 1);
- });
- }
- }
-
- const flags = initalData.props?.pageProps?.flags;
- if (flags) {
- flags.adsEnabled = false;
- }
- const session = initalData.props?.pageProps?.session;
- if (session?.user) {
- const user = session.user;
- user.allowAds = false;
- }
- node.textContent = JSON.stringify(initalData);
- }
-
- }
-
- function paraseDownloadUrl(button) {
- let originalColor = window.getComputedStyle(button).color;
- const restoreTimeout = 10000;
- let interval = null;
- const restore = () => {
- button.style.color = originalColor;
- };
-
- const onError = () => {
- clearInterval(interval);
- interval = null;
- button.style.color = '#FF0000';
- setTimeout(() => {
- restore();
- }, restoreTimeout);
- };
-
- const onSuccess = () => {
- clearInterval(interval);
- interval = null;
- navigator.clipboard.writeText(button.downloadUrl).then(() => {
- }).catch((e) => {
- alert('copy error:' + e.message);
- });
- button.style.color = '#00FF00';
- setTimeout(() => {
- restore();
- }, restoreTimeout);
- };
- if (button.downloadUrl) {
- onSuccess();
- return;
- }
- const uri = button.getAttribute('href');
- interval = setInterval(() => {
- button.style.color = `rgb(${Math.floor(Math.random() * 256)}, ${Math.floor(Math.random() * 256)}, ${Math.floor(Math.random() * 256)})`;
- }, 100);
-
- GM_xmlhttpRequest({
- method: "GET",
- url: `https://civitai.com${uri}`,
- timeout: 10000,
- anonymous: false,
- redirect: 'manual',
- maxRedirects: 0,
- onload: function (response) {
- const downloadUrl = response.responseHeaders.match(/location:(.*?)(?:\r?\n)/i)?.[1];
- button.downloadUrl = downloadUrl;
- downloadUrl ? onSuccess() : onError();
- },
- onerror: function (error) {
- console.log('onerror', error);
- onError();
- },
- ontimeout: function () {
- console.log('ontimeout');
- onError();
- }
- });
- }
-
- function hookDownloadButton(node) {
- let isClick = false;
- let timers = [];
- node.addEventListener('click', function (e) {
- if (isClick) {
- isClick = false;
- return;
- }
- e.preventDefault();
- const timer = setTimeout(() => {
- isClick = true;
- timers.forEach(timer => clearTimeout(timer));
- timers.length = 0;
- node.click();
- }, 300);
- timers.push(timer);
- });
- node.addEventListener('dblclick', function (e) {
- e.preventDefault();
- timers.forEach(timer => clearTimeout(timer));
- timers.length = 0;
- paraseDownloadUrl(node);
- });
- }
-
- function hookCreateElement() {
- const origin_createElement = unsafeWindow.document.createElement;
- unsafeWindow.document.createElement = function () {
- const node = origin_createElement.apply(this, arguments);
- if (arguments[0].toUpperCase() === 'A') {
- const originSetAttribute = node.setAttribute;
- node.setAttribute = function (name, value) {
- if (name === 'href' ) {
- if (value?.startsWith('/api/download/models/')){
- console.log('hookButton');
- hookDownloadButton(node);
- }
- if (value?.includes('/pricing?utm_campaign=holiday_promo')) {
- node.style.display = 'none';
- console.log('hookPricing');
- }
- }
- return originSetAttribute.call(this, name, value);
- };
- }
- if (arguments[0].toUpperCase() === 'IFRAME') {
- return null;
- }
- return node;
- };
- unsafeWindow.document.createElement.toString = origin_createElement.toString.bind(origin_createElement);
- }
-
- })();