TinyGrail AutoTemple

小圣杯自动建塔

  1. // ==UserScript==
  2. // @name TinyGrail AutoTemple
  3. // @namespace https://github.com/bangumi/scripts/tree/master/liaune
  4. // @version 0.2
  5. // @description 小圣杯自动建塔
  6. // @author Liaune
  7. // @include /^https?://(bgm\.tv|bangumi\.tv|chii\.in)/(user|character|rakuen\/topic\/crt).*
  8. // @grant GM_addStyle
  9. // ==/UserScript==
  10. let api = 'https://tinygrail.com/api/';
  11.  
  12. function getData(url, callback) {
  13. if (!url.startsWith('http'))
  14. url = api + url;
  15. $.ajax({
  16. url: url,
  17. type: 'GET',
  18. xhrFields: { withCredentials: true },
  19. success: callback
  20. });
  21. }
  22. function postData(url, data, callback) {
  23. let d = JSON.stringify(data);
  24. if (!url.startsWith('http'))
  25. url = api + url;
  26. $.ajax({
  27. url: url,
  28. type: 'POST',
  29. contentType: 'application/json',
  30. data: d,
  31. xhrFields: { withCredentials: true },
  32. success: callback
  33. });
  34. }
  35.  
  36. let autoTempleList = JSON.parse(localStorage.getItem('TinyGrail_autoTempleList')) || [];
  37. if(autoTempleList.length){
  38. setInterval(function(){
  39. autoTempleList = JSON.parse(localStorage.getItem('TinyGrail_autoTempleList'));
  40. autoBuildTemple(autoTempleList);
  41. },30*60*1000);
  42. }
  43.  
  44. async function retryPromise(callback, n=10) {
  45. let error;
  46. while(n--) {
  47. try {
  48. return await new Promise(callback);
  49. } catch (err) {
  50. error = err;
  51. await new Promise(resolve => setTimeout(resolve, 300)); // sleep 300 ms
  52. }
  53. }
  54. throw error;
  55. };
  56.  
  57. async function autoBuildTemple(charas){
  58. closeDialog();
  59. var dialog = `<div id="TB_overlay" class="TB_overlayBG TB_overlayActive"></div>
  60. <div id="TB_window" class="dialog" style="display:block;max-width:640px;min-width:400px;">
  61. <div class="info_box">
  62. <div class="title">自动建塔检测中</div>
  63. <div class="result" style="max-height:500px;overflow:auto;"></div>
  64. </div>
  65. <a id="TB_closeWindowButton" title="Close">X关闭</a>
  66. </div>
  67. </div>`;
  68. $('body').append(dialog);
  69. $('#TB_closeWindowButton').on('click', closeDialog);
  70. $('#TB_overlay').on('click', closeDialog);
  71. function buildTemple(chara, index, amount){
  72. postData(`chara/sacrifice/${chara.charaId}/${amount}/false`, null);
  73. //if (d.State == 0) {
  74. $('.info_box .result').prepend(`<div class="row">#${chara.charaId} ${chara.name} 献祭${amount}</div>`);
  75. $('#autobuildButton').text('[自动建塔]');
  76. autoTempleList.splice(index,1); //建塔完成,取消自动建塔
  77. localStorage.setItem('TinyGrail_autoTempleList',JSON.stringify(autoTempleList));
  78. //} else {
  79. //$('.info_box .result').prepend(`<div class="row">${d.Message}</div>`);
  80. //}
  81. }
  82. function postBid(chara, price, amount){
  83. postData(`chara/bid/${chara.charaId}/${price}/${amount}`, null, function(d, s) {
  84. if(d.Message){
  85. $('.info_box .result').prepend(`<div class="row">#${charaId} ${chara.name} ${d.Message}</div>`);
  86. }
  87. else{
  88. $('.info_box .result').prepend(`<div class="row">买入成交 #${charaId} ${chara.name} ${price}*${amount}</div>`);
  89. }
  90. });
  91. }
  92. for (let i = 0; i < charas.length; i++) {
  93. $('.info_box .title').text(`自动建塔检测中 ${i+1} / ${charas.length}`);
  94. let chara = charas[i];
  95. let index = i;
  96. $('.info_box .result').prepend(`<div class="row">check #${chara.charaId} ${chara.name}</div>`);
  97. await retryPromise(resolve => getData(`chara/user/${chara.charaId}`, function (d, s) {
  98. let Amount = d.Value.Amount;
  99. if(Amount >= chara.target){ //持股达到数量,建塔
  100. buildTemple(chara, index, chara.target);
  101. }
  102. else getData(`chara/depth/${chara.charaId}`,function (d, s) {
  103. let AskPrice = d.Value.Asks[0] ? d.Value.Asks[0].Price : 0;
  104. let AskAmount = d.Value.Asks[0] ? d.Value.Asks[0].Amount : 0;
  105. if(AskPrice && AskPrice <= chara.BidPrice){ //最低卖单低于买入上限,买入
  106. postBid(chara, AskPrice, Math.min(AskAmount,chara.target - Amount));
  107. }
  108. });
  109. resolve();
  110. if(i == charas.length-1){
  111. $('.info_box .title').text(`自动建塔检测完毕! ${i+1} / ${charas.length}`);
  112. setTimeout(()=>{closeDialog();},1*1000);
  113. }
  114. }));
  115. }
  116. }
  117.  
  118. function closeDialog() {
  119. $('#TB_overlay').remove();
  120. $('#TB_window').remove();
  121. }
  122.  
  123.  
  124. function openBuildDialog(chara){
  125. autoTempleList = JSON.parse(localStorage.getItem('TinyGrail_autoTempleList')) || [];
  126. let target = 500, bidPrice = 10;
  127. let intempleList = false, index = 0;
  128. for(let i = 0; i < autoTempleList.length; i++){
  129. if(autoTempleList[i].charaId == chara.Id){
  130. target = autoTempleList[i].target;
  131. bidPrice = autoTempleList[i].bidPrice;
  132. intempleList = true;
  133. index = i;
  134. }
  135. }
  136. let dialog = `<div id="TB_overlay" class="TB_overlayBG TB_overlayActive"></div>
  137. <div id="TB_window" class="dialog" style="display:block;">
  138. <div class="title" title="目标数量 / 买入价格">
  139. 自动建塔 - #${chara.Id} ${chara.Name}」 ${target} / ${bidPrice}</div>
  140. <div class="desc"><p>设置目标数量之前请先检查是否已经献祭建塔,当持股数超过目标数量时将献祭目标数量建塔</p>
  141. 输入 目标数量 / 买入价格(不超过此价格的卖单将自动买入)</div>
  142. <div class="label"><div class="trade build">
  143. <input class="target" type="number" style="width:150px" title="目标数量" value="${target}">
  144. <input class="bidPrice" type="number" style="width:150px" title="卖出下限" value="${bidPrice}">
  145. <button id="startBuildButton" class="active">自动建塔</button><button id="cancelBuildButton">取消建塔</button></div>
  146. <div class="loading" style="display:none"></div>
  147. <a id="TB_closeWindowButton" title="Close">X关闭</a>
  148. </div>`;
  149. $('body').append(dialog);
  150.  
  151. $('#TB_closeWindowButton').on('click', closeDialog);
  152.  
  153. $('#cancelBuildButton').on('click', function(){
  154. if(intempleList){
  155. autoTempleList.splice(index,1);
  156. localStorage.setItem('TinyGrail_autoTempleList',JSON.stringify(autoTempleList));
  157. alert(`取消自动建塔${chara.Name}`);
  158. location.reload();
  159. }
  160. closeDialog();
  161. });
  162.  
  163. $('#startBuildButton').on('click', function () {
  164. let info = {};
  165. info.charaId = chara.Id.toString();
  166. info.name = chara.Name;
  167. info.target = $('.trade.build .target').val();
  168. info.bidPrice = $('.trade.build .bidPrice').val();
  169. if(intempleList){
  170. autoTempleList.splice(index,1);
  171. autoTempleList.unshift(info);
  172. }
  173. else autoTempleList.unshift(info);
  174. localStorage.setItem('TinyGrail_autoTempleList',JSON.stringify(autoTempleList));
  175. alert(`启动自动建塔#${chara.Id} ${chara.Name}`);
  176. closeDialog();
  177. $('#autobuildButton').text('[自动建塔中]');
  178. autoBuildTemple(autoTempleList);
  179. });
  180. }
  181.  
  182. function setBuildTemple(charaId){
  183. let charas = [];
  184. for(let i = 0; i < autoTempleList.length; i++){
  185. charas.push(autoTempleList[i].charaId);
  186. }
  187. let button;
  188. if(charas.includes(charaId)){
  189. button = `<button id="autobuildButton" class="text_button">[自动建塔中]</button>`;
  190. }
  191. else{
  192. button = `<button id="autobuildButton" class="text_button">[自动建塔]</button>`;
  193. }
  194. $('#buildButton').after(button);
  195.  
  196. $('#autobuildButton').on('click', () => {
  197. getData(`chara/${charaId}`, (d) => {
  198. let chara = d.Value;
  199. openBuildDialog(chara);
  200. });
  201. });
  202. }
  203.  
  204. function observeChara(mutationList) {
  205. if(!$('#grailBox .progress_bar, #grailBox .assets_box').length) {
  206. fetched = false;
  207. return;
  208. }
  209. if(fetched) return;
  210. if($('#grailBox .assets_box').length) {
  211. fetched = true;
  212. let charaId = $('#grailBox .title .name a')[0].href.split('/').pop();
  213. setBuildTemple(charaId);
  214. } // use '.progress_bar' to detect (and skip) ICO characters
  215. else if($('#grailBox .progress_bar').length) {
  216. observer.disconnect();
  217. }
  218. }
  219. let fetched = false;
  220. let parentNode=null, observer;
  221. if(location.pathname.startsWith('/rakuen/topic/crt')) {
  222. parentNode = document.getElementById('subject_info');
  223. observer = new MutationObserver(observeChara);
  224. } else if(location.pathname.startsWith('/character')) {
  225. parentNode = document.getElementById('columnCrtB')
  226. observer = new MutationObserver(observeChara);
  227. }
  228. if(parentNode) observer.observe(parentNode, {'childList': true, 'subtree': true});
  229.  
  230.  
  231.  
  232.  
  233.  
  234.  
  235.  
  236.  
  237.  
  238.  
  239.  
  240.  
  241.  
  242.  
  243.  
  244.  
  245.  
  246.  

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址