SimpCity Video & Image Link Copier

Collect and copy video/image links across current or all XenForo thread pages, one page at a time, using localStorage clipboard workaround (Cloudflare safe). Supports separate video/image modes with domain matching.

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         SimpCity Video & Image Link Copier
// @version      2.0
// @namespace    RiisDevScripts
// @description  Collect and copy video/image links across current or all XenForo thread pages, one page at a time, using localStorage clipboard workaround (Cloudflare safe). Supports separate video/image modes with domain matching.
// @author       https://github.com/RiisDev/
// @match        *https://simpcity.cr/threads/*
// @grant        GM_setClipboard
// @icon         https://simpcity.cr/data/assets/logo/favicon.png
// @run-at       document-end
// @license      MIT
// ==/UserScript==

(function () {
	'use strict';

	function showChoiceDialog(autoCopy) {
		const overlay = document.createElement('div');
		overlay.style.cssText = `
        	position: fixed; top: 0; left: 0; width: 100vw; height: 100vh;
			background: rgba(0,0,0,0.3);
			backdrop-filter: blur(5px);
			-webkit-backdrop-filter: blur(5px);
			display: flex; align-items: center; justify-content: center;
			z-index: 10000;
		`;

		const modal = document.createElement('div');
		modal.className = 'p-body-background p-body-background--main';
		modal.style.cssText = `
			background: var(--xf-palette-pureWhite);
			border-radius: 6px;
			padding: 20px;
			max-width: 320px;
			text-align: center;
			box-shadow: 0 4px 12px rgba(0,0,0,0.15);
		`;

		const title = document.createElement('h3');
		title.textContent = 'Auto Copy Links';
		title.style.marginBottom = '12px';

		const prompt = document.createElement('p');
		prompt.textContent = 'Which links do you want to auto copy across all pages?';
		prompt.style.marginBottom = '20px';

		const btnContainer = document.createElement('div');
		btnContainer.style.display = 'flex';
		btnContainer.style.justifyContent = 'space-between';
		btnContainer.style.gap = '10px';

		function createOptionBtn(text, type) {
			const btn = document.createElement('button');
			btn.className = 'button--primary button rippleButton';
			btn.textContent = text;
			btn.style.flex = '1';
			if (autoCopy){
				btn.addEventListener('click', () => {
					document.body.removeChild(overlay);
					startAutoCopy(type);
				});
			}
			else {
				btn.addEventListener('click', () => {
					const links = extractLinks(type);
					if (links.length > 0) {
						GM_setClipboard(links.join('\n'));
						showXenforoNotice(`✅ Copied ${links.length} ${type} link(s)!`);
					} else {
						showXenforoNotice(`⚠️ No ${type} links found.`);
					}
					document.body.removeChild(overlay);
					
					localStorage.removeItem(XF_LINKS_TYPE);
					localStorage.removeItem(STORAGE_KEY);
				});
			}
			return btn;
		}

		const btnVideo = createOptionBtn('Video', 'video');
		const btnImage = createOptionBtn('Image', 'image');
		const btnBoth = createOptionBtn('Both', 'all');

		const btnCancel = document.createElement('button');
		btnCancel.className = 'button--link button rippleButton';
		btnCancel.textContent = 'Cancel';
		btnCancel.style.flex = '1';
		btnCancel.addEventListener('click', () => {
			document.body.removeChild(overlay);
			if (autoCopy) {
				showXenforoNotice('Auto copy cancelled.', 'warning');
			} else {
				showXenforoNotice('Copy cancelled.', 'warning');
			}
		});

		btnContainer.appendChild(btnVideo);
		btnContainer.appendChild(btnImage);
		btnContainer.appendChild(btnBoth);
		btnContainer.appendChild(btnCancel);

		modal.appendChild(title);
		modal.appendChild(prompt);
		modal.appendChild(btnContainer);
		overlay.appendChild(modal);
		document.body.appendChild(overlay);
	}

	function showXenforoNotice(message, type = 'success', duration = 3000) {
		const notice = document.createElement('div');
		notice.className = `p-notice p-notice--${type}`;
		notice.style.cssText = `
            position: fixed;
            top: 80px;
            right: 30px;
            z-index: 9999;
            max-width: 300px;
            padding: 10px 15px;
            box-shadow: 0 2px 5px rgba(0,0,0,0.3);
            border-radius: 5px;
            font-size: 14px;
            opacity: 0;
            border: 2px solid hsla(var(--xf-textColorEmphasized));
            animation: fadeInOut 0.3s forwards;
        `;
		notice.innerHTML = `<div class="p-notice__content">${message}</div>`;
		document.body.appendChild(notice);

		setTimeout(() => {
			notice.style.animation = 'fadeOut 0.3s forwards';
			setTimeout(() => notice.remove(), 300);
		}, duration);
	}

	const style = document.createElement('style');
	style.textContent = `
        @keyframes fadeInOut {
            from { opacity: 0; transform: translateY(-10px); }
            to { opacity: 1; transform: translateY(0); }
        }
        @keyframes fadeOut {
            from { opacity: 1; }
            to { opacity: 0; transform: translateY(-10px); }
        }
    `;
	document.head.appendChild(style);

	const XF_LINKS_TYPE = 'xf_links_type';
	const STORAGE_KEY = 'xf_links_collector';
	const imageDomains = ['bunkr', 'cyberdrop', 'pixeldrain', 'ibb.co', 'postimg', 'jpg5.su'];
	const videoDomains = ['gofile', 'pixeldrain', 'mega', 'mediafire', 'xhamster', 'pornhub', 'bunkr', 'cyberdrop', 'cyberfile', 'saint2', 'pvvstream', 'noodlemagazine.com', 'xvideos', 'vk'];

	const isVideoLink = url => { try { return videoDomains.some(domain => new URL(url).hostname.includes(domain)); } catch { return false; } };
	const isImageLink = url => { try { return imageDomains.some(domain => new URL(url).hostname.includes(domain)); } catch { return false; } };
	function GetStorageLinks() { return JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]'); }
	function GetType() { return localStorage.getItem(XF_LINKS_TYPE); }

	function extractLinks(type) {
		if (!GetType()) { localStorage.setItem(XF_LINKS_TYPE, type);}

		const links = new Set();

		const anchors = document.querySelectorAll('.message--post a[href]');
		anchors.forEach(a => {
			const href = a.href.trim();
			if (
				href &&
				(
					(type === 'video' && isVideoLink(href)) ||
					(type === 'image' && isImageLink(href)) ||
					(type === 'all' && (isVideoLink(href) || isImageLink(href)))
				)
			) {
				links.add(href);
			}
		});

		const iframes = document.querySelectorAll('iframe.saint-iframe[src]');
		if (type !== 'image') {
			iframes.forEach(iframe => {
				const src = iframe.src.trim();
				if (src && isVideoLink(src)) {
					links.add(src);
				}
			});
		}

		return Array.from(links);
	};

	function getNextPageUrl() {
		const nextBtn = document.querySelector('.pageNav-jump--next');
		return nextBtn ? nextBtn.href : null;
	};

	function saveLinks(newLinks) {
		const existing = GetStorageLinks();
		const merged = [...new Set([...existing, ...newLinks])];
		localStorage.setItem(STORAGE_KEY, JSON.stringify(merged));
		return merged;
	};

	function copyAndFinish(type) {
		const allLinks = GetStorageLinks();
		if (allLinks.length > 0) {
			GM_setClipboard(allLinks.join('\n'));
			showXenforoNotice(`✅ Copied ${allLinks.length} ${type} link(s) from all pages!`);
		} else {
			showXenforoNotice(`⚠️ No ${type} links were found.`);
		}

		localStorage.removeItem(STORAGE_KEY);
		localStorage.removeItem(XF_LINKS_TYPE);
	};

	function startAutoCopy(type) {
		localStorage.removeItem(STORAGE_KEY);
		localStorage.removeItem(XF_LINKS_TYPE);

		const links = extractLinks(type);
		saveLinks(links);

		const nextPage = getNextPageUrl();
		if (nextPage) {
			window.location.href = nextPage;
		} else {
			copyAndFinish(type);
		}
	}
	
	function autoContinueIfNeeded() {
		const linksExist = GetStorageLinks();
		const type = GetType();
		if (!linksExist || !type) return;

		const links = extractLinks(type);
		saveLinks(links);

		const nextPage = getNextPageUrl();
		if (nextPage) {
			setTimeout(() => {
				window.location.href = nextPage;
			}, 1000);
		} else {
			copyAndFinish(type);
		}
	};

	function addButton(label, clickHandler) {
		const buttonGroup = document.querySelector('.buttonGroup');
		if (!buttonGroup) return;

		const btn = document.createElement('a');
		btn.href = '#';
		btn.className = 'button--link button rippleButton';
		btn.innerHTML = `<span class="button-text">${label}</span>`;
		btn.addEventListener('click', e => { e.preventDefault(); clickHandler(); });
		buttonGroup.prepend(btn);
	};

	function addButtons() {
		addButton('📋 Start Auto Copy', () => {
			showChoiceDialog(true);
		});

		addButton('📋 Copy Links', () => {
			showChoiceDialog(false);
		});
	};

	window.addEventListener('load', () => {
		addButtons();
		autoContinueIfNeeded();
	});
})();