Greasy Fork

Greasy Fork is available in English.

Weibo Huati Check-in

超级话题集中签到

目前为 2017-10-26 提交的版本。查看 最新版本

// ==UserScript==
// @name		Weibo Huati Check-in
// @description	超级话题集中签到
// @namespace	http://greasyfork.icu/users/10290
// @version		0.2.20171026
// @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==

if (+$CONFIG.islogin)
(() => {
/**
 * see config
 * @const	{object}	DEFAULT_CONFIG	默认设置
 */
const DEFAULT_CONFIG = Object.freeze({
	autoCheckin: true,
	checkNormal: true,
	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	{object}	config				脚本设置
 * @global	{boolean}	config.autoCheckin	自动签到
 * @global	{boolean}	config.checkNormal	普话签到
 * @global	{boolean}	config.openDetail	展开详情
 * @global	{int}		config.maxHeight	详情限高(px)
 * @global	{int}		config.timeout		操作超时(ms)
 * @global	{int}		config.retry		重试次数
 * @global	{int}		config.delay		操作延时(ms)
 */
let config = Object.assign(Object.assign({},DEFAULT_CONFIG), JSON.parse(GM_getValue(`config${USER.UID}`, '{}'))),

	/** @global	{object}	lastCheckin	上次签到记录 */
	lastCheckin = JSON.parse(GM_getValue(`lastCheckin${USER.UID}`, '{}'));

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

/**  隐藏游戏按钮,替换为超话签到 */
var logname,
	checkinBtn = document.createElement("li");
checkinBtn.id = 'checkinBtn';
checkinBtn.innerHTML = `<a href="javascript: void(0);"><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);

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

function alterCheckinBtn(text) {
	checkinBtn.style['pointer-events'] = 'none';
	Array.from(checkinBtn.querySelectorAll('em')).forEach((em) => {em.style.color = '#fa7d3c';});
	checkinBtn.querySelector('.signBtn').innerText = `${text}中…`;
}

let addBtn = setInterval(() => {
	if (document.querySelector('.gn_nav_list li:last-child')) {
		clearInterval(addBtn);
		document.querySelector('.gn_nav_list li:last-child').before(checkinBtn);
		document.querySelector('a[nm="game"]').parentNode.style.display = 'none';
	}

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

/** @param	{boolean}	auto	自动开始*/
function huatiCheckin(auto=true) {
	console.group(logname='微博超话签到');
	/**
	 * 任务构造,初始化通用 xhr 参数
	 * @constructor
	 * @param	{string}	name			任务名称
	 * @param	{object}	options			附加 xhr 参数
	 * @param	{function}	load			成功加载函数
	 * @param	{function}	retry			重试函数
	 * @param	{function}	[retryButton=]	重试按钮函数
	 */
	var	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}异常,最终网址为${this.xhr.finalUrl},返回值为${this.xhr.response}`);

			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.style = 'cusor: pointer';
					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);
	};

	/**
	 * 获取话题列表
	 * @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]			列表页码
	 */
	function getHuatiList(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),
			() => getHuatiList(huatiList, type, total, page)
		);

		function parsePage(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 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(name)) {
							if (lastCheckinDate != date) {
								let huati = log['待签'].find((huati) => name === huati.name);
								if (checked)
									huati = log['待签'].splice(log['待签'].findIndex((huati) => name === huati.name), 1).pop();
								hash = huati.hash;
								element = huati.element;
							} else {
								hash = log['已签'][name];
								element = document.getElementById(`${hash}`);
							}
						} else {
							hash = /100808([\w\d]+)&/.exec(card.scheme)[1];
							element = initElement(name, hash);
						}
						huatiList.push({name, checked, hash, level});

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

				if (huatiList.length < total)
					getHuatiList(huatiList, type, total, page + 1);
				else if (config.checkNormal && type != 'normal')
					getHuatiList(huatiList, 'normal', total);
				else {
					setStatus(`关注列表获取完毕,共${total}个${config.checkNormal ? '话题' : '超话'},` + (log.hasOwnProperty('待签') ? `${log['待签'].length}个待签` : '全部已签'));
					console.table(huatiList);
					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.style = 'cusor: pointer';
							startCheckin.onclick = () => checkin(log['待签'].shift());
							startCheckin.innerText = '[开始签到]';
							checkinStatus.appendChild(startCheckin);
						}
					} else {
						clearTask();
						initCheckinBtn();
					}
				}
			}
		}
	}

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

				if (code === 100000 || code === 382004) {
					checkinDone.appendChild(huati.element);
					log['已签'][huati.name] = huati.hash;
					Object.assign(lastCheckin, {date, nick: USER.NICK});
					Object.assign(lastCheckin, log['已签']);
					GM_setValue(`lastCheckin${USER.UID}`, JSON.stringify(lastCheckin));
				} else {
					initLog('异常', {});
					log['异常'][huati.name] = {huati: huati, code: data.code, msg: data.msg};
					huatiCheckin.onerror('error');
				}
				if (code === 100000)
					setStatus(`签到第${/\d+/g.exec(data.data.alert_title)[0]}名,经验+${/\d+/g.exec(data.data.alert_subtitle)[0]}`, huati.element);
				else
					setStatus(data.msg, huati.element);
				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();
					}
				}
			},
			() => 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.style = 'cusor: pointer';
				retryHuati.onclick = () => checkin(Object.assign({}, huati), false);
				retryHuati.innerText = '[重试]';
				setStatus(retryHuati, huati.element, true);
			}
		);
	}

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

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

	function initElement(name, hash) {
		let element = document.createElement('li');
		element.id = hash;
		element.innerHTML = `<span class="order"></span>.<a href="https://weibo.com/p/100808${hash}" target="_blank">${name}</a><span class="info"></span>`;
		return element;
	}

	/**
	 * @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;

	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}#checkinDetail a {cusor: 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 id="checkinToDo" open style="display: none"><summary class="W_f14 W_fb">待签</summary><ol></ol></details><details id="checkinDone" style="display: none"><summary class="W_f14 W_fb">已签</summary><ol></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);

		var checkinStatus = document.getElementById('checkinStatus'),
			checkinMore = document.getElementById('checkinMore'),
			checkinClose = document.getElementById('checkinClose'),
			checkinDetail = document.getElementById('checkinDetail'),
			checkinDone = document.querySelector('#checkinDone ol'),
			checkinToDo = document.querySelector('#checkinToDo ol');
		checkinMore.onclick = function() {
			if (this.innerText === 'd') {
				this.innerText = 'c';
				this.title = '收起';
				checkinDetail.style.display = '';
			} 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';
					if (this.parentNode.style.display === 'none')
						this.parentNode.removeAttribute('style');
					this.previousSibling.innerText = `${i ? '已' : '待'}签${ol.childElementCount - (isRemoval ? 1 : 0)}个话题`;
					Array.from(this.querySelectorAll('li .order')).forEach((span) =>
						span.innerText = Array.from(span.parentNode.parentNode.querySelectorAll('li')).findIndex((li) =>
							li === span.parentNode) + (isRemoval ? 0 : 1));
				})));

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

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

function setupConfig() {
	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 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}#configForm input[type=number] {width: 48px}#configForm input[type=button] {padding: 0 12px}#configForm th {font-weight: bold;padding-top: 6px}#configForm table {float: left;margin: 0 6px}#configForm pre {padding: 6px;height: 160px;overflow-y: scroll;background-color: whitesmoke;line-height: 1.5}${scrollbarStyle('#configForm pre')}#configForm span {float: right}`;
	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="https://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><input type="checkbox" name="autoCheckin" ${config.autoCheckin ? 'checked' : ''}>自动签到<br><input type="checkbox" name="checkNormal" ${config.checkNormal ? 'checked' : ''}>普话签到</fieldset><fieldset><legend>视觉偏好</legend><input type="checkbox" name="openDetail" ${config.openDetail ? 'checked' : ''}>自动展开签到详情<br>详情最大高度 <input type="number" name="maxHeight" value=${config.maxHeight} min=60 max=1080 step=60> 像素</fieldset><fieldset><legend>运行参数</legend>请求延时 <input type="number" name="delay" value=${config.delay} min=0 step=100> 毫秒<br>请求超时 <input type="number" name="timeout" value=${config.timeout} min=0 step=100> 毫秒<br>超时及异常自动重试 <input type="number" name="retry" value=${config.retry} min=0> 次</fieldset></fieldset><fieldset><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="configClear" ${Object.keys(lastCheckin).length != 0 ? '' : 'disabled'}></th></tr></tbody></table><pre>${Object.keys(lastCheckin).filter((key) => !['date', 'nick', 'allChecked'].includes(key)).join('\n')}</pre></fieldset><footer><input type="button" value="保存" id="configSave"><input type="button" value="还原" id="configRestore" disabled><span title="请 F12 打开控制台查看错误记录,与出错情况一并反馈"><input type="button" value="重置" id="configDefault" ${isEqual(config, DEFAULT_CONFIG) ? 'disabled' : ''}>Bug反馈请到<a href=http://greasyfork.icu/zh-CN/scripts/32143-weibo-huati-check-in/feedback target=_blank>GreasyFork</a>/ <a href=https://gist.github.com/xyauhideto/b9397058ca3166b87e706cbb7249bd54 target=_blank>Gist</a> / 微博@<a href=https://weibo.com/678896489 target=_blank>作者</a></span></footer> </form>`;
	document.body.appendChild(configForm);
	let inputs = Array.from(document.querySelectorAll('#configForm fieldset > input')),
		getInputs = () => inputs.reduce((conf, input) => {
			conf[input.name] = input.type != 'checkbox' ? +input.value : input.checked;
			return conf;
		}, {}),
		configRestore = document.getElementById('configRestore'),
		configDefault = document.getElementById('configDefault');

	document.getElementById('configSave').onclick = function() {
		config = getInputs();
		GM_setValue(`config${USER.UID}`, JSON.stringify(config));
		initForm();
	};
	configRestore.onclick = () => initForm();
	configDefault.onclick = function() {
		console.warn('重置原设置');
		console.table(config);
		GM_deleteValue(`config${USER.UID}`);
		initForm(DEFAULT_CONFIG);
	};
	document.getElementById('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 pre').innerText = '';
		this.disabled = true;
	};
	document.getElementById('configClose').onclick = function() {
		configCSS.remove();
		configForm.remove();
		initCheckinBtn();
	};

	inputs.forEach(function(input) {
		input.onchange = () => {
			configRestore.disabled = isEqual(getInputs(), config) ? true : false;
			configDefault.disabled = isEqual(getInputs(), DEFAULT_CONFIG) ? true : false;
		};
	});

	function initForm(conf=config) {
		config = conf;
		for (let key in conf) {
			if (typeof conf[key] === 'boolean')
				document.getElementsByName(key)[0].checked = conf[key];
			else
				document.getElementsByName(key)[0].value = conf[key];
		}
		inputs.forEach((input) => input.onchange());
	}

	/** 简单对象比较
	 * @param	{object}	x	比较对象x
	 * @param	{object}	y	比较对象y
	 * @return	{boolean}	比较结果
	 */
	function isEqual(x, y) {
		if (Object.values(x).length != Object.values(y).length)
			return false;
		for (let key in x) {
			if (typeof y[key] === 'undefined' || x[key] != y[key])
				return false;
		}
		return true;
	}
}

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

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


function scrollbarStyle(elementSelector) {
	return `${elementSelector}::-webkit-scrollbar {width: 4px;background-color: #f2f2f5;border-radius: 2px;}${elementSelector}::-webkit-scrollbar-thumb {width: 4px;background-color: #808080;border-radius: 2px;}`;
}
})();