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