🏠 Home 

TinyGrail Helper Next

为小圣杯增加一些小功能, 讨论/反馈:https://bgm.tv/group/topic/358167

  1. // ==UserScript==
  2. // @name TinyGrail Helper Next
  3. // @description 为小圣杯增加一些小功能, 讨论/反馈:https://bgm.tv/group/topic/358167
  4. // @namespace https://gitee.com/Yinr/TinyGrail-Helper-Next
  5. // @include http*://bgm.tv/*
  6. // @include http*://bangumi.tv/*
  7. // @include http*://chii.in/*
  8. // @version 3.2.6
  9. // @author Liaune, Cedar, no1xsyzy(InQβ), Yinr
  10. // @homepage https://github.com/Yinr/TinyGrail-Helper-Next
  11. // @license MIT
  12. // @grant GM_addStyle
  13. // ==/UserScript==
  14. (function () {
  15. 'use strict';
  16. var css_248z = "#grail li.title.hide_grail_title h2:after{font-size:12px;content:\"(点击显示)\"}#grail .temple_list .item{margin:5px 5px 5px 0;width:107px}.hide_grail{display:none!important}#valhalla li.initial_item.chara .valhalla-sacrifices{margin-top:2px;text-align:center;line-height:0;white-space:nowrap}ul.timelineTabs li a{margin:2px 0 0;padding:5px 10px}.item-info-tag{padding:0 4px;margin-left:2px;vertical-align:top;font-size:50%;font-weight:700;text-shadow:1px 1px 1px #666;border:none;border-radius:4px;background:linear-gradient(#4f93cf,#369cf8)}.item-info-tag,.item-info-tags{display:inline-block}.item-info-tags .item-info-tag{display:inline-block;padding:0;width:10px;height:10px;background-size:contain;margin-left:2px}.item-info-tags .item-info-chara-icon{background-image:url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAABhGlDQ1BJQ0MgcHJvZmlsZQAAKJF9kT1Iw0AYht+mSlUqInYQcchQnSyIijhqFYpQIdQKrTqYXPoHTRqSFBdHwbXg4M9i1cHFWVcHV0EQ/AFxcnRSdJESv0sKLWK847iH97735e47QKiXmWZ1jAOabpupRFzMZFfF0CsEdKOfJmRmGXOSlITv+LpHgO93MZ7lX/fn6FVzFgMCIvEsM0ybeIN4etM2OO8TR1hRVonPicdMuiDxI9cVj984F1wWeGbETKfmiSPEYqGNlTZmRVMjniKOqppO+ULGY5XzFmetXGXNe/IXhnP6yjLXaQ0jgUUsQYIIBVWUUIaNGO06KRZSdB738Q+5folcCrlKYORYQAUaZNcP/ge/e2vlJye8pHAc6HxxnI8RILQLNGqO833sOI0TIPgMXOktf6UOzHySXmtp0SOgbxu4uG5pyh5wuQMMPhmyKbtSkJaQzwPvZ/RNWWDgFuhZ8/rWPMfpA5CmXiVvgINDYLRA2es+7+5q79u/Nc3+/QDatXJqsNTf9AAAAAlwSFlzAAALEwAACxMBAJqcGAAAA6JJREFUOMuVlEtslVUQx39zvsftfVBuKxYKLa1ijEohsECjLgo23ZgQJcEQZcfOhS58ING4MEogMdEYWbIwxkghFoPBR4xIiQ8IiZi2vCEEilCsfdy+73e+7zvj4lYsz4RJJpOTc+Z3MnPO/OEutnerob8T7NCPRH/vo38vdHxg7paCf7fNxuoiJh02U30/fYWLRVLWr26Ym8LIHXPueF3owYL5wyRB8WWvrrDOq699Pg2KrySFEUKPewO2LIaTX/jYSRrJrdoRFKsIqzOQf+rjaJzm01/6tCy+B2DHlgzppEcqNe/XrmyDyKLWUruiFRc0bY0nPDq2hLcFigC7NgsPLTIYPETAGAspT9+/ctOvNUuaZp2G0oXLDPyxc40aupQQdYoTJU0dT77mkAOf+BQTcPh4pjxPlbUivFS9ZENrw4rHAohBZoAKIj5Xei/EpbOf/66O3SLscza4Kr4yrQ45sp1GP9+0rdi4qi2TKyzIVM8hO7dImDWos5UaZFZNqoj4JJEwNTpKNDZONDkxONrf3RUNn3nLT5XnlrW+sNFDUBSMAGXUVkrE###QgWqCAQqFkEKuFtKaeQsfeWJ99/5tR8z0OJ3HD+6OsdOQxIiziLPgLIJFdMa5Kf53LokxXpbjh/bHE9cGdxkRv3+s/1rr2d7DibgIZmAwy+XGtWJBLbgIMcL5U+fS0rmDbRJy1SuVMjz+cPLX0OUrJyImN9TV14NJES+9HsXMuKSgKaIpQohEWU4fPcClY10vGl+/b99ehfmmd5okBZS9fX+eenVoqA8hBrGIZ8GLER/wEkQjxI6io/3oxcMM9X7Exd6e19XpnkQNUP6/3Xs2QRyRe3R18cLSB5vmkwOqAiScQszJygOnlap1CpiEM31msPuQPmB8ndj42U3iUI4hKRshHTOUuyEAyYIEIBnAA0nATYPMgE2AF0+JBDm9VW3iCGKrYTYreQXEA/FBqkByFaBGYFJwAahANks+cVqlMeO3zHISgXOay/jk0OuDgRgwecFUCyasgFBQhUygoRPm2PJt9NBGIEbygVR+hC/wz7DQ8wvlb3fydVwiWfumrFu+jMLCghLHIBFIlup45HZAC2JMlIQ+o0HsfvuB8vlOt2NpM2/X15JKLazpFvl0h767qF02r26XrMtmTDxlp5Mona0fFfvwGahvafDDGu/ZnvcujTk4VreGMQRChbKr9NQoDPxMXmHF8nea77Ml+93AiavJG10Vzr/VrK4oLFhtxgAAAABJRU5ErkJggg==\")}.item-info-tags .item-info-auc-icon{background-image:url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAABhGlDQ1BJQ0MgcHJvZmlsZQAAKJF9kT1Iw0AcxV9TxQ8qInYo4pChOlkQFXHUKhShQqgVWnUwufQLmjQkKS6OgmvBwY/FqoOLs64OroIg+AHi5Oik6CIl/i8ptIjx4Lgf7+497t4BQr3MNKtjHNB020wl4mImuyp2vUJADwYQQVBmljEnSUn4jq97BPh6F+NZ/uf+HH1qzmJAQCSeZYZpE28QT2/aBud94jAryirxOfGYSRckfuS64vEb54LLAs8Mm+nUPHGYWCy0sdLGrGhqxFPEUVXTKV/IeKxy3uKslauseU/+wlBOX1nmOs1hJLCIJUgQoaCKEsqwEaNVJ8VCivbjPv4h1y+RSyFXCYwcC6hAg+z6wf/gd7dWfnLCSwrFgc4Xx/kYAbp2gUbNcb6PHadxAgSfgSu95a/UgZlP0mstLXoE9G8DF9ctTdkDLneAyJMhm7IrBWkK+TzwfkbflAUGb4HeNa+35j5OH4A0dZW8AQ4OgdECZa/7vLu7vbd/zzT7+wHzpXJ0393cywAAAAlwSFlzAAALEwAACxMBAJqcGAAAA8hJREFUOMuVU1tsVUUUXXvO3HN6+6KRqi3WUK0SFaJSEU2joqSY0ICFSqjcJjYpKlVDQq2StIgGX/URK/5gIogmhBSNfpj4Cj4/MDEGtRgNaqkx9ia1cNt777nnMefMOTN+tPIo3Ag7mZ+VvdesNXsNoUi1b1qGR7auR21FLdl+vpuIGpVWr2utf1lav7HYGPhswEwaCP0YD/W3IBIC43Z6G2PGDgKY1vp2qeIlAByeYIikOouQzQaaNyzCgeHHMT41DqH8x7zYedaReVaQebhRwTUZj35Mv4ObmhacUyHNBj6e6ENJrhoZMd5DZAye1pm+vPzKb0zDSksl+5SO0XRF9/9b/vPYGDifqGdkvHgK1X/Nn3P1twqqQ8Q+KRW7I97oc5+PvIyNLS/h75HJ4pYrrWq4oeNk/Uk7608i609mq0trv+bM6PAil7zIhVDimTqrdgs0sPP9R5Gs5MUJL7q0HCIOMq50VuaDXH9Vydx+y0ikfOlBnDoUROI1L3J7CyqHvYe2FH9DwyRs/eROxJGCwUzce107G80e2weo1Dl2oGIV37+qYd27o1MjhivdgFB0Vxo/jL2NMArBGb/syInvnwSo+/QBrbU7r7x+3yVlNc1aq+MKWHOWZWYQ1nfdgcMTuxHpGAbja4iMnxfObfzUDQsf2YENO7BhC1skedl7ZWZppxPaV7nSafKl03KGQiJAa+Cr0VcBzWAy6wEGegMEDo3xKTG1eHjiu11Kq7aa8ro98yrr1gG6amY8YjCWnqFw5YYl+Oz3ASxv6IXWerNU4W6hBBex0KEOnw4iZyJdGOs0jZKBCqt8rS2yVbbIwRZ534+8zlCFP51UuG1XOxY1NqBiThKMjKcIbDsBXANgRL0aatBkJfg1cwRE2APQzIfW4MRTJpUMWQlzOjabnl+B62+bj5rai1mkwgGpgh2B8rlQHqQK3rxrwepBRgbart0OO5hCQeQ+yPkZJ+dnCoUg3xXGwZDiEh2LB0Ftm29GV/8KxFkDvvQHiVjPfzcDbMiqYimRjXDfDa8AAPo+vBuUjElBXzMdZPoNwtAv3HNweqnLUwtROCFYxsvs9WO/x4tcuNKBF3lfhsp/+J/McXCLwSw1AAADrQcRSqkBHAVwNJTyJBkA0M5DqSTniSECa9XQMymkPwi4hYHlum99C8QIWmmcT3Gp5Y2hlK3Q03kmIM2Jt1g8mSNGYJygovMjAwAeRMFwrOMvoNEMgs1Aaw3GRzUZeGLZ/gsiAwAexZEfxmGrJvUggYYTzDqcSBD6Vu1HHFwYGQD8C6Dv40ZNwMKjAAAAAElFTkSuQmCC\")}.item-info-tags .item-info-ico-icon{background-image:url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAeCAMAAAAM7l6QAAABhGlDQ1BJQ0MgcHJvZmlsZQAAKJF9kT1Iw0AcxV9Ta0UqIlYQcchQnSyIijhqFYpQodQKrTqYXPoFTRqSFBdHwbXg4Mdi1cHFWVcHV0EQ/ABxcnRSdJES/5cUWsR4cNyPd/ced+8AoV5mqtkxDqiaZaTiMTGTXRWDrxDQhwAG0CkxU59LJhPwHF/38PH1LsqzvM/9OXqUnMkAn0g8y3TDIt4gnt60dM77xGFWlBTic+Ixgy5I/Mh12eU3zgWHBZ4ZNtKpeeIwsVhoY7mNWdFQiaeII4qqUb6QcVnhvMVZLVdZ8578haGctrLMdZrDiGMRS0hChIwqSijDQpRWjRQTKdqPefiHHH+SXDK5SmDkWEAFKiTHD/4Hv7s185MTblIoBgRebPtjBAjuAo2abX8f23bjBPA/A1day1+pAzOfpNdaWuQI6N0GLq5bmrwHXO4Ag0+6ZEiO5Kcp5PPA+xl9UxbovwW619zemvs4fQDS1FXiBjg4BEYLlL3u8e6u9t7+PdPs7wfpq3JwocxM2QAAAS9QTFRFblUA////gbxEERIkQn0tAAAA2trbP3osg75EgbxFQn0sPXgrW6IkhMBFP3orf7tBDg8ifbo8RoEuebRAerk2TogxAAAaVpA0c64+ZqE5WpU1bKY8O3kjAAAXd7cvUYsxSoUwYZw3AAATdLM5LWsZaasw5PDZutme9frwMXQSdbFAlJSaAAAdiImQ0eW+8vjsjcJXsdWR1ObDqNCDwt6qTIM6v9C5iquA0d7M4urggKR0l7WOvM62RoEkyOCytN2Jhr9O3evNHx8tKSo4QUFMbW52e3uCTExWXFxknctylsZksNOSWItHZJNWJ3AAobyZc51nU58D2uTXmsSAdq9QUJUhIWMKwOKcrdeApcKVga1kaJpMd6Ngx+eoU5kimMdrqquu6+vrw8XIoqOoNDVBlpiZAgAAAAF0Uk5TAEDm2GYAAAAJcEhZcwAACxMAAAsTAQCanBgAAAFHSURBVCjP1ZI/a8MwEMWD0NIsAosg3WlwhCkELRYUNQnOoDVGJSZjOyT9/l+iJ1tN+i/dOvQGg/Xs9353utnsr6v/XTbgvL/9MzqDaPWNT6JlVNJYtObOn755GzaVqVNaopH9JxsrGaJgzIbddlOBBkRw8XSJZppzQ4/zsTvuuCSfDKMv0QKVYJav23MKvEQZOcm6vCNPw2M7###KisI4yQBylOGpXYdtxzVTHEluijl1RLhMVOu23YQ6+yCTYArbXjYu4yI/pLkayShaq9KeVNY0TSMJt1JaFBJZFznieCChjv00O5mjwU2yRzbCap6655cH7TTB2MbBdWyLTMPDYTisgspJ4h7ew2noitO92LR5XQ2VFBpqtC7ur1c29m556OZ1btN/WBE/sREALqyJ/dftsSJLtBHR73/aJoWgb++Tj/3sv9UbToMt591g/JcAAAAASUVORK5CYII=\")}.item-info-tags .item-info-temple-icon{background-image:url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAABhGlDQ1BJQ0MgcHJvZmlsZQAAKJF9kT1Iw0AcxV9TpSJVByuIKGSoThakijhqFYpQIdQKrTqYXPoFTRqSFBdHwbXg4Mdi1cHFWVcHV0EQ/ABxcnRSdJES/9cUWsR4cNyPd/ced+8AoVZimtUxAWi6bSbjMTGdWRUDrxDQiwGMICozy5iTpAQ8x9c9fHy9i/As73N/jh41azHAJxLPMsO0iTeIpzdtg/M+cYgVZJX4nHjcpAsSP3JdcfmNc77BAs8MmankPHGIWMy3sdLGrGBqxFPEYVXTKV9Iu6xy3uKslSqseU/+wmBWX1nmOs1hxLGIJUgQoaCCIkqwEaFVJ8VCkvZjHv6hhl8il0KuIhg5FlCGBrnhB/+D391aucmomxSMAZ0vjvMxCgR2gXrVcb6PHad+AvifgSu95S/XgJlP0qstLXwE9G0DF9ctTdkDLneAwSdDNuWG5Kcp5HLA+xl9UwbovwW619zemvs4fQBS1FXiBjg4BMbylL3u8e6u9t7+PdPs7weLtXKxP2nsHAAAAAlwSFlzAAALEwAACxMBAJqcGAAAAexJREFUOMul1LtPVEEUBvDfXHZdKhOJVLoFQQvJ+oihMBoT/wIrxMeGh4rG1tLCiJWddjZYyBKEEAtiZ2diTDRWKmhjYaSEwtgI4TEWzIUbXGKALznJmXPOfPnmzJwJXr/lyyfev6O1QsjsCDGyuMiZcxw/qcWp03z8QGsrIdgxQqBcZu4HpZLgcp1KBduQ1QfhQlq9Mf58O6ksLQn6bjRXNnCLleUuPMLFFH2Fe0rlr0ZHmh4/k2XrhEWrD1atLDcwWyCT/Fkryw31weo/+7JMMDC0WX5toB0PMITKf7q3hGd46MXo/EZLs+u3rV3pg2Hcxf4dXstvPMFwmGjIxEipDG27IJP2tCmXhRDSkUPgaj/040gq/IwTyf+Zjni0Se47GiYasiyTbTR0cizdvfvJDhT8SlLSLBdNjm28lK1jMZ2UzGEEMyk+lUyKjaSaJUy35M8uKChcD3YnNVUxHkItkZxNBrWUq6ba7tV8YgSllvwdxmiV9sJInS8oP5zUNMu1F2ZFaTVG4lo+epUtJDkObmlNZ8Gv5FMbY1TKpSbsK/TtT8GfL/QPfhX8fRv7A8HNO+uL3nre/LjT/wa9psYhVwh6cMnu0COEl+uEm1hI47cbLGzKffyUbzP2jIiumkxHJ8dqeyfsqtHR6S+Wb3sPk3BhEAAAAABJRU5ErkJggg==\")}#grailBox .trade_box button{min-width:50px;padding:0 9px}#grailBox .hide_grail_block_title:after{font-size:12px;content:\" (点击显示)\"}#grailBox .hide_grail_block_on{display:none!important}#grailBox .hide_grail_block_not_me .link:not(.my_link),#grailBox .hide_grail_block_not_me .rank_list:not(.my_rank),#grailBox .hide_grail_block_not_me>.item:not(.my_temple){display:none}#grailBox .my_auction,#TB_window .my_auction{color:#ffa7cc;margin-right:5px}#grailBox .user_auction,#TB_window .user_auction{color:#a7e3ff;margin-right:5px}.assets .my_temple.item .name a{font-weight:700;color:#0084b4}.assets .my_temple.item .card{box-shadow:3px 3px 5px #ffeb3b;border:1px solid #ffc107}html[data-theme=dark] .assets .my_temple.item .card{box-shadow:0 0 15px #ffeb3b;border:1px solid #ffc107}.assets_box .item{margin:5px 5px 5px 0;width:90px}.assets_box .item .card{width:90px;height:120px}#lastTemples .assets .item{margin:5px 5px 5px 0;width:107px}.item .card{width:105px;height:140px}.link.swapped .swapPos{text-decoration:underline}.link .swapPos{margin-left:.5em;cursor:pointer}#TB_overlay{z-index:102}#TB_window[data-name]{z-index:102;padding:7px;box-shadow:0 0 4px 1px #56bce0}#TB_window .action .text_button{margin:0 8px 0 0;padding:0;width:-webkit-fit-content;width:-moz-fit-content;width:fit-content;height:-webkit-fit-content;height:-moz-fit-content;height:fit-content;min-width:-webkit-fit-content;min-width:-moz-fit-content;min-width:fit-content}#TB_window .desc .text_button{cursor:pointer}#TB_window img.cover{background-color:transparent}#TB_window.temple .container{text-align:center}#TB_window.temple .container .line{text-align:left}#TB_window .dialog-tab-titlebar{display:flex;margin-top:8px;border-bottom:2px solid;padding:0 8px}#TB_window .dialog-tab-titlebar .dialog-tab-title{cursor:pointer;padding:2px 8px;opacity:.8;border:1px solid;border-bottom:none;border-radius:5px 5px 0 0}#TB_window .dialog-tab-titlebar .dialog-tab-title.open{font-weight:700;opacity:1;border-width:2px}#TB_window .setting-tab-content table{width:98%;text-align:center;border-spacing:0}#TB_window .setting-tab-content table input[type=number]{width:7em}#TB_window .setting-tab-content table input[type=number].chara-id{width:5em}#TB_window .setting-tab-content table input[type=number].chara-level{width:4em}#TB_window .setting-tab-content table tr{border-top:1px dotted}#TB_window .setting-tab-content table tr.hide-row{display:none}#TB_window .setting-tab-content table tr td{text-align:left;vertical-align:middle;padding:5px}#TB_window .setting-tab-content table tr td:first-child{width:60%}#TB_window .setting-tab-content table tr.setting-collapse{cursor:pointer}#TB_window .setting-tab-content table tr.setting-collapse td:before{content:\"▾ \"}#TB_window .setting-tab-content table tr.setting-collapse.setting-collapse-close td:before{content:\"▸ \"}#TB_window .setting-tab-content table tr.setting-collapse-item td:first-child{padding-left:1.2em}#TB_window .setting-tab-content table .setting-row-btn td{vertical-align:baseline}#TB_window .setting-tab-content table .txtBtn{font-size:smaller;text-decoration:underline;cursor:pointer}#TB_window .batch-tab-content .batch-element{padding:0 2px}#TB_window .batch-tab-content [title]{border-bottom:thin dotted}";
  17. GM_addStyle(css_248z);
  18. const configGenerator = (name, defaultValue, config = {}) => {
  19. const storageName = `TinyGrail_${name}`;
  20. const { postGet, preSet, storage } = {
  21. storage: localStorage,
  22. ...config
  23. };
  24. return {
  25. name,
  26. storageName,
  27. getRaw () {
  28. return storage.getItem(storageName)
  29. },
  30. get () {
  31. let value = null;
  32. try {
  33. value = JSON.parse(this.getRaw());
  34. if (postGet) {
  35. value = postGet(value);
  36. }
  37. } catch (err) {
  38. console.error(`Fail to get config of ${storageName}`, { valueString: this.getRaw(), value, err });
  39. }
  40. return value || defaultValue
  41. },
  42. setRaw (valueString, raiseError = false) {
  43. try {
  44. storage.setItem(storageName, valueString);
  45. } catch (err) {
  46. console.error(`Fail to set config of ${storageName}`, { valueString, err });
  47. if (raiseError) throw err
  48. }
  49. },
  50. set (value) {
  51. if (preSet) {
  52. try {
  53. value = preSet(value);
  54. } catch (err) {
  55. console.warn(`Fail to preparse config of ${storageName}`, { value, err });
  56. }
  57. }
  58. this.setRaw(JSON.stringify(value));
  59. return value
  60. }
  61. }
  62. };
  63. const formatDate = (date) => {
  64. date = new Date(date);
  65. return date.format('yyyy-MM-dd hh:mm:ss')
  66. };
  67. const formatTime = (timeStr, countDown = false) => {
  68. const now = new Date();
  69. const time = new Date(timeStr) - (new Date().getTimezoneOffset() + 8 * 60) * 60 * 1000;
  70. let times = (time - now) / 1000;
  71. let day = 0;
  72. let hour = 0;
  73. let minute = 0;
  74. let second = 0;
  75. if (times > 0) {
  76. day = Math.floor(times / (60 * 60 * 24));
  77. hour = Math.floor(times / (60 * 60)) - Math.floor(times / (60 * 60 * 24)) * 24;
  78. minute = Math.floor(times / 60) - Math.floor(times / (60 * 60)) * 60;
  79. if (day > 0) return `剩余${day}天${hour}小时`
  80. else if (hour > 0) return `剩余${hour}小时${minute}分钟`
  81. else if (countDown) return `即将结束 剩余${minute}分钟`
  82. else return '刚刚'
  83. } else {
  84. if (countDown) return '已结束'
  85. times = Math.abs(times);
  86. day = Math.floor(times / (60 * 60 * 24));
  87. hour = Math.floor(times / (60 * 60));
  88. minute = Math.floor(times / 60);
  89. second = Math.floor(times);
  90. if (minute < 1) {
  91. return `${second}s ago`
  92. } else if (minute < 60) {
  93. return `${minute}m ago`
  94. } else if (hour < 24) {
  95. return `${hour}h ago`
  96. }
  97. if (day > 365) return 'years ago'
  98. return `[${new Date(timeStr).format('yyyy-MM-dd')}] ${day}d ago`
  99. }
  100. };
  101. const formatNumber = (number, decimals, dec_point, thousands_sep) => {
  102. number = (number + '').replace(/[^0-9+-Ee.]/g, '');
  103. const n = !isFinite(+number) ? 0 : +number;
  104. const prec = !isFinite(+decimals) ? 2 : Math.abs(decimals);
  105. const sep = (typeof thousands_sep === 'undefined') ? ',' : thousands_sep;
  106. const dec = (typeof dec_point === 'undefined') ? '.' : dec_point;
  107. let s = '';
  108. s = (prec ? n.toFixed(prec) : '' + Math.round(n)).split('.');
  109. const re = /(-?\d+)(\d{3})/;
  110. while (re.test(s[0])) {
  111. s[0] = s[0].replace(re, '$1' + sep + '$2');
  112. }
  113. if ((s[1] || '').length < prec) {
  114. s[1] = s[1] || '';
  115. s[1] += new Array(prec - s[1].length + 1).join('0');
  116. }
  117. return s.join(dec)
  118. };
  119. const formatMoney = (number) => {
  120. return '₵' + formatNumber(number, 2)
  121. };
  122. const formatAskPrice = (price) => {
  123. if (Number.isInteger(parseFloat(price))) return price
  124. else return (price - Math.floor(price)) > 0.5 ? Math.ceil(price) : Math.floor(price) + 0.5
  125. };
  126. const parseIntArray = (arr) => arr.map(item => parseInt(item));
  127. const removeEmpty = (array) => {
  128. const arr = [];
  129. for (let i = 0; i < array.length; i++) {
  130. if (array[i]) arr.push(array[i]);
  131. }
  132. return arr
  133. };
  134. const processor = (value) => ({
  135. ...value,
  136. charas: parseIntArray(value.charas),
  137. auctions: parseIntArray(value.auctions)
  138. });
  139. const FollowList = configGenerator('followList', {
  140. user: '',
  141. charas: [],
  142. auctions: []
  143. }, {
  144. postGet: processor,
  145. preSet: processor
  146. });
  147. const getMe = () => {
  148. const followList = FollowList.get();
  149. let me = followList.user;
  150. if (!me) {
  151. if (location.pathname.startsWith('/rakuen/topic/crt') || location.pathname.startsWith('/character')) {
  152. me = $('#new_comment .reply_author a')[0].href.split('/').pop();
  153. } else
  154. if (location.pathname.startsWith('/rakuen/home')) {
  155. me = $('#grailBox2').data('name');
  156. } else
  157. if (location.pathname.startsWith('/rakuen/topiclist')) ; else
  158. if (location.pathname.startsWith('/user')) {
  159. me = $('.idBadgerNeue a.avatar').attr('href').split('/').pop();
  160. }
  161. followList.user = me;
  162. FollowList.set(followList);
  163. }
  164. return me
  165. };
  166. const getDayOfWeek = () => {
  167. let asiaTime = new Date().toLocaleString('en-US', { timeZone: 'Asia/Shanghai' });
  168. asiaTime = new Date(asiaTime);
  169. return asiaTime.getDay()
  170. };
  171. const isDayOfWeek = (day) => getDayOfWeek() === (day % 7);
  172. const normalizeAvatar = (avatar) => {
  173. if (!avatar) return '//lain.bgm.tv/pic/user/l/icon.jpg'
  174. if (avatar.startsWith('https://tinygrail.oss-cn-hangzhou.aliyuncs.com/')) { return avatar + '!w120' }
  175. const a = avatar.replace('http://', '//');
  176. return a
  177. };
  178. const launchObserver = ({
  179. parentNode,
  180. selector,
  181. failCallback = null,
  182. successCallback = null,
  183. stopWhenSuccess = true,
  184. config = { childList: true, subtree: true }
  185. }) => {
  186. if (!parentNode) return
  187. const observeFunc = mutationList => {
  188. if (!document.querySelector(selector)) {
  189. if (failCallback) failCallback();
  190. return
  191. }
  192. if (stopWhenSuccess) observer.disconnect();
  193. mutationList.itemFilter = (fn, type = 'addedNodes') => mutationList.map(i => Array.from(i[type]).filter(fn)).reduce((arr, val) => arr.concat(val), []);
  194. if (successCallback) successCallback(mutationList);
  195. };
  196. const observer = new MutationObserver(observeFunc);
  197. observer.observe(parentNode, config);
  198. };
  199. const api = 'https://tinygrail.com/api/';
  200. const getData = (url) => {
  201. if (!url.startsWith('http') && !url.startsWith('/')) url = api + url;
  202. return new Promise((resolve, reject) => {
  203. $.ajax({
  204. url: url,
  205. type: 'GET',
  206. xhrFields: { withCredentials: true },
  207. success: res => { resolve(res); },
  208. error: err => { reject(err); }
  209. });
  210. })
  211. };
  212. const postData = (url, data) => {
  213. const d = JSON.stringify(data);
  214. if (!url.startsWith('http') && !url.startsWith('/')) url = api + url;
  215. return new Promise((resolve, reject) => {
  216. $.ajax({
  217. url: url,
  218. type: 'POST',
  219. contentType: 'application/json',
  220. data: d,
  221. xhrFields: { withCredentials: true },
  222. success: res => { resolve(res); },
  223. error: err => { reject(err); }
  224. });
  225. })
  226. };
  227. const getSacrifices = async (charaId, username) => {
  228. let Sacrifices = 0;
  229. let Assets = 0;
  230. const templeInfo = await getData(`chara/user/temple/${username || getMe()}/1/100?keyword=${charaId}`);
  231. const temple = templeInfo.Value.Items.find(i => i.CharacterId === charaId);
  232. if (temple !== undefined) {
  233. Sacrifices = temple.Sacrifices || 0;
  234. Assets = temple.Assets || 0;
  235. }
  236. const Damage = Math.max(Sacrifices - Assets, 0);
  237. return { Sacrifices, Assets, Damage }
  238. };
  239. const AutoTempleList = configGenerator('autoTempleList', [], {
  240. postGet: value => value.map(item => ({
  241. ...item,
  242. charaId: parseInt(item.charaId),
  243. target: parseInt(item.target),
  244. bidPrice: parseFloat(item.bidPrice)
  245. }))
  246. });
  247. const addBuildTemple = (info) => {
  248. const autoTempleList = AutoTempleList.get();
  249. const index = autoTempleList.findIndex(temple => parseInt(temple.charaId) === info.charaId);
  250. if (index >= 0) {
  251. autoTempleList.splice(index, 1);
  252. }
  253. autoTempleList.unshift(info);
  254. AutoTempleList.set(autoTempleList);
  255. };
  256. const removeBuildTemple = (charaId) => {
  257. const autoTempleList = AutoTempleList.get();
  258. const index = autoTempleList.findIndex(temple => parseInt(temple.charaId) === charaId);
  259. if (index >= 0) {
  260. autoTempleList.splice(index, 1);
  261. }
  262. $(`#grailBox.chara${charaId} #autobuildButton`).text('[自动建塔]');
  263. AutoTempleList.set(autoTempleList);
  264. };
  265. const autoBuildTemple = async (charas = undefined) => {
  266. const buildTemple = (chara, amount) => {
  267. postData(`chara/sacrifice/${chara.charaId}/${amount}/false`, null).then((d) => {
  268. if (d.State === 0) {
  269. console.log(`#${chara.charaId} ${chara.name} #祭${amount} 获得金额 ${d.Value.Balance.toFixed(2)}`);
  270. $(`#grailBox.chara${chara.charaId} #autobuildButton`).text('[自动建塔]');
  271. removeBuildTemple(chara.charaId);
  272. } else {
  273. console.log(`${d.Message}`);
  274. }
  275. });
  276. };
  277. const postBid = (chara, price, amount, myAmount, Needed) => {
  278. postData(`chara/bid/${chara.charaId}/${price}/${amount}`, null).then((d) => {
  279. if (d.Message) console.log(`#${chara.charaId} ${chara.name} ${d.Message}`);
  280. else {
  281. console.log(`买入成交 #${chara.charaId} ${chara.name} ${price}*${amount}`);
  282. if ((myAmount + amount) >= Needed) {
  283. buildTemple(chara, Needed);
  284. }
  285. }
  286. });
  287. };
  288. const getAskin = (Asks, low_price) => {
  289. let [price, amount] = [0, 0];
  290. for (let i = 0; i < Asks.length; i++) {
  291. if (Asks[i].Price > 0 && Asks[i].Price <= low_price) {
  292. amount += Asks[i].Amount;
  293. price = Asks[i].Price;
  294. }
  295. }
  296. return [price, amount]
  297. };
  298. const remove_myAsks = (Asks, myAsks) => {
  299. for (let i = 0; i < Asks.length; i++) {
  300. for (let j = 0; j < myAsks.length; j++) {
  301. if (formatAskPrice(Asks[i].Price) === formatAskPrice(myAsks[j].Price)) Asks[i].Amount -= myAsks[j].Amount;
  302. }
  303. if (Asks[i].Amount === 0) delete Asks[i];
  304. }
  305. Asks = removeEmpty(Asks);
  306. return Asks
  307. };
  308. if (charas === undefined) {
  309. charas = AutoTempleList.get();
  310. }
  311. for (let i = 0; i < charas.length; i++) {
  312. const chara = charas[i];
  313. console.log(`自动建塔 check #${chara.charaId} ${chara.name}`);
  314. const charaInfo = await getData(`chara/user/${chara.charaId}`);
  315. const myAsks = charaInfo.Value.Asks;
  316. const Amount = charaInfo.Value.Amount;
  317. const { Assets, Damage } = await getSacrifices(chara.charaId);
  318. const Needed = chara.target - Assets - Math.floor(Damage / 2);
  319. if (Needed <= 0) {
  320. removeBuildTemple(chara.charaId);
  321. } else if (Amount >= Needed) {
  322. buildTemple(chara, Needed);
  323. } else {
  324. getData(`chara/depth/${chara.charaId}`).then((d) => {
  325. let Asks = d.Value.Asks;
  326. Asks = remove_myAsks(Asks, myAsks);
  327. const AskPrice = Asks[0] ? Asks[0].Price : 0;
  328. if (AskPrice && AskPrice <= chara.bidPrice) {
  329. const [price, amount] = getAskin(Asks, chara.bidPrice);
  330. postBid(chara, price, Math.min(amount, Needed - Amount), Amount, Needed);
  331. }
  332. });
  333. }
  334. }
  335. };
  336. const closeDialog = (name = 'main') => {
  337. if (name === 'main') {
  338. $('#TB_overlay').remove();
  339. $('#TB_window').remove();
  340. } else {
  341. $(`#TB_overlay[data-name=${name}]`).remove();
  342. $(`#TB_window[data-name=${name}]`).remove();
  343. }
  344. };
  345. const showDialog = (innerHTML, config = {}) => {
  346. const { name, maxWidth, minWidth, canClose, closeBefore } = {
  347. name: 'main',
  348. maxWidth: '640px',
  349. minWidth: '400px',
  350. canClose: true,
  351. closeBefore: false,
  352. ...config
  353. };
  354. const dialog = `
  355. <div id="TB_overlay" data-name="${name}" class="TB_overlayBG TB_overlayActive"></div>
  356. <div id="TB_window" data-name="${name}" class="dialog" style="display:block;max-width:${maxWidth};min-width:${minWidth};">
  357. ${innerHTML}
  358. <a id="TB_closeWindowButton" data-name="${name}" title="Close" ${canClose ? '' : 'style="display: none;"'}>X关闭</a>
  359. </div>
  360. `;
  361. if (closeBefore) closeDialog(name);
  362. $('body').append(dialog);
  363. if (canClose) {
  364. $(`#TB_closeWindowButton[data-name=${name}]`).on('click', () => closeDialog(name));
  365. $(`#TB_overlay[data-name=${name}]`).on('click', () => closeDialog(name));
  366. }
  367. return {
  368. element: $(`#TB_window[data-name=${name}]`),
  369. closeDialog: () => { closeDialog(name); }
  370. }
  371. };
  372. const FillICOList = configGenerator('fillicoList', [], {
  373. postGet: value => value.map(item => ({
  374. ...item,
  375. Id: parseInt(item.Id),
  376. charaId: parseInt(item.charaId),
  377. target: parseInt(item.target),
  378. end: item.end
  379. })).sort((a, b) => a.end - b.end)
  380. });
  381. const ICOStandardList = [];
  382. const ICO_TAX = 500000;
  383. const addFillICO = (info) => {
  384. const fillICOList = FillICOList.get();
  385. const index = fillICOList.findIndex(item => parseInt(item.Id) === info.Id);
  386. if (index >= 0) {
  387. fillICOList.splice(index, 1);
  388. }
  389. if (info.target > 0) fillICOList.push(info);
  390. FillICOList.set(fillICOList);
  391. };
  392. const calculateICO = (ico, targetLevel, fillMin, joined, balance) => {
  393. const heads = ico.Users + (targetLevel === undefined || joined ? 0 : 1);
  394. const headLevel = Math.max(Math.floor((heads - 10) / 5), 0);
  395. ICOStandard((targetLevel || headLevel) + 1);
  396. const moneyTotal = ico.Total + (balance || 0);
  397. const moneyLevel = ICOStandardList.filter(i => i.Total <= moneyTotal).length;
  398. let icoMoneyLevel = Infinity;
  399. let level = 0;
  400. if (fillMin) {
  401. icoMoneyLevel = ICOStandardList.filter(i => i.Total < ico.Total).length;
  402. if (balance === undefined || moneyLevel > icoMoneyLevel) icoMoneyLevel++;
  403. level = Math.min(headLevel, icoMoneyLevel, targetLevel);
  404. } else if (targetLevel === undefined) {
  405. level = Math.min(headLevel, moneyLevel);
  406. } else if (balance === undefined) {
  407. level = Math.min(targetLevel, headLevel);
  408. } else {
  409. level = Math.min(targetLevel, headLevel, moneyLevel);
  410. }
  411. const levelInfo = ICOStandard(level);
  412. const price = (Math.max(ico.Total, levelInfo.Total) - ICO_TAX) / levelInfo.Amount;
  413. const needMoney = Math.max(levelInfo.Total - ico.Total, 0);
  414. let message = '';
  415. if (headLevel === 0) {
  416. message = '人数不足';
  417. } else if (level === 0 && moneyLevel === 0) {
  418. message = '余额不足';
  419. } else if (level === targetLevel) {
  420. message = '设定目标等级';
  421. } else if (level === headLevel) {
  422. message = fillMin ? '人数最低等级' : '人数最高等级';
  423. } else if (level === icoMoneyLevel) {
  424. message = '当前注资最低等级';
  425. if (levelInfo.Total < ico.Total) message += '(余额不足补至下一等级)';
  426. } else if (level === moneyLevel) {
  427. message = '余额最高等级';
  428. }
  429. return {
  430. Level: level,
  431. Total: levelInfo.Total,
  432. Price: price,
  433. Amount: levelInfo.Amount,
  434. Users: levelInfo.Users,
  435. NeedMoney: needMoney,
  436. Message: message,
  437. NextLevel: ICOStandard(level + 1)
  438. }
  439. };
  440. const ICOStandard = (lv) => {
  441. if (lv === Infinity) return ICOStandard(ICOStandardList.length)
  442. lv = parseInt(lv < 1 ? 1 : lv);
  443. if (lv > ICOStandardList.length) {
  444. for (let level = ICOStandardList.length + 1; level <= lv; level++) {
  445. ICOStandardList.push({
  446. Level: level,
  447. Users: level * 5 + 10,
  448. Amount: 10000 + (level - 1) * 7500,
  449. Total: level === 1 ? 100000 + ICO_TAX : (Math.pow(level, 2) * 100000 + ICOStandardList[level - 1 - 1].Total)
  450. });
  451. }
  452. }
  453. return ICOStandardList[lv - 1]
  454. };
  455. const fullfillICO = async (icoList) => {
  456. const dialog = `<div class="info_box">
  457. <div class="title">自动补款检测中</div>
  458. <div class="r###lt" style="max-height:500px;overflow:auto;"></div>
  459. </div>`;
  460. if (!$('#TB_window').length) showDialog(dialog);
  461. const failedICOList = [];
  462. let balance = await getData('chara/user/assets').then(d => d.Value ? d.Value.Balance : undefined).catch((e) => { console.log('获取余额失败', e); return undefined });
  463. for (let i = 0; i < icoList.length; i++) {
  464. const Id = icoList[i].Id;
  465. const charaId = icoList[i].charaId;
  466. const targetlv = icoList[i].target;
  467. const fillMin = icoList[i].fillMin;
  468. const [icoInfo, myInitial] = await Promise.all([
  469. getData(`chara/${charaId}`).then(d => d.State === 0 ? d.Value : undefined).catch((e) => { console.log(`获取 #${charaId} ICO信息失败`, e); return undefined }),
  470. getData(`chara/initial/${Id}`).then(d => d.Value).catch((e) => { console.log(`获取 #${charaId} ICO信息失败`, e); return null })
  471. ]);
  472. if (icoInfo) {
  473. const joined = myInitial !== undefined;
  474. const predicted = calculateICO(icoInfo, targetlv, fillMin, joined, balance);
  475. if (!predicted.Level) {
  476. $('.info_box .r###lt').prepend(`<div class="row">#${charaId} 目标: lv${targetlv} 人数: ${icoInfo.Users}, ${predicted.Message}, 未补款</div>`);
  477. console.log(`#${charaId},目标:lv${targetlv},人数:${icoInfo.Users},${predicted.Message},未补款`);
  478. } else if (predicted.NeedMoney > 0) {
  479. const offer = Math.max(predicted.NeedMoney, 5000);
  480. const joinRes = await postData(`chara/join/${Id}/${offer}`, null);
  481. if (joinRes.State === 0) {
  482. $('.info_box .r###lt').prepend(`<div class="row">#${charaId} 目标: lv${targetlv}, 自动补款至${predicted.Message} lv${predicted.Level}, 补款: ${offer}cc</div>`);
  483. console.log(`#${charaId},目标:lv${targetlv},自动补款至${predicted.Message}lv${predicted.Level},补款:${offer}cc`);
  484. if (balance) balance -= offer;
  485. } else {
  486. $('.info_box .r###lt').prepend(`<div class="row">#${charaId} ${joinRes.Message}</div>`);
  487. console.log(joinRes.Message);
  488. }
  489. } else if (predicted.Level === targetlv) {
  490. $('.info_box .r###lt').prepend(`<div class="row">#${charaId} 目标: lv${targetlv} 总额: ${icoInfo.Total}, 已达标, 无需补款</div>`);
  491. console.log(`#${charaId},目标:lv${targetlv},总额:${icoInfo.Total},已达标,无需补款`);
  492. } else {
  493. $('.info_box .r###lt').prepend(`<div class="row">#${charaId} 目标: lv${targetlv} 总额: ${icoInfo.Total}, 已达到${predicted.Message} lv${predicted.Level}, 未补款</div>`);
  494. console.log(`#${charaId},目标:lv${targetlv},总额:${icoInfo.Total},已达到${predicted.Message}lv${predicted.Level},未补款`);
  495. }
  496. } else {
  497. $('.info_box .r###lt').prepend(`<div class="row">#${charaId} 目标: lv${targetlv}, 获取角色 ICO 信息失败, 稍后重试</div>`);
  498. console.log(`#${charaId},目标:lv${targetlv},获取角色ICO信息失败,稍后重试`);
  499. failedICOList.push(icoList[i]);
  500. }
  501. }
  502. if (failedICOList.length) fullfillICO(failedICOList);
  503. };
  504. const autoFillICO = () => {
  505. const fillICOList = FillICOList.get();
  506. const icoList = [];
  507. for (let i = 0; i < fillICOList.length; i++) {
  508. const endTime = new Date(new Date(fillICOList[i].end) - (new Date().getTimezoneOffset() + 8 * 60) * 60 * 1000);
  509. const leftTime = (new Date(endTime).getTime() - new Date().getTime()) / 1000;
  510. if (leftTime < 60) {
  511. console.log(`ico check#${fillICOList[i].charaId} -「${fillICOList[i].name}」 目标等级:lv${fillICOList[i].target} ${leftTime}s left`);
  512. icoList.push(fillICOList[i]);
  513. delete fillICOList[i];
  514. }
  515. }
  516. FillICOList.set(removeEmpty(fillICOList));
  517. if (icoList.length) fullfillICO(icoList);
  518. };
  519. const openICODialog = (chara) => {
  520. const fillICOList = FillICOList.get();
  521. let target = 1;
  522. let fillMin = true;
  523. const item = fillICOList.find(item => parseInt(item.Id) === chara.Id);
  524. if (item) {
  525. target = item.target;
  526. fillMin = item.fillMin;
  527. }
  528. const dialog = `<div class="title">自动补款 - #${chara.CharacterId} ${chara.Name}」 lv${target}</div>
  529. <div class="desc">
  530. 最高目标等级:<input type="number" class="target" min="1" max="20" step="1" value="${target}" style="width:50px">
  531. </div>
  532. <div class="option">
  533. <button id="fillMinButton" class="checkbox">
  534. 按不爆注的最低等级补款<span class="slider"><span class="button"></span></span>
  535. </button>
  536. </div>
  537. <div class="label" style="margin-bottom: 7px;"><span id="fillMinInfo" class="info"></span></div>
  538. <div class="label"><div class="trade ico">
  539. <button id="startfillICOButton" class="active">自动补款</button>
  540. <button id="fillICOButton" style="background-color: #5fda15;">立即补款</button>
  541. <button id="cancelfillICOButton">取消补款</button>
  542. </div></div>
  543. <div class="loading" style="display:none"></div>`;
  544. const { closeDialog } = showDialog(dialog);
  545. $('#fillMinButton').on('click', (e) => {
  546. if ($('#fillMinButton').hasClass('on')) {
  547. $('#fillMinButton').removeClass('on');
  548. $('#fillMinButton .button').animate({ 'margin-left': '0px' });
  549. $('#fillMinButton .button').css('background-color', '#ccc');
  550. $('#fillMinInfo').html('根据参与者人数、已筹集资金、个人资金余额,<br/>补款至不超过设定等级的<b>最高等级</b>。');
  551. } else {
  552. $('#fillMinButton').addClass('on');
  553. $('#fillMinButton .button').animate({ 'margin-left': '20px' });
  554. $('#fillMinButton .button').css('background-color', '#7fc3ff');
  555. $('#fillMinInfo').html('根据参与者人数、已筹集资金、个人资金余额,<br/>补款至不爆注的<b>最低等级</b>。');
  556. }
  557. }).trigger('click');
  558. if (fillMin === false) $('#fillMinButton').trigger('click');
  559. $('#cancelfillICOButton').on('click', function () {
  560. const fillICOList = FillICOList.get();
  561. const index = fillICOList.findIndex(item => parseInt(item.Id) === chara.Id);
  562. if (index >= 0) {
  563. alert(`取消自动补款${chara.Name}`);
  564. $(`#grailBox.chara${chara.CharacterId} #followICOButton`).text('[自动补款]');
  565. fillICOList.splice(index, 1);
  566. FillICOList.set(fillICOList);
  567. }
  568. closeDialog();
  569. console.log(fillICOList);
  570. });
  571. $('#startfillICOButton').on('click', function () {
  572. const target = parseFloat($('.desc .target').val());
  573. if (target <= 0 || !Number.isInteger(target)) {
  574. alert('请输入正整数!');
  575. return
  576. }
  577. const info = {};
  578. info.Id = parseInt(chara.Id);
  579. info.charaId = parseInt(chara.CharacterId);
  580. info.name = chara.Name;
  581. info.target = target;
  582. info.fillMin = $('#fillMinButton').hasClass('on');
  583. info.end = chara.End;
  584. addFillICO(info);
  585. alert(`启动自动补款#${chara.Id} ${chara.Name}`);
  586. $(`#grailBox.chara${chara.CharacterId} #followICOButton`).text('[自动补款中]');
  587. closeDialog();
  588. console.log(fillICOList);
  589. });
  590. $('#fillICOButton').on('click', function () {
  591. const target = parseFloat($('.desc .target').val());
  592. if (target <= 0 || !Number.isInteger(target)) {
  593. alert('请输入正整数!');
  594. return
  595. }
  596. const info = {};
  597. info.Id = chara.Id;
  598. info.charaId = chara.CharacterId;
  599. info.name = chara.Name;
  600. info.target = target;
  601. info.fillMin = $('#fillMinButton').hasClass('on');
  602. info.end = chara.End;
  603. closeDialog();
  604. if (confirm(`立即补款#${chara.Id} ${chara.Name} lv${target}`)) {
  605. fullfillICO([info]);
  606. }
  607. });
  608. };
  609. const setFullFillICO = (chara) => {
  610. let button = '<button id="followICOButton" class="text_button">[自动补款]</button>';
  611. if (FillICOList.get().some(item => parseInt(item.charaId) === chara.CharacterId)) {
  612. button = '<button id="followICOButton" class="text_button">[自动补款中]</button>';
  613. }
  614. $(`#grailBox.chara${chara.CharacterId} .title .text`).after(button);
  615. $(`#grailBox.chara${chara.CharacterId} #followICOButton`).on('click', () => {
  616. openICODialog(chara);
  617. });
  618. };
  619. const autoJoinICO = async (icoList) => {
  620. for (let i = 0; i < icoList.length; i++) {
  621. const charaId = icoList[i].CharacterId;
  622. await getData(`chara/${charaId}`).then((d) => {
  623. if (d.State === 0) {
  624. const offer = 5000;
  625. const Id = d.Value.Id;
  626. if (d.Value.Total < 100000 && d.Value.Users < 15) {
  627. getData(`chara/initial/${Id}`).then((d) => {
  628. if (d.State === 1 && d.Message === '尚未参加ICO。') {
  629. postData(`chara/join/${Id}/${offer}`, null).then((d) => {
  630. if (d.State === 0) {
  631. console.log(`#${charaId} 追加注资成功。`);
  632. $(`#eden_tpc_list li[data-id=${charaId}] .row`).append(`<small class="raise">+${offer}</small>`);
  633. }
  634. });
  635. }
  636. });
  637. }
  638. }
  639. });
  640. }
  641. };
  642. const autoBeginICO = async (icoList) => {
  643. for (let i = 0; i < icoList.length; i++) {
  644. const charaId = icoList[i];
  645. await getData(`chara/${charaId}`).then(async (d) => {
  646. if (d.State === 1 && d.Message === '找不到人物信息。') {
  647. const offer = 10000;
  648. await postData(`chara/init/${charaId}/${offer}`, null).then((d) => {
  649. if (d.State === 0) {
  650. console.log(`#${charaId} ICO 启动成功。`);
  651. $(`#eden_tpc_list li[data-id=${charaId}] .row`).append(`<small class="raise">+${offer}</small>`);
  652. } else {
  653. console.log(d.Message);
  654. }
  655. });
  656. }
  657. });
  658. }
  659. };
  660. const autoJoinFollowIco = () => {
  661. const followList = FollowList.get();
  662. const joinList = [];
  663. if (followList.charas.length) {
  664. postData('chara/list', followList.charas).then(d => {
  665. d.Value.forEach(chara => {
  666. if (chara.End) {
  667. const endTime = new Date(new Date(chara.End) - (new Date().getTimezoneOffset() + 8 * 60) * 60 * 1000);
  668. const leftTime = (new Date(endTime).getTime() - new Date().getTime()) / 1000;
  669. console.log(`ICO check #${chara.CharacterId} -「${chara.Name}」 ${leftTime}s left`);
  670. if (leftTime > 0 && leftTime <= 60 * 60) {
  671. joinList.push(chara);
  672. }
  673. }
  674. });
  675. autoJoinICO(joinList);
  676. });
  677. }
  678. };
  679. const getWeeklyShareBonus = () => {
  680. if (!confirm('已经周六了,赶紧领取股息吧?')) return
  681. getData('event/share/bonus').then((d) => {
  682. if (d.State === 0) { alert(d.Value); } else { alert(d.Message); }
  683. });
  684. };
  685. const getShareBonus = () => {
  686. if (isDayOfWeek(6)) {
  687. getData('event/share/bonus/check').then((d) => {
  688. if (d.State === 0) {
  689. getWeeklyShareBonus();
  690. }
  691. });
  692. }
  693. };
  694. const hideBonusButton = () => {
  695. if (!$('#bonusButton').length) return
  696. getData('event/share/bonus/test').then((d) => {
  697. const x = d.Value.Share / 10000;
  698. const allowance = Math.log10(x + 1) * 30 - x;
  699. if (d.State === 0 && allowance < 0) $('#bonusButton').remove();
  700. });
  701. };
  702. const loadUserAuctions = (d) => {
  703. d.Value.forEach((a) => {
  704. $(`.item_list[data-id=${a.CharacterId}] .user_auction`).remove();
  705. $(`.item_list[data-id=${a.CharacterId}] .my_auction`).remove();
  706. if (a.State !== 0) {
  707. const userAuction = `<span class="user_auction auction_tip" title="竞拍人数 / 竞拍数量">${formatNumber(a.State, 0)} / ${formatNumber(a.Type, 0)}</span>`;
  708. $(`.item_list[data-id=${a.CharacterId}] .time`).after(userAuction);
  709. $(`#grailBox.chara${a.CharacterId} #auctionHistoryButton`).before(userAuction);
  710. $('#TB_window.dialog .desc').append(userAuction);
  711. }
  712. if (a.Price !== 0) {
  713. const myAuction = `<span class="my_auction auction_tip" title="出价 / 数量">₵${formatNumber(a.Price, 2)} / ${formatNumber(a.Amount, 0)}</span>`;
  714. $(`.item_list[data-id=${a.CharacterId}] .time`).after(myAuction);
  715. $(`#grailBox.chara${a.CharacterId} #auctionHistoryButton`).before(myAuction);
  716. $('#TB_window.dialog .desc').append(myAuction);
  717. }
  718. });
  719. };
  720. const loadValhalla = async (ids) => {
  721. for (let i = 0; i < ids.length; i++) {
  722. const Id = ids[i];
  723. await getData(`chara/user/${Id}/tinygrail/false`).then((d) => {
  724. $(`.item_list[data-id=${Id}] .valhalla`).remove();
  725. const valhalla = `<small class="even valhalla" title="拍卖底价 / 拍卖数量">₵${formatNumber(d.Value.Price, 2)} / ${d.Value.Amount}</small>`;
  726. $(`.fill_auction[data-id=${Id}]`).before(valhalla);
  727. if (d.Value.Amount > d.Value.AuctionTotal) {
  728. const $fillAuc = $(`.fill_auction[data-id=${Id}]`);
  729. $fillAuc.show();
  730. $fillAuc.attr('title', $fillAuc.attr('title').replace(/(\(₵\d+\))?$/, `(₵${formatNumber(d.Value.Price * (d.Value.Amount - d.Value.AuctionTotal))})`));
  731. }
  732. });
  733. }
  734. };
  735. const bidAuction = (chara) => {
  736. $('#TB_window .loading').show();
  737. $('#TB_window .label').hide();
  738. $('#TB_window .desc').hide();
  739. $('#TB_window .trade').hide();
  740. const price = $('.trade.auction .price').val();
  741. const amount = $('.trade.auction .amount').val();
  742. postData(`chara/auction/${chara.Id}/${price}/${amount}`, null).then((d) => {
  743. $('#TB_window .loading').hide();
  744. $('#TB_window .label').show();
  745. $('#TB_window .desc').show();
  746. $('#TB_window .trade').show();
  747. if (d.State === 0) {
  748. const message = d.Value;
  749. $('#TB_window .trade').hide();
  750. $('#TB_window .label').hide();
  751. $('#TB_window .desc').text(message);
  752. } else {
  753. alert(d.Message);
  754. }
  755. });
  756. };
  757. const cancelAuction = (chara) => {
  758. let message = '确定取消竞拍?';
  759. if (isDayOfWeek(6)) message = '周六取消竞拍将收取20%税,确定取消竞拍?';
  760. if (!confirm(message)) return
  761. $('#TB_window .loading').show();
  762. $('#TB_window .label').hide();
  763. $('#TB_window .desc').hide();
  764. $('#TB_window .trade').hide();
  765. getData('chara/user/auction/1/100').then((d) => {
  766. let id = 0;
  767. for (let i = 0; i < d.Value.Items.length; i++) {
  768. if (chara.Id === d.Value.Items[i].CharacterId) {
  769. id = d.Value.Items[i].Id;
  770. break
  771. }
  772. }
  773. if (id) {
  774. postData(`chara/auction/cancel/${id}`, null).then((d) => {
  775. $('#TB_window .loading').hide();
  776. $('#TB_window .label').show();
  777. $('#TB_window .desc').show();
  778. $('#TB_window .trade').show();
  779. if (d.State === 0) {
  780. $('#TB_window .trade').hide();
  781. $('#TB_window .label').hide();
  782. $('#TB_window .desc').text('取消竞拍成功');
  783. } else alert(d.Message);
  784. });
  785. } else {
  786. $('#TB_window .loading').hide();
  787. $('#TB_window .desc').text('未找到竞拍角色');
  788. }
  789. });
  790. };
  791. const Settings = configGenerator('settings', {
  792. pre_temple: 'on',
  793. hide_grail: 'off',
  794. hide_link: 'off',
  795. hide_temple: 'off',
  796. hide_board: 'off',
  797. auction_num: 'one',
  798. merge_order: 'off',
  799. get_bonus: 'on',
  800. gallery: 'off',
  801. valhalla_sacrifices: 'on'
  802. });
  803. const openAuctionDialog = (chara, auction) => {
  804. let auction_num = chara.State;
  805. if (Settings.get().auction_num === 'one') auction_num = 1;
  806. const charaId = chara.CharacterId || chara.Id;
  807. const price = Math.ceil(chara.Price * 100) / 100;
  808. const total = formatNumber(price * chara.State, 2);
  809. const dialog = `<div class="title" title="拍卖底价 / 竞拍数量 / 流通股份">股权拍卖 - #${chara.Id} ${chara.Name}」 ${formatNumber(chara.Price, 2)} / ${chara.State} / ${chara.Total}</div>
  810. <div class="desc">
  811. <button id="fullfill_auction" class="text_button" title="当竞拍数量未满时补满数量">[补满]</button>
  812. <button id="change_amount" class="text_button" title="按修改后的价格确定数量,保持总价不变">[改量]</button>
  813. <button id="change_price" class="text_button" title="按修改后的数量确定价格,保持总价不变">[改价]</button>
  814. </div><div class="label">
  815. <span class="input">价格</span><span class="input">数量</span><span class="total">合计 -₵${total}</span>
  816. </div><div class="trade auction">
  817. <input class="price" type="number" style="width: 100px" min="${price}" value="${price}">
  818. <input class="amount" type="number" style="width: 100px" min="1" max="${chara.State}" value="${auction_num}">
  819. <button id="bidAuctionButton" class="active">确定</button><button id="cancelAuctionButton" style="display: none">取消竞拍</button></div>
  820. <div class="loading" style="display:none"></div>`;
  821. showDialog(dialog);
  822. $(`#grailBox.chara${charaId} .assets_box .auction_tip`).remove();
  823. loadUserAuctions(auction);
  824. $('#cancelAuctionButton').on('click', function () {
  825. cancelAuction(chara);
  826. });
  827. $('#bidAuctionButton').on('click', function () {
  828. bidAuction(chara);
  829. });
  830. if (!auction.Value.length) {
  831. auction.Value = [{ Price: 0, Amount: 0, Type: 0, State: 0 }];
  832. }
  833. if (auction.Value[0].Price) {
  834. $('.trade.auction .price').val(auction.Value[0].Price);
  835. $('.trade.auction .amount').val(auction.Value[0].Amount);
  836. const total = formatNumber(auction.Value[0].Price * auction.Value[0].Amount, 2);
  837. $('#TB_window .label .total').text(`合计 -₵${total}`);
  838. $('#cancelAuctionButton').show();
  839. }
  840. $('#TB_window .auction input').on('keyup', () => {
  841. const price = $('.trade.auction .price').val();
  842. const amount = $('.trade.auction .amount').val();
  843. const total = formatNumber(price * amount, 2);
  844. $('#TB_window .label .total').text(`合计 -₵${total}`);
  845. });
  846. $('#fullfill_auction').on('click', function () {
  847. const total_auction = chara.State;
  848. const amount = total_auction - auction.Value[0].Type + auction.Value[0].Amount;
  849. const price = Math.ceil(chara.Price * 100) / 100;
  850. $('.trade.auction .price').val(price);
  851. $('.trade.auction .amount').val(amount);
  852. $('#TB_window .label .total').text(`合计 -₵${formatNumber(price * amount, 2)}`);
  853. });
  854. $('#change_amount').on('click', function () {
  855. const price = parseFloat($('.trade.auction .price').val());
  856. const total = auction.Value[0].Price * auction.Value[0].Amount;
  857. const amount = Math.ceil(total / price);
  858. $('.trade.auction .amount').val(amount);
  859. $('#TB_window .label .total').text(`合计 -₵${formatNumber(price * amount, 2)}`);
  860. });
  861. $('#change_price').on('click', function () {
  862. const amount = parseInt($('.trade.auction .amount').val());
  863. const total = auction.Value[0].Price * auction.Value[0].Amount;
  864. const price = Math.ceil(total / amount * 100) / 100;
  865. $('.trade.auction .price').val(price);
  866. $('#TB_window .label .total').text(`合计 -₵${formatNumber(price * amount, 2)}`);
  867. });
  868. };
  869. const openAuctionDialogSimple = (chara) => {
  870. postData('chara/auction/list', [chara.CharacterId || chara.Id]).then((d) => {
  871. openAuctionDialog(chara, d);
  872. });
  873. };
  874. const showTopWeek = () => {
  875. getData('chara/topweek').then((d) => {
  876. let totalExtra = 0; let totalPeople = 0;
  877. for (let i = 0; i < d.Value.length; i++) {
  878. totalExtra += d.Value[i].Extra;
  879. totalPeople += d.Value[i].Type;
  880. }
  881. console.log(totalExtra + '/' + totalPeople + '=' + totalExtra / totalPeople);
  882. $('#topWeek .auction_button').hide();
  883. for (let i = 0; i < d.Value.length; i++) {
  884. const score = d.Value[i].Extra + d.Value[i].Type * totalExtra / totalPeople;
  885. const tag = $('#topWeek .assets .item')[i].querySelector('.tag');
  886. $(tag).attr('title', '综合得分:' + formatNumber(score, 2));
  887. const average = (d.Value[i].Extra + d.Value[i].Price * d.Value[i].Sacrifices) / d.Value[i].Assets;
  888. const buff = $('#topWeek .assets .item')[i].querySelector('.buff');
  889. $(buff).attr('title', '平均拍价:' + formatNumber(average, 2));
  890. const charaId = d.Value[i].CharacterId;
  891. const auction_button = $(`<div class="name auction" data-id="${charaId}">
  892. <span title="竞拍人数 / 竞拍数量 / 拍卖总数">${d.Value[i].Type} / ${d.Value[i].Assets} / ${d.Value[i].Sacrifices}</span></div>`);
  893. $($('#topWeek .assets .item')[i]).append(auction_button);
  894. const chara = { Id: d.Value[i].CharacterId, Name: d.Value[i].CharacterName, Price: d.Value[i].Price, State: d.Value[i].Sacrifices, Total: 0 };
  895. auction_button.css('cursor', 'pointer').on('click', () => {
  896. postData('chara/auction/list', [charaId]).then((d) => {
  897. openAuctionDialog(chara, d);
  898. });
  899. });
  900. }
  901. });
  902. };
  903. const PersistentCache = configGenerator('PersistentCache', {});
  904. const addValhallaElement = (charaElement, amount, sacrifices, damage) => {
  905. const title = `持股数 | #祭值${damage ? ' (损耗值)' : ''},点击刷新`;
  906. const text = `${amount} | ${sacrifices}${damage ? `(${damage})` : ''}`;
  907. charaElement = $(charaElement);
  908. const info = charaElement.find('.valhalla-sacrifices');
  909. if (info.length === 0) {
  910. charaElement.find('a.avatar[data-id] > img').after(`<div class="valhalla-sacrifices" title="${title}"><small>${text}</small></div>`);
  911. } else {
  912. info.attr('title', title).find('small').text(text);
  913. }
  914. };
  915. const showValhallaItems = async (itemList) => {
  916. const sacrificesCache = PersistentCache.get().sacrifices || {};
  917. const freshItem = [];
  918. const time = (new Date()).valueOf();
  919. const cacheEnd = time - 24 * 60 * 60 * 1000;
  920. itemList.forEach(i => {
  921. const charaId = i.querySelector('a.avatar[data-id]').dataset.id;
  922. if (charaId in sacrificesCache) {
  923. const sacrifices = sacrificesCache[charaId];
  924. addValhallaElement(i, sacrifices.amount, sacrifices.sacrifices, sacrifices.damage);
  925. if ((new Date(sacrifices.time)) < cacheEnd) {
  926. delete sacrifices[charaId];
  927. freshItem.push(i);
  928. }
  929. } else { freshItem.push(i); }
  930. });
  931. for (const [key, value] of Object.entries(sacrificesCache)) {
  932. if ((new Date(value.time)) < cacheEnd) delete sacrificesCache[key];
  933. }
  934. PersistentCache.set({ ...PersistentCache.get(), sacrifices: sacrificesCache });
  935. for (let i = 0; i < freshItem.length; i++) {
  936. const chara = $(itemList[i]);
  937. const id = chara.find('a.avatar[data-id]').data('id');
  938. const charaInfo = await getData(`chara/user/${id}`);
  939. const amount = charaInfo.Value.Amount;
  940. const { Sacrifices, Damage } = await getSacrifices(id);
  941. addValhallaElement(chara, amount, Sacrifices, Damage);
  942. sacrificesCache[id.toString()] = { amount: amount, sacrifices: Sacrifices, damage: Damage, time: time };
  943. }
  944. PersistentCache.set({ ...PersistentCache.get(), sacrifices: sacrificesCache });
  945. };
  946. const showValhallaPersonal = () => {
  947. launchObserver({
  948. parentNode: document.getElementById('valhalla'),
  949. selector: '#valhalla li.initial_item.chara',
  950. successCallback: (mutationList) => {
  951. const itemList = mutationList.itemFilter(i => i.classList && i.classList.contains('initial_item'));
  952. showValhallaItems(itemList);
  953. },
  954. stopWhenSuccess: false
  955. });
  956. $('#valhalla').on('click', '.valhalla-sacrifices', (e) => {
  957. showValhallaItems($(e.currentTarget).closest('li.initial_item.chara'));
  958. e.stopPropagation();
  959. });
  960. };
  961. let lastEven = false;
  962. const renderBalanceLog = (item, even) => {
  963. const line = even ? 'line_even' : 'line_odd';
  964. let change = '';
  965. if (item.Change > 0) {
  966. change = `<span class="tag raise large">+₵${formatNumber(item.Change, 2)}</span>`;
  967. } else if (item.Change < 0) {
  968. change = `<span class="tag fall large">-₵${formatNumber(Math.abs(item.Change), 2)}</span>`;
  969. }
  970. let amount = '';
  971. if (item.Amount > 0) {
  972. amount = `<span class="tag new large">+${formatNumber(item.Amount, 0)}</span>`;
  973. } else if (item.Amount < 0) {
  974. amount = `<span class="tag even large">${formatNumber(item.Amount, 0)}</span>`;
  975. }
  976. let id = '';
  977. if (item.Type >= 4 && item.Type <= 13) {
  978. id = `data-id="${item.RelatedId}"`;
  979. }
  980. return `<li class="${line} item_list item_log" ${id}>
  981. <div class="inner">₵${formatNumber(item.Balance, 2)}
  982. <small class="grey">${formatTime(item.LogTime)}</small>
  983. <span class="row"><small class="time">${item.Description}</small></span>
  984. </div>
  985. <span class="tags">
  986. ${change}
  987. ${amount}
  988. </span>
  989. </li>`
  990. };
  991. const renderCharacterDepth = (chara) => {
  992. const depth = `<small class="raise">+${formatNumber(chara.Bids, 0)}</small><small class="fall">-${formatNumber(chara.Asks, 0)}</small><small class="even">${formatNumber(chara.Change, 0)}</small>`;
  993. return depth
  994. };
  995. const renderCharacterTag = (chara, item) => {
  996. let flu = '--';
  997. let tclass = 'even';
  998. if (chara.Fluctuation > 0) {
  999. tclass = 'raise';
  1000. flu = `+${formatNumber(chara.Fluctuation * 100, 2)}%`;
  1001. } else if (chara.Fluctuation < 0) {
  1002. tclass = 'fall';
  1003. flu = `${formatNumber(chara.Fluctuation * 100, 2)}%`;
  1004. }
  1005. const tag = `<div class="tag ${tclass}" title="₵${formatNumber(chara.MarketValue, 0)} / ${formatNumber(chara.Total, 0)}">₵${formatNumber(chara.Current, 2)} ${flu}</div>`;
  1006. return tag
  1007. };
  1008. const renderBadge = (item, withCrown, withNew, withLevel) => {
  1009. let badge = '';
  1010. if (withLevel) {
  1011. badge = `<span class="badge level lv${item.Level}">lv${item.Level}</span>`;
  1012. }
  1013. if (item.Type === 1 && withNew) {
  1014. badge += `<span class="badge new" title="+${formatNumber(item.Rate, 1)}新番加成剩余${item.Bonus}期"${item.Bonus}</span>`;
  1015. }
  1016. if (item.State > 0 && withCrown) {
  1017. badge += `<span class="badge crown" title="获得${item.State}次萌王"${item.State}</span>`;
  1018. }
  1019. return badge
  1020. };
  1021. const renderCharacter = (item, type, even, showCancel) => {
  1022. const line = even ? 'line_even' : 'line_odd';
  1023. const amount = `<small title="固定资产">${formatNumber(item.Sacrifices, 0)}</small>`;
  1024. const tag = renderCharacterTag(item);
  1025. const depth = renderCharacterDepth(item);
  1026. let id = item.Id;
  1027. if (item.CharacterId && item.Id !== item.CharacterId) {
  1028. id = item.CharacterId;
  1029. if (type === 'auction') type += '-ico';
  1030. }
  1031. const time = item.LastOrder;
  1032. let avatar = `<a href="/rakuen/topic/crt/${id}?trade=true" class="avatar l" target="right"><span class="avatarNeue avatarReSize32 ll" style="background-image:url('${normalizeAvatar(item.Icon)}')"></span></a>`;
  1033. let cancel = '';
  1034. if (showCancel) {
  1035. cancel = type.startsWith('auction')
  1036. ? `<small data-id="${id}" class="cancel_auction" title="取消关注竞拍">[取关]</small>`
  1037. : `<span><small data-id="${id}" class="cancel_auction">[取消]</small></span>`;
  1038. }
  1039. let badge = renderBadge(item, true, true, true);
  1040. let chara;
  1041. if (item.NotExist) {
  1042. chara = `<li class="${line} item_list" data-id="${id}">${avatar}<div class="inner">
  1043. <a href="/rakuen/topic/crt/${id}?trade=true" class="title avatar l" target="right">${item.Name}</a> <small class="grey"></small>
  1044. <div class="row"><small class="time"></small>
  1045. <span><small data-id="${id}" class="reload_chara" title="刷新角色信息" style="display: none;">[重新加载]</small>
  1046. <small data-id="${id}" data-name="${item.Name}" class="begin_ico" title="开启 ICO">[开启 ICO]</small></span>
  1047. </div></div></li>`;
  1048. } else if (type === 'auction') {
  1049. chara = `<li class="${line} item_list" data-id="${id}">${avatar}<div class="inner">
  1050. <a href="/rakuen/topic/crt/${id}?trade=true" class="title avatar l" target="right">${item.Name}${badge}</a> <small class="grey">(+${item.Rate.toFixed(2)})</small>
  1051. <div class="row"><small class="time">${formatTime(time)}</small>
  1052. <span><small data-id="${id}" class="fill_auction" title="当竞拍数量未满时补满数量" style="display: none;">[补满]</small>${cancel}</span>
  1053. </div></div>${tag}</li>`;
  1054. } else if (type === 'temple') {
  1055. let costs = '';
  1056. if (item.Assets - item.Sacrifices < 0) {
  1057. costs = `<small class="fall" title="损耗">${item.Assets - item.Sacrifices}</small>
  1058. <span><small data-id="${id}" data-lv="${item.CharacterLevel}" data-cost="${item.Sacrifices - item.Assets}" class="fill_costs">[补充]</small></span>`;
  1059. }
  1060. avatar = `<a href="/rakuen/topic/crt/${id}?trade=true" class="avatar l" target="right"><span class="avatarNeue avatarReSize32 ll" style="background-image:url('${normalizeAvatar(item.Cover)}')"></span></a>`;
  1061. chara = `<li class="${line} item_list" data-id="${id}" data-lv="${item.CharacterLevel}">${avatar}<div class="inner">
  1062. <a href="/rakuen/topic/crt/${id}?trade=true" class="title avatar l" target="right">${item.Name}<span class="badge lv${item.CharacterLevel}">lv${item.CharacterLevel}</span></a> <small class="grey">(+${item.Rate.toFixed(2)})</small>
  1063. <div class="row"><small class="time" title="创建时间">${formatTime(item.Create)}</small><small title="固有资产 / #祭值">${item.Assets} / ${item.Sacrifices}</small>${costs}</div></div>
  1064. <div class="tag lv${item.Level}">${item.Level}级圣殿</div></li>`;
  1065. } else if (item.Id !== item.CharacterId) {
  1066. const pre = calculateICO(item);
  1067. const percent = formatNumber(item.Total / pre.NextLevel.Total * 100, 0);
  1068. const icoState = item.Users === 0
  1069. ? `<small title="当前人数 / 当前资金">${formatNumber(item.State, 0)} / ${formatNumber(item.Total, 1)}</small>`
  1070. : `<small title="距下级还差${formatNumber(Math.max(pre.NextLevel.Users - item.Users, 0), 0)}人 / 当前资金(占下一等级百分比) / 预计价格">${formatNumber(item.Users, 0)}人 / ${formatNumber(item.Total, 1)}(${percent}%) / ${formatNumber(pre.Price, 2)}</small>`;
  1071. badge = renderBadge(item, false, false, false);
  1072. chara = `<li class="${line} item_list" data-id="${id}">${avatar}<div class="inner">
  1073. <a href="/rakuen/topic/crt/${id}?trade=true" class="title avatar l" target="right">${item.Name}${badge}</a>
  1074. <small class="grey set_autofillico" title="点击设置自动补款" data-id="${id}" data-icoid="${item.Id}" data-name="${item.Name}" data-end="${item.End}">(ICO进行中: lv${pre.Level})</small>
  1075. <div class="row"><small class="time">${formatTime(item.End, true)}</small><span class="ico-state">${icoState}</span>${cancel}</div></div><div class="tags tag lv${pre.Level}">ICO进行中</div></li>`;
  1076. } else {
  1077. chara = `<li class="${line} item_list" data-id="${id}">${avatar}<div class="inner">
  1078. <a href="/rakuen/topic/crt/${id}?trade=true" class="title avatar l" target="right">${item.Name}${badge}</a> <small class="grey">(+${item.Rate.toFixed(2)} / ${formatNumber(item.Total, 0)} / ${formatNumber(item.MarketValue, 0)})</small>
  1079. <div class="row"><small class="time">${formatTime(item.LastOrder)}</small>${amount}<span title="买入 / 卖出 / 成交">${depth}</span>
  1080. ${cancel}</div></div>${tag}</li>`;
  1081. }
  1082. return chara
  1083. };
  1084. const listItemClicked = function () {
  1085. const link = $(this).find('a.avatar').attr('href');
  1086. if (link) {
  1087. if (parent.window.innerWidth < 1200) {
  1088. $(parent.document.body).find('#split #listFrameWrapper').animate({ left: '-450px' });
  1089. }
  1090. window.open(link, 'right');
  1091. }
  1092. };
  1093. const fillCosts = (id, lv, cost) => {
  1094. const dialog = `<div class="title" title="用一个角色的活股或固定资产,给另一个角色的圣殿消耗进行补充,目标人物的等级要小于或等于发动攻击圣殿的人物等级">星光碎片</div>
  1095. <div class="desc">当前版本可以通过资产重组进行补塔(1:2 补充损耗),如需资产重组在角色页面进行,同时可使用自动建塔保证重组数量<br>星光碎片只能使用活股充能,请勾选活股以确认</div>
  1096. <table align="center" width="98%" cellspacing="0" cellpadding="5" class="settings">
  1097. <tr><td>能源:<input id="supplyId" type="number" style="width:60px" value=""></td>
  1098. <td>目标:<input id="toSupplyId" type="number" style="width:60px" value="${id}" disabled></td></tr>
  1099. <td>类型:<input id="isCirculating" type="checkbox" style="margin: 0 5px;" title="当前版本小圣杯已不支持圣殿股进行充能,勾选以确认使用活股充能">活股</input></td>
  1100. <td>数量:<input id="amount" type="number" style="width:60px" value="${cost}"></td></tr>
  1101. <tr><td><input class="inputBtn" value="充能" id="submit_stardust" type="submit"></td></tr>
  1102. </tbody></table>`;
  1103. const { closeDialog } = showDialog(dialog, { closeBefore: true });
  1104. $('#submit_stardust').on('click', () => {
  1105. const supplyId = parseInt($('#supplyId').val());
  1106. const toSupplyId = parseInt($('#toSupplyId').val());
  1107. const isTemple = !$('#isCirculating').is(':checked');
  1108. const amount = parseInt($('#amount').val());
  1109. if (isTemple) {
  1110. alert('当前版本小圣杯已不支持圣殿股进行充能,请在[类型]中勾选[活股]以确认使用活股充能');
  1111. return
  1112. }
  1113. if (supplyId) {
  1114. postData(`magic/stardust/${supplyId}/${toSupplyId}/${amount}/${isTemple}`, null).then((d) => {
  1115. closeDialog();
  1116. if (d.State === 0) {
  1117. alert(d.Value);
  1118. $(`.fill_costs[data-id=${id}]`).remove();
  1119. } else alert(d.Message);
  1120. });
  1121. } else alert('角色id不能为空');
  1122. });
  1123. };
  1124. const loadCharacterList = (list, page, total, more, type, showCancel) => {
  1125. $('#eden_tpc_list ul .load_more').remove();
  1126. if (page === 1) $('#eden_tpc_list ul').html('');
  1127. for (let i = 0; i < list.length; i++) {
  1128. const item = list[i];
  1129. if (type === 'balance') {
  1130. const log = renderBalanceLog(item, lastEven);
  1131. $('#eden_tpc_list ul').append(log);
  1132. } else {
  1133. const chara = renderCharacter(item, type, lastEven, showCancel);
  1134. $('#eden_tpc_list ul').append(chara);
  1135. }
  1136. lastEven = !lastEven;
  1137. }
  1138. $('.cancel_auction').off('click');
  1139. $('.cancel_auction').on('click', (e) => {
  1140. const id = $(e.target).data('id');
  1141. const followList = FollowList.get();
  1142. if (type === 'chara') followList.charas.splice(followList.charas.indexOf(id), 1);
  1143. else if (type === 'auction') followList.auctions.splice(followList.auctions.indexOf(id), 1);
  1144. FollowList.set(followList);
  1145. $(`#eden_tpc_list li[data-id=${id}]`).remove();
  1146. });
  1147. $('.fill_costs').off('click');
  1148. $('.fill_costs').on('click', (e) => {
  1149. const { id, lv, cost } = $(e.target).data();
  1150. fillCosts(id, lv, cost);
  1151. e.stopPropagation();
  1152. });
  1153. $('.fill_auction').off('click');
  1154. $('.fill_auction').on('click', (e) => {
  1155. e.stopPropagation();
  1156. const id = $(e.target).data('id');
  1157. const isAucDay = isDayOfWeek(6);
  1158. getData(`chara/user/${id}/tinygrail/false`).then(d => {
  1159. const aucInfo = {
  1160. basePrice: d.Value.Price,
  1161. totalAmount: d.Value.Amount,
  1162. userCount: d.Value.AuctionUsers,
  1163. userAmount: d.Value.AuctionTotal,
  1164. myPrice: 0,
  1165. myAmount: 0
  1166. };
  1167. postData('chara/auction/list', [id]).then(d => {
  1168. if (d.Value[0]) {
  1169. aucInfo.myPrice = d.Value[0].Price;
  1170. aucInfo.myAmount = d.Value[0].Amount;
  1171. }
  1172. const remains = aucInfo.totalAmount - aucInfo.userAmount;
  1173. if (remains > 0) {
  1174. let price = Math.ceil(aucInfo.basePrice * 100) / 100;
  1175. const amount = remains + aucInfo.myAmount;
  1176. if (isAucDay && price * amount < aucInfo.myPrice * aucInfo.myAmount) {
  1177. price = Math.ceil(aucInfo.myPrice * aucInfo.myAmount / amount * 100) / 100;
  1178. }
  1179. postData(`chara/auction/${id}/${price}/${amount}`, null).then(d => {
  1180. if (d.State === 0) {
  1181. console.log(`自动补满拍卖 #${id} 耗资 ${price * amount}(₵${price} x ${amount})`);
  1182. postData('chara/auction/list', [id]).then(d => {
  1183. loadUserAuctions(d);
  1184. });
  1185. } else alert(d.Message);
  1186. });
  1187. } else { console.log(`#${id} 已拍满`); }
  1188. });
  1189. });
  1190. });
  1191. $('.begin_ico').off('click');
  1192. $('.begin_ico').on('click', (e) => {
  1193. e.stopPropagation();
  1194. const id = $(e.target).data('id');
  1195. const name = $(e.target).data('name');
  1196. if (confirm(`确定为 #${id}「${name}」开启 ICO?`)) {
  1197. autoBeginICO([id]);
  1198. }
  1199. });
  1200. $('.reload_chara').off('click');
  1201. $('.reload_chara').on('click', (e) => {
  1202. e.stopPropagation();
  1203. const id = $(e.target).data('id');
  1204. getNonCharacter(id);
  1205. });
  1206. $('.set_autofillico').off('click');
  1207. $('.set_autofillico').on('click', (e) => {
  1208. e.stopPropagation();
  1209. const itemData = $(e.target).data();
  1210. const fillICOList = FillICOList.get();
  1211. const item = fillICOList.find(item => item.charaId === parseInt(itemData.id)) || { Id: parseInt(itemData.icoid), charaId: parseInt(itemData.id), name: itemData.name, end: itemData.end };
  1212. if (item) openICODialog({ Id: item.Id, CharacterId: item.charaId, Name: item.name, End: item.end });
  1213. });
  1214. $('#eden_tpc_list .item_list').on('click', listItemClicked);
  1215. if (page !== total && total > 0) {
  1216. const loadMore = `<li class="load_more"><button id="loadMoreButton" class="load_more_button" data-page="${page + 1}">[加载更多]</button></li>`;
  1217. $('#eden_tpc_list ul').append(loadMore);
  1218. $('#loadMoreButton').on('click', function () {
  1219. const page = $(this).data('page');
  1220. if (more) more(page);
  1221. });
  1222. } else {
  1223. let noMore = '暂无数据';
  1224. if (total > 0) { noMore = '加载完成'; }
  1225. $('#eden_tpc_list ul').append(`<li class="load_more sub">[${noMore}]</li>`);
  1226. }
  1227. };
  1228. const updateNonCharacter = chara => {
  1229. const $item = $(`.item_list[data-id=${chara.CharacterId}]`);
  1230. if (chara.Icon) $item.find('span.avatarNeue').css('background-image', `url('${normalizeAvatar(chara.Icon)}')`);
  1231. $item.find('.title.avatar.l').text(chara.Name);
  1232. $item.find('.row .begin_ico').attr('data-name', chara.Name).data('name', chara.Name);
  1233. if (chara.Reload) {
  1234. $item.find('.row .reload_chara').show();
  1235. } else {
  1236. $item.find('.row .reload_chara').hide();
  1237. }
  1238. if (chara.bad) {
  1239. $item.find('.begin_ico').hide();
  1240. $item.find('.title.avatar.l').text((i, value) => value + ' (虚空角色,请谨慎操作)');
  1241. }
  1242. };
  1243. const getNonCharacter = id => {
  1244. getData(`/rakuen/topic/crt/${id}`).then(bgmPage => {
  1245. let bgmInfo = bgmPage.match(/class="avatar"><img\s+src="([^"]+)"\s+class="avatar\s+ll"><\/a>\s+<a href=".*"\s+target="_parent">.*<\/a><\/span><br\s*\/>(.+)<\/h1>/);
  1246. let bad = false;
  1247. if (bgmInfo === null) {
  1248. bgmInfo = bgmPage.match(/<a href="([^"]+)" class="cover thickbox" alt="([^"]+)" title="/);
  1249. bad = true;
  1250. }
  1251. updateNonCharacter({
  1252. Id: id,
  1253. CharacterId: id,
  1254. Icon: bgmInfo[1],
  1255. Name: bgmInfo[2],
  1256. NotExist: true,
  1257. bad
  1258. });
  1259. }).catch(e => {
  1260. console.log(`未开启 ICO 角色 #${id} 信息加载失败`, e);
  1261. updateNonCharacter({
  1262. Id: id,
  1263. CharacterId: id,
  1264. Name: `未知角色 #${id}`,
  1265. Reload: true,
  1266. NotExist: true
  1267. });
  1268. });
  1269. return {
  1270. Id: id,
  1271. CharacterId: id,
  1272. Name: `角色 #${id} 信息加载中...`,
  1273. NotExist: true
  1274. }
  1275. };
  1276. const generateCharacterList = async ids => {
  1277. const charas = await postData('chara/list', ids);
  1278. const charasInfo = [];
  1279. if (charas.State === 0) {
  1280. for (let i = 0; i < ids.length; i++) {
  1281. let item = charas.Value.find(chara => chara.CharacterId === parseInt(ids[i]));
  1282. if (!item) item = getNonCharacter(ids[i]);
  1283. charasInfo.push(item);
  1284. }
  1285. return charasInfo
  1286. } else {
  1287. console.log(charas);
  1288. }
  1289. };
  1290. const loadFollowChara = (page) => {
  1291. const followList = FollowList.get();
  1292. const start = 50 * (page - 1);
  1293. const ids = followList.charas.slice(start, start + 50);
  1294. const totalPages = Math.ceil(followList.charas.length / 50);
  1295. generateCharacterList(ids).then(list => {
  1296. loadCharacterList(list, page, totalPages, loadFollowChara, 'chara', true);
  1297. });
  1298. };
  1299. const loadFollowAuction = (page) => {
  1300. const followList = FollowList.get();
  1301. const start = 20 * (page - 1);
  1302. const ids = followList.auctions.slice(start, start + 20);
  1303. const totalPages = Math.ceil(followList.auctions.length / 20);
  1304. postData('chara/list', ids).then((d) => {
  1305. if (d.State === 0) {
  1306. loadCharacterList(d.Value, page, totalPages, loadFollowAuction, 'auction', true);
  1307. postData('chara/auction/list', ids).then((d) => {
  1308. loadUserAuctions(d);
  1309. });
  1310. loadValhalla(ids);
  1311. }
  1312. });
  1313. };
  1314. const loadMyICOAmount = async (charaList) => {
  1315. for (let i = 0; i < charaList.length; i++) {
  1316. const r###lt = await getData(`chara/initial/${charaList[i].Id}`);
  1317. if (r###lt && r###lt.State === 0) {
  1318. const amount = formatMoney(r###lt.Value.Amount);
  1319. const span = $(`#eden_tpc_list li.item_list[data-id=${charaList[i].CharacterId}] .row span`);
  1320. span.find('small.my_amount').remove();
  1321. span.append(`<small class="my_amount" title="已注资 ${amount}">/ ${amount}</small>`);
  1322. }
  1323. }
  1324. };
  1325. const loadMyICO = (page) => {
  1326. getData(`chara/user/initial/0/${page}/50`).then(d => {
  1327. if (d.State === 0) {
  1328. postData('chara/list', d.Value.Items.sort((a, b) => (new Date(a.End)) - (new Date(b.End))).map(i => i.CharacterId)).then(d2 => {
  1329. if (d2.State === 0) {
  1330. loadCharacterList(d2.Value, d.Value.CurrentPage, d.Value.TotalPages, loadMyICO, 'ico', false);
  1331. } else {
  1332. loadCharacterList(d.Value.Items.sort((a, b) => (new Date(a.End)) - (new Date(b.End))),
  1333. d.Value.CurrentPage, d.Value.TotalPages, loadMyICO, 'ico', false);
  1334. }
  1335. loadMyICOAmount(d.Value.Items);
  1336. if (d.Value.TotalItems > 0 && page === 1) {
  1337. $('#eden_tpc_list ul').prepend('<li id="copyICO" class="line_odd item_list item_function" style="text-align: center;">[复制我的 ICO]</li>');
  1338. $('#copyICO').on('click', function () {
  1339. getData('chara/user/initial/0/1/1000').then(async d => {
  1340. if (d.Value.TotalItems > 1000) {
  1341. try {
  1342. d = getData(`chara/user/initial/0/1/${d.Value.TotalItems}`);
  1343. } catch (e) { console.log(`获取全部 ${d.Value.TotalItems} ICO 列表出错`, e); }
  1344. }
  1345. const list_text = d.Value.Items.map(i => `https://bgm.tv/character/${i.CharacterId} ${i.Name}`).join('\n');
  1346. const dialog = `<div class="bibeBox" style="padding:10px">
  1347. <label>我的 ICO 列表(若复制按钮无效,请手动复制)</label>
  1348. <textarea rows="10" class="quick" name="myICO"></textarea>
  1349. <input class="inputBtn" value="复制" id="copy_list" type="submit" style="padding: 3px 5px;">
  1350. </div>`;
  1351. showDialog(dialog, { closeBefore: true });
  1352. $('.bibeBox textarea').val(list_text);
  1353. $('#copy_list').on('click', () => {
  1354. $('.bibeBox label').children().remove();
  1355. let res_info = '复制 ICO 列表出错,请手动复制';
  1356. $('.bibeBox textarea').select();
  1357. try {
  1358. if (document.execCommand('copy')) { res_info = `已复制 ${list_text.split('\n').length} ICO`; }
  1359. } catch (e) { console.log('复制 ICO 列表出错', e); }
  1360. $('.bibeBox label').append(`<br><span>${res_info}</span>`);
  1361. });
  1362. });
  1363. });
  1364. }
  1365. });
  1366. }
  1367. });
  1368. };
  1369. const loadLostTemple = (page) => {
  1370. const lostTemples = [];
  1371. getData(`chara/user/temple/0/${page}/500`).then((d) => {
  1372. if (d.State === 0) {
  1373. for (let i = 0; i < d.Value.Items.length; i++) {
  1374. if (d.Value.Items[i].Assets - d.Value.Items[i].Sacrifices < 0) lostTemples.push(d.Value.Items[i]);
  1375. }
  1376. loadCharacterList(lostTemples, 2, 2, loadLostTemple, 'temple', false);
  1377. }
  1378. if (page < d.Value.TotalPages) {
  1379. page++;
  1380. loadLostTemple(page);
  1381. }
  1382. });
  1383. };
  1384. const loadMyTemple = (page) => {
  1385. getData(`chara/user/temple/0/${page}/50`).then((d) => {
  1386. if (d.State === 0) {
  1387. loadCharacterList(d.Value.Items, d.Value.CurrentPage, d.Value.TotalPages, loadMyTemple, 'temple', false);
  1388. if (page === 1) {
  1389. $('#eden_tpc_list ul').prepend('<li id="lostTemple" class="line_odd item_list item_function" style="text-align: center;">[查看受损圣殿]</li>');
  1390. $('#lostTemple').on('click', function () {
  1391. $('#eden_tpc_list ul').html('');
  1392. loadLostTemple(1);
  1393. });
  1394. }
  1395. }
  1396. });
  1397. };
  1398. const loadBalance = () => {
  1399. const dialog = `<table align="center" width="98%" cellspacing="0" cellpadding="5" class="settings">
  1400. <tr><td>类型:<select id="balanceType" style="width:100px">
  1401. <option value="0" selected="selected">全部</option>
  1402. <option value="18">魔法道具</option>
  1403. <option value="1">彩票刮刮乐</option>
  1404. <option value="2">参与ICO</option>
  1405. <option value="3">ICO退款</option>
  1406. <option value="13">ICO结果</option>
  1407. <option value="4">买入委托</option>
  1408. <option value="5">取消买入</option>
  1409. <option value="6">卖出委托</option>
  1410. <option value="8">取消卖出</option>
  1411. <option value="7">交易印花税</option>
  1412. <option value="9">资产重组</option>
  1413. <option value="10">参与竞拍</option>
  1414. <option value="11">修改竞拍</option>
  1415. <option value="12">竞拍结果</option>
  1416. <option value="14">个人所得税</option>
  1417. <option value="16">发出红包</option>
  1418. <option value="17">收到红包</option>
  1419. <option value="15">系统修复</option>
  1420. </select></td>
  1421. <td>第<input id="page" type="number" style="width:30px" value="1">页</td>
  1422. <td>每页<input id="amount" type="number" style="width:50px" value="1000">条</td>
  1423. <td><input class="inputBtn" value="查询" id="submit_search" type="submit"></td></tr>
  1424. </tbody></table>`;
  1425. const { closeDialog } = showDialog(dialog, { closeBefore: true });
  1426. $('#submit_search').on('click', () => {
  1427. const Type = parseInt($('#balanceType').val());
  1428. const page = parseInt($('#page').val());
  1429. const amount = parseInt($('#amount').val());
  1430. const Logs = [];
  1431. getData(`chara/user/balance/${page}/${amount}`).then((d) => {
  1432. closeDialog();
  1433. if (d.State === 0) {
  1434. for (let i = 0; i < d.Value.Items.length; i++) {
  1435. if (d.Value.Items[i].Type === Type || Type === 0) Logs.push(d.Value.Items[i]);
  1436. }
  1437. loadCharacterList(Logs, 1, 1, loadBalance, 'balance', false);
  1438. $('#eden_tpc_list ul li').on('click', function (e) {
  1439. let id = $(e.target).data('id');
  1440. if (id == null) {
  1441. const r###lt = $(e.target).find('small.time').text().match(/#(\d+)/);
  1442. if (r###lt && r###lt.length > 0) { id = r###lt[1]; }
  1443. }
  1444. if (id != null && id.length > 0) {
  1445. if (parent.window.innerWidth < 1200) {
  1446. $(parent.document.body).find('#split #listFrameWrapper').animate({ left: '-450px' });
  1447. }
  1448. window.open(`/rakuen/topic/crt/${id}?trade=true`, 'right');
  1449. }
  1450. });
  1451. }
  1452. });
  1453. });
  1454. };
  1455. const loadAutoBuild = (page) => {
  1456. const autoTempleList = AutoTempleList.get();
  1457. autoBuildTemple(autoTempleList);
  1458. const charas = [];
  1459. for (let i = 0; i < autoTempleList.length; i++) charas.push(autoTempleList[i].charaId);
  1460. const start = 50 * (page - 1);
  1461. const ids = charas.slice(start, start + 50);
  1462. const totalPages = Math.ceil(charas.length / 50);
  1463. postData('chara/list', ids).then((d) => {
  1464. if (d.State === 0) {
  1465. loadCharacterList(d.Value, page, totalPages, loadAutoBuild, 'chara', false);
  1466. }
  1467. });
  1468. };
  1469. const loadAutoFillICO = (page) => {
  1470. const list = FillICOList.get().sort((a, b) => (new Date(a.end)) - (new Date(b.end)));
  1471. const charas = [];
  1472. for (let i = 0; i < list.length; i++) charas.push(list[i].charaId);
  1473. const start = 50 * (page - 1);
  1474. const ids = charas.slice(start, start + 50);
  1475. const totalPages = Math.ceil(charas.length / 50);
  1476. generateCharacterList(ids).then(list => {
  1477. loadCharacterList(list, page, totalPages, loadAutoFillICO, 'autofillico', false);
  1478. });
  1479. };
  1480. const joinAuctions = async (ids) => {
  1481. for (let i = 0; i < ids.length; i++) {
  1482. const Id = ids[i];
  1483. await postData('chara/auction/list', [Id]).then((d) => {
  1484. let myAuctionAmount = 0;
  1485. if (d.Value.length) myAuctionAmount = d.Value[0].Amount;
  1486. if (myAuctionAmount) {
  1487. const myAuction = `<span class="my_auction auction_tip" title="出价 / 数量">₵${formatNumber(d.Value[0].Price, 2)} / ${myAuctionAmount}</span>`;
  1488. $(`.item_list[data-id=${Id}] .time`).after(myAuction);
  1489. } else {
  1490. getData(`chara/user/${Id}/tinygrail/false`).then((d) => {
  1491. const price = Math.ceil(d.Value.Price * 100) / 100;
  1492. const amount = 1;
  1493. postData(`chara/auction/${Id}/${price}/${amount}`, null).then((d) => {
  1494. if (d.State === 0) {
  1495. const myAuction = `<span class="my_auction auction_tip" title="出价 / 数量">₵${price} / ${amount}</span>`;
  1496. $(`.item_list[data-id=${Id}] .time`).after(myAuction);
  1497. } else {
  1498. console.log(d.Message);
  1499. }
  1500. });
  1501. });
  1502. }
  1503. });
  1504. }
  1505. };
  1506. let charasList = [];
  1507. const getCharasList = () => {
  1508. const charas = $('.bibeBox textarea').val().split('\n');
  1509. for (let i = 0; i < charas.length; i++) {
  1510. try {
  1511. const charaId = parseInt(charas[i].match(/(character\/|crt\/)?(\d+)/)[2]);
  1512. charasList.push(charaId);
  1513. } catch (e) {}
  1514. }
  1515. return charasList
  1516. };
  1517. const loadTemperaryList = (page) => {
  1518. const start = 50 * (page - 1);
  1519. const ids = charasList.slice(start, start + 50);
  1520. console.log(ids);
  1521. const totalPages = Math.ceil(charasList.length / 50);
  1522. generateCharacterList(ids).then(list => {
  1523. loadCharacterList(list, page, totalPages, loadTemperaryList, 'chara', false);
  1524. });
  1525. };
  1526. const createTemporaryList = () => {
  1527. charasList = [];
  1528. const batchElement = {
  1529. basic: (text, desc, example) => `<span class="batch-element" title="${desc}, 例如:\n${example}">${text}</span>`,
  1530. int: (text, desc, example = '1\n20') => batchElement.basic(text, desc, example),
  1531. float: (text, desc, example = '1\n0.1\n3.14') => batchElement.basic(text, desc, example),
  1532. boolean: (text, desc, example = '1(是)\nY(是)\n0(否)\nN(否)') => batchElement.basic(text, desc, example)
  1533. };
  1534. batchElement.splitor = batchElement.basic(',', '分隔符', ',(逗号)\n (空格)\n\t(制表符)');
  1535. batchElement.id = batchElement.basic('ID', '角色 ID 或网址', '29282\nhttps://bgm.tv/character/29282');
  1536. const dialog = `
  1537. <div class="batch-tab-titlebar dialog-tab-titlebar">
  1538. <div data-tabid="batch-tab-temp" class="batch-tab-title dialog-tab-title open">临时列表</div>
  1539. <div data-tabid="batch-tab-onekey" class="batch-tab-title dialog-tab-title">一键操作</div>
  1540. <div data-tabid="batch-tab-trade" class="batch-tab-title dialog-tab-title">批量交易</div>
  1541. </div>
  1542. <div class="batch-tab-content">
  1543. <div class="bibeBox" style="padding:10px">
  1544. <label class="batch-tab-temp dialog-tab-content">
  1545. 在超展开左边创建角色列表 请输入角色urlid,如 https://bgm.tv/character/29282 或 29282,一行一个
  1546. </label>
  1547. <label class="batch-tab-onekey dialog-tab-content" style="display: none;">
  1548. 一键参与凑人头或开启ICO 请输入角色urlid,如 https://bgm.tv/character/29282 或 29282,一行一个
  1549. </label>
  1550. <label class="batch-tab-trade dialog-tab-content" style="display: none;">
  1551. 按照以下格式录入数据后进行批量操作<small>(鼠标移入下方元素显示示例)</small><br>
  1552. -<span title="批量设置自动建塔, 格式如下\n29282, 520.00, 12500\n29282, 10, 500\n29282(不加价格与目标,默认 10 cc 建 500 塔)">批量自动建塔</span>:<br>
  1553. &nbsp;${batchElement.id} ${batchElement.splitor}
  1554. ${batchElement.float('价格', '自动建塔买入时最低价格', '10\n520.00')} ${batchElement.splitor}
  1555. ${batchElement.int('目标#祭值', '目标建塔数量', '500\n2500')}<br>
  1556. -<span title="批量设置自动补款, 格式如下\n29282, 11, L, 0\n29282, 11, H, 1\n29282, 11(默认最低补款且不立即补款)\n29282(默认目标等级 lv1)">批量自动补款</span>:<br>
  1557. &nbsp;${batchElement.id} ${batchElement.splitor}
  1558. ${batchElement.int('目标等级', '自动补款目标等级', '0(取消自动补款)\n1\n2')} ${batchElement.splitor}
  1559. ${batchElement.basic('补款类型', '自动补款类型(详见自动补款界面)', 'L:按不爆注的最低等级补款\nH:按能达到的最高等级补款')} ${batchElement.splitor}
  1560. ${batchElement.boolean('立即补款', '是否立即补款')}
  1561. </label>
  1562. <textarea rows="10" class="quick" name="urls"></textarea>
  1563. <div class="batch-tab-temp dialog-tab-content">
  1564. <input class="inputBtn" value="创建列表" id="submit_list" type="submit" style="padding: 3px 5px;">
  1565. <input class="inputBtn" value="关注角色" id="add_follow" type="submit" style="padding: 3px 5px;">
  1566. <input class="inputBtn" value="关注竞拍" id="add_auction" type="submit" style="padding: 3px 5px;">
  1567. </div>
  1568. <div class="batch-tab-onekey dialog-tab-content" style="display: none;">
  1569. <input class="inputBtn" value="参与竞拍" id="join_auction" type="submit" style="padding: 3px 5px;">
  1570. <input class="inputBtn" value="参与 ICO" id="join_ico" type="submit" style="padding: 3px 5px;">
  1571. <input class="inputBtn" value="启动 ICO" id="begin_ico" type="submit" style="padding: 3px 5px;">
  1572. </div>
  1573. <div class="batch-tab-trade dialog-tab-content" style="display: none;">
  1574. <input class="inputBtn" value="批量建塔" id="trade_auto_temple" type="submit" style="padding: 3px 5px;">
  1575. <input class="inputBtn" value="批量补款" id="trade_auto_fill_ico" type="submit" style="padding: 3px 5px;">
  1576. </div>
  1577. </div>
  1578. </div>`;
  1579. const { closeDialog } = showDialog(dialog, { closeBefore: true });
  1580. $('.dialog-tab-title').on('click', e => {
  1581. $('.dialog-tab-content').hide();
  1582. $(`.dialog-tab-content.${e.target.dataset.tabid}`).show();
  1583. $('.dialog-tab-title').removeClass('open');
  1584. $(e.target).addClass('open');
  1585. });
  1586. $('#submit_list').on('click', () => {
  1587. getCharasList();
  1588. loadTemperaryList(1);
  1589. closeDialog();
  1590. });
  1591. $('#add_follow').on('click', () => {
  1592. getCharasList();
  1593. const followList = FollowList.get();
  1594. for (let i = 0; i < charasList.length; i++) {
  1595. const charaId = charasList[i];
  1596. if (followList.charas.includes(charaId)) {
  1597. followList.charas.splice(followList.charas.indexOf(charaId), 1);
  1598. followList.charas.unshift(charaId);
  1599. } else {
  1600. followList.charas.unshift(charaId);
  1601. }
  1602. FollowList.set(followList);
  1603. }
  1604. loadFollowChara(1);
  1605. closeDialog();
  1606. });
  1607. $('#add_auction').on('click', () => {
  1608. getCharasList();
  1609. const followList = FollowList.get();
  1610. for (let i = 0; i < charasList.length; i++) {
  1611. const charaId = charasList[i];
  1612. if (followList.auctions.includes(charaId)) {
  1613. followList.auctions.splice(followList.auctions.indexOf(charaId), 1);
  1614. followList.auctions.unshift(charaId);
  1615. } else {
  1616. followList.auctions.unshift(charaId);
  1617. }
  1618. FollowList.set(followList);
  1619. }
  1620. loadFollowAuction(1);
  1621. closeDialog();
  1622. });
  1623. $('#join_auction').on('click', () => {
  1624. getCharasList();
  1625. $('#eden_tpc_list ul').html('');
  1626. loadTemperaryList(1);
  1627. joinAuctions(charasList);
  1628. closeDialog();
  1629. });
  1630. $('#join_ico').on('click', () => {
  1631. getCharasList();
  1632. postData('chara/list', charasList).then((d) => {
  1633. autoJoinICO(d.Value);
  1634. loadTemperaryList(1);
  1635. closeDialog();
  1636. });
  1637. });
  1638. $('#begin_ico').on('click', () => {
  1639. getCharasList();
  1640. $('#begin_ico').attr('value', '正在开启 ICO...').closest('.bibeBox').find('.inputBtn').attr('disabled', true);
  1641. autoBeginICO(charasList).then(() => {
  1642. loadTemperaryList(1);
  1643. closeDialog();
  1644. });
  1645. });
  1646. const regElement = {
  1647. int: '(\\d+)',
  1648. float: '(\\d+(?:\\.\\d+)?)',
  1649. boolean: '([01ynYNtTfF])',
  1650. splitor: '[, \\t,|]+',
  1651. id: '(?:character\\/|crt\\/|#)?(\\d+)'
  1652. };
  1653. $('#trade_auto_temple').on('click', () => {
  1654. $('#trade_auto_temple').attr('value', '正在批量设置自动建塔...').closest('.bibeBox').find('.inputBtn').attr('disabled', true);
  1655. const items = $('.bibeBox textarea').val().split('\n');
  1656. const regAutoTemple = new RegExp(`${regElement.id}(?:${regElement.splitor}${regElement.float}${regElement.splitor}${regElement.int})?`, 'i');
  1657. const tradeAutoTempleList = [];
  1658. for (let i = 0; i < items.length; i++) {
  1659. try {
  1660. const [, charaId, price, target] = items[i].match(regAutoTemple);
  1661. charasList.push(parseInt(charaId));
  1662. tradeAutoTempleList.push({
  1663. charaId: parseInt(charaId),
  1664. name: '',
  1665. target: target === undefined ? 500 : parseInt(target),
  1666. bidPrice: price === undefined ? 10 : parseFloat(price)
  1667. });
  1668. } catch (e) {
  1669. console.debug(`批量建塔第 ${i + 1} 行解析出错: ${items[i]}`);
  1670. }
  1671. }
  1672. postData('chara/list', charasList).then((d) => {
  1673. const list = tradeAutoTempleList.map(item => {
  1674. const chara = d.Value.find(i => i.CharacterId === item.charaId);
  1675. if (chara !== undefined) {
  1676. item.name = chara.Name;
  1677. }
  1678. addBuildTemple(item);
  1679. return item
  1680. });
  1681. console.log('批量建塔', list);
  1682. autoBuildTemple(list);
  1683. loadTemperaryList(1);
  1684. closeDialog();
  1685. });
  1686. });
  1687. $('#trade_auto_fill_ico').on('click', () => {
  1688. $('#trade_auto_fill_ico').attr('value', '正在批量设置自动补款...').closest('.bibeBox').find('.inputBtn').attr('disabled', true);
  1689. const items = $('.bibeBox textarea').val().split('\n');
  1690. const regAutoICO = new RegExp(`${regElement.id}(?:${regElement.splitor}${regElement.int}(?:${regElement.splitor}([lhLH])${regElement.splitor}${regElement.boolean})?)?`, 'i');
  1691. const tradeAutoICOList = [];
  1692. for (let i = 0; i < items.length; i++) {
  1693. try {
  1694. const [, charaId, target, type, now] = items[i].match(regAutoICO);
  1695. charasList.push(parseInt(charaId));
  1696. tradeAutoICOList.push({
  1697. Id: undefined,
  1698. charaId: parseInt(charaId),
  1699. name: '',
  1700. target: target === undefined ? 1 : parseInt(target),
  1701. fillMin: type === undefined ? true : type.toLowerCase() !== 'h',
  1702. end: undefined,
  1703. now: now === undefined ? false : /[1yt]/i.test(now)
  1704. });
  1705. } catch (e) {
  1706. console.debug(`批量补款第 ${i + 1} 行解析出错: ${items[i]}`);
  1707. }
  1708. }
  1709. console.log('批量补款', tradeAutoICOList);
  1710. postData('chara/list', charasList).then((d) => {
  1711. const charas = d.Value.filter(i => i.Current === undefined);
  1712. const list = [];
  1713. tradeAutoICOList.filter(i => charas.some(c => c.CharacterId === i.charaId)).forEach(item => {
  1714. const chara = charas.find(i => i.CharacterId === item.charaId);
  1715. if (chara !== undefined) {
  1716. item.Id = chara.Id;
  1717. item.name = chara.Name;
  1718. item.end = chara.End;
  1719. }
  1720. if (item.target > 0 && item.now) {
  1721. list.push(item);
  1722. } else {
  1723. delete item.now;
  1724. addFillICO(item);
  1725. }
  1726. });
  1727. closeDialog();
  1728. if (list.length > 0) {
  1729. console.log('立即批量补款', list);
  1730. fullfillICO(list);
  1731. }
  1732. loadTemperaryList(1);
  1733. });
  1734. });
  1735. };
  1736. const ItemsSetting = configGenerator('ItemsSetting', {});
  1737. const loadScratch = () => {
  1738. $('#eden_tpc_list ul').html('');
  1739. $('#eden_tpc_list ul').append('<li class="line_odd item_list" style="text-align: center;">[刮刮乐]</li>');
  1740. const scratch_r###lts = [];
  1741. const scratch_ids = [];
  1742. const chaosCube_r###lts = [];
  1743. const chaosCube_ids = [];
  1744. const lotusland_r###lts = [];
  1745. const lotusland_ids = [];
  1746. const scratch = () => {
  1747. getData('event/scratch/bonus2').then((d) => {
  1748. if (d.State === 0) {
  1749. for (let i = 0; i < d.Value.length; i++) {
  1750. scratch_r###lts.push(d.Value[i]);
  1751. scratch_ids.push(d.Value[i].Id);
  1752. }
  1753. if (!d.Value.length) {
  1754. scratch_r###lts.push(d.Value);
  1755. scratch_ids.push(d.Value.Id);
  1756. }
  1757. scratch();
  1758. } else {
  1759. postData('chara/list', scratch_ids).then((d) => {
  1760. for (let i = 0; i < d.Value.length; i++) {
  1761. d.Value[i].Sacrifices = scratch_r###lts[i].Amount;
  1762. d.Value[i].Current = scratch_r###lts[i].SellPrice;
  1763. }
  1764. loadCharacterList(d.Value, 2, 2, loadScratch, 'chara', false);
  1765. startChaosCube();
  1766. });
  1767. }
  1768. });
  1769. };
  1770. const chaosCubeTempleId = ItemsSetting.get().chaosCube;
  1771. const startChaosCube = () => {
  1772. if (chaosCubeTempleId) {
  1773. $('#eden_tpc_list ul').append('<li class="line_odd item_list" style="text-align: center;">[混沌魔方]</li>');
  1774. chaosCube();
  1775. } else {
  1776. alert('未设置施放混沌魔方的圣殿,请先在角色圣殿施放一次混沌魔方即可完成预设');
  1777. startLotusland();
  1778. }
  1779. };
  1780. const chaosCube = () => {
  1781. postData(`magic/chaos/${chaosCubeTempleId}`, null).then((d) => {
  1782. console.log(d);
  1783. if (d.State === 0) {
  1784. for (let i = 0; i < d.Value.length; i++) {
  1785. chaosCube_r###lts.push(d.Value[i]);
  1786. chaosCube_ids.push(d.Value[i].Id);
  1787. }
  1788. if (!d.Value.length) {
  1789. chaosCube_r###lts.push(d.Value);
  1790. chaosCube_ids.push(d.Value.Id);
  1791. }
  1792. chaosCube();
  1793. } else {
  1794. if (d.Message !== '今日已超过使用次数限制或资产不足。') {
  1795. alert(d.Message);
  1796. chaosCube();
  1797. } else {
  1798. postData('chara/list', chaosCube_ids).then((d) => {
  1799. for (let i = 0; i < d.Value.length; i++) {
  1800. d.Value[i].Sacrifices = chaosCube_r###lts[i].Amount;
  1801. d.Value[i].Current = chaosCube_r###lts[i].SellPrice;
  1802. }
  1803. loadCharacterList(d.Value, 2, 2, loadScratch, 'chara', false);
  1804. startLotusland();
  1805. });
  1806. }
  1807. }
  1808. });
  1809. };
  1810. const lotuslandPrice = ItemsSetting.get().lotusland;
  1811. const startLotusland = () => {
  1812. if (lotuslandPrice !== undefined) {
  1813. $('#eden_tpc_list ul').append('<li class="line_odd item_list" style="text-align: center;" title="可在设置界面设置抽奖金额上限">[幻想乡]</li>');
  1814. lotusland();
  1815. } else {
  1816. alert('未设置幻想乡自动抽奖金额上限,请在助手设置界面进行设置(设为 0 可不自动抽幻想乡)');
  1817. }
  1818. };
  1819. const lotusland = () => {
  1820. getData('event/daily/count/10').then((d) => {
  1821. if (d.State === 0) {
  1822. const count = d.Value * 1;
  1823. const price = Math.pow(2, count) * 2000;
  1824. if (price <= lotuslandPrice) {
  1825. getData('event/scratch/bonus2/true').then((d) => {
  1826. console.log(d);
  1827. if (d.State === 0) {
  1828. for (let i = 0; i < d.Value.length; i++) {
  1829. lotusland_r###lts.push(d.Value[i]);
  1830. lotusland_ids.push(d.Value[i].Id);
  1831. }
  1832. if (!d.Value.length) {
  1833. lotusland_r###lts.push(d.Value);
  1834. lotusland_ids.push(d.Value.Id);
  1835. }
  1836. lotusland();
  1837. } else {
  1838. endLotusland();
  1839. console.warn('小圣杯助手自动抽幻想乡未知回应', d);
  1840. }
  1841. });
  1842. } else {
  1843. endLotusland();
  1844. }
  1845. } else {
  1846. endLotusland();
  1847. console.warn('小圣杯助手获取幻想乡价格未知回应', d);
  1848. }
  1849. });
  1850. };
  1851. const endLotusland = () => {
  1852. postData('chara/list', lotusland_ids).then((d) => {
  1853. for (let i = 0; i < d.Value.length; i++) {
  1854. d.Value[i].Sacrifices = lotusland_r###lts[i].Amount;
  1855. d.Value[i].Current = lotusland_r###lts[i].SellPrice;
  1856. }
  1857. loadCharacterList(d.Value, 2, 2, loadScratch, 'chara', false);
  1858. });
  1859. };
  1860. scratch();
  1861. };
  1862. const loadMagic = () => {
  1863. const itemsSetting = ItemsSetting.get();
  1864. const templeId = itemsSetting.chaosCube || '';
  1865. const monoId = itemsSetting.guidepost ? itemsSetting.guidepost.monoId : '';
  1866. const toMonoId = itemsSetting.guidepost ? itemsSetting.guidepost.toMonoId : '';
  1867. const attackId = itemsSetting.starbreak ? itemsSetting.starbreak.attackId : '';
  1868. const toAttackId = itemsSetting.starbreak ? itemsSetting.starbreak.toAttackId : '';
  1869. const dialog = `<table align="center" width="98%" cellspacing="0" cellpadding="5" class="settings">
  1870. <tr><td title="消耗圣殿10点固定资产,获取随机角色20-200股随机数量">混沌魔方</td>
  1871. <td>炮塔:<input id="chaosCube" type="number" style="width:60px" value="${templeId}"></td><td></td>
  1872. <td><input class="inputBtn" value="发射" id="submit_chaosCube" type="submit"></td></tr>
  1873. <tr><td title="消耗圣殿100点固定资产,获取指定股票10-100股随机数量,目标人物的等级要小于或等于发动攻击圣殿的人物等级">虚空道标</td>
  1874. <td>炮塔:<input id="monoId" type="number" style="width:60px" value="${monoId}"></td>
  1875. <td>目标:<input id="toMonoId" type="number" style="width:60px" value="${toMonoId}"></td>
  1876. <td><input class="inputBtn" value="发射" id="submit_guidepost" type="submit"></td></tr>
  1877. <tr><td title="消耗圣殿100点固定资产,获取指定角色星之力造成一定数量随机伤害,伤害随机范围与二者人物等级差有关">闪光结晶</td>
  1878. <td>炮塔:<input id="attackId" type="number" style="width:60px" value="${attackId}"></td>
  1879. <td>目标:<input id="toAttackId" type="number" style="width:60px" value="${toAttackId}"></td>
  1880. <td><input class="inputBtn" value="发射" id="submit_starbreak" type="submit"></td></tr>
  1881. <tr><td title="用一个角色的活股或固定资产,给另一个角色的圣殿消耗进行补充,补充数量与二者人物等级差有关">星光碎片</td>
  1882. <td>能源:<input id="supplyId" type="number" style="width:60px"></td>
  1883. <td>目标:<input id="toSupplyId" type="number" style="width:60px"></td></tr>
  1884. <td></td><td>类型:<input id="isCirculating" type="checkbox" style="margin: 0 5px;" title="当前版本小圣杯已不支持圣殿股进行充能,勾选以确认使用活股充能">活股</input></td>
  1885. <td>数量:<input id="amount" type="number" style="width:60px" value="100"></td>
  1886. <td><input class="inputBtn" value="充能" id="submit_stardust" type="submit" title="当前版本小圣杯已不支持圣殿股进行充能,勾选活股类型以确认使用活股充能"></td></tr>
  1887. </tbody></table>`;
  1888. const { closeDialog } = showDialog(dialog, { closeBefore: true });
  1889. $('#submit_chaosCube').on('click', () => {
  1890. const templeId = parseInt($('#chaosCube').val());
  1891. ItemsSetting.set({ ...ItemsSetting.get(), chaosCube: templeId });
  1892. if (templeId === 0) return
  1893. postData(`magic/chaos/${templeId}`, null).then((d) => {
  1894. closeDialog();
  1895. console.log(d);
  1896. if (d.State === 0) {
  1897. $('#eden_tpc_list ul').html('');
  1898. $('#eden_tpc_list ul').append('<li class="line_odd item_list" style="text-align: center;">[混沌魔方]</li>');
  1899. const Id = d.Value.Id;
  1900. const Amount = d.Value.Amount;
  1901. const SellPrice = d.Value.SellPrice;
  1902. postData('chara/list', [Id]).then((d) => {
  1903. for (let i = 0; i < d.Value.length; i++) {
  1904. d.Value[i].Sacrifices = Amount;
  1905. d.Value[i].Current = SellPrice;
  1906. }
  1907. loadCharacterList(d.Value, 2, 2, loadScratch, 'chara', false);
  1908. });
  1909. } else alert(d.Message);
  1910. });
  1911. });
  1912. $('#submit_guidepost').on('click', () => {
  1913. const monoId = parseInt($('#monoId').val());
  1914. const toMonoId = parseInt($('#toMonoId').val());
  1915. ItemsSetting.set({ ...ItemsSetting.get(), guidepost: { monoId: monoId, toMonoId: toMonoId } });
  1916. if (monoId === 0 || toMonoId === 0) return
  1917. postData(`magic/guidepost/${monoId}/${toMonoId}`, null).then((d) => {
  1918. closeDialog();
  1919. console.log(d);
  1920. if (d.State === 0) {
  1921. $('#eden_tpc_list ul').html('');
  1922. $('#eden_tpc_list ul').append('<li class="line_odd item_list" style="text-align: center;">[虚空道标]</li>');
  1923. const Id = d.Value.Id;
  1924. const Amount = d.Value.Amount;
  1925. const SellPrice = d.Value.SellPrice;
  1926. postData('chara/list', [Id]).then((d) => {
  1927. for (let i = 0; i < d.Value.length; i++) {
  1928. d.Value[i].Sacrifices = Amount;
  1929. d.Value[i].Current = SellPrice;
  1930. }
  1931. loadCharacterList(d.Value, 2, 2, loadScratch, 'chara', false);
  1932. });
  1933. } else alert(d.Message);
  1934. });
  1935. });
  1936. $('#submit_starbreak').on('click', () => {
  1937. const attackId = parseInt($('#attackId').val());
  1938. const toAttackId = parseInt($('#toAttackId').val());
  1939. ItemsSetting.set({ ...ItemsSetting.get(), starbreak: { attackId, toAttackId } });
  1940. if (attackId === 0 || toAttackId === 0) return
  1941. postData(`magic/starbreak/${attackId}/${toAttackId}`, null).then((d) => {
  1942. closeDialog();
  1943. console.log(d);
  1944. if (d.State === 0) {
  1945. alert(d.Value);
  1946. $('#eden_tpc_list ul').html('');
  1947. $('#eden_tpc_list ul').append('<li class="line_odd item_list" style="text-align: center;">[闪光结晶]</li>');
  1948. const Amount = d.Value.match(/造成([0-9]+)点伤害/)[1];
  1949. postData('chara/list', [attackId, toAttackId]).then((d) => {
  1950. d.Value[1].Sacrifices = Amount;
  1951. loadCharacterList(d.Value, 2, 2, null, 'chara', false);
  1952. });
  1953. } else alert(d.Message);
  1954. });
  1955. });
  1956. $('#submit_stardust').on('click', () => {
  1957. const supplyId = $('#supplyId').val();
  1958. const toSupplyId = $('#toSupplyId').val();
  1959. const isTemple = !$('#isCirculating').is(':checked');
  1960. const amount = $('#amount').val();
  1961. if (supplyId === 0 || toSupplyId === 0 || amount === 0) return
  1962. if (isTemple) {
  1963. alert('当前版本小圣杯已不支持圣殿股进行充能,请在[类型]中勾选[活股]以确认使用活股充能');
  1964. return
  1965. }
  1966. postData(`magic/stardust/${supplyId}/${toSupplyId}/${amount}/${isTemple}`, null).then((d) => {
  1967. closeDialog();
  1968. console.log(d);
  1969. if (d.State === 0) {
  1970. alert(d.Value);
  1971. $('#eden_tpc_list ul').html('');
  1972. $('#eden_tpc_list ul').append('<li class="line_odd item_list" style="text-align: center;">[星光碎片]</li>');
  1973. postData('chara/list', [supplyId, toSupplyId]).then((d) => {
  1974. loadCharacterList(d.Value, 2, 2, loadScratch, 'chara', false);
  1975. });
  1976. } else alert(d.Message);
  1977. });
  1978. });
  1979. };
  1980. const CharaInitPrice = configGenerator('chara_initPrice', {});
  1981. const sellOut = () => {
  1982. $('#eden_tpc_list .item_list').removeAttr('onclick');
  1983. $('#eden_tpc_list .item_list').each((_, e) => {
  1984. let id = $(e).data('id');
  1985. if (!id) {
  1986. const r###lt = $(e).find('small.time').text().match(/#(\d+)/);
  1987. if (r###lt && r###lt.length > 0) {
  1988. id = r###lt[1];
  1989. $(e).attr('data-id', id).data('id', id);
  1990. }
  1991. }
  1992. if (!id || $(e).find('.sell_btn').length > 0) return
  1993. $(`.sell_btn[data-id=${id}]`).remove();
  1994. $(`#eden_tpc_list li[data-id=${id}] .row`).append(`<span><small data-id="${id}" class="sell_btn">[卖出]</small></span>`);
  1995. });
  1996. $('.sell_btn').off('click').on('click', (e) => {
  1997. const id = $(e.target).data('id');
  1998. if (id) {
  1999. const charaInitPrice = CharaInitPrice.get();
  2000. const priceTagEl = $(`#eden_tpc_list li[data-id=${id}]`).find('div.tag')[0];
  2001. const priceTag = priceTagEl ? priceTagEl.innerText.match(/₵([0-9]*(\.[0-9]{1,2})?)/) : null;
  2002. const priceNow = priceTag ? priceTag[1] : 0;
  2003. getData(`chara/user/${id}`).then((d) => {
  2004. const amount = d.Value.Amount;
  2005. if (amount) {
  2006. getData(`chara/${id}`).then((d) => {
  2007. const initPrice = charaInitPrice[id] ? charaInitPrice[id].init_price : d.Value.Price;
  2008. const price = Math.max(parseFloat(priceNow), parseFloat(initPrice).toFixed(2), d.Value.Current.toFixed(2));
  2009. postData(`chara/ask/${id}/${price}/${amount}`, null).then((d) => {
  2010. if (d.Message) console.log(`#${id}: ${d.Message}`);
  2011. else console.log(`卖出委托#${id} ${price}*${amount}`);
  2012. });
  2013. });
  2014. }
  2015. });
  2016. }
  2017. $(`.sell_btn[data-id=${id}]`).remove();
  2018. });
  2019. };
  2020. const cancelAllBids = async (charas, Balance) => {
  2021. for (let i = 0; i < charas.length; i++) {
  2022. const id = charas[i].Id;
  2023. const name = charas[i].Name;
  2024. const avatar = `<a href="/rakuen/topic/crt/${id}?trade=true" class="avatar l" target="right"><span class="avatarNeue avatarReSize32 ll" style="background-image:url('${normalizeAvatar(charas[i].Icon)}')"></span></a>`;
  2025. await getData(`chara/user/${id}`).then((d) => {
  2026. let line = 'line_even';
  2027. if (i % 2 === 0) line = 'line_odd';
  2028. const tid = d.Value.Bids[0].Id;
  2029. const price = d.Value.Bids[0].Price;
  2030. const amount = d.Value.Bids[0].Amount;
  2031. Balance += price * amount;
  2032. postData(`chara/bid/cancel/${tid}`, null).then((d) => {
  2033. const message = `<li class="${line} item_list item_log" data-id="${id}">${avatar}<span class="tag raise">+${formatNumber(price * amount, 2)}</span>
  2034. ${formatNumber(Balance, 2)}<span class="row"><small class="time">取消买单(${tid}) #${id} ${name}」 ${price}*${amount}</small></span></li>`;
  2035. $('#eden_tpc_list ul').prepend(message);
  2036. });
  2037. });
  2038. }
  2039. };
  2040. const cancelBids = () => {
  2041. if (!confirm('此操作将无法恢复,确定取消全部买单?')) return
  2042. $('#eden_tpc_list ul').html('');
  2043. getData('chara/user/assets').then((d) => {
  2044. const Balance = d.Value.Balance;
  2045. getData('chara/bids/0/1/1000').then((d) => {
  2046. if (d.Value.TotalItems > 1000) {
  2047. try {
  2048. d = getData(`chara/user/initial/0/1/${d.Value.TotalItems}`);
  2049. } catch (e) { console.log(`获取全部 ${d.Value.TotalItems} 条买单数据出错`, e); }
  2050. }
  2051. cancelAllBids(d.Value.Items, Balance);
  2052. });
  2053. });
  2054. };
  2055. const LinkPosList = configGenerator('LinkPosList', []);
  2056. const configVersion = 3;
  2057. const configList = [
  2058. Settings,
  2059. FollowList,
  2060. FillICOList,
  2061. AutoTempleList,
  2062. ItemsSetting,
  2063. LinkPosList
  2064. ];
  2065. const exportConfig = () => JSON.stringify({
  2066. meta: {
  2067. project: 'TinyGrail_Helper_Next',
  2068. confver: configVersion,
  2069. exportTime: (new Date()).toISOString()
  2070. },
  2071. config: configList.reduce((config, configItem) => {
  2072. const configValue = configItem.getRaw();
  2073. return configValue
  2074. ? { ...config, [configItem.name]: configValue }
  2075. : config
  2076. }, {})
  2077. });
  2078. const importSingleConfig = (configStorage, configToImport) => {
  2079. if (configStorage.name in configToImport) {
  2080. console.log(`importing ${configStorage.name}`);
  2081. configStorage.setRaw(configToImport[configStorage.name], true);
  2082. }
  2083. };
  2084. const importConfig = (configString) => {
  2085. try {
  2086. const errors = [];
  2087. const { meta, config } = JSON.parse(configString);
  2088. console.log(`import config version: ${meta.confver}`);
  2089. configList.forEach(configItem => {
  2090. try {
  2091. importSingleConfig(configItem, config);
  2092. } catch (err) {
  2093. errors.push(configItem.name);
  2094. }
  2095. });
  2096. return errors
  2097. } catch (err) {
  2098. console.error('设置导入出错:', { configString, err });
  2099. }
  2100. };
  2101. const openSettings = () => {
  2102. const settingRowBtn = `
  2103. <tr class="setting-row-btn">
  2104. <td><span class="txtBtn setting-btn-export">[导入导出设置]</span></td>
  2105. <td><input class="inputBtn setting-btn-submit" value="保存" type="submit"></td>
  2106. </tr>
  2107. `;
  2108. const dialog = `
  2109. <div class="setting-tab-titlebar dialog-tab-titlebar">
  2110. <div data-tabid="setting-tab-feat" class="dialog-tab-title open">功能</div>
  2111. <div data-tabid="setting-tab-ui" class="dialog-tab-title">界面</div>
  2112. <div data-tabid="setting-tab-magic" class="dialog-tab-title">魔法</div>
  2113. </div>
  2114. <div class="setting-tab-content">
  2115. <div class="setting-tab-feat dialog-tab-content">
  2116. <table><tbody>
  2117. <tr><td>默认拍卖数量</td>
  2118. <td><select id="set_auction_num"><option value="one" selected="selected">1</option><option value="all">全部</option></td></tr>
  2119. <tr><td>周六自动提醒领取股息</td>
  2120. <td><select id="set_get_bonus"><option value="on" selected="selected">是</option><option value="off">否</option></td></tr>
  2121. <tr><td title="小圣杯界面左右键切换查看圣殿图">圣殿画廊</td>
  2122. <td><select id="set_gallery"><option value="off" selected="selected">关</option><option value="on">开</option></td></tr>
  2123. <tr><td>合并历史订单</td>
  2124. <td><select id="set_merge_order"><option value="on" selected="selected">是</option><option value="off">否</option></td></tr>
  2125. <tr><td>幻想乡自动抽奖金额上限</td>
  2126. <td><input id="item_set_lotus" type="number" min="0" step="1000" value="0"> cc</td></tr>
  2127. ${settingRowBtn}
  2128. </tbody></table>
  2129. </div>
  2130. <div class="setting-tab-ui dialog-tab-content" style="display: none;">
  2131. <table><tbody>
  2132. <tr><td>用户主页小圣杯默认显示状态</td>
  2133. <td><select id="set_hide_grail"><option value="off" selected="selected">显示</option><option value="on">隐藏</option></select></td></tr>
  2134. <tr><td>[连接] 默认显示状态</td>
  2135. <td><select id="set_hide_link"><option value="off" selected="selected">显示</option><option value="not_me">仅显示自己</option><option value="on">隐藏</option></select></td></tr>
  2136. <tr><td>[圣殿] 默认显示状态</td>
  2137. <td><select id="set_hide_temple"><option value="off" selected="selected">显示</option><option value="not_me">仅显示自己</option><option value="on">隐藏</option></select></td></tr>
  2138. <tr><td>[董事会] 默认显示状态</td>
  2139. <td><select id="set_hide_board"><option value="off" selected="selected">显示</option><option value="on">隐藏</option></select></td></tr>
  2140. <tr><td>将自己圣殿或连接排到第一个显示</td>
  2141. <td><select id="set_pre_temple"><option value="on" selected="selected">是</option><option value="off">否</option></td></tr>
  2142. <tr><td>英灵殿个人持股显示</td>
  2143. <td><select id="set_valhalla_sacrifices"><option value="on" selected="selected">是</option><option value="off">否</option></select></td></tr>
  2144. ${settingRowBtn}
  2145. </tbody></table>
  2146. </div>
  2147. <div class="setting-tab-magic dialog-tab-content" style="display: none;">
  2148. <table><tbody>
  2149. <tr><td>混沌魔方 - 炮塔角色ID</td>
  2150. <td><input id="item_set_chaos" class="chara-id" type="number" min="0" step="1" value="0"></td></tr>
  2151. <tr><td>虚空道标 - 炮塔角色ID</td>
  2152. <td><input id="item_set_guidepost" class="chara-id" type="number" min="0" step="1" value="0"></td></tr>
  2153. <tr><td>虚空道标 - 目标角色ID</td>
  2154. <td><input id="item_set_guidepost_to" class="chara-id" type="number" min="0" step="1" value="0"></td></tr>
  2155. <tr><td>闪光结晶 - 炮塔角色ID</td>
  2156. <td><input id="item_set_starbreak" class="chara-id" type="number" min="0" step="1" value="0"></td></tr>
  2157. <tr><td>闪光结晶 - 目标角色ID</td>
  2158. <td><input id="item_set_starbreak_to" class="chara-id" type="number" min="0" step="1" value="0"></td></tr>
  2159. ${settingRowBtn}
  2160. </tbody></table>
  2161. </div>
  2162. </div>
  2163. `;
  2164. const { closeDialog } = showDialog(dialog, { closeBefore: true });
  2165. $('.dialog-tab-title').on('click', e => {
  2166. $('.dialog-tab-content').hide();
  2167. $(`.dialog-tab-content.${e.target.dataset.tabid}`).show();
  2168. $('.dialog-tab-title').removeClass('open');
  2169. $(e.target).addClass('open');
  2170. });
  2171. const settings = Settings.get();
  2172. const itemSetting = ItemsSetting.get();
  2173. $('#set_hide_grail').val(settings.hide_grail);
  2174. $('#set_hide_link').val(settings.hide_link);
  2175. $('#set_hide_temple').val(settings.hide_temple);
  2176. $('#set_hide_board').val(settings.hide_board);
  2177. $('#set_pre_temple').val(settings.pre_temple);
  2178. $('#set_valhalla_sacrifices').val(settings.valhalla_sacrifices || 'on');
  2179. $('#set_auction_num').val(settings.auction_num);
  2180. $('#set_merge_order').val(settings.merge_order);
  2181. $('#set_get_bonus').val(settings.get_bonus);
  2182. $('#set_gallery').val(settings.gallery);
  2183. $('#item_set_lotus').val(itemSetting.lotusland || 0);
  2184. $('#item_set_chaos').val(itemSetting.chaosCube || 0);
  2185. if (itemSetting.guidepost) {
  2186. $('#item_set_guidepost').val(itemSetting.guidepost.monoId || 0);
  2187. $('#item_set_guidepost_to').val(itemSetting.guidepost.toMonoId || 0);
  2188. }
  2189. if (itemSetting.starbreak) {
  2190. $('#item_set_starbreak').val(itemSetting.starbreak.attackId || 0);
  2191. $('#item_set_starbreak_to').val(itemSetting.starbreak.toAttackId || 0);
  2192. }
  2193. $('#item_set_lotus').on('change', (e) => {
  2194. const el = e.target;
  2195. if (parseInt(el.value) > 3000) {
  2196. el.style.color = 'red';
  2197. el.style.fontWeight = 'bold';
  2198. } else {
  2199. el.style.color = '';
  2200. el.style.fontWeight = '';
  2201. }
  2202. });
  2203. $('.setting-btn-submit').on('click', () => {
  2204. settings.hide_grail = $('#set_hide_grail').val();
  2205. settings.hide_link = $('#set_hide_link').val();
  2206. settings.hide_temple = $('#set_hide_temple').val();
  2207. settings.hide_board = $('#set_hide_board').val();
  2208. settings.pre_temple = $('#set_pre_temple').val();
  2209. settings.valhalla_sacrifices = $('#set_valhalla_sacrifices').val();
  2210. settings.auction_num = $('#set_auction_num').val();
  2211. settings.merge_order = $('#set_merge_order').val();
  2212. settings.get_bonus = $('#set_get_bonus').val();
  2213. settings.gallery = $('#set_gallery').val();
  2214. Settings.set(settings);
  2215. ItemsSetting.set({
  2216. lotusland: parseInt($('#item_set_lotus').val()),
  2217. chaosCube: parseInt($('#item_set_chaos').val()),
  2218. guidepost: {
  2219. monoId: parseInt($('#item_set_guidepost').val()),
  2220. toMonoId: parseInt($('#item_set_guidepost_to').val())
  2221. },
  2222. starbreak: {
  2223. attackId: parseInt($('#item_set_starbreak').val()),
  2224. toAttackId: parseInt($('#item_set_starbreak_to').val())
  2225. }
  2226. });
  2227. $('#submit_setting').val('已保存');
  2228. setTimeout(() => { closeDialog(); }, 500);
  2229. });
  2230. $('.setting-btn-export').on('click', () => {
  2231. const dialog = `<div class="bibeBox" style="padding:10px">
  2232. <label>设置导入/导出</label>
  2233. <p><b>导入方式:</b>将之前导出的设置文本粘贴到下方输入框后点击导入按钮</p>
  2234. <p><b>导出方式:</b>复制下方输入框中内容并妥善保存(若复制按钮无效,请手动复制)</p>
  2235. <textarea rows="10" class="quick" name="setting_value"></textarea>
  2236. <label id="info"></label>
  2237. <input class="inputBtn" value="导入" id="import_setting" type="submit" style="padding: 3px 5px;">
  2238. <input class="inputBtn" value="复制" id="copy_setting" type="submit" style="padding: 3px 5px;">
  2239. </div>`;
  2240. showDialog(dialog, { closeBefore: true });
  2241. const configValue = exportConfig();
  2242. $('.bibeBox textarea').val(configValue);
  2243. $('#copy_setting').on('click', () => {
  2244. $('.bibeBox label#info').children().remove();
  2245. let resInfo = '复制设置出错,请手动复制';
  2246. $('.bibeBox textarea').select();
  2247. try {
  2248. if (document.execCommand('copy')) { resInfo = '设置已复制,请自行保存以便后续导入'; }
  2249. } catch (e) { console.log('复制设置出错', e); }
  2250. $('.bibeBox label#info').append(`<span>${resInfo}</span><br>`);
  2251. });
  2252. $('#import_setting').on('click', () => {
  2253. if (!confirm('导入设置将会覆盖原有设置,确定操作后将无法恢复,是否确定继续?')) return
  2254. $('.bibeBox label#info').children().remove();
  2255. let resInfo = '导入设置出错,请重新检查导入文本';
  2256. const importString = $('.bibeBox textarea').val();
  2257. try {
  2258. const importErrors = importConfig(importString);
  2259. if (importErrors.length === 0) {
  2260. resInfo = '导入成功';
  2261. } else {
  2262. resInfo = `以下设置导入出错,请重新检查导入文本:\n${importErrors.join(', ')}`;
  2263. console.warn(resInfo);
  2264. }
  2265. $('.bibeBox label#info').append(`<span>${resInfo}</span><br>`);
  2266. } catch (e) { console.log('导入设置出错', e); }
  2267. });
  2268. });
  2269. };
  2270. const bindShowBidsTotal = () => {
  2271. $('#bidMenu').on('click', () => {
  2272. $('#bidsTotalInfo').remove();
  2273. $('#eden_tpc_list ul').prepend('<li id="bidsTotalInfo" class="line_odd item_list item_function" style="text-align: center;">[统计买单总额]</li>');
  2274. const calculateBidsTotal = () => {
  2275. $('#bidsTotalInfo').off('click').text('[买单统计中,请稍候...]');
  2276. getAllBids().then(res => {
  2277. if (res) {
  2278. $('#bidsTotalInfo').text(res);
  2279. } else {
  2280. $('#bidsTotalInfo').text('[买单统计出错,点击重试]').on('click', calculateBidsTotal);
  2281. }
  2282. });
  2283. };
  2284. $('#bidsTotalInfo').on('click', calculateBidsTotal);
  2285. });
  2286. };
  2287. const getAllBids = async () => {
  2288. let bidsRes = await getData('chara/bids/0/1/50').catch(e => console.warn('统计买单总额时获取买单信息失败', e));
  2289. if (!bidsRes) return false
  2290. if (bidsRes.State === 0 && bidsRes.Value) {
  2291. if (bidsRes.Value.TotalPages > 1) {
  2292. const totalItems = bidsRes.Value.TotalItems;
  2293. bidsRes = await getData(`chara/bids/0/1/${totalItems}`).catch(e => console.warn('统计买单总额时获取买单信息失败', e));
  2294. if (!bidsRes) return false
  2295. }
  2296. const bidsCharas = [];
  2297. bidsRes.Value.Items.forEach(chara => {
  2298. if (!bidsCharas.some(i => i.CharacterId === chara.CharacterId)) bidsCharas.push(chara);
  2299. });
  2300. return await sumBids(bidsCharas)
  2301. } else if (bidsRes.State !== 0) {
  2302. console.warn(bidsRes);
  2303. return false
  2304. }
  2305. };
  2306. const sumBids = async (charas) => {
  2307. let sum = 0;
  2308. let count = 0;
  2309. for (let i = 0; i < charas.length; i++) {
  2310. const d = await getData(`chara/user/${charas[i].CharacterId}`).catch(e => console.warn(e));
  2311. if (d.State === 0) {
  2312. sum += d.Value.Bids.reduce((s, i) => s + i.Price * i.Amount, 0);
  2313. count += d.Value.Bids.length;
  2314. } else {
  2315. console.warn(d);
  2316. }
  2317. }
  2318. return `共有 ${formatNumber(count, 0)} 买单,涉及 ${formatNumber(charas.length, 0)} 角色,合计 ${formatMoney(sum)}`
  2319. };
  2320. const bindShowAsksTotal = () => {
  2321. $('#askMenu').on('click', () => {
  2322. $('#asksTotalInfo').remove();
  2323. $('#eden_tpc_list ul').prepend('<li id="asksTotalInfo" class="line_odd item_list item_function" style="text-align: center;">[统计卖单总数]</li>');
  2324. const calculateAsksTotal = () => {
  2325. $('#asksTotalInfo').off('click').text('[卖单统计中,请稍候...]');
  2326. getAllAsks().then(res => {
  2327. if (res) {
  2328. $('#asksTotalInfo').text(res);
  2329. } else {
  2330. $('#asksTotalInfo').text('[卖单统计出错,点击重试]').on('click', calculateAsksTotal);
  2331. }
  2332. });
  2333. };
  2334. $('#asksTotalInfo').on('click', calculateAsksTotal);
  2335. });
  2336. };
  2337. const getAllAsks = async () => {
  2338. let asksRes = await getData('chara/asks/0/1/50').catch(e => console.warn('统计卖单总数时获取卖单信息失败', e));
  2339. if (!asksRes) return false
  2340. if (asksRes.State === 0 && asksRes.Value) {
  2341. if (asksRes.Value.TotalPages > 1) {
  2342. const totalItems = asksRes.Value.TotalItems;
  2343. asksRes = await getData(`chara/asks/0/1/${totalItems}`).catch(e => console.warn('统计卖单总数时获取卖单信息失败', e));
  2344. if (!asksRes) return false
  2345. }
  2346. const asksCharas = [];
  2347. asksRes.Value.Items.forEach(chara => {
  2348. if (!asksCharas.some(i => i.CharacterId === chara.CharacterId)) asksCharas.push(chara);
  2349. });
  2350. return await sumAsks(asksCharas)
  2351. } else if (asksRes.State !== 0) {
  2352. console.warn(asksRes);
  2353. return false
  2354. }
  2355. };
  2356. const sumAsks = async (charas) => {
  2357. let sum = 0;
  2358. let count = 0;
  2359. for (let i = 0; i < charas.length; i++) {
  2360. const d = await getData(`chara/user/${charas[i].CharacterId}`).catch(e => console.warn(e));
  2361. if (d.State === 0) {
  2362. sum += d.Value.Asks.reduce((s, i) => s + i.Amount, 0);
  2363. count += d.Value.Asks.length;
  2364. } else {
  2365. console.warn(d);
  2366. }
  2367. }
  2368. return `共有 ${formatNumber(count, 0)} 卖单,涉及 ${formatNumber(charas.length, 0)} 角色,合计 ${formatNumber(sum, 0)} 股`
  2369. };
  2370. const bindShowAuctionTotal = () => {
  2371. $('#auctionMenu').on('click', () => {
  2372. $('#auctionTotalInfo').remove();
  2373. $('#eden_tpc_list ul').prepend('<li id="auctionTotalInfo" class="line_odd item_list item_function" style="text-align: center;">[统计拍卖总额]</li>');
  2374. const calculateAuctionTotal = () => {
  2375. $('#auctionTotalInfo').off('click').text('[拍卖统计中,请稍候...]');
  2376. getAllAuction().then(res => {
  2377. if (res) {
  2378. $('#auctionTotalInfo').text(res);
  2379. } else {
  2380. $('#auctionTotalInfo').text('[拍卖统计出错,点击重试]').on('click', calculateAuctionTotal);
  2381. }
  2382. });
  2383. };
  2384. $('#auctionTotalInfo').on('click', calculateAuctionTotal);
  2385. });
  2386. };
  2387. const getAllAuction = async () => {
  2388. let page = 0;
  2389. const auctionCharas = [];
  2390. let retry = false;
  2391. do {
  2392. if (!retry) page++;
  2393. const auctionRes = await getData(`chara/user/auction/${page}/50`).catch(e => console.warn(`统计拍卖总额时获取第${page}页拍卖信息失败`, e));
  2394. if (auctionRes) {
  2395. retry = false;
  2396. if (auctionRes.State === 0 && auctionRes.Value) {
  2397. auctionCharas.push(...auctionRes.Value.Items.filter(i => i.State === 0));
  2398. } else if (auctionRes.State !== 0) {
  2399. console.warn(auctionRes);
  2400. }
  2401. } else { retry = true; }
  2402. } while (retry || auctionCharas.length >= 50 * page)
  2403. const sum = auctionCharas.reduce((s, i) => s + i.Price * i.Amount, 0);
  2404. return `共参与 ${formatNumber(auctionCharas.length, 0)} 角色竞拍,合计 ${formatMoney(sum)}`
  2405. };
  2406. const menuItemClicked = (callback, parentNodeId = '#helperMenu') => {
  2407. $('.timelineTabs a').removeClass('focus');
  2408. $('.timelineTabs a').removeClass('top_focus');
  2409. $(parentNodeId).addClass('focus');
  2410. if (callback) callback(1);
  2411. };
  2412. const loadHelperMenu = () => {
  2413. const item = `<li><a href="#" id="helperMenu" class="top">助手</a><ul>
  2414. <li><a href="#" id="temporaryList">临时列表</a></li>
  2415. <li><a href="#" id="followChara">关注角色</a></li>
  2416. <li><a href="#" id="followAuction">关注竞拍</a></li>
  2417. <li><a href="#" id="scratch">抽奖</a></li>
  2418. <li><a href="#" id="magic">魔法道具</a></li>
  2419. <li><a href="#" id="balance">资金日志分类</a></li>
  2420. <li><a href="#" id="sell" title="为当前列表角色增加一键卖出按钮">卖出</a></li>
  2421. <li><a href="#" id="autoBuild">自动建塔</a></li>
  2422. <li><a href="#" id="autoICO">自动补款</a></li>
  2423. <li><a href="#" id="cancelBids">取消买单</a></li>
  2424. <li><a href="#" id="settings">设置</a></li>
  2425. </ul></li>`;
  2426. $('.timelineTabs').append(item);
  2427. $('#followChara').on('click', () => menuItemClicked(loadFollowChara));
  2428. $('#followAuction').on('click', () => menuItemClicked(loadFollowAuction));
  2429. $('#balance').on('click', () => menuItemClicked(loadBalance));
  2430. $('#autoBuild').on('click', () => menuItemClicked(loadAutoBuild));
  2431. $('#autoICO').on('click', () => menuItemClicked(loadAutoFillICO));
  2432. $('#temporaryList').on('click', () => menuItemClicked(createTemporaryList));
  2433. $('#scratch').on('click', () => menuItemClicked(loadScratch));
  2434. $('#magic').on('click', () => menuItemClicked(loadMagic));
  2435. $('#sell').on('click', () => menuItemClicked(sellOut));
  2436. $('#cancelBids').on('click', () => menuItemClicked(cancelBids));
  2437. $('#settings').on('click', () => menuItemClicked(openSettings));
  2438. $('#logMenu').closest('li').before(`
  2439. <li><a href="#" id="myICO">我的 ICO</a></li>
  2440. <li><a href="#" id="myTemple">我的圣殿</a></li>
  2441. `);
  2442. const tinygrailMenuId = '#recentMenu';
  2443. $('#myICO').on('click', () => menuItemClicked(loadMyICO, tinygrailMenuId));
  2444. $('#myTemple').on('click', () => menuItemClicked(loadMyTemple, tinygrailMenuId));
  2445. bindShowBidsTotal();
  2446. bindShowAsksTotal();
  2447. bindShowAuctionTotal();
  2448. };
  2449. const openBuildDialog = (chara) => {
  2450. const autoTempleList = AutoTempleList.get();
  2451. const charaId = chara.CharacterId || chara.Id;
  2452. let target = 500; let bidPrice = 10;
  2453. const temple = autoTempleList.find(temple => parseInt(temple.charaId) === charaId);
  2454. if (temple !== undefined) {
  2455. target = parseInt(temple.target);
  2456. bidPrice = parseFloat(temple.bidPrice);
  2457. }
  2458. const dialog = `<div class="title" title="目标数量 / 买入价格">
  2459. 自动建塔 - #${charaId} ${chara.Name}」 ${target} / ${bidPrice}</div>
  2460. <div class="desc"><p>当已#祭股数+持有股数达到目标数量时将自动建塔</p>
  2461. 输入 目标数量 / 买入价格(不超过此价格的卖单将自动买入)</div>
  2462. <div class="desc action"><p>便捷设定圣殿等级:
  2463. <span data-lv="1" class="text_button setToLv">[一级]</span>
  2464. <span data-lv="2" class="text_button setToLv">[二级]</span>
  2465. <span data-lv="3" class="text_button setToLv">[三级]</span></p></div>
  2466. <div class="label"><div class="trade build">
  2467. <input class="target" type="number" style="width:150px" title="目标数量" min="0" step="1" value="${target}">
  2468. <input class="bidPrice" type="number" style="width:100px" title="卖出下限" min="0" value="${bidPrice}">
  2469. <button id="startBuildButton" class="active">自动建塔</button><button id="cancelBuildButton">取消建塔</button></div></div>
  2470. <div class="loading" style="display:none"></div>`;
  2471. const { closeDialog } = showDialog(dialog);
  2472. $('#cancelBuildButton').on('click', function () {
  2473. const autoTempleList = AutoTempleList.get();
  2474. const index = autoTempleList.findIndex(temple => parseInt(temple.charaId) === charaId);
  2475. if (index >= 0) {
  2476. autoTempleList.splice(index, 1);
  2477. AutoTempleList.set(autoTempleList);
  2478. alert(`取消自动建塔${chara.Name}`);
  2479. }
  2480. $(`#grailBox.chara${charaId} #autobuildButton`).text('[自动建塔]');
  2481. closeDialog();
  2482. });
  2483. $('#startBuildButton').on('click', function () {
  2484. const info = {
  2485. charaId: parseInt(charaId),
  2486. name: chara.Name,
  2487. target: parseInt($('.trade.build .target').val()),
  2488. bidPrice: parseFloat($('.trade.build .bidPrice').val())
  2489. };
  2490. addBuildTemple(info);
  2491. alert(`启动自动建塔#${info.charaId} ${info.name}`);
  2492. closeDialog();
  2493. $(`#grailBox.chara${charaId} #autobuildButton`).text('[自动建塔中]');
  2494. autoBuildTemple([info]);
  2495. });
  2496. $('.action .setToLv').on('click', e => {
  2497. const level = $(e.target).data('lv');
  2498. $('.trade.build .target').val(Math.pow(5, level - 1) * 500);
  2499. });
  2500. };
  2501. const markFollow = () => {
  2502. const followInfoTagsClass = 'item-info-tags';
  2503. const followInfoTag = `<small class="${followInfoTagsClass}"></small>`;
  2504. const followChara = '<div title="已关注角色" class="item-info-tag item-info-chara-icon"></div>';
  2505. const followAuc = '<div title="已关注拍卖" class="item-info-tag item-info-auc-icon"></div>';
  2506. const followIco = '<div title="已自动补款" class="item-info-tag item-info-ico-icon"></div>';
  2507. const followTemple = '<div title="已自动建塔" class="item-info-tag item-info-temple-icon"></div>';
  2508. $('.item_list').each((_, el) => {
  2509. const itemEl = $(el);
  2510. let id = itemEl.data('id');
  2511. if (!id) {
  2512. const avatarUrl = itemEl.find('a.avatar').attr('href');
  2513. const recMatch = itemEl.find('.row .time').text().match(/#(\d+)/) || ['', ''];
  2514. id = parseInt(avatarUrl ? avatarUrl.match(/topic\/crt\/(\d+)([?/]|$)/)[1] : recMatch[1]);
  2515. }
  2516. let followInfoTagEl = itemEl.find(`.${followInfoTagsClass}`);
  2517. if (!followInfoTagEl.length) followInfoTagEl = itemEl.find('.inner .row').before(followInfoTag).prev();
  2518. if (!id || !followInfoTagEl) return
  2519. let followInfo = '';
  2520. const followList = FollowList.get();
  2521. if (followList.charas.includes(id)) followInfo += followChara;
  2522. if (followList.auctions.includes(id)) followInfo += followAuc;
  2523. const templeInfo = AutoTempleList.get().find(e => parseInt(e.charaId) === id);
  2524. if (templeInfo) {
  2525. followInfo += followTemple;
  2526. if (templeInfo.bidPrice !== undefined && templeInfo.target !== undefined) {
  2527. followInfo += `<small class="item-info-temple-text" title="自动建塔价 × 数量" data-id="${id}" data-name="${templeInfo.name}">(${templeInfo.bidPrice} * ${templeInfo.target})</small>`;
  2528. }
  2529. }
  2530. const fillIcoInfo = FillICOList.get().find(e => parseInt(e.charaId) === id);
  2531. if (fillIcoInfo) {
  2532. followInfo += followIco;
  2533. if (fillIcoInfo.target) {
  2534. followInfo += `<small title="自动补款目标" class="item-info-ico-text" data-id="${id}" data-icoid="${fillIcoInfo.Id}" data-name="${fillIcoInfo.Name}" data-end="${fillIcoInfo.End}" data-fillmin="${fillIcoInfo.fillMin}">(lv${fillIcoInfo.target})</small>`;
  2535. }
  2536. }
  2537. followInfoTagEl.html(followInfo);
  2538. });
  2539. $('.item_list .item-info-temple-text').off('click').on('click', (e) => {
  2540. const item = $(e.currentTarget);
  2541. openBuildDialog({ CharacterId: item.data('id'), Name: item.data('name') });
  2542. e.stopPropagation();
  2543. });
  2544. $('.item_list .item-info-ico-text').off('click').on('click', (e) => {
  2545. const itemData = $(e.currentTarget).data();
  2546. const fillICOList = FillICOList.get();
  2547. const item = fillICOList.find(item => item.charaId === parseInt(itemData.id)) || { Id: parseInt(itemData.icoid), charaId: parseInt(itemData.id), name: itemData.name, end: itemData.end };
  2548. if (item) openICODialog({ Id: item.Id, CharacterId: item.charaId, Name: item.name, End: item.end });
  2549. e.stopPropagation();
  2550. });
  2551. };
  2552. const changeLinkPos = (parentNode) => {
  2553. const me = getMe();
  2554. let user;
  2555. if (location.pathname.startsWith('/user')) {
  2556. user = location.pathname.split('/').pop();
  2557. }
  2558. const swapLink = (linkEl) => {
  2559. const $link = $(linkEl).closest('.link');
  2560. const $left = $link.find('.left');
  2561. const $right = $link.find('.right');
  2562. const $content = $link.find('.content');
  2563. $left.toggleClass('left').toggleClass('right');
  2564. $right.toggleClass('left').toggleClass('right');
  2565. const $names = $content.find('span');
  2566. $($names[0]).replaceWith($names[1]);
  2567. $content.append($names[0]);
  2568. $link.toggleClass('swapped');
  2569. const thisUser = user || $link.find('.name a').attr('href').split('/').pop();
  2570. const thisLinkPos = [$right, $left].map((el) => $(el).find('.card').data('temple').CharacterId).join('#');
  2571. const thisConfig = `${thisUser}#${thisLinkPos}`;
  2572. console.log(thisConfig);
  2573. if (thisUser === me) {
  2574. const reverseConfig = thisConfig.replace(/^(.+)#(\d+)#(\d+)$/, '$1#$3#$2');
  2575. let linkPosList = LinkPosList.get();
  2576. linkPosList = linkPosList.filter((i) => ![thisConfig, reverseConfig].includes(i));
  2577. if ($link.hasClass('swapped')) {
  2578. linkPosList.push(thisConfig);
  2579. console.log('saved link pos: ' + thisConfig);
  2580. }
  2581. LinkPosList.set(linkPosList);
  2582. }
  2583. };
  2584. const linkPosList = LinkPosList.get();
  2585. $(parentNode).find('.link .name').each((i, el) => {
  2586. if ($(el).find('.swapPos').length > 0) return
  2587. $(el).append('<span class="swapPos" title="交换连接两塔的顺序">[换序]</span>');
  2588. let thisUser = user;
  2589. if (!thisUser) thisUser = $(el).find('a').attr('href').split('/').pop();
  2590. if (thisUser === me) {
  2591. const $link = $(el).closest('.link');
  2592. const leftId = $link.find('.left .card').data('temple').CharacterId;
  2593. const rightId = $link.find('.right .card').data('temple').CharacterId;
  2594. const reverseConfig = `${thisUser}#${rightId}#${leftId}`;
  2595. if (linkPosList.includes(reverseConfig)) swapLink($link);
  2596. }
  2597. });
  2598. $(parentNode).on('click', '.swapPos', (e) => {
  2599. swapLink($(e.currentTarget).closest('.link'));
  2600. });
  2601. };
  2602. const showGallery = () => {
  2603. if (Settings.get().gallery === 'on') {
  2604. let index = 0;
  2605. $('body').on('keydown', function (event) {
  2606. switch (event.key || event.keyCode) {
  2607. case 'ArrowLeft':
  2608. case 37:
  2609. closeDialog();
  2610. $('.item .card')[index - 1].click();
  2611. break
  2612. case 'ArrowRight':
  2613. case 39:
  2614. closeDialog();
  2615. $('.item .card')[index + 1].click();
  2616. break
  2617. }
  2618. });
  2619. $('body').on('touchstart', '#TB_window.temple', function (e) {
  2620. let touch = e.originalEvent;
  2621. const startX = touch.changedTouches[0].pageX;
  2622. $(this).on('touchmove', function (e) {
  2623. e.preventDefault();
  2624. touch = e.originalEvent.touches[0] || e.originalEvent.changedTouches[0];
  2625. if (touch.pageX - startX > 20) {
  2626. closeDialog();
  2627. $('.item .card')[index - 1].click();
  2628. $(this).off('touchmove');
  2629. } else if (touch.pageX - startX < -20) {
  2630. closeDialog();
  2631. $('.item .card')[index + 1].click();
  2632. $(this).off('touchmove');
  2633. }
  2634. });
  2635. }).on('touchend', function () {
  2636. $(this).off('touchmove');
  2637. });
  2638. setInterval(function () {
  2639. $('.item .card').on('click', (e) => {
  2640. index = $('.item .card').index(e.currentTarget);
  2641. });
  2642. }, 1000);
  2643. }
  2644. };
  2645. const followChara = (charaId) => {
  2646. let button = '<button id="followCharaButton" class="text_button">[关注角色]</button>';
  2647. if (FollowList.get().charas.includes(charaId)) {
  2648. button = '<button id="followCharaButton" class="text_button">[取消关注]</button>';
  2649. }
  2650. if ($(`#grailBox.chara${charaId} #kChartButton`).length) $(`#grailBox.chara${charaId} #kChartButton`).before(button);
  2651. else $(`#grailBox.chara${charaId} .title .text`).after(button);
  2652. $(`#grailBox.chara${charaId} #followCharaButton`).on('click', () => {
  2653. const followList = FollowList.get();
  2654. if (followList.charas.includes(charaId)) {
  2655. followList.charas.splice(followList.charas.indexOf(charaId), 1);
  2656. $(`#grailBox.chara${charaId} #followCharaButton`).text('[关注角色]');
  2657. } else {
  2658. followList.charas.unshift(charaId);
  2659. $(`#grailBox.chara${charaId} #followCharaButton`).text('[取消关注]');
  2660. }
  2661. FollowList.set(followList);
  2662. });
  2663. };
  2664. const followAuctions = (charaId) => {
  2665. getData(`chara/user/${charaId}/tinygrail/false`).then((d) => {
  2666. if (d.State === 0) {
  2667. let button;
  2668. if (FollowList.get().auctions.includes(charaId)) {
  2669. button = '<button id="followAuctionButton" class="text_button">[取消关注]</button>';
  2670. } else {
  2671. button = '<button id="followAuctionButton" class="text_button">[关注竞拍]</button>';
  2672. }
  2673. $(`#grailBox.chara${charaId} #buildButton`).before(button);
  2674. $(`#grailBox.chara${charaId} #followAuctionButton`).on('click', () => {
  2675. const followList = FollowList.get();
  2676. if (followList.auctions.includes(charaId)) {
  2677. followList.auctions.splice(followList.auctions.indexOf(charaId), 1);
  2678. $(`#grailBox.chara${charaId} #followAuctionButton`).text('[关注竞拍]');
  2679. } else {
  2680. followList.auctions.unshift(charaId);
  2681. $(`#grailBox.chara${charaId} #followAuctionButton`).text('[取消关注]');
  2682. }
  2683. FollowList.set(followList);
  2684. });
  2685. }
  2686. });
  2687. };
  2688. const sell_out = (charaId, init_price) => {
  2689. $(`#grailBox.chara${charaId} .info .text .listed`).before('<button id="sell_out" class="text_button" title="以发行价全部卖出">[全部卖出]</button>');
  2690. $(`#grailBox.chara${charaId} #sell_out`).on('click', function () {
  2691. getData(`chara/user/${charaId}`).then((d) => {
  2692. $(`#grailBox.chara${charaId} .ask .price`).val(init_price);
  2693. $(`#grailBox.chara${charaId} .ask .amount`).val(d.Value.Amount);
  2694. });
  2695. });
  2696. };
  2697. const showInitialPrice = (chara) => {
  2698. const charaId = chara.CharacterId || chara.Id;
  2699. let time = formatDate(chara.ListedDate);
  2700. const charaInitPrice = CharaInitPrice.get();
  2701. if (charaInitPrice[charaId]) {
  2702. const init_price = charaInitPrice[charaId].init_price;
  2703. time = time || charaInitPrice[charaId].time;
  2704. $(`#grailBox.chara${charaId} .info .text .listed`).before(`<span title="上市时间:${time}">发行价:${init_price}</span>`);
  2705. sell_out(charaId, init_price);
  2706. } else {
  2707. getData(`chara/charts/${charaId}/2019-08-08`).then((d) => {
  2708. if (d.Value[0]) {
  2709. const init_price = d.Value[0].Begin.toFixed(2);
  2710. time = time || d.Value[0].Time.replace('T', ' ');
  2711. const charaInitPrice = CharaInitPrice.get();
  2712. charaInitPrice[charaId] = { init_price: init_price };
  2713. CharaInitPrice.set(charaInitPrice);
  2714. $(`#grailBox.chara${charaId} .info .text .listed`).before(`<span title="上市时间:${time}">发行价:${init_price}</span>`);
  2715. sell_out(charaId, init_price);
  2716. }
  2717. });
  2718. }
  2719. };
  2720. const priceWarning = (charaId) => {
  2721. const price = $(`#grailBox.chara${charaId} .bid .price`).val();
  2722. $(`#grailBox.chara${charaId} #bidButton`).after('<button style="display:none" id="confirm_bidButton" class="active bid">买入</button>');
  2723. $(`#grailBox.chara${charaId} .bid .price`).on('input', function () {
  2724. const price_now = $(`#grailBox.chara${charaId} .bid .price`).val();
  2725. if (price_now > Math.max(price * 3, 100)) {
  2726. $(`#grailBox.chara${charaId} .bid .price`).css({ color: 'red' });
  2727. $(`#grailBox.chara${charaId} #confirm_bidButton`).show();
  2728. $(`#grailBox.chara${charaId} #bidButton`).hide();
  2729. } else {
  2730. $(`#grailBox.chara${charaId} #confirm_bidButton`).hide();
  2731. $(`#grailBox.chara${charaId} #bidButton`).show();
  2732. $(`#grailBox.chara${charaId} .bid .price`).css({ color: 'inherit' });
  2733. }
  2734. });
  2735. $(`#grailBox.chara${charaId} #confirm_bidButton`).on('click', function () {
  2736. const price = $(`#grailBox.chara${charaId} .bid .price`).val();
  2737. const amount = $(`#grailBox.chara${charaId} .bid .amount`).val();
  2738. if (!confirm(`买入价格过高提醒!\n确定以${price}的价格买入${amount}股?`)) {
  2739. return
  2740. }
  2741. $(`#grailBox.chara${charaId} #bidButton`).click();
  2742. });
  2743. };
  2744. const changeBaseAmount = (charaId) => {
  2745. $(`#grailBox.chara${charaId} input.amount`).val(1);
  2746. };
  2747. const mergeorderList = (orderListHistory) => {
  2748. const mergedorderList = []; let i = 0;
  2749. mergedorderList.push(orderListHistory[0]);
  2750. for (let j = 1; j < orderListHistory.length; j++) {
  2751. if ((orderListHistory[j].Price === mergedorderList[i].Price) && Math.abs(new Date(orderListHistory[j].TradeTime) - new Date(mergedorderList[i].TradeTime)) < 10 * 1000) {
  2752. mergedorderList[i].Amount += orderListHistory[j].Amount;
  2753. } else {
  2754. mergedorderList.push(orderListHistory[j]);
  2755. i++;
  2756. }
  2757. }
  2758. return mergedorderList
  2759. };
  2760. const mergeorderListHistory = (charaId) => {
  2761. if (Settings.get().merge_order === 'on') {
  2762. getData(`chara/user/${charaId}`).then((d) => {
  2763. if (d.State === 0 && d.Value) {
  2764. $(`.chara${charaId} .ask .ask_list li[class!=ask]`).hide();
  2765. const askHistory = mergeorderList(d.Value.AskHistory);
  2766. for (let i = 0; i < askHistory.length; i++) {
  2767. const ask = askHistory[i];
  2768. if (ask) $(`.chara${charaId} .ask .ask_list`).prepend(`<li title="${formatDate(ask.TradeTime)}">₵${formatNumber(ask.Price, 2)} / ${formatNumber(ask.Amount, 0)} / +${formatNumber(ask.Amount * ask.Price, 2)}<span class="cancel">[成交]</span></li>`);
  2769. }
  2770. $(`.chara${charaId} .bid .bid_list li[class!=bid]`).hide();
  2771. const bidHistory = mergeorderList(d.Value.BidHistory);
  2772. for (let i = 0; i < bidHistory.length; i++) {
  2773. const bid = bidHistory[i];
  2774. if (bid) $(`.chara${charaId} .bid .bid_list`).prepend(`<li title="${formatDate(bid.TradeTime)}">₵${formatNumber(bid.Price, 2)} / ${formatNumber(bid.Amount, 0)} / -${formatNumber(bid.Amount * bid.Price, 2)}<span class="cancel">[成交]</span></li>`);
  2775. }
  2776. }
  2777. });
  2778. }
  2779. };
  2780. const showOwnTemple = (charaId) => {
  2781. const pre_temple = Settings.get().pre_temple;
  2782. const temples = $(`#grailBox.chara${charaId} .assets_box #lastTemples.assets .item`);
  2783. const me = getMe();
  2784. for (let i = 0; i < temples.length; i++) {
  2785. const user = temples[i].querySelector('.name a').href.split('/').pop();
  2786. if (user === me) {
  2787. temples[i].classList.add('my_temple');
  2788. temples[i].classList.remove('replicated');
  2789. if (pre_temple === 'on') $(`#grailBox.chara${charaId} .assets_box #lastTemples.assets`).prepend(temples[i]);
  2790. break
  2791. }
  2792. }
  2793. $(`#grailBox.chara${charaId} #expandButton`).on('click', () => { showOwnTemple(charaId); });
  2794. };
  2795. const changeTempleCover = (charaId) => {
  2796. const setChaosCube = (temple) => {
  2797. $('#chaosCubeButton').on('click', () => {
  2798. const templeId = temple.CharacterId;
  2799. const itemsSetting = ItemsSetting.get();
  2800. itemsSetting.chaosCube = templeId;
  2801. ItemsSetting.set(itemsSetting);
  2802. });
  2803. };
  2804. const addButton = (temple, user) => {
  2805. $('#TB_window .action').append(`<button id="changeCoverButton2" class="text_button" title="修改圣殿封面">[修改]</button>
  2806. <button id="copyCoverButton" class="text_button" title="复制圣殿图片为自己圣殿的封面">[复制]</button>`);
  2807. $('#changeCoverButton2').on('click', () => {
  2808. const cover = prompt('图片url(你可以复制已有圣殿图片的url):');
  2809. const url = 'https://tinygrail.oss-cn-hangzhou.aliyuncs.com/' + cover.match(/cover\/\S+\.jpg/)[0];
  2810. postData(`chara/temple/cover/${charaId}/${temple.UserId}`, url).then((d) => {
  2811. if (d.State === 0) {
  2812. alert('更换封面成功。');
  2813. $('#TB_window img.cover').attr('src', cover);
  2814. $(`#grailBox.chara${charaId} .assets_box .assets .item`).each(function () {
  2815. if (user === this.querySelector('.name a').href.split('/').pop()) { $(this).find('div.card').css({ 'background-image': 'url(https://tinygrail.mange.cn/' + cover.match(/cover\/\S+\.jpg/)[0] + '!w150)' }); }
  2816. });
  2817. } else {
  2818. alert(d.Message);
  2819. }
  2820. });
  2821. });
  2822. $('#copyCoverButton').on('click', () => {
  2823. const cover = $('#TB_window .container .cover').attr('src');
  2824. const url = 'https://tinygrail.oss-cn-hangzhou.aliyuncs.com/' + cover.match(/cover\/\S+\.jpg/)[0];
  2825. postData(`chara/temple/cover/${charaId}`, url).then((d) => {
  2826. if (d.State === 0) {
  2827. alert('更换封面成功。');
  2828. location.reload();
  2829. } else {
  2830. alert(d.Message);
  2831. }
  2832. });
  2833. });
  2834. };
  2835. $(`#grailBox.chara${charaId} .assets .item`).on('click', (e) => {
  2836. const me = getMe();
  2837. const $el = $(e.currentTarget);
  2838. let temple = $el.data('temple');
  2839. let isLink = false;
  2840. if (temple === undefined && ($el.hasClass('left') || $el.hasClass('right'))) {
  2841. temple = $el.find('.card').data('temple');
  2842. isLink = true;
  2843. }
  2844. if (temple === undefined) return
  2845. let user = temple.Name;
  2846. if (temple.LinkId === parseInt(charaId)) {
  2847. user = $el.siblings('.item').find('.card').data('temple').Name;
  2848. }
  2849. if (user !== me && temple.CharacterId !== parseInt(charaId)) return
  2850. if (isLink) {
  2851. if (user === me) setChaosCube(temple);
  2852. else addButton(temple, user);
  2853. } else {
  2854. launchObserver({
  2855. parentNode: document.body,
  2856. selector: '#TB_window .action',
  2857. successCallback: () => {
  2858. if (user === me) setChaosCube(temple);
  2859. else addButton(temple, user);
  2860. }
  2861. });
  2862. }
  2863. });
  2864. };
  2865. const showOwnLink = (charaId) => {
  2866. const pre_link = Settings.get().pre_temple;
  2867. const links = $(`#grailBox.chara${charaId} .assets_box #lastLinks.assets .link.item`);
  2868. const me = getMe();
  2869. for (let i = 0; i < links.length; i++) {
  2870. const user = links[i].querySelector('.name a').href.split('/').pop();
  2871. if (user === me) {
  2872. links[i].classList.add('my_link');
  2873. links[i].closest('.rank_list').classList.add('my_rank');
  2874. if (pre_link === 'on') $(links[i]).siblings('.rank.item').after(links[i]);
  2875. break
  2876. }
  2877. }
  2878. };
  2879. const openHistoryDialog = (chara, page) => {
  2880. const dialog = `<div class="title">上${page}周拍卖结果 - #${chara.Id} ${chara.Name}」 ${formatNumber(chara.Current, 2)} / ${formatNumber(chara.Total, 0)}</div>
  2881. <div class="desc" style="display:none"></div>
  2882. <div class="r###lt" style="display:none; max-height: 500px; overflow: auto;"></div>
  2883. <div class="page_inner">
  2884. <a id="nextweek" class="p" style="display:none; float: left;margin-bottom: 5px;margin-left: 50px;">后一周</a>
  2885. <a id="lastweek" class="p" style="display:none; float: right;margin-bottom: 5px;margin-right: 50px;">前一周</a>
  2886. </div>
  2887. <div class="loading"></div>`;
  2888. const { closeDialog } = showDialog(dialog);
  2889. const charaInitPrice = CharaInitPrice.get();
  2890. const week_ms = 7 * 24 * 3600 * 1000;
  2891. const templeWeek = Math.floor((new Date() - new Date('2019/10/05')) / week_ms + 1);
  2892. const icoWeek = Math.floor((new Date() - new Date(charaInitPrice[chara.Id].time)) / week_ms + 1);
  2893. const week = Math.min(templeWeek, icoWeek);
  2894. getData(`chara/auction/list/${chara.Id}/${page}`).then((d) => {
  2895. $('#TB_window .loading').hide();
  2896. if (d.State === 0 && d.Value.length > 0) {
  2897. let success = 0;
  2898. let total = 0;
  2899. d.Value.forEach((a) => {
  2900. let state = 'even';
  2901. let name = '失败';
  2902. if (a.State === 1) {
  2903. success++;
  2904. total += a.Amount;
  2905. state = 'raise';
  2906. name = '成功';
  2907. }
  2908. const record = `<div class="row"><span class="time">${formatDate(a.Bid)}</span>
  2909. <span class="user"><a target="_blank" href="/user/${a.Username}">${a.Nickname}</a></span>
  2910. <span class="price">₵${formatNumber(a.Price, 2)} / ${formatNumber(a.Amount, 0)}</span>
  2911. <span class="tag ${state}">${name}</span></div>`;
  2912. $('#TB_window .r###lt').append(record);
  2913. });
  2914. $('#TB_window .desc').text(`共有${d.Value.length}人参与拍卖,成功${success}人 / ${total}股`);
  2915. $('#TB_window .r###lt').show();
  2916. } else {
  2917. $('#TB_window .desc').text('暂无拍卖数据');
  2918. }
  2919. $('#TB_window .desc').show();
  2920. if (page > 1) $('#nextweek').show();
  2921. if (page < week) $('#lastweek').show();
  2922. $('#nextweek').on('click', () => {
  2923. page--;
  2924. closeDialog();
  2925. openHistoryDialog(chara, page);
  2926. });
  2927. $('#lastweek').on('click', () => {
  2928. page++;
  2929. closeDialog();
  2930. openHistoryDialog(chara, page);
  2931. });
  2932. });
  2933. };
  2934. const showAuctionHistory = (chara) => {
  2935. const charaId = chara.CharacterId || chara.Id;
  2936. const button = '<button id="auctionHistorys" class="text_button">[往期拍卖]</button>';
  2937. $(`#grailBox.chara${charaId} #auctionHistoryButton`).after(button);
  2938. $(`#grailBox.chara${charaId} #auctionHistoryButton`).hide();
  2939. $(`#grailBox.chara${charaId} #auctionHistorys`).on('click', () => {
  2940. openHistoryDialog(chara, 1);
  2941. });
  2942. };
  2943. const openTradeHistoryDialog = (chara) => {
  2944. const dialog = `<div class="title">交易历史记录 - #${chara.Id} ${chara.Name}」 ${formatNumber(chara.Current, 2)} / ${formatNumber(chara.Total, 0)}</div>
  2945. <div class="r###lt" style="display:none; max-height: 500px; overflow: auto;"></div>
  2946. <div class="desc" style="display:none"></div>
  2947. <div class="loading"></div>`;
  2948. showDialog(dialog);
  2949. const loadTradeHistory = (page) => {
  2950. $('#TB_window .loading').hide();
  2951. $('#TB_window .r###lt').show();
  2952. $('#TB_window .r###lt').html('');
  2953. const records = tradeHistory.slice(50 * (page - 1), 50 * page);
  2954. if (records.length) {
  2955. for (let i = 0; i < records.length; i++) {
  2956. const record = `<div class="row">
  2957. <span class="time" title="交易时间">${formatDate(records[i].Time)}</span>
  2958. <span class="price" title="价格">₵${formatNumber((records[i].Price / records[i].Amount), 2)}</span>
  2959. <span class="amount" title="数量">${formatNumber(records[i].Amount, 0)}</span>
  2960. <span class="price" title="交易额">₵${formatNumber(records[i].Price, 2)}</span>
  2961. </div>`;
  2962. $('#TB_window .r###lt').append(record);
  2963. }
  2964. $('#TB_window .desc').html('');
  2965. $('#TB_window .desc').text(`共有${tradeHistory.length}条记录,当前 ${page} / ${totalPages} 页`);
  2966. for (let i = 1; i <= totalPages; i++) {
  2967. const pager = `<span class="page" data-page="${i}">[${i}]</span>`;
  2968. $('#TB_window .desc').append(pager);
  2969. }
  2970. $('#TB_window .desc .page').on('click', (e) => {
  2971. const page = $(e.target).data('page');
  2972. loadTradeHistory(page);
  2973. });
  2974. $('#TB_window .r###lt').show();
  2975. } else {
  2976. $('#TB_window .desc').text('暂无交易记录');
  2977. }
  2978. $('#TB_window .desc').show();
  2979. };
  2980. let tradeHistory, totalPages;
  2981. getData(`chara/charts/${chara.Id}/2019-08-08`).then((d) => {
  2982. if (d.State === 0) {
  2983. tradeHistory = d.Value.reverse();
  2984. totalPages = Math.ceil(d.Value.length / 50);
  2985. loadTradeHistory(1);
  2986. }
  2987. });
  2988. };
  2989. const showTradeHistory = (chara) => {
  2990. const charaId = chara.CharacterId || chara.Id;
  2991. $(`#grailBox.chara${charaId} #kChartButton`).after('<button id="tradeHistoryButton" class="text_button">[交易记录]</button>');
  2992. $(`#grailBox.chara${charaId} #tradeHistoryButton`).on('click', () => {
  2993. openTradeHistoryDialog(chara);
  2994. });
  2995. };
  2996. const showPrice = (chara) => {
  2997. const charaId = chara.CharacterId || chara.Id;
  2998. const price = chara.Price.toFixed(2);
  2999. $(`#grailBox.chara${charaId} .info .text .listed`).before(`<span>评估价:${price}</span>`);
  3000. };
  3001. const showTempleRate = (chara) => {
  3002. const charaId = chara.CharacterId || chara.Id;
  3003. getData(`chara/temple/${chara.Id}`).then((d) => {
  3004. const templeAll = { 1: 0, 2: 0, 3: 0 };
  3005. for (let i = 0; i < d.Value.length; i++) {
  3006. templeAll[d.Value[i].Level]++;
  3007. }
  3008. $(`#grailBox.chara${charaId} .assets_box .bold .sub`).before(`<span class="sub"> (${templeAll[3]} + ${templeAll[2]} + ${templeAll[1]})</span>`);
  3009. });
  3010. };
  3011. const setBuildTemple = (chara) => {
  3012. const charaId = chara.CharacterId || chara.Id;
  3013. let button = '<button id="autobuildButton" class="text_button">[自动建塔]</button>';
  3014. if (AutoTempleList.get().some(item => parseInt(item.charaId) === parseInt(charaId))) {
  3015. button = '<button id="autobuildButton" class="text_button">[自动建塔中]</button>';
  3016. }
  3017. if ($(`#grailBox.chara${charaId} #buildButton`).length) $(`#grailBox.chara${charaId} #buildButton`).after(button);
  3018. else $(`#grailBox.chara${charaId} .title .text`).after(button);
  3019. $(`#grailBox.chara${charaId} #autobuildButton`).on('click', () => {
  3020. openBuildDialog(chara);
  3021. });
  3022. };
  3023. const fixAuctions = (chara) => {
  3024. const charaId = chara.CharacterId || chara.Id;
  3025. getData(`chara/user/${chara.Id}/tinygrail/false`).then((d) => {
  3026. chara.Price = d.Value.Price;
  3027. chara.State = d.Value.Amount;
  3028. let button = '<button id="auctionButton2" class="text_button">[萌王投票]</button>';
  3029. if (d.State === 0 && d.Value.Amount > 0) button = '<button id="auctionButton2" class="text_button">[参与竞拍]</button>';
  3030. $(`#grailBox.chara${charaId} #buildButton`).before(button);
  3031. $(`#grailBox.chara${charaId} #auctionButton`).hide();
  3032. launchObserver({
  3033. parentNode: document.body,
  3034. selector: `#grailBox.chara${charaId} #auctionButton`,
  3035. successCallback: () => {
  3036. $(`#grailBox.chara${charaId} #auctionButton`).hide();
  3037. }
  3038. });
  3039. postData('chara/auction/list', [chara.Id]).then((d) => {
  3040. loadUserAuctions(d);
  3041. $(`#grailBox.chara${charaId} #auctionButton2`).on('click', () => {
  3042. openAuctionDialog(chara, d);
  3043. });
  3044. });
  3045. });
  3046. };
  3047. const showEndTime = (chara) => {
  3048. const charaId = chara.CharacterId || chara.Id;
  3049. const endTime = (chara.End).slice(0, 19);
  3050. $(`#grailBox.chara${charaId} .title .text`).append(`<div class="sub" style="margin-left: 20px">结束时间: ${endTime}</div>`);
  3051. };
  3052. const showHideBlock = (titleSelector, blockSelector, settings) => {
  3053. const toggleBlock = (set) => {
  3054. const $linkTitle = $(titleSelector);
  3055. const $linkBlock = $(blockSelector);
  3056. if ($linkTitle.hasClass('hide_grail_block_title')) {
  3057. $linkTitle.removeClass('hide_grail_block_title');
  3058. $linkBlock.removeClass('hide_grail_block_on').removeClass('hide_grail_block_not_me');
  3059. } else {
  3060. if (set === 'off') set = 'on';
  3061. $linkTitle.addClass('hide_grail_block_title');
  3062. $linkBlock.addClass(`hide_grail_block_${set}`);
  3063. }
  3064. };
  3065. if (settings !== 'off') toggleBlock(settings);
  3066. $(titleSelector).css('cursor', 'pointer').attr('title', '显示/隐藏').off('click').on('click', () => toggleBlock(settings));
  3067. };
  3068. const showHideLink = (charaId) => {
  3069. const titleSelector = `#grailBox.chara${charaId} .link_desc .link_count`;
  3070. const blockSelector = `#grailBox.chara${charaId} #lastLinks`;
  3071. const config = Settings.get().hide_link;
  3072. showHideBlock(titleSelector, blockSelector, config);
  3073. };
  3074. const showHideTemple = (charaId) => {
  3075. const titleSelector = `#grailBox.chara${charaId} .temple_desc .temple_count`;
  3076. const blockSelector = `#grailBox.chara${charaId} #lastTemples`;
  3077. const config = Settings.get().hide_temple;
  3078. showHideBlock(titleSelector, blockSelector, config);
  3079. };
  3080. const showHideBoard = (charaId) => {
  3081. const titleSelector = `#grailBox.chara${charaId} .board_box .desc .bold`;
  3082. const blockSelector = `#grailBox.chara${charaId} .board_box .users, #grailBox.chara${charaId} #loadBoardMemeberButton`;
  3083. const config = Settings.get().hide_board;
  3084. showHideBlock(titleSelector, blockSelector, config);
  3085. };
  3086. const addCharaInfo = (cid) => {
  3087. try {
  3088. const charaId = cid || parseInt($('#grailBox .title .name a')[0].href.split('/').pop());
  3089. $(`#grailBox.chara${charaId} .assets_box`).addClass('tinygrail-helped');
  3090. followChara(charaId);
  3091. followAuctions(charaId);
  3092. priceWarning(charaId);
  3093. changeBaseAmount(charaId);
  3094. mergeorderListHistory(charaId);
  3095. launchObserver({
  3096. parentNode: document.body,
  3097. selector: `#grailBox.chara${charaId} #lastTemples .item`,
  3098. successCallback: () => {
  3099. showOwnTemple(charaId);
  3100. changeTempleCover(charaId);
  3101. }
  3102. });
  3103. launchObserver({
  3104. parentNode: document.body,
  3105. selector: `#grailBox.chara${charaId} #lastLinks .link.item`,
  3106. successCallback: () => {
  3107. showOwnLink(charaId);
  3108. changeLinkPos(`#grailBox.chara${charaId} #lastLinks`);
  3109. }
  3110. });
  3111. launchObserver({
  3112. parentNode: document.body,
  3113. selector: `#grailBox.chara${charaId} .board_box .users .user`,
  3114. successCallback: () => {
  3115. showHideLink(charaId);
  3116. showHideTemple(charaId);
  3117. showHideBoard(charaId);
  3118. }
  3119. });
  3120. showGallery();
  3121. getData(`chara/${charaId}`).then((d) => {
  3122. const chara = d.Value;
  3123. showAuctionHistory(chara);
  3124. showTradeHistory(chara);
  3125. showPrice(chara);
  3126. showInitialPrice(chara);
  3127. showTempleRate(chara);
  3128. setBuildTemple(chara);
  3129. fixAuctions(chara);
  3130. });
  3131. } catch (e) { console.log(e); }
  3132. };
  3133. const addICOInfo = (cid) => {
  3134. const charaId = cid || parseInt(location.pathname.split('/').pop());
  3135. $(`#grailBox.chara${charaId} .trade .money`).addClass('tinygrail-helped');
  3136. followChara(charaId);
  3137. getData(`chara/${charaId}`).then((d) => {
  3138. const chara = d.Value;
  3139. showEndTime(chara);
  3140. setBuildTemple(chara);
  3141. setFullFillICO(chara);
  3142. });
  3143. };
  3144. const toggleGrailBox = () => {
  3145. const $title = $('#grail li.title');
  3146. if ($title.hasClass('hide_grail_title')) {
  3147. $title.closest('div.horizontalOptions').next().removeClass('hide_grail');
  3148. $('div.grail.page_inner').removeClass('hide_grail');
  3149. $title.removeClass('hide_grail_title');
  3150. } else {
  3151. $title.closest('div.horizontalOptions').next().addClass('hide_grail');
  3152. $('div.grail.page_inner').addClass('hide_grail');
  3153. $title.addClass('hide_grail_title');
  3154. }
  3155. };
  3156. const showHideGrailBox = () => {
  3157. const hide = Settings.get().hide_grail;
  3158. if (hide === 'on') {
  3159. toggleGrailBox();
  3160. }
  3161. $('#grail li.title').attr('title', '显示/隐藏小圣杯').on('click', toggleGrailBox);
  3162. launchObserver({
  3163. parentNode: document.querySelector('#user_home'),
  3164. selector: '.grail.page_inner',
  3165. successCallback: () => {
  3166. console.log('modify page inner');
  3167. if ($('#grail li.title').hasClass('hide_grail_title')) {
  3168. $('div.grail.page_inner').addClass('hide_grail');
  3169. }
  3170. },
  3171. config: { childList: true },
  3172. stopWhenSuccess: false
  3173. });
  3174. };
  3175. if (!location.pathname.startsWith('/rakuen/topic')) {
  3176. setInterval(autoBuildTemple, 60 * 60 * 1000);
  3177. setInterval(autoFillICO, 30 * 1000);
  3178. setInterval(autoJoinFollowIco, 60 * 60 * 1000);
  3179. }
  3180. const listenToGrailBox = (parentNode = document.body, listenIco = true) => {
  3181. launchObserver({
  3182. parentNode: parentNode,
  3183. selector: '#grailBox .assets_box:not(.tinygrail-helped)',
  3184. successCallback: () => {
  3185. addCharaInfo(parseInt($('#grailBox .assets_box:not(.tinygrail-helped)').closest('#grailBox').attr('class').match(/chara(\d+)/)[1]));
  3186. },
  3187. stopWhenSuccess: false
  3188. });
  3189. if (listenIco) {
  3190. launchObserver({
  3191. parentNode: parentNode,
  3192. selector: '#grailBox .trade .money:not(.tinygrail-helped)',
  3193. successCallback: () => {
  3194. addICOInfo(parseInt($('#grailBox .trade .money:not(.tinygrail-helped)').closest('#grailBox').attr('class').match(/chara(\d+)/)[1]));
  3195. },
  3196. stopWhenSuccess: false
  3197. });
  3198. }
  3199. };
  3200. if (location.pathname.startsWith('/rakuen/topic/crt') || location.pathname.startsWith('/character')) {
  3201. const parentNode = document.getElementById('subject_info') || document.getElementById('columnCrtB');
  3202. listenToGrailBox(parentNode);
  3203. } else
  3204. if (location.pathname.startsWith('/rakuen/home')) {
  3205. if (Settings.get().get_bonus === 'on') getShareBonus();
  3206. launchObserver({
  3207. parentNode: document.body,
  3208. selector: '#topWeek',
  3209. successCallback: () => {
  3210. hideBonusButton();
  3211. showTopWeek();
  3212. showGallery();
  3213. }
  3214. });
  3215. launchObserver({
  3216. parentNode: document.body,
  3217. selector: '#lastLinks.tab_page_item .assets .link.item:not(.swap-checked)',
  3218. successCallback: () => {
  3219. changeLinkPos('#lastLinks');
  3220. $('#lastLinks.tab_page_item .assets .link.item:not(.swap-checked)').addClass('swap-checked');
  3221. },
  3222. stopWhenSuccess: false
  3223. });
  3224. listenToGrailBox(document.body);
  3225. if (Settings.get().valhalla_sacrifices === 'on') {
  3226. launchObserver({
  3227. parentNode: document.body,
  3228. selector: '#valhalla',
  3229. successCallback: () => {
  3230. showValhallaPersonal();
  3231. }
  3232. });
  3233. }
  3234. launchObserver({
  3235. parentNode: document.body,
  3236. selector: '.grail_index .auction_button',
  3237. successCallback: () => {
  3238. $(document).off('click', '.grail_index .auction_button');
  3239. $(document).on('click', '.grail_index .auction_button', (e) => {
  3240. openAuctionDialogSimple($(e.currentTarget).data('chara'));
  3241. });
  3242. },
  3243. stopWhenSuccess: false
  3244. });
  3245. } else
  3246. if (location.pathname.startsWith('/rakuen/topiclist')) {
  3247. if ($('.timelineTabs #recentMenu').length > 0) {
  3248. loadHelperMenu();
  3249. } else {
  3250. launchObserver({
  3251. parentNode: document.querySelector('.timelineTabs'),
  3252. selector: '#recentMenu',
  3253. successCallback: () => {
  3254. loadHelperMenu();
  3255. }
  3256. });
  3257. }
  3258. launchObserver({
  3259. parentNode: document.body,
  3260. selector: 'ul .load_more',
  3261. successCallback: (mutationList) => {
  3262. if (mutationList.some(muRecord => Array.from(muRecord.addedNodes.values()).some(el => $(el).is('.load_more')))) {
  3263. markFollow();
  3264. }
  3265. },
  3266. stopWhenSuccess: false
  3267. });
  3268. } else
  3269. if (location.pathname.startsWith('/user')) {
  3270. launchObserver({
  3271. parentNode: document.body,
  3272. selector: '#grail',
  3273. successCallback: () => {
  3274. showHideGrailBox();
  3275. showGallery();
  3276. }
  3277. });
  3278. launchObserver({
  3279. parentNode: document.body,
  3280. selector: '.link_list .grail_list:not([style]) .link .item',
  3281. successCallback: () => {
  3282. if ($('.link_list .grail_list:not([style]) .link .swapPos').length > 0) return
  3283. changeLinkPos('.link_list .grail_list:not([style])');
  3284. },
  3285. stopWhenSuccess: false
  3286. });
  3287. listenToGrailBox(document.body);
  3288. }
  3289. }());