Weibo Huati Check-in

超级话题集中签到

目前為 2018-05-31 提交的版本,檢視 最新版本

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

You will need to install an extension such as Tampermonkey to install this script.

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name		Weibo Huati Check-in
// @description	超级话题集中签到
// @namespace	https://greasyfork.org/users/10290
// @version		0.4.2018053114
// @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('unload', () => console.groupEnd());
window.addEventListener('load', () => {
	try {
		if ($CONFIG && '1' !== $CONFIG.islogin) {
			console.warn('尚未登录微博');
			return;
		}
		/**
			 * @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: 'followList',
			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, checkinInfo;//, configForm;

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

			/* @return	{string}	当前东八区日期 */
			getDate = () => 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 = this.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 (!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 = '[跳过]';
							checkinInfo.querySelector('.status').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;
				document.querySelector('.checkin .close').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() {
				if (config.autoCheckState && logName === '微博超话签到')
					checkState();
				if (config.openDetail && document.querySelector('.checkin .detail'))
					document.querySelector('.checkin .done').parentNode.setAttribute('open', '');
				checkinBtn.style = 'cursor: pointer';
				Array.from(checkinBtn.querySelectorAll('em')).forEach((em) => {em.removeAttribute('style');});
				checkinBtn.querySelector('em:last-child').innerText = '超话签到';
				checkinBtn.title = '左击开始签到/右击配置脚本';
				if (logName)
					console.groupEnd();
				logName = document.querySelector('.checkin.config') ? '微博超话签到设置' : null;
			},

			/* @param	{string}	operationName	操作名称 */
			alterCheckinBtn = function(operationName) {
				checkinBtn.style.pointerEvents = 'none';
				Array.from(checkinBtn.querySelectorAll('em')).forEach((em) => {em.style.color = '#fa7d3c';});
				checkinBtn.querySelector('em:last-child').innerText = `${operationName}中…`;
				document.querySelector('.checkin .close').title = '中止';
				logName = '微博超话签到' + (operationName !== '签到' ? operationName : '');
				if (!logName.includes('查询') || operationName !== '设置')
					console.group(logName);
			},

			/* @param	{boolean}	auto	自动开始*/
			huatiCheckin = function(auto=true) {
				const date = getDate();

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

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

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

							if (!data.ok) {
								getPage.onerror('error');
							} else {
								//let cards = data.data.cards.find(c => c.card_type_name.includes('follow'));
								data.data.cards.forEach(c => c.card_group.forEach(function(card) {
									if ([4,8].includes(+card.card_type)) {
										let huati = {
											name: (card.title_sub || card.desc).replace(/#(.*)#/,'$1'),
											level: null,
											checked: !!card.title_flag_pic,
											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 (!lastHuatiList || !lastHuatiList.includes(huati.name) || !todayChecked) {
											if (huati.checked) {
												checkinInfo.querySelector('.done').appendChild(huati.element);
												initLog('已签', {});
												log.已签[huati.name] = huati.hash;
											} else {
												checkinInfo.querySelector('.toDo').appendChild(huati.element);
												initLog('待签', []);
												log.待签.push(huati);
											}
										}
										if (huati.level)
											setStatus(`Lv.${huati.level}`, huati.element);
									}
								}));
								debugger;
								if (data.data.cardlistInfo.since_id)
									getFollowList(huatiList,data.data.cardlistInfo.since_id,type);
								else if (config.checkNormal && type == 'super')
									getFollowList(huatiList,'','normal');
								else {
									setStatus(`关注列表获取完毕,共${huatiList.length}个话题,` + (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 = '[开始签到]';
								checkinInfo.querySelector('.status').appendChild(startCheckin);
							}
						} else {
							clearTask();
							initCheckinBtn();
						}
					},

					/* 获取话题编号	@param	{array}	list	话题名称列表 */
					getHash = function(list) {
						let name = list.shift(),
							huatiGetHash = new Task(
								`${name}话题信息获取`,
								{
									method: 'get',
									url: `https://m.weibo.cn/api/container/getIndex?containerid=100103type%3D1%26q%3D%23${name}%23&page_type=searchall`,
								},
								(xhr) => {
									if (xhr.status === 200) {
										//let regexp = /fid%3D100808(\w+)/g,
										//	hash = regexp.exec(xhr.responseHeaders.match(regexp).pop())[1];
										let data = JSON.parse(xhr.responseText);
										// console.log(data);

										if (!data.ok) {
											getPage.onerror('error');
										} else {
											let hash = data.data.cardlistInfo.containerid.slice(6),
												element = initElement(name, hash);
											checkinInfo.querySelector('.toDo').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>');
							checkinInfo.querySelector('.status').querySelector('a').onclick = () => {
								setupConfig();
								checkinInfo.querySelector('.whitelist .mode').click();
								checkinInfo.querySelector('.whitelist .edit').click();
								checkinInfo.querySelector('.whitelist .box').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;
								// console.log(data);

								switch (code) {
									case 100000:
										if (Object.keys(data.data).length)
											setStatus(
												/\d+/g.exec(data.data.alert_title) ?
												`签到第${/\d+/g.exec(data.data.alert_title)[0]}名,经验+${/\d+/g.exec(data.data.alert_subtitle)[0]}` :
												(console.log(JSON.stringify(data.data)), '签到成功'), huati.element, true);
									case 382004: {
										if (code !== 100000 || 0 === Object.keys(data.data).length)
											setStatus('已签', huati.element, true);
										checkinInfo.querySelector('.done').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} 签到完成`);
										checkinInfo.querySelector('.toDo').parentNode.removeAttribute('open');
										Object.assign(lastCheckin, {allChecked: true});
										GM_setValue(`lastCheckin${USER.UID}`, JSON.stringify(lastCheckin));
										console.info(log);
										initCheckinBtn();
									}
								}
							},
							() => 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 = `<i class=order></i>.<a href=//weibo.com/p/100808${hash} target=_blank title=${name}>${shorten(name, 12)}</a><span class=info></span>`;
						return element;
					};

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

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

					//var
					checkinInfo = document.querySelector('.checkin.info') || document.createElement('div');
					//checkinInfo.id = 'checkinInfo';
					checkinInfo.className = 'W_layer checkin info';
					checkinInfo.innerHTML = `<div class=content><div><div class=detail><details open style=display:none><summary class="W_f14 W_fb">待签</summary><ol class=toDo></ol></details><details style=display:none><summary class="W_f14 W_fb">已签</summary><ol class=done></ol></details></div></div><div class=W_layer_title>${USER.NICK}<span class=status></span><a title=${config.openDetail ? '收起' :'详情'} class="W_ficon S_ficon more">${config.openDetail ? 'c' : 'd'}</a><a title=${currentTask ? '中止' : '关闭'} class="W_ficon S_ficon close">X</a></div></div>`;
					document.body.appendChild(checkinInfo);

					alterCheckinBtn('签到');

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

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

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

					/* 开始获取话题列表 */

					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) {
									checkinInfo.querySelector('.toDo').appendChild(element);
									initLog('待签', []);
									log.待签.push({name, hash, element});
								} else {
									checkinInfo.querySelector('.done').appendChild(element);
									initLog('已签', {});
									log.已签[name] = hash;
								}
							}
						}
						if (!todayChecked)
							lastCheckin = {};
						if (log.hasOwnProperty('待签') && log.待签.length) {
							setStatus(`话题列表读取完毕,共${log.待签.length}个话题待签`);
							if (config.checkinMode === 'followList') {
								if (config.autoCheckin)
									checkin(log.待签.shift());
								else {
									/* 开始签到按钮 */
									let startCheckin = document.createElement('a');
									startCheckin.classList.add('S_ficon');
									startCheckin.onclick = () => checkin(log.待签.shift());
									startCheckin.innerText = '[开始签到]';
									checkinInfo.querySelector('.status').appendChild(startCheckin);
								}
							}
						} else
							initCheckinBtn();
					}
					switch (config.checkinMode) {
						case 'followList':
							getFollowList();
							break;
						case 'whitelist':
							getWhitelist();
							break;
					}
				} else
					initCheckinBtn();
			},

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

			checkState = function(list=importWhitelist()) {
				if (!arguments.length) {
					console.group('话题状态查询');
					alterCheckinBtn('查询');
				}
				let load = (xhr, name, hash) => {
					try {
						let data = JSON.parse(xhr.responseText),
							element = document.getElementById(`_${hash}`);
						if (!data.ok) {
							list.push(name);
						} else {
							let cards = data.data.cards;
							setStatus((
								'我的经验值' != cards[1].card_type_name ? '' : 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);
							let countsCard = !cards[0].card_group ?
								19 != +cards[0].card_type ? null :
							cards[0] : cards[0].card_group.pop();
							if (countsCard)
								element.title = countsCard.group.map(
									(item) => item.item_title + item.item_desc).join() + (!cards[3] || 4 != cards[3].card_type ? '' : ',' + cards[3].desc.replace('超级话题', ''));
							if (cards[2].card_group && cards[2].card_group[1].group)
								setStatus(';' + cards[2].card_group[1].group.map(
									(item) => item.item_desc + item.item_title).join(), element, true);
						}
					} catch (e) {
						console.error(e);
						list.push(name);
					}
					checkState(list);
				};
				if (!list.length) {
					setStatus('查询完毕。');
					clearTask();
					initCheckinBtn();
					console.groupEnd('话题状态查询');
				} 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}_-_detail`,
							},
							(xhr) => load(xhr, name, hash)
						);
				}
			};

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

			//var
			configForm = document.createElement('div');
			//configForm.id = 'configForm';
			configForm.className = 'W_layer checkin config';
			configForm.innerHTML = `<form class=content><header class=W_layer_title><img src=//img.t.sinajs.cn/t6/style/images/pagecard/icon.png>签到脚本设置<span class=status></span><a title=关闭 class="W_ficon S_ficon close">X</a></header><main><fieldset><legend>参数设定</legend>
<fieldset><legend>签到模式</legend><label class=followList title=先获取话题关注列表再进行签到><input type=radio value=followList name=checkinMode>关注列表模式  <label for=checkNormal><input type=checkbox name=checkNormal for=followList class=sub>普话签到</label></label><br>
<label class=whitelist title=只读取本地名单并按顺序签到><input type=radio value=whitelist name=checkinMode>白名单模式  <input type=button class="edit sub" value=编辑名单></label></fieldset>
<fieldset><legend>运行参数</legend>请求延时 <input type=number name=delay min=0 max=1000 step=100> 毫秒<span><label for=autoCheckin><input type=checkbox name=autoCheckin>自动签到</label><br><label title=自动查询等级、连续签到天数、话题数据及主持人考核进度><input type=checkbox name=autoCheckState>自动查询</label></span><br>请求超时 <input type=number name=timeout min=1000 max=10000 step=100> 毫秒<br>自动重试 <input type=number name=retry min=0 max=10> 次</fieldset>
<fieldset><legend>签到详情</legend>最大高度 <input type=number name=maxHeight min=60 max=1080 step=60> 像素<span><label for=openDetail><input type=checkbox name=openDetail>自动展开</label></span></fieldset>
</fieldset>
<fieldset class=account><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=状态查询 class=stateCheck></th></tr><tr><th><input type=button value=清空记录 class=clear ${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 class="whitelist editor" style=display:none><legend>签到名单编辑</legend>请在下方编辑名单,每行一个话题名,完成后点击[保存名单]按钮。<br><textarea class=box placeholder="每行一个话题名,不带#号,如\n读书\n美食">${whitelist.join('\n')}</textarea><span><input type=button class=save value=保存名单 disabled><input type=button class=import value=导入列表 title=导入签到记录中的话题列表></fieldset>
<footer><input type=button value=保存 class=save disabled><input type=button value=还原 class=restore disabled><input type=button value=重置 class=default><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);
			alterCheckinBtn('设置');

			let inputs = Array.from(configForm.querySelectorAll('input:not([type=button])')),

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

				initForm = function(conf=config) {
					for (let [key, value] of Object.entries(conf)) {
						let input = typeof value === 'string' ? configForm.querySelector(`[name=${key}][value=${value}]`) : document.querySelector(`[name=${key}]`);
						if (typeof value === 'boolean')
							input.checked = value;
						else if (typeof value === 'string') {
							input.checked = true;
							input.parentNode.querySelector('.sub').removeAttribute('disabled');
							let other = configForm.querySelector(`[name=${key}]:not([value=${value}])`).parentNode.querySelector('.sub');
							if (other.value === '退出编辑')
								other.click();
							other.disabled = true;
						} else
							input.value = value;
					}
					configForm.querySelector('.restore').disabled = isEqual(conf, config);
					configForm.querySelector('.default').disabled = isEqual(conf, DEFAULT_CONFIG);
					configForm.querySelector('footer .save').disabled = configForm.querySelector('.restore').disabled;
					configForm.querySelector('.whitelist .box').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;
				};

			configForm.querySelector('.stateCheck').onclick = ()=>checkState();

			configForm.querySelector('footer .save').onclick = function() {
				config = getInputs();
				if (!configForm.querySelector('.whitelist .save').disabled && confirm('尚未保存签到名单,一起保存?'))
					configForm.querySelector('.whitelist .save').click();
				if (configForm.querySelector('.whitelist .edit').value === '退出编辑')
					configForm.querySelector('.whitelist .edit').click();
				GM_setValue(`config${USER.UID}`, JSON.stringify(config));
				initForm();
			};
			configForm.querySelector('.restore').onclick = () => initForm();
			configForm.querySelector('.default').onclick = function() {
				GM_deleteValue(`config${USER.UID}`);
				initForm(DEFAULT_CONFIG);
			};
			configForm.querySelector('.clear').onclick = function() {
				console.warn('清空上次签到');
				console.table(lastCheckin);
				GM_deleteValue(`lastCheckin${USER.UID}`);
				lastCheckin = {};
				configForm.querySelector('tr:nth-of-type(6)>td').innerText = '尚无记录';
				configForm.querySelector('div').innerText = '';
				this.disabled = true;
			};
			configForm.querySelector('.close').onclick = function() {
				if (currentTask) {
					currentTask.xhr.abort();
					setStatus(`${currentTask.name}中止`);
					clearTask();
					initCheckinBtn();
				} else {
					configCSS.remove();
					configForm.remove();
					initCheckinBtn();
				}
			};

			inputs.forEach(function(input) {
				input.onchange = () => initForm(getInputs());
				if (input.parentNode.title) {
					input.onfocus = () => {
						let tip = document.createElement('i');
						tip.innerText = input.parentNode.title;
						tip.style = `position:absolute;left:${input.offsetLeft - 10 * input.parentNode.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();
				}
			});

			configForm.querySelector('.whitelist .edit').onclick = function() {
				if (this.value === '编辑名单') {
					configForm.querySelector('.whitelist .box').value = whitelist.join('\n');
					configForm.querySelector('.account').style.display = 'none';
					configForm.querySelector('.whitelist.editor').removeAttribute('style');
					this.value = '退出编辑';
				} else {
					configForm.querySelector('.whitelist.editor').style.display = 'none';
					configForm.querySelector('.account').removeAttribute('style');
					this.value = '编辑名单';
				}
			};
			configForm.querySelector('.whitelist .save').onclick = function() {
				let whitelist_ = getWhitelist();
				if (whitelist_.length || confirm('尚未设定白名单,继续保存?')) {
					whitelist = whitelist_;
					GM_setValue(`whitelist${USER.UID}`, JSON.stringify(whitelist));
					configForm.querySelector('.whitelist.editor').style.display = 'none';
					configForm.querySelector('.account').removeAttribute('style');
					configForm.querySelector('.whitelist .edit').value = '编辑名单';
					configForm.querySelector('.whitelist .box').oninput();
				}
			};
			configForm.querySelector('.checkin .whitelist .import').onclick = function() {
				configForm.querySelector('.whitelist .box').value = importWhitelist().join('\n');
				configForm.querySelector('.whitelist .box').oninput();
			};
			configForm.querySelector('.whitelist .box').oninput = function() {
				let whitelist_ = getWhitelist();
				configForm.querySelector('.whitelist .save').disabled = isEqual(Object.assign({}, whitelist_), Object.assign({}, whitelist));
				configForm.querySelector('.whitelist .import').disabled = isEqual(whitelist_, importWhitelist());
			};

			initForm();
		},

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

			if (typeof status === 'string' && status)
				console.info(status);

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

			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 checkin S_ficon">s</em><em class="S_txt1">超话签到</em></a>`;
		checkinBtn.addEventListener('contextmenu', e => {
			e.preventDefault();
			e.stopPropagation();
			setupConfig();
		});
		checkinBtn.addEventListener('click', () => huatiCheckin(false));

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

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

		/* 自动签到 */
		if (config.autoCheckin)
			huatiCheckin();
	} catch (ReferenceError) {
		console.error(ReferenceError);
		setTimeout(this.onload, 500);
	}
});