Greasy Fork is available in English.

TinyGrail AutoTemple

小圣杯自动建塔

// ==UserScript==
// @name         TinyGrail AutoTemple
// @namespace    https://github.com/bangumi/scripts/tree/master/liaune
// @version      0.2
// @description  小圣杯自动建塔
// @author       Liaune
// @include     /^https?://(bgm\.tv|bangumi\.tv|chii\.in)/(user|character|rakuen\/topic\/crt).*
// @grant        GM_addStyle
// ==/UserScript==
let api = 'https://tinygrail.com/api/';

function getData(url, callback) {
	if (!url.startsWith('http'))
		url = api + url;
	$.ajax({
		url: url,
		type: 'GET',
		xhrFields: { withCredentials: true },
		success: callback
	});
}
function postData(url, data, callback) {
	let d = JSON.stringify(data);
	if (!url.startsWith('http'))
		url = api + url;
	$.ajax({
		url: url,
		type: 'POST',
		contentType: 'application/json',
		data: d,
		xhrFields: { withCredentials: true },
		success: callback
	});
}

let autoTempleList = JSON.parse(localStorage.getItem('TinyGrail_autoTempleList')) || [];
if(autoTempleList.length){
	setInterval(function(){
		autoTempleList = JSON.parse(localStorage.getItem('TinyGrail_autoTempleList'));
		autoBuildTemple(autoTempleList);
	},30*60*1000);
}

async function retryPromise(callback, n=10) {
	let error;
	while(n--) {
		try {
			return await new Promise(callback);
		} catch (err) {
			error = err;
			await new Promise(resolve => setTimeout(resolve, 300)); // sleep 300 ms
		}
	}
	throw error;
};

async function autoBuildTemple(charas){
	closeDialog();
	var dialog = `<div id="TB_overlay" class="TB_overlayBG TB_overlayActive"></div>
<div id="TB_window" class="dialog" style="display:block;max-width:640px;min-width:400px;">
<div class="info_box">
<div class="title">自动建塔检测中</div>
<div class="result" style="max-height:500px;overflow:auto;"></div>
</div>
<a id="TB_closeWindowButton" title="Close">X关闭</a>
</div>
</div>`;
	$('body').append(dialog);
	$('#TB_closeWindowButton').on('click', closeDialog);
	$('#TB_overlay').on('click', closeDialog);
	function buildTemple(chara, index, amount){
		postData(`chara/sacrifice/${chara.charaId}/${amount}/false`, null);
			//if (d.State == 0) {
				$('.info_box .result').prepend(`<div class="row">#${chara.charaId} ${chara.name} 献祭${amount}</div>`);
				$('#autobuildButton').text('[自动建塔]');
				autoTempleList.splice(index,1); //建塔完成,取消自动建塔
				localStorage.setItem('TinyGrail_autoTempleList',JSON.stringify(autoTempleList));
			//} else {
				//$('.info_box .result').prepend(`<div class="row">${d.Message}</div>`);
			//}
	}
	function postBid(chara, price, amount){
		postData(`chara/bid/${chara.charaId}/${price}/${amount}`, null, function(d, s) {
			if(d.Message){
				$('.info_box .result').prepend(`<div class="row">#${charaId} ${chara.name} ${d.Message}</div>`);
			}
			else{
				$('.info_box .result').prepend(`<div class="row">买入成交 #${charaId} ${chara.name} ${price}*${amount}</div>`);
			}
		});
	}
	for (let i = 0; i < charas.length; i++) {
		$('.info_box .title').text(`自动建塔检测中 ${i+1} / ${charas.length}`);
		let chara = charas[i];
		let index = i;
		$('.info_box .result').prepend(`<div class="row">check #${chara.charaId} ${chara.name}</div>`);
		await retryPromise(resolve => getData(`chara/user/${chara.charaId}`, function (d, s) {
			let Amount = d.Value.Amount;
			if(Amount >= chara.target){ //持股达到数量,建塔
				buildTemple(chara, index, chara.target);
			}
			else getData(`chara/depth/${chara.charaId}`,function (d, s) {
				let AskPrice = d.Value.Asks[0] ? d.Value.Asks[0].Price : 0;
				let AskAmount = d.Value.Asks[0] ? d.Value.Asks[0].Amount : 0;
				if(AskPrice && AskPrice <= chara.BidPrice){ //最低卖单低于买入上限,买入
					postBid(chara, AskPrice, Math.min(AskAmount,chara.target - Amount));
				}
			});
			resolve();
			if(i == charas.length-1){
				$('.info_box .title').text(`自动建塔检测完毕! ${i+1} / ${charas.length}`);
				setTimeout(()=>{closeDialog();},1*1000);
			}
		}));
	}
}

function closeDialog() {
	$('#TB_overlay').remove();
	$('#TB_window').remove();
}


function openBuildDialog(chara){
	autoTempleList = JSON.parse(localStorage.getItem('TinyGrail_autoTempleList')) || [];
	let target = 500, bidPrice = 10;
	let intempleList = false, index = 0;
	for(let i = 0; i < autoTempleList.length; i++){
		if(autoTempleList[i].charaId == chara.Id){
			target = autoTempleList[i].target;
			bidPrice = autoTempleList[i].bidPrice;
			intempleList = true;
			index = i;
		}
	}
	let dialog = `<div id="TB_overlay" class="TB_overlayBG TB_overlayActive"></div>
<div id="TB_window" class="dialog" style="display:block;">
<div class="title" title="目标数量 / 买入价格">
自动建塔 - #${chara.Id} 「${chara.Name}」 ${target} / ₵${bidPrice}</div>
<div class="desc"><p>设置目标数量之前请先检查是否已经献祭建塔,当持股数超过目标数量时将献祭目标数量建塔</p>
输入 目标数量 / 买入价格(不超过此价格的卖单将自动买入)</div>
<div class="label"><div class="trade build">
<input class="target" type="number" style="width:150px" title="目标数量" value="${target}">
<input class="bidPrice" type="number" style="width:150px" title="卖出下限" value="${bidPrice}">
<button id="startBuildButton" class="active">自动建塔</button><button id="cancelBuildButton">取消建塔</button></div>
<div class="loading" style="display:none"></div>
<a id="TB_closeWindowButton" title="Close">X关闭</a>
</div>`;
	$('body').append(dialog);

	$('#TB_closeWindowButton').on('click', closeDialog);

	$('#cancelBuildButton').on('click', function(){
		if(intempleList){
			autoTempleList.splice(index,1);
			localStorage.setItem('TinyGrail_autoTempleList',JSON.stringify(autoTempleList));
			alert(`取消自动建塔${chara.Name}`);
			location.reload();
		}
		closeDialog();
	});

	$('#startBuildButton').on('click', function () {
		let info = {};
		info.charaId = chara.Id.toString();
		info.name = chara.Name;
		info.target = $('.trade.build .target').val();
		info.bidPrice =  $('.trade.build .bidPrice').val();
		if(intempleList){
			autoTempleList.splice(index,1);
			autoTempleList.unshift(info);
		}
		else autoTempleList.unshift(info);
		localStorage.setItem('TinyGrail_autoTempleList',JSON.stringify(autoTempleList));
		alert(`启动自动建塔#${chara.Id} ${chara.Name}`);
		closeDialog();
		$('#autobuildButton').text('[自动建塔中]');
		autoBuildTemple(autoTempleList);
	});
}

function setBuildTemple(charaId){
	let charas = [];
	for(let i = 0; i < autoTempleList.length; i++){
		charas.push(autoTempleList[i].charaId);
	}
	let button;
	if(charas.includes(charaId)){
		button = `<button id="autobuildButton" class="text_button">[自动建塔中]</button>`;
	}
	else{
		button = `<button id="autobuildButton" class="text_button">[自动建塔]</button>`;
	}
	$('#buildButton').after(button);

	$('#autobuildButton').on('click', () => {
		getData(`chara/${charaId}`, (d) => {
			let chara = d.Value;
			openBuildDialog(chara);
		});
	});
}

function observeChara(mutationList) {
	if(!$('#grailBox .progress_bar, #grailBox .assets_box').length) {
		fetched = false;
		return;
	}
	if(fetched) return;
	if($('#grailBox .assets_box').length) {
		fetched = true;
		let charaId = $('#grailBox .title .name a')[0].href.split('/').pop();
		setBuildTemple(charaId);
	} // use '.progress_bar' to detect (and skip) ICO characters
	else if($('#grailBox .progress_bar').length) {
		observer.disconnect();
	}
}
let fetched = false;
let parentNode=null, observer;
if(location.pathname.startsWith('/rakuen/topic/crt')) {
	parentNode = document.getElementById('subject_info');
	observer = new MutationObserver(observeChara);
} else if(location.pathname.startsWith('/character')) {
	parentNode = document.getElementById('columnCrtB')
	observer = new MutationObserver(observeChara);
}
if(parentNode) observer.observe(parentNode, {'childList': true, 'subtree': true});