Weibo Huati Check-in

超级话题集中签到

目前為 2017-11-10 提交的版本,檢視 最新版本

// ==UserScript==
// @name		Weibo Huati Check-in
// @description	超级话题集中签到
// @namespace	https://gf.qytechs.cn/users/10290
// @version		0.3.2017111107
// @author		xyau
// @match		http*://*.weibo.com/*
// @match		http*://weibo.com/*
// @icon		https://n.sinaimg.cn/photo/5b5e52aa/20160628/supertopic_top_area_big_icon_default.png
// @grant		GM_getValue
// @grant		GM_setValue
// @grant		GM_deleteValue
// @grant		GM_xmlhttpRequest
// @connect		m.weibo.cn
// @connect		login.sina.com.cn
// @connect		passport.weibo.cn
// @connect		weibo.com
// ==/UserScript==

window.addEventListener('load', () => {
if (/\bun\b/.test(document.cookie)) {
/**
 * @const	{object}	DEFAULT_CONFIG				默认设置
 * @const	{boolean}	DEFAULT_CONFIG.autoCheckin	自动签到
 * @const	{string}	DEFAULT_CONFIG.checkinMode	签到模式
 * @const	{boolean}	DEFAULT_CONFIG.checkNormal	普话签到
 * @const	{boolean}	DEFAULT_CONFIG.autoCheckState	自动查询状态
 * @const	{boolean}	DEFAULT_CONFIG.openDetail	展开详情
 * @const	{int}		DEFAULT_CONFIG.maxHeight	详情限高(px)
 * @const	{int}		DEFAULT_CONFIG.timeout		操作超时(ms)
 * @const	{int}		DEFAULT_CONFIG.retry		重试次数
 * @const	{int}		DEFAULT_CONFIG.delay		操作延时(ms)
 */
const DEFAULT_CONFIG = Object.freeze({
	autoCheckin: true,
	checkinMode: 'followListMode',
	checkNormal: true,
	autoCheckState: false,
	openDetail: true,
	maxHeight: 360,
	timeout: 5000,
	retry: 5,
	delay: 0,
}),

	/**
	 * @const	{object}	USER		当前用户
	 * @const	{string}	USER.UID	用户ID
	 * @const	{string}	USER.NICK	用户昵称
	 */
	USER = Object.freeze({
		  UID: $CONFIG.uid,
		  NICK: $CONFIG.nick,
	  });
/* @global	{string}	记录名称 */
var logName;

/**
 * @global	{object}	log		签到记录
 * @global	{object[]}	log.已签	已签话题列表
 * @global	{object[]}	log.待签	待签话题列表
 * @global	{object}	log.异常	签到异常列表
 */
let	log = {},

	/* @global	{string}	date		当前东八区日期 */
	date = new Date(new Date().getTime() + 288e5).toJSON().substr(0, 10).replace(/-0?/g, '/'),

	/* @global	{Task|null}	currentTask 当前 xhr 任务 */
	currentTask = null,

	/**
	 * 任务构造,初始化通用 xhr 参数
	 * @constructor
	 * @param	{string}	name			任务名称
	 * @param	{object}	options			附加 xhr 参数
	 * @param	{function}	load			成功加载函数
	 * @param	{function}	retry			重试函数
	 * @param	{function}	[retryButton=]	重试按钮函数
	 */
	Task = window.Task || function (name, options, load, retry, retryButton) {
		this.name = name;
		this.onerror = function(errorType='timeout') {
			initLog(name, 0);
			log[name] += 1;

			if (errorType != 'timeout') {
				console.error(`${name}异常`);
				console.info(this);
			}

			if (log[name] < config.retry + 1) {
				setStatus(name + (errorType === 'timeout' ? `超过${config.timeout / 1e3}秒` : '异常') + `,第${log[name]}次重试…`);
				retry();
			} else {
				setStatus(`${name}超时/异常${log[name]}次,停止自动重试`);

				if (retryButton)
					retryButton();
				else
					clearTask();
			}
		};
		this.xhrConfig = {
			synchoronous: false,
			timeout: config.timeout,
			onloadstart: () => {
				currentTask = this;
				if (checkinStatus){
					if (!log.hasOwnProperty(name))
						setStatus(`${name}…`);
					if (retryButton) {
						/* 跳过按钮 */
						let skipHuati = document.createElement('a');
						skipHuati.classList.add('S_ficon');
						skipHuati.onclick = () => {
							this.xhr.abort();
							retryButton();
							skipHuati.remove();
						};
						skipHuati.innerText = '[跳过]';
						checkinStatus.appendChild(skipHuati);
					}
				}
			},
			onload: (xhr) => {
				if (xhr.finalUrl.includes('login')) {
					xhr.timeout = 0;
					/* 登录(不可用)跳转 */
					let loginJump = GM_xmlhttpRequest({
						method: 'GET',
						synchronous: false,
						timeout: config.timeout,
						url: /url=&#39;([^']+)&#39;/.exec(xhr.responseText)[1],
						onloadstart: () => this.xhr = loginJump,
						onload: (xhr) => this.load(xhr),
						ontimeout: xhr.ontimeout,
					});
				}
				else
					this.load(xhr);
			},
			ontimeout: () => this.onerror(),
		};

		Object.assign(this.xhrConfig, options);

		this.load = (xhr) => setTimeout(load(xhr), config.delay);
		this.xhr = GM_xmlhttpRequest(this.xhrConfig);
	},

	clearTask = function() {
		currentTask = null;
		checkinClose.title = '关闭';
	},

	initLog = function(key, initialValue) {
		if (!log.hasOwnProperty(key))
			log[key] = initialValue;
	},

	/**
	 * see DEFAULT_CONFIG
	 * @global	{object}	config		脚本设置
	 * @global	{object}	lastCheckin	上次签到记录
	 * @global	{array}		whitelist	话题白名单
	 */
	config = Object.assign(Object.assign({},DEFAULT_CONFIG), JSON.parse(GM_getValue(`config${USER.UID}`, '{}'))),
	lastCheckin = JSON.parse(GM_getValue(`lastCheckin${USER.UID}`, '{}')),
	whitelist = JSON.parse(GM_getValue(`whitelist${USER.UID}`, '[]')),

	initCheckinBtn = function() {
	checkinBtn.style = 'cursor: pointer';
	Array.from(checkinBtn.querySelectorAll('em')).forEach((em) => {em.removeAttribute('style');});
	checkinBtn.querySelector('.signBtn').innerText = '超话签到';
	checkinBtn.title = '左击开始签到/右击配置脚本';
	console.groupEnd(logName);
	},

	/* @param	{string}	operationName	操作名称 */
	alterCheckinBtn = function(operationName) {
		checkinBtn.style.pointerEvents = 'none';
		Array.from(checkinBtn.querySelectorAll('em')).forEach((em) => {em.style.color = '#fa7d3c';});
		checkinBtn.querySelector('.signBtn').innerText = `${operationName}中…`;
	},

	/* @param	{boolean}	auto	自动开始*/
	huatiCheckin = function(auto=true) {
		console.group(logName='微博超话签到');

		/**
		 * 获取关注话题列表
		 * @param	{object[]}	[huatiList=[]]		关注话题列表
		 * @param	{string}	huatiList[].name	名称
		 * @param	{string}	huatiList[].hash	编号
		 * @param	{int|null}	huatiList[].level	超话等级
		 * @param	{boolean}	huatiList[].checked	超话已签
		 * @param	{string}	[type='super']		超话或普话, 'super'/'normal'
		 * @param   {int}		[total=0]			关注话题数量
		 * @param	{int}		[page=1]			列表页码
		 */
		let getFollowList = function(huatiList=[], type='super', total=0, page=1) {

			let getPage = new Task(
				`获取${type === 'super' ? '超' : '普'}话列表第${page}页`,
				{
					method: 'GET',
					url: `https://m.weibo.cn/api/container/getIndex?containerid=100803_-_page_my_follow_${type}&page=${page}`,
				},
				(xhr) => parsePage(xhr),
				() => getFollowList(huatiList, type, total, page)
			),

			parsePage = function(xhr) {
				let data = JSON.parse(xhr.responseText);

				if (!data.cardlistInfo) {
					getPage.onerror('error');
				} else {
					if (page === 1)
						total += data.cardlistInfo.total;

					data.cards[0].card_group.forEach(function(card) {
						if (card.card_type === 4) {
							let huati = {
								name: card.desc.slice(1, -1),
								level: type === 'super' ?
								+/level(\d+)\./.exec(card.icon)[1] : null,
								checked: !!card.avatar_url,
								hash: null,
								element: null
							};

							if (lastHuatiList && lastHuatiList.includes(huati.name)) {
								if (!todayChecked) {
									Object.assign(huati, log.待签.find((huati_) => huati_.name === huati.name));
									if (huati.checked)
										Object.assign(huati, log.待签.splice(log.待签.findIndex((huati_) => huati_.name === huati.name), 1).pop());
								} else {
									huati.hash = log.已签[huati.name];
									huati.element = document.getElementById(`_${huati.hash}`);
								}
							} else {
								huati.hash = /100808(\w+)&/.exec(card.scheme)[1];
								huati.element = initElement(huati.name, huati.hash);
							}
							huatiList.push(huati);

							if (huati.checked) {
								if (!lastHuatiList || (lastHuatiList.includes(huati.name) ? !todayChecked : true)) {
									checkinDone.appendChild(huati.element);
									initLog('已签', {});
									log.已签[huati.name] = huati.hash;
								}
							} else if (!lastHuatiList || (lastHuatiList.includes(huati.name) ? todayChecked && type === "super" : true)){
								checkinToDo.appendChild(huati.element);
								initLog('待签', []);
								log.待签.push(huati);
							}
							if (huati.level)
								setStatus(`Lv.${huati.level}`, huati.element);
						}
					});

					if (huatiList.length < total)
						getFollowList(huatiList, type, total, page + 1);
					else if (config.checkNormal && type != 'normal')
						getFollowList(huatiList, 'normal', total);
					else {
						setStatus(`关注列表获取完毕,共${total}个${config.checkNormal ? '话题' : '超话'},` + (log.hasOwnProperty('待签') ? `${log.待签.length}个待签` : '全部已签'));
						console.table(huatiList);
						readyCheckin();
					}
				}
			};
		},

			readyCheckin = function(){
				console.info(log);

				if (log.hasOwnProperty('待签')) {
					if (config.autoCheckin)
						checkin(log.待签.shift());
					else {
						clearTask();
						/* 开始签到按钮 */
						let startCheckin = document.createElement('a');
						startCheckin.classList.add('S_ficon');
						startCheckin.onclick = () => checkin(log.待签.shift());
						startCheckin.innerText = '[开始签到]';
						checkinStatus.appendChild(startCheckin);
					}
				} else {
					clearTask();
					initCheckinBtn();
				}
			},

			/* 获取话题编号	@param	{array}	list	话题名称列表 */
			getHash = function(list) {
				let name = list.shift(),
					huatiGetHash = new Task(
					`${name}话题信息获取`,
					{
						method: 'HEAD',
						url: `https://m.weibo.cn/api/container/getIndex?type=topic&value=${name}`,
					},
					(xhr) => {
						if (xhr.status === 200) {
							let regexp = /fid%3D100808(\w+)/g,
								hash = regexp.exec(xhr.responseHeaders.match(regexp).pop())[1];
							let element = initElement(name, hash);
							checkinToDo.append(element);
							initLog('待签', []);
							log.待签.push({name, hash, element});
							if (list.length)
								getHash(list);
							else {
								setStatus(`话题列表获取完毕,共${(log.hasOwnProperty('已签') ? Object.keys(log.已签).length : 0) + (log.hasOwnProperty('待签') ? log.待签.length : 0)}个话题` + (log.hasOwnProperty('待签') ? `${log.待签.length}个待签` : '全部已签'));
								readyCheckin();
							}
						}
					});
				},

			getWhitelist = function() {
				let toDoList = whitelist.slice(0);
				if (!whitelist.length) {
					setStatus('尚未设置签到话题白名单!<a>[设置]</a>');
					checkinStatus.querySelector('a').onclick = () => {
						setupConfig();
						whitelistMode.click();
						editWhitelist.click();
						whitelistBox.focus();
					};
					clearTask();
					initCheckinBtn();
				} else {
					if (lastHuatiList) {
						for (let name of lastHuatiList) {
							if (!whitelist.includes(name)) {
								if (!todayChecked) {
									let index = log.待签.findIndex((huati) => huati.name === name);
									log.待签[index].element.remove();
									log.待签.splice(index, 1);
								}
							} else
								toDoList.splice(toDoList.indexOf(name), 1);
						}
					}
					if (toDoList.length)
						getHash(toDoList);
					else {
						setStatus(`话题列表获取完毕,共${(log.hasOwnProperty('已签') ? Object.keys(log.已签).length : 0) + (log.hasOwnProperty('待签') ? log.待签.length : 0)}个话题` + (log.hasOwnProperty('待签') ? `${log.待签.length}个待签` : '全部已签'));
						readyCheckin();
					}
				}
			},

			/**
			 * 话题签到
			 * @param	{object}	huati		话题,参见 {@link getFollowList#huatiList}
			 * @param	{boolean}	checkinAll	签到全部话题
			 */
			checkin = function(huati, checkinAll=true) {
				let huatiCheckin = new Task(
					`${huati.name}话题签到`,
					{
						method: 'GET',
						url: `/p/aj/general/button?api=http://i.huati.weibo.com/aj/super/checkin&id=100808${huati.hash}`,
					},
					(xhr) => {
						let data = JSON.parse(xhr.responseText),
							code = +data.code;

						switch (code) {
							case 100000:
								setStatus(`签到第${/\d+/g.exec(data.data.alert_title)[0]}名,经验+${/\d+/g.exec(data.data.alert_subtitle)[0]}`, huati.element, true);
							case 382004: {
								if (code != 100000)
									setStatus('已签', huati.element, true);
								checkinDone.appendChild(huati.element);
								initLog('已签', {});
								log.已签[huati.name] = huati.hash;
								Object.assign(lastCheckin, {date, nick: USER.NICK});
								Object.assign(lastCheckin, log.已签);
								GM_setValue(`lastCheckin${USER.UID}`, JSON.stringify(lastCheckin));
								break;
							}
							default: {
								setStatus(data.msg, huati.element, true);
								initLog('异常', {});
								log.异常[huati.name] = {huati, code: data.code, msg: data.msg, xhr: xhr};
								huatiCheckin.onerror('error');
							}
						}
						if (checkinAll) {
							if (log.待签.length > 0)
								checkin(log.待签.shift());
							else {
								clearTask();
								setStatus(`${date} 签到完成`);
								checkinToDo.parentNode.style.display = 'none';
								checkinDone.parentNode.setAttribute('open', '');
								Object.assign(lastCheckin, {allChecked: true});
								GM_setValue(`lastCheckin${USER.UID}`, JSON.stringify(lastCheckin));
								console.info(log);
								initCheckinBtn();
								if (config.autoCheckState)
									checkState();
							}
						}
					},
					() => checkin(huati, false),
					() => {
						log.待签.push(huati);
						if (log.待签.length > 0)
							checkin(log.待签.shift());
						else
							clearTask();

						let retryHuati =document.createElement('a');
						retryHuati.classList.add('S_ficon');
						retryHuati.onclick = () => checkin(Object.assign({}, huati), false);
						retryHuati.innerText = '[重试]';
						setStatus(retryHuati, huati.element, true);
					}
				);
			},

			initElement = function(name, hash) {
				/**
				 * 文本限宽输出
				 * @param	{string}	text	输入文本
				 * @param	{int}		length	宽度限定
				 * @return	{string}	输出文本
				 */
				let shorten = function(text, length) {
						let count = 0;
						for (let index in text) {
							let increment = /[\x00-\x7f]/.test(text[index]) ? 1 : 2;
							if (count + increment > length - 2)
								return `${text.substr(0, index)}…`;
							count += increment;
						}
						return text;
					},
					element = document.createElement('li');
				element.id = `_${hash}`;
				element.innerHTML = `<span class=order></span>.<a href=//weibo.com/p/100808${hash} target=_blank title=${name}>${shorten(name, 12)}</a><span class=info></span>`;
				return element;
			};

		alterCheckinBtn('签到');

		if (!lastCheckin.date || lastCheckin.date != date || !lastCheckin.allChecked || !auto) {

			/* 设置信息展示界面 */
			let checkinCSS = document.getElementById('checkinCSS') || document.createElement('style');
			checkinCSS.id = 'checkinCSS';
			checkinCSS.type = 'text/css';
			checkinCSS.innerHTML = `#checkinInfo {z-index:10000;position:fixed;left: 0px;bottom: 0px;min-width:320px;max-width: 640px;opacity: 0.9}#checkinInfo .W_layer_title {border-top: solid 1px #fa7f40}#checkinStatus {float: right;padding: 0 60px 0 10px}#checkinMore {right: 36px}#checkinClose {right: 12px}#checkinDetail {display: ${config.openDetail ? '' : 'none'};margin: 6px 12px;padding: 2px;max-height: ${config.maxHeight}px;overflow-y:auto;}${scrollbarStyle('#checkinDetail')}#checkinDetail summary {margin: 2px}#checkinDetail ol {column-count: 3}#checkinDetail li {line-height: 1.5}#checkinInfo a {cursor: pointer}#checkinDetail .info {float: right}#checkinStatus ~ .W_ficon {position: absolute;bottom: 0px;font-size: 18px;}`;
			document.head.appendChild(checkinCSS);

			let	checkinInfo = document.getElementById('checkinInfo') || document.createElement('div');
			checkinInfo.id = 'checkinInfo';
			checkinInfo.classList.add('W_layer');
			checkinInfo.innerHTML = `<div class=content><div><div id=checkinDetail><details open style=display:none><summary class="W_f14 W_fb">待签</summary><ol id=checkinToDo></ol></details><details style=display:none><summary class="W_f14 W_fb">已签</summary><ol id=checkinDone></ol></details></div></div><div class=W_layer_title>${USER.NICK}<span id=checkinStatus></span><a id=checkinMore title=${config.openDetail ? '收起' :'详情'} class="W_ficon S_ficon">${config.openDetail ? 'c' : 'd'}</a><a id=checkinClose title=${currentTask ? '中止' : '关闭'} class=W_ficon S_ficon>X</a></div></div>`;
			document.body.appendChild(checkinInfo);

			checkinMore.onclick = function() {
				if (this.innerText === 'd') {
					this.innerText = 'c';
					this.title = '收起';
					checkinDetail.removeAttribute('style');
				} else {
					this.innerText = 'd';
					this.title = '详情';
					checkinDetail.style.display = 'none';
				}
			};

			checkinClose.onclick = function() {
				if (currentTask) {
					currentTask.xhr.abort();
					setStatus(`${currentTask.name}中止`);
					clearTask();
					initCheckinBtn();
				} else {
					checkinInfo.remove();
					checkinCSS.remove();
					initCheckinBtn();
				}
			};

			[checkinToDo, checkinDone].forEach((ol, i) =>
				['DOMNodeInserted', 'DOMNodeRemoved'].forEach((event) =>
				ol.addEventListener(event, function() {
				let isRemoval = event != 'DOMNodeInserted',
					subtotal = ol.childElementCount - (isRemoval ? 1 : 0);
				if (this.parentNode.style.display === 'none')
					this.parentNode.removeAttribute('style');
				this.previousSibling.innerText = `${i ? '已' : '待'}签${subtotal}个话题`;
				Array.from(this.querySelectorAll('li .order')).forEach((span) => /* 计算序号并按小计添加 en quad 进行格式化 */
					span.innerText = (Array.from(span.parentNode.parentNode.querySelectorAll('li')).findIndex((li) =>
						li === span.parentNode) + (isRemoval ? 0 : 1)).toString().padStart(subtotal.toString().length).replace(/ /g, String.fromCharCode(8192)));
			})));

			/* 开始获取话题列表 */
			checkinClose.title = '中止';

			if (lastCheckin.date) {
				setStatus(`从${lastCheckin.date}签到记录读取话题列表`);
				var lastHuatiList = [],
					todayChecked = lastCheckin.date === date;
				for (let name in lastCheckin) {
					if (!['date', 'nick', 'allChecked'].includes(name)) {
						lastHuatiList.push(name);
						let hash = lastCheckin[name],
							element = initElement(name, hash);
						if (!todayChecked) {
							checkinToDo.appendChild(element);
							initLog('待签', []);
							log.待签.push({name, hash, element});
						} else {
							checkinDone.appendChild(element);
							initLog('已签', {});
							log.已签[name] = hash;
						}
					}
				}
				if (!todayChecked)
					lastCheckin = {};
				if (log.hasOwnProperty('待签') && log.待签.length) {
					setStatus(`话题列表读取完毕,共${log.待签.length}个话题待签`);
					if (config.checkinMode === 'followListMode') {
						if (config.autoCheckin)
							checkin(log.待签.shift());
						else {
							/* 开始签到按钮 */
							let startCheckin = document.createElement('a');
							startCheckin.classList.add('S_ficon');
							startCheckin.onclick = () => checkin(log.待签.shift());
							startCheckin.innerText = '[开始签到]';
							checkinStatus.appendChild(startCheckin);
						}
					}
				}
			}
			switch (config.checkinMode) {
				case 'followListMode':
					getFollowList();
					break;
				case 'whitelistMode':
					getWhitelist();
					break;
			}
		} else
			initCheckinBtn();
	},

	importWhitelist = () => Object.keys(lastCheckin).filter((key) => !['date', 'nick', 'allChecked'].includes(key)),

	checkState = function(list=importWhitelist(), manageList=[]) {
		if (!arguments.length) {
			console.group('话题状态查询');
			alterCheckinBtn('查询');
		}
		let load = (xhr, name, hash, manage=false) => {
			try {
				let data = JSON.parse(manage ? xhr.responseText.slice(1, -1) : xhr.responseText),
					element = document.getElementById(`_${hash}`);
				if (!data.ok) {
					(manage ? manageList : list).push(name);
				} else if (manage) {
					setStatus(';' + data.card_group[1].group.map(
							(item) => item.item_desc + item.item_title).join(), element, true);
				} else {
					if (Object.keys(data.cardlistInfo.button).length)
						manageList.push(name);
					let cards = data.cards;
					setStatus((
							11 != +cards[1].card_type ? '' : cards[1].card_group.reduce(
								(text, card) => 4 != +card.card_type || !/\d/.test(card.desc) ? text : text +
								card.desc.replace(/[^\.\d]*(\.?\d+)\D.*/g,
												  (_, match) => (match.includes('.') ? 'Lv' : '-') + match),
								'')), element);
					element.title = cards[0].group.map(
							(item) => item.item_title + item.item_desc).join() + (!cards[3] || 4 != cards[3].card_type ? '' : ',' + cards[3].desc.replace('超级话题', ''));
				}
			} catch (e) {
				console.error(e);
				(manage ? manageList : list).push(name);
			}
			checkState(list, manageList);
		};
		if (!list.length) {
			if (!manageList.length) {
				setStatus('查询完毕。');
				clearTask();
				initCheckinBtn();
				console.groupEnd('话题状态查询');
			} else {
			let name = manageList.shift(),
				hash = lastCheckin[name],
				stateCheck = new Task(
					`查询${name}话题考核进度`,
					{
						method: 'GET',
						url: `https://m.weibo.cn/api/container/getItem?itemid=100808${hash}_-_assess_progress`,
					},
					(xhr) => load(xhr, name, hash, true)
				);
			}
		} else {
			let name = list.shift(),
				hash = lastCheckin[name],
				stateCheck = new Task(
					`查询${name}话题状态`,
					{
						method: 'GET',
						url: `https://m.weibo.cn/api/container/getIndex?containerid=231140${hash}_-_super_detail`,
					},
					(xhr) => load(xhr, name, hash)
			);
		}
	},

	setupConfig = function() {
		console.group(logName='微博超话签到设置');
		alterCheckinBtn('设置');

		let configCSS = document.createElement('style');
		configCSS.id = 'configCSS';
		configCSS.type = 'text/css';
		configCSS.innerHTML = `#configForm {z-index:6666;position:fixed;right: 0px;top: 50px;width:540px;opacity: 0.9}#configForm a {cursor: pointer}#configForm form {height: 288px}#configForm header {text-align: center}#configClose {position: absolute;z-index: 2;left: 12px;top: 2px;font-size: 18px;}#configForm header img {position: relative;top: 3px;padding-right: 6px}#configForm footer {position: absolute;bottom: 0px;padding: 12px;width: 492px;border-top: solid 1px #ccc}#configForm footer input {margin: 0 12px}#configForm main {margin: 6px 12px;}#configForm fieldset:first-child {width: 240px;float:left;margin-right: 12px}#configForm fieldset {padding: 1px 12px}
#configForm fieldset > fieldset > legend {text-align: right; padding:3px}#configForm input[type=number] {width: 48px}#configForm input[type=button] {padding: 0 12px}#configForm th {font-weight: bold;padding: 6px 0 3px}#configForm table {float: left;margin: 0 6px}#configForm div {padding: 6px;height: 160px;overflow-y: scroll;background-color: whitesmoke;line-height: 1.5}${scrollbarStyle('#configForm textarea', '#configForm div')}#configForm span {float: right; margin-top: 3px}
#configForm textarea {width: 120px; height: 90px;padding: 6px;margin: 6px 0}`;
		document.head.appendChild(configCSS);

		let configForm = document.createElement('div');
		configForm.id = 'configForm';
		configForm.classList.add('W_layer');
		configForm.innerHTML = `<form class=content><header class=W_layer_title><img src=//img.t.sinajs.cn/t6/style/images/pagecard/icon.png>签到脚本设置<a title=关闭 class=W_ficon S_ficon id=configClose>X</a></header><main><fieldset><legend>参数设定</legend>
<fieldset><legend>签到模式</legend><label for=followListMode><input id=followListMode type=radio value=followListMode name=checkinMode title=先获取话题关注列表再进行签到>关注列表模式  <label for=checkNormal><input type=checkbox id=checkNormal for=followListMode name=checkinModeSub>普话签到</label></label><br>
<label for=whitelistMode><input id=whitelistMode type=radio value=whitelistMode name=checkinMode title=只读取本地名单并按顺序签到>白名单模式  <input type=button id=editWhitelist for=whitelistMode name=checkinModeSub value=编辑名单></label></fieldset>
<fieldset><legend>运行参数</legend>请求延时 <input type=number id=delay min=0 max=1000 step=100> 毫秒<span><label for=autoCheckin><input type=checkbox id=autoCheckin>自动签到</label><br><label for=autoCheckState><input type=checkbox id=autoCheckState title=自动查询等级、连续签到天数、话题数据及主持人考核进度>自动查询</label></span><br>请求超时 <input type=number id=timeout min=1000 max=10000 step=100> 毫秒<br>自动重试 <input type=number id=retry min=0 max=10> 次</fieldset>
<fieldset><legend>签到详情</legend>最大高度 <input type=number id=maxHeight min=60 max=1080 step=60> 像素<span><label for=openDetail><input type=checkbox id=openDetail>自动展开</label></span></fieldset>
</fieldset>
<fieldset id=accountInfo><legend>账户信息</legend><table><tbody><tr><th>昵称</th></tr><tr><td>${USER.NICK}</td></tr><tr><th>ID</th></tr><tr><td>${USER.UID}</td></tr><tr><th>上次签到</th></tr><tr><td>${lastCheckin.date || '尚无记录'}</td></tr><tr><th><input type=button value=状态查询 id=stateCheck></th></tr><tr><th><input type=button value=清空记录 id=configClear ${Object.keys(lastCheckin).length != 0 ? '' : 'disabled'}></th></tr></tbody></table><div>${importWhitelist().map((name) => {
			let hash = lastCheckin[name];
			return '<p id=_' + hash + '><a href=//weibo.com/p/100808' + hash + ' target=_blank>' + name + '</a><i class=info></i></p>';
		}).join('')}</div></fieldset>
<fieldset id=whitelistEditor style=display:none><legend>签到名单编辑</legend>请在下方编辑名单,每行一个话题名,完成后点击[保存名单]按钮。<br><textarea id=whitelistBox placeholder="每行一个话题名,不带#号,如\n读书\n美食">${whitelist.join('\n')}</textarea><span><input type=button id=whitelistSave value=保存名单 disabled><input type=button id=whitelistImport value=导入列表 title=导入签到记录中的话题列表></fieldset>
<footer><input type=button value=保存 id=configSave disabled><input type=button value=还原 id=configRestore disabled><input type=button value=重置 id=configDefault><span><a href=//gf.qytechs.cn/scripts/32143/feedback target=_blank>GreasyFork</a> / <a href=//gist.github.com/xyauhideto/b9397058ca3166b87e706cbb7249bd54 target=_blank>Gist</a> / <a href=//weibo.com/678896489 target=_blank>微博</a> 报错请F12提供后台记录</span></footer> </form>`;
		document.body.appendChild(configForm);
		let inputs = Array.from(configForm.querySelectorAll('input:not([type=button])')),

			getWhitelist = () => whitelistBox.value.split('\n').filter((name) => name.trim().length),
			getInputs = () => inputs.reduce((conf, input) => {
				if (!(input.type === 'radio' && !input.checked))
					conf[input.type != 'radio' ? input.id : input.name] = input.type != 'number' ? input.type != 'checkbox' ? input.value : input.checked : Math.max(+input.min, Math.min(+input.max, +input.value));
//				console.log(conf);
				return conf;
			}, {}),

			initForm = function(conf=config) {
				for (let [key, value] of Object.entries(conf)) {
					let input = document.getElementById(key) || document.querySelector(`[name=${key}][value=${value}]`);
					if (typeof value === 'boolean')
						input.checked = value;
					else if (typeof value === 'string') {
						input.checked = true;
						document.querySelector(`input[for=${value}]`).removeAttribute('disabled');
						let other = document.querySelector(`[name=${key}Sub]:not([for=${value}])`);
						if (other.value === '退出编辑')
							other.click();
						other.disabled = true;
					} else
						input.value = value;
				}
				configRestore.disabled = isEqual(conf, config);
				configDefault.disabled = isEqual(conf, DEFAULT_CONFIG);
				configSave.disabled = configRestore.disabled;
				whitelistBox.oninput();
			},

			/**
			 * 简单对象、阵列比较
			 * @param	{object|array}	x	比较对象/阵列x
			 * @param	{object|array}	y	比较对象/阵列y
			 * @return	{boolean}	比较结果
			 */
			isEqual = function(x, y) {
				if (Object.values(x).length != Object.values(y).length)
					return false;
				if (x instanceof Array) {
					for (let value of x) {
						if (!y.includes(value))
							return false;
					}
				} else {
					for (let key in x) {
						if (!y.hasOwnProperty(key) || x[key] != y[key])
							return false;
					}
				}
				return true;
			};

		stateCheck.onclick = ()=>checkState();

		configSave.onclick = function() {
			config = getInputs();
			if (!whitelistSave.disabled && confirm('尚未保存签到名单,一起保存?'))
				whitelistSave.click();
			if (editWhitelist.value === '退出编辑')
				editWhitelist.click();
			GM_setValue(`config${USER.UID}`, JSON.stringify(config));
			initForm();
		};
		configRestore.onclick = () => initForm();
		configDefault.onclick = function() {
			GM_deleteValue(`config${USER.UID}`);
			initForm(DEFAULT_CONFIG);
		};
		configClear.onclick = function() {
			console.warn('清空上次签到');
			console.table(lastCheckin);
			GM_deleteValue(`lastCheckin${USER.UID}`);
			lastCheckin = {};
			document.querySelector('#configForm tr:nth-of-type(6)>td').innerText = '尚无记录';
			document.querySelector('#configForm div').innerText = '';
			this.disabled = true;
		};
		configClose.onclick = function() {
			configCSS.remove();
			configForm.remove();
			initCheckinBtn();
		};

		inputs.forEach(function(input) {
			input.onchange = () => initForm(getInputs());
		});

		inputs.forEach((input) => {
			if (input.title) {
				input.onfocus = () => {
					let tip = document.createElement('i');
					tip.innerText = input.title;
					tip.style = `position:absolute;left:${input.offsetLeft - 10 * input.title.length}px;top:${input.offsetTop + 15}px;padding:3px;border:1px solid grey;color:grey;background-color:white;box-shadow:1px 1px 2px`;
					input.parentNode.append(tip);
				};
				input.onblur = () => input.parentNode.lastChild.remove();
			}
		});

		editWhitelist.onclick = function() {
			if (this.value === '编辑名单') {
				whitelistBox.value = whitelist.join('\n');
				accountInfo.style.display = 'none';
				whitelistEditor.removeAttribute('style');
				this.value = '退出编辑';
			} else {
				whitelistEditor.style.display = 'none';
				accountInfo.removeAttribute('style');
				this.value = '编辑名单';
			}
		};
		whitelistSave.onclick = function() {
			let whitelist_ = getWhitelist();
			if (whitelist_.length || confirm('尚未设定白名单,继续保存?')) {
				whitelist = whitelist_;
				GM_setValue(`whitelist${USER.UID}`, JSON.stringify(whitelist));
				whitelistEditor.style.display = 'none';
				accountInfo.removeAttribute('style');
				editWhitelist.value = '编辑名单';
			}
		};
		whitelistImport.onclick = function() {
			whitelistBox.value = importWhitelist().join('\n');
			whitelistBox.oninput();
		};
		whitelistBox.oninput = function() {
			let whitelist_ = getWhitelist();
			whitelistSave.disabled = isEqual(Object.assign({}, whitelist_), Object.assign({}, whitelist));
			whitelistImport.disabled = isEqual(whitelist_, importWhitelist());
		};

		initForm();
	},

	/**
	 * 提示签到状态
	 * @param	{string|node}	status					当前状态
	 * @param	{node}			[element=checkinStatus]	显示提示的节点
	 * @param	{boolean}		[append=false]			追加节点
	 */
	setStatus = function(status, element=document.getElementById('checkinStatus'), append=false) {
		try {
			if (element.id && element.id.startsWith('_'))
				element = element.querySelector('.info');
			else if (!append)
				console.info(status);

			if (append) {
				if (typeof status != 'string')
					element.appendChild(status);
				else
					element.innerHTML += status;
			} else
				element.innerHTML = status;
		} catch (e) {
			console.error(e);
		}
	},

	scrollbarStyle = function() {
		return Array.from(arguments).map((elementSelector) => `${elementSelector}::-webkit-scrollbar {width: 4px;background-color: #f2f2f5;border-radius: 2px;}${elementSelector}::-webkit-scrollbar-thumb {width: 4px;background-color: #808080;border-radius: 2px;}`).join('');
	},

	/* 隐藏游戏按钮,替换为超话签到 */
	checkinBtn = document.createElement("li");
checkinBtn.id = 'checkinBtn';
checkinBtn.innerHTML = `<a><em class="W_ficon ficon_checkin S_ficon">s</em><em class="S_txt1 signBtn">超话签到</em></a>`;
checkinBtn.addEventListener('contextmenu', () => setupConfig(), true);
checkinBtn.addEventListener('click', () => huatiCheckin(false),	true);

let navLast = document.querySelector('.gn_nav_list li:last-child');
navLast.parentNode.insertBefore(checkinBtn, navLast);
document.querySelector('a[nm=game]').parentNode.style.display = 'none';

/* 清理旧版数据 */
['autoSignbox', 'todaySigned'].forEach((key) => GM_deleteValue(key));

/* 自动签到 */
if (config.autoCheckin)
	huatiCheckin();
}});

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址