Greasy Fork

Greasy Fork is available in English.

Weibo Huati Check-in

超级话题集中签到

当前为 2017-11-10 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name		Weibo Huati Check-in
// @description	超级话题集中签到
// @namespace	http://greasyfork.icu/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=//greasyfork.org/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();
}});