Digit77 Helper

自动复制提取码,跳过ouo.io的三秒等待时间!

// ==UserScript==
// @name         Digit77 Helper
// @namespace    cn.XYZliang.digit77Helper
// @version      2.4.6
// @description  自动复制提取码,跳过ouo.io的三秒等待时间!
// @require      https://code.jquery.com/jquery-3.7.1.min.js
// @license      GNU General Public License v3.0
// @author       XYZliang
// @homepage     https://greasyfork.org/zh-CN/scripts/495107-digit77-helper
// @match        *://www.digit77.com/*
// @match        *://ouo.io/*
// @match        *://ouo.press/*
// @match        *://cloaking.link/*
// @match        *://*.sharepoint.com/*
// @match        *://www.aliyundrive.com/*
// @match        *://pan.quark.cn/*
// @icon         https://www.digit77.com/_nuxt/logo-s.BqVYlxIi.png
// @grant        unsafeWindow
// @grant        GM_setClipboard
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_deleteValue
// @grant        GM_listValues
// @grant        GM_xmlhttpRequest
// @grant        GM_notification
// @grant        GM_getClipboard
// @run-at       document-end
// @connect      *
// ==/UserScript==
/* globals jQuery, $ */
'use strict';

// 用户设置
let settings = GM_getValue('settings', {
	autofill: true,
	ouo: true,
	cloaking: true,
	quark: true,
	baidu: true,
	onedrive: true,
	aliyun: true,
	error: true,
});

// Clean up
cleanupStorage();

let url = location.host;
// Main logic goes here ---------------------------------------------------------
if (url.includes('digit77.com')) {
	handleDigit77();
} else if (url.includes('ouo')) {
	handleOuo();
} else if (url.includes('cloaking')) {
	handleCloaking();
} else if (url.includes('pan.quark.cn')) {
	hanndleQuark();
} else if (url.includes('sharepoint.com')) {
	if (settings.onedrive)
		doFillAction('#txtPassword', '#btnSubmitPassword', 'digit77');
} else {
	console.log('Unknown url! (' + url + ')');
	return;
}

// Function definitions ---------------------------------------------------------
function handleCloakingGo(pw) {
	GM_xmlhttpRequest({
		method: 'POST',
		url: `${location.origin}/links/go`,
		data: $('#go-link').serialize(),
		headers: {
			'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',
		},
		onload: function (response) {
			console.log('Onload response:', response.responseText);
			if (response.status === 200) {
				try {
					var data = JSON.parse(response.responseText);
					if (data.status !== 'error') {
						let realLink = data.url;
						let finalUrlWithPwd = addGetParameter(
							realLink,
							'Digit77HelperPwd',
							//GM_getValue(pw),
							getPasscode(pw),
						);
						setTimeout(() => {
							window.location.href = finalUrlWithPwd;
						}, 1000);
					} else {
						console.error('Error from server:', data);
					}
				} catch (e) {
					console.error('Failed to parse response:', e);
				}
			} else {
				console.error('Failed to get the real link', response.status);
			}
		},
		onerror: function (response) {
			console.error(
				'Request failed:',
				response.status,
				response.statusText,
			);
		},
	});
}

// Function definitions ---------------------------------------------------------
function handleCloaking() {
	if (!settings.cloaking) return;
	console.group(`[Digital77 Helper] -- ${location.origin}`);
	consoleLog('正在跳过Cloaking');
	// Directly set text using .text() for better consistency and compatibility.
	$('h1').text('Digit77 Helper正在跳过等待!');
	$('#form-continue > button').text('欢迎使用Digit77 Helper');
	let pathSegments = location.pathname.split('/')[1];

	if (settings.error) consoleLog('path segments: ' + pathSegments);
	// set cloaking status to 0 for the first time
	GM_setValue('cloaking', 0);
	// if the cloaking status is 0, then set the cloaking status to 1
	if (GM_getValue('cloaking') === 0) {
		GM_setValue('cloaking', 1);
		// click the continue button
		$('#form-continue > button').click();

		//request url from cloaking is https://cloaking.link/links/go
		setTimeout(() => {
			handleCloakingGo(pathSegments);
		}, 1000);
	}
	// if find element body > div.container > div > div > div > div:nth-child(5) > a, then extract the link form it
	if (
		$('body > div.container > div > div > div > div:nth-child(5) > a')
			.length > 0
	) {
		WaitForLink();
	}
	console.groupEnd();
}

function WaitForLink() {
	// focuse on the link
	$(
		'body > div.container > div > div > div > section.link-tab-body.mb-5 > div > div > div:nth-child(2) > div > fieldset > div > div.col-md-3.g-link-body > div > div > a',
	).focus();
	// wait for 5 seconds to get the link
	setTimeout(() => {
		let link = $(
			'body > div.container > div > div > div > section.link-tab-body.mb-5 > div > div > div:nth-child(2) > div > fieldset > div > div.col-md-3.g-link-body > div > div > a',
		).attr('href');
		if (link.includes('javascript')) WaitForLink();
		let pw = location.pathname.split('/')[1];
		let passcode = getPasscode(pw)
		if (settings.error)
			consoleLog('Cloaking link: ' + pw + ' pwd: ' + passcode);
		let finalUrl = addGetParameter(
			link,
			'Digit77HelperPwd',
			//GM_getValue(pw),
			passcode,
		);
		window.location.href = finalUrl;
	}, 2800);
}

function hanndleQuark() {
	if (!settings.quark) return;
	// get the password from parameter
	const urlObj = new URL(window.location.href);

	// if the url contains the parameter Digit77HelperPwd, then fill the password

	if (!urlObj.search.includes('Digit77HelperPwd')) {
		return;
	}

	const queryParams = new URLSearchParams(urlObj.search);
	const digit77HelperPwd = queryParams.get('Digit77HelperPwd');
	if (settings.error) {
		console.log('Debugging <夸克网盘>: ');
		console.log('href: ', window.location.href);
		console.log('code: ', digit77HelperPwd);
	}
	if (digit77HelperPwd !== undefined) {
		doFillAction(
			'#ice-container > div.ShareReceivePC--wrapcontainer--3OAJUiU.share-container-cls-name-for-get-dom > div.ShareReceivePC--wrapcontent--2fA9pbO > div > div.ShareReceivePC--content--3zjCAuj > div.ShareReceivePC--input-wrap--2FUw27N > input',
			'#ice-container > div.ShareReceivePC--wrapcontainer--3OAJUiU.share-container-cls-name-for-get-dom > div.ShareReceivePC--wrapcontent--2fA9pbO > div > div.ShareReceivePC--content--3zjCAuj > div:nth-child(5) > button',
			digit77HelperPwd,
		);
	} else alert('请手动粘贴提取码:', GM_getClipboard());
}

function handleOuo() {
	if (!settings.ouo) return;

	console.group(`[Digital77 Helper] -- ${location.href}`);
	consoleLog('正在跳过ouo');

	// Use .ready() to ensure the DOM is fully loaded before attempting to modify elements.
	$(document).ready(function () {
		// Directly set text using .text() for better consistency and compatibility.
		$('h4').text('Digit77 Helper正在跳过等待!');
		$('.btn-main').text('欢迎使用Digit77 Helper');

		let pathSegments = location.pathname.split('/');
		let passcode = getPasscode(pathSegments[2]);
		if (settings.error)
			consoleLog(
				'path segments: ' +
					pathSegments +
					' pwd key: ' +
					pathSegments[2] +
					' pwd: ' +
					//GM_getValue(pathSegments[2]),
					passcode,
			);
		// Check if the path contains 'go' and proceed with the specific logic for those pages.
		if (pathSegments[1] === 'go') {
			let reallyUrlGetter = `${location.origin}/xreallcygo/${pathSegments[2]}`;
			let reallyUrlData = $('#form-go').serialize();

			// Use GM_xmlhttpRequest for cross-origin requests.
			GM_xmlhttpRequest({
				method: 'POST',
				url: reallyUrlGetter,
				data: reallyUrlData,
				headers: {
					'Content-Type':
						'application/x-www-form-urlencoded;charset=UTF-8',
				},
				onload: function (response) {
					// Construct the final URL with the password parameter if the request was successful.
					let finalUrl = addGetParameter(
						response.finalUrl,
						'Digit77HelperPwd',
						passcode,
					);
					if (response.status === 200) {
						// Redirect after a slight delay to enhance ad revenue potentially.
						setTimeout(
							() => (window.location.href = finalUrl),
							1000,
						);
					} else {
						failedToGetJumpAddress(getPasscode(pathSegments[2]));
					}
				},
				onerror: function () {
					failedToGetJumpAddress(getPasscode(pathSegments[2]));
				},
			});
		} else {
			// For non-'go' pages, wait before clicking the main button to pass through ads.
			setTimeout(() => $('.btn-main').click(), 1500);
		}
	});

	console.groupEnd();
}

// Main logic for Digit77 ---------------------------------------------------------
function handleDigit77() {
	$(document).ready(function () {
		// Create settings form directly
		const settingsFormHtml = `
		<details style="margin-top: 20px;">
			<summary style="background-color: crimson;border-radius: 5px;color:white">Digit77 Helper设置</summary>
			<div class="table-wrapper" style="padding-right: 10px;overflow-x: hidden;">
				<div id="settingsForm" class="settings-form"></div>
			</div>
		</details>`;

		console.group(`[Digital77 Helper] -- ${location.href}`);

		console.log('Digit77 Helper 加载成功!');

		// Options for the observer (which mutations to observe)
		const config = { childList: true, subtree: true };

		// Callback function to execute when mutations are observed
		const callback = function (mutationsList, observer) {
			for (let mutation of mutationsList) {
				if (mutation.type === 'childList') {
					let table = document.querySelector('table > tbody');
					if (
						table &&
						table.querySelectorAll('tr > td > div > button')
							.length > 0
					) {
						observer.disconnect(); // Stop observing
						handleLinks();

						// Select description part from the page
						let description = $(
							'#__nuxt > div > section > section > main > div.page_single > div.single_content > div.post_content',
						);
						if (description.length) {
							description.empty();
							description.append(settingsFormHtml);
							populateSettingsForm();
						}

						$(
							'#__nuxt > div > section > section > main > div.page_single > div.single_content > div.AdBox > div > div > div.el-table__inner-wrapper > div.el-table__body-wrapper > div > div.el-scrollbar__wrap.el-scrollbar__wrap--hidden-default > div > table > thead > tr > th.el-table_1_column_4.is-center.is-leaf.el-table__cell > div',
						).text('下载链接 (已成功加载Digit77 Helper)');

						break;
					}
				}
			}
		};

		// Create an instance of MutationObserver
		const observer = new MutationObserver(callback);

		// Select the node that will be observed for mutations
		const targetNode = document.querySelector('#__nuxt');

		// Start observing the target node for configured mutations
		observer.observe(targetNode, config);

		console.groupEnd();
	});
}

function handleLinks() {
	const nuxtDataElement = document.querySelector('#__NUXT_DATA__');

	// Early exit if no Nuxt data found
	if (!nuxtDataElement) {
		console.error('Failed to get #__NUXT_DATA__');
		return;
	}

	try {
		// Parse the JSON data directly from the innerHTML
		const data = JSON.parse(nuxtDataElement.innerHTML);

		// Validate parsed data and slice from 21 to 42
		if (!Array.isArray(data)) {
			throw new Error('Parsed data is not an array');
		}
		const relevantData = data.slice(21, 42);

		// Process each relevant data element
		relevantData.forEach((item, i) => {
			if (
				typeof item === 'string' &&
				(item.includes('cloaking.link') || item.includes('ouo.io'))
			) {
				const nextItem = relevantData[i + 1];
				if (
					nextItem &&
					typeof nextItem === 'string' &&
					nextItem.length < 5 &&
					settings.cloaking &&
					!nextItem.includes('盘') &&
					!nextItem.includes('drive')
				) {
					const key = item.split('/')[3];
					const pwd = nextItem;
					if (settings.error)
						console.log('Key:', key, 'Password:', pwd);
					if (settings.autofill) setPasscode(key, pwd);
				}
			}
		});
	} catch (e) {
		console.error('Failed to parse #__NUXT_DATA__:', e);
	}

	if (settings.error) GM_ShowAllValues();
}

// Helper functions ---------------------------------------------------------
function failedToGetJumpAddress(pwd) {
	if (!settings.error) {
		return;
	}
	GM_notification(
		'获取ouo跳转链接失败!这导致无法自动填写提取码,请手动粘贴提取码!',
		'Digit77 helper错误',
	);
	GM_setClipboard(pwd);
}

function getToday() {
	let date = new Date();
	let day = date.getDate();
	let month = date.getMonth() + 1;
	let year = date.getFullYear();
	let dateKey = `${day}-${month}-${year}`;
	return dateKey;
}

function setPasscode(key, pwd) {
	// append the password to the list of passcodes in the GM storage
	let passcodes = GM_getValue('passcodes', {});
	// store the passwords by the day-month-year
	let dateKey = getToday();
	if (!passcodes[dateKey]) passcodes[dateKey] = {};
	passcodes[dateKey][key] = pwd;
	GM_setValue('passcodes', passcodes);
}

function getPasscode(key) {
	let passcodes = GM_getValue('passcodes', {});
	let dateKey = getToday();
	if (passcodes[dateKey] && passcodes[dateKey][key]) {
		return passcodes[dateKey][key];
	}
	return null;
}

function addGetParameter(url, name, value) {
	url += (url.split('?')[1] ? '&' : '?') + name + '=' + value;
	return url;
}

//  以下代码修改自 网盘智能识别助手
let util = {
	parseQuery(name) {
		let reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)', 'i');
		let r = location.search.substr(1).match(reg);
		if (r != null) return r[2];
		return null;
	},

	sleep(time) {
		return new Promise((resolve) => setTimeout(resolve, time));
	},

	addStyle(id, tag, css) {
		tag = tag || 'style';
		let doc = document,
			styleDom = doc.getElementById(id);
		if (styleDom) return;
		let style = doc.createElement(tag);
		style.rel = 'stylesheet';
		style.id = id;
		tag === 'style' ? (style.innerHTML = css) : (style.href = css);
		document.head.appendChild(style);
	},

	isHidden(el) {
		try {
			return el.offsetParent === null;
		} catch (e) {
			return false;
		}
	},

	query(selector) {
		if (Array.isArray(selector)) {
			let obj = null;
			for (let i = 0; i < selector.length; i++) {
				let o = document.querySelector(selector[i]);
				if (o) {
					obj = o;
					break;
				}
			}
			return obj;
		}
		return document.querySelector(selector);
	},
};

function doFillAction(inputSelector, buttonSelector, pwd) {
	let maxTime = 10;
	let ins = setInterval(async () => {
		maxTime--;
		let input = util.query(inputSelector);
		let button = util.query(buttonSelector);
		if (input && !util.isHidden(input)) {
			clearInterval(ins);
			let lastValue = input.value;
			input.value = pwd;
			//Vue & React 触发 input 事件
			let event = new Event('input', {
				bubbles: true,
			});
			let tracker = input._valueTracker;
			if (tracker) {
				tracker.setValue(lastValue);
			}
			input.dispatchEvent(event);
			await util.sleep(500); //1秒后点击按钮
			button.click();
		} else {
			maxTime === 0 && clearInterval(ins);
		}
	}, 333);
}

function populateSettingsForm() {
	const setting_template = [
		{
			id: 'autofill',
			text: '开启自动填写提取码',
			checked: settings.autofill,
		},
		{
			id: 'cloaking',
			text: '跳过cloaking广告页面的等待时间',
			checked: settings.cloaking,
		},
		{ id: 'ouo', text: '跳过ouo广告页面的等待时间', checked: settings.ouo },
		{ id: 'quark', text: '开启夸克网盘自动提取', checked: settings.quark },
		{ id: 'baidu', text: '开启百度网盘自动提取', checked: settings.baidu },
		{
			id: 'onedrive',
			text: '开启OneDrive自动提取',
			checked: settings.onedrive,
		},
		{ id: 'error', text: 'Debug 模式', checked: settings.error },
		// Continue with other settings as needed...
	];

	const formContainer = $('#settingsForm');
	formContainer.empty(); // Clear previous contents if any

	// Dynamically create and append settings controls
	setting_template.forEach((setting) => {
		const settingControlHtml = `
            <div class="custom-switch custom-control">
                <input class="custom-control-input" type="checkbox" ${
					setting.checked ? 'checked="checked"' : ''
				} id="${setting.id}" name="${setting.id}" />
                <label class="custom-control-label" for="${setting.id}">
                    ${setting.text}
                </label>
            </div>`;
		formContainer.append(settingControlHtml);
	});

	// Append action buttons
	const actionButtonsHtml = `
        <a class="btn btn-rd btn-block btn-lg btn-coral-pink" id="save">保存设置</a>
        <a class="btn btn-rd btn-block btn-lg btn-coral-pink" id="clean">清除缓存</a>
        <a href="https://github.com/lgcyaxi" target="_blank">关于插件<span class="icon-md fab fa-github linklogo"></span></a>`;
	formContainer.append(actionButtonsHtml);

	// Attach event listeners for buttons
	$('#save').on('click', function () {
		let updatedSettings = {};
		$('#settingsForm .custom-control-input').each(function () {
			let settingId = $(this).attr('id');
			let isChecked = $(this).is(':checked');
			updatedSettings[settingId] = isChecked;
		});

		// Save the updated settings object directly
		GM_setValue('settings', updatedSettings);
		alert('Settings have been saved successfully.');
		if (settings.error) {
			console.log('Debugging: ', GM_getValue('settings'));
		}
	});

	$('#clean').on('click', function () {
		let allValues = GM_listValues();
		allValues.forEach((value) => {
			if (value !== 'settings') GM_deleteValue(value);
		});
		alert('Cache cleared, except for settings.');
	});
}

function consoleLog(info, ...args) {
	if (typeof info === 'object') {
		let output = '';
		let styles = [];

		Object.entries(info).forEach(([key, value]) => {
			output += `%c${key}: %c${value}\n`;
			styles.push('color: #007BFF; font-weight: bold;', 'color: black;');
		});

		console.log(output, ...styles, ...args);
	} else {
		console.log(
			`%cDetails:\n%c${info}`,
			'color: #007BFF; font-weight: bold;',
			'color: black;',
			...args,
		);
	}
}

function GM_ShowAllValues() {
	let output = '';
	let styles = [];

	function processValue(value, indent = '') {
		if (typeof value === 'object' && value !== null) {
			Object.entries(value).forEach(([key, val]) => {
				output += `${indent}%c${key}: %c${val}\n`;
				styles.push('color: green;', 'color: black;');
				if (typeof val === 'object' && val !== null) {
					processValue(val, indent + '    ');
				}
			});
		} else {
			output += `${indent}%c${value}: %c${GM_getValue(value)}\n`;
			styles.push('color: green;', 'color: black;');
		}
	}

	GM_listValues().forEach((value) => {
		const val = GM_getValue(value);
		processValue({ [value]: val });
	});

	consoleLog(output, ...styles);
}

function cleanupStorage() {
	let values = GM_listValues();
	let todayKey = getToday();
	values.forEach((value) => {
		if (value !== 'passcodes' && value !== 'settings') {
			GM_deleteValue(value);
		} else if (value === 'passcodes') {
			let passcodes = GM_getValue('passcodes', {});
			Object.keys(passcodes).forEach((dateKey) => {
				if (dateKey !== todayKey) {
					delete passcodes[dateKey];
				}
			});
			GM_setValue('passcodes', passcodes);
		}
	});
	consoleLog('已自动清除缓存!');
}