绿联云 NAS 助手

直接通过浏览器使用绿联云功能, 免装绿联云客户端

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name					绿联云 NAS 助手
// @namespace			http://tampermonkey.net/
// @version				0.21
// @description		直接通过浏览器使用绿联云功能, 免装绿联云客户端
// @author				cuteribs
// @include				*
// @grant					GM.xmlHttpRequest
// @grant					GM.notification
// @grant					GM.openInTab
// @grant					GM.registerMenuCommand
// @require				https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/core.min.js
// @require				https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/md5.min.js
// ==/UserScript==

// 请自行修改下述配置
const config = {
	// baseUrl: 'http://192.168.6.100:9999'		// NAS 内网地址端口
	baseUrl: 'https://ugreen.kooldns.cn', // NAS 外网中转地址
	userName: 'paigu', // NAS 本地账号
	password: 'paigu', // NAS 本地账号密码
	downloadPath: '/' // 离线下载路径
};

// 实现代码
(function () {
	'use strict';

	const env = {
		title: '绿联云 NAS 助手',
		ugreen_no: null,
		api_token: null,
		partitions: []
	};

	async function login() {
		let passwordHash = CryptoJS.MD5(config.password).toString();
		passwordHash = CryptoJS.MD5(passwordHash).toString();

		const url = `${config.baseUrl}/v1/user/offline/login`;
		const data = {
			platform: 0,
			offline_username: config.userName,
			offline_password: passwordHash
		};

		try {
			const res = await post(url, null, JSON.stringify(data));
			env.ugreen_no = res.data.data.ugreen_no;
			env.api_token = res.data.api_token;
		} catch (error) {
			console.error(error);
		}

		return null;
	}

	async function heartbeat() {
		if (!env.api_token) {
			console.warn('绿联云 NAS 未登录');
			await login();
		}

		let url = `${config.baseUrl}/v1/file/storages?api_token=${env.api_token}`;
		let res = await get(url);

		if (res.code === 8013) {
			await login();
			res = await post(url);
		}

		if (res.code === 200) {
			env.partitions = res.data.storages
				.filter((s) => !s.isExternal)
				.map((s) => s.partitions)
				.flat();
			GM.registerMenuCommand('🚀 启动迅雷远程', launchXunlei);

			for (const p of env.partitions) {
				GM.registerMenuCommand(`🚀 离线下载到: ${p.label} (${calcSpace(p.size, p.used)})`, () =>
					remoteDownload(p.uuid)
				);
			}

			return true;
		}

		alert('绿联云 NAS 登录失败');
		return false;
	}

	async function launchXunlei(e) {
		console.warn('launchXunlei', e);
		if (!(await heartbeat())) return;

		let url = `${config.baseUrl}/thunder/bindcode/get?api_token=${env.api_token}`;
		let bindInfo;

		try {
			const res = await post(url);
			bindInfo = res.data;
		} catch (error) {
			console.error(error);
		}

		if (!bindInfo) return alert('迅雷远程绑定失败');

		url = `https://act-vip-ssl.xunlei.com/remote_zspace/index.html?biz=ug&sn=${bindInfo.sn}&bindcode=${bindInfo.bindcode}`;
		GM.openInTab(url);
	}

	async function remoteDownload(uuid) {
		const downloadUrl = prompt('输入下载连接');

		if (!downloadUrl) return;

		const url = `${config.baseUrl}/v1/dl/add?api_token=${env.api_token}`;
		const data = new FormData();
		data.append('uuid', uuid);
		data.append('path', `/.ugreen_nas/${env.ugreen_no}${config.downloadPath}`);
		data.append('type', 8);
		data.append('uri', downloadUrl);

		try {
			const res = await post(url, { 'Content-Type': undefined }, data);
			notify('离线下载添加成功', env.title);
			alert('离线下载添加成功');
		} catch (error) {
			console.error(error);
		}
	}

	function get(url, headers) {
		return new Promise((resolve, reject) => {
			console.log('get', url, headers);
			GM.xmlHttpRequest({
				url,
				headers,
				method: 'GET',
				responseType: 'json',
				onload: (res) => resolve(res.response),
				onerror: (res) => reject(res)
			});
		});
	}

	function post(url, headers, data) {
		return new Promise((resolve, reject) => {
			console.log('post', url, headers, data);
			GM.xmlHttpRequest({
				url,
				headers,
				data,
				method: 'POST',
				responseType: 'json',
				onload: (res) => resolve(res.response),
				onerror: (res) => reject(res)
			});
		});
	}

	function calcSpace(size, used) {
		const available = (size - used).getFileSize();
		const total = size.getFileSize();
		return `${available.size.toFixed(2)}/${total.size.toFixed(2)} ${total.unit}`;
	}

	function notify(text, title) {
		GM.notification(text, title);
	}

	Number.prototype.getFileSize = function () {
		const units = ['KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
		let size = this,
			index = 0;

		while (size >= 1024) {
			size /= 1024;
			index++;
		}

		return { size, unit: units[index] };
	};

	GM.registerMenuCommand('☁ 登录绿联云', heartbeat);
})();