Greasy Fork is available in English.

Weibo Huati Check-in

超级话题集中签到

// ==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);
	}
});