Greasy Fork is available in English.
Скрипт расширения аудита игровых записей Mortal (Mahjong Soul/Tenhou/Riichi City)
// ==UserScript==// @name Mortal 显示恶手率// @name:zh Mortal牌谱解析增强脚本// @name:zh-CN Mortal牌谱解析增强脚本// @name:zh-TW Mortal牌譜解析增強腳本// @name:ja Mortal牌譜レビュー強化スクリプト// @name:ko Mortal 게임 기록 감사 강화 스크립트// @name:ru Скрипт расширения аудита игровых записей Mortal// @name:en Mortal game record review enhancement script// @description Mortal牌谱解析增强脚本 (雀魂麻将/天凤(天鳳)(てんほう)/麻雀一番街)(Mahjong Soul/Tenhou/Riichi City)// @description:zh Mortal牌谱解析增强脚本 (雀魂麻将/天凤/麻雀一番街)// @description:zh-CN Mortal牌谱解析增强脚本 (雀魂麻将/天凤/麻雀一番街)// @description:zh-TW Mortal牌譜解析增強腳本 (雀魂/天鳳/麻雀一番街)// @description:ja Mortal牌譜レビュー強化スクリプト (雀魂/天鳳(てんほう)/麻雀一番街)// @description:ko Mortal 게임 기록 감사 강화 스크립트 (작혼/천봉/마작일번가)// @description:ru Скрипт расширения аудита игровых записей Mortal (Mahjong Soul/Tenhou/Riichi City)// @description:en Mortal game record review enhancement script (Mahjong Soul/Tenhou/Riichi City)// @version 2.2.22.1// @homepage https://www.bilibili.com/read/cv26608482/// @namespace https://viayoo.com/// @author Miku39// @icon data:image/x-icon;base64,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5v/o5+b/6Ofm/+jn5v/o5+b/6Ofm/+jn5v/o5+b/6Ofm/+jn5v+Zj9X/NR6//0Ywwv/Kxd//6Ofm/+jn5v/o5+b/6Ofm/+jn5v/o5+b/6Ofm/+jn5v/o5+b/6Ofm/+jn5v/o5+b/6Ofm/+jn5v/o5+b/6Ofm/+fn5rwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5+fmvOjn5v/o5+b/6Ofm/+jn5v/o5+b/6Ofm/+jn5v/o5+b/6Ofm/+jn5v/o5+b/6Ofm/+jn5v/o5+b/6Ofm/+jn5v+YjdT/NR6//0Iswv++uN3/6Ofm/+jn5v/o5+b/6Ofm/+jn5v/o5+b/6Ofm/+jn5v/o5+b/6Ofm/+jn5v/o5+b/6Ofm/+jn5v/o5+b/6Ofm/+fn5rwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5+fmvOjn5v/o5+b/6Ofm/+jn5v/o5+b/6Ofm/+jn5v/o5+b/6Ofm/+jn5v/o5+b/6Ofm/+jn5v/o5+b/6Ofm/+jn5v+YjdT/NR6//z8owf+1rtv/6Ofm/+jn5v/o5+b/6Ofm/+jn5v/o5+b/6Ofm/+jn5v/o5+b/6Ofm/+jn5v/o5+b/6Ofm/+jn5v/o5+b/6Ofm/+fn5rwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5+fmvOjn5v/o5+b/6Ofm/+jn5v/o5+b/6Ofm/+jn5v/o5+b/6Ofm/+jn5v/o5+b/6Ofm/+jn5v/o5+b/6Ofm/+jn5v+YjdT/NR6//zwmwP+vptn/6Ofm/+jn5v/o5+b/6Ofm/+jn5v/o5+b/6Ofm/+jn5v/o5+b/6Ofm/+jn5v/o5+b/6Ofm/+jn5v/o5+b/6Ofm/+fn5rwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5+fmvOjn5v/o5+b/6Ofm/+jn5v/o5+b/6Ofm/+jn5v/o5+b/6Ofm/+jn5v/o5+b/6Ofm/+jn5v/o5+b/6Ofm/+jn5v+YjdT/NR6//zskwP+qodj/6Ofm/+jn5v/o5+b/6Ofm/+jn5v/o5+b/6Ofm/+jn5v/o5+b/6Ofm/+jn5v/o5+b/6Ofm/+jn5v/o5+b/6Ofm/+fn5rwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5+fmvOjn5v/o5+b/6Ofm/+jn5v/o5+b/6Ofm/+jn5v/o5+b/6Ofm/+jn5v/o5+b/6Ofm/+jn5v/o5+b/6Ofm/+jn5v+YjdT/NR6//zojwP+nntj/6Ofm/+jn5v/o5+b/6Ofm/+jn5v/o5+b/6Ofm/+jn5v/o5+b/6Ofm/+jn5v/o5+b/6Ofm/+jn5v/o5+b/6Ofm/+fn5rwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5+fmvOjn5v/o5+b/6Ofm/+jn5v/o5+b/6Ofm/+jn5v/o5+b/6Ofm/+jn5v/o5+b/6Ofm/+jn5v/o5+b/6Ofm/+jn5v+YjdT/NR6//zkiwP+lnNf/6Ofm/+jn5v/o5+b/6Ofm/+jn5v/o5+b/6Ofm/+jn5v/o5+b/6Ofm/+jn5v/o5+b/6Ofm/+jn5v/o5+b/6Ofm/+fn5rwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5+fmvOjn5v/o5+b/6Ofm/+jn5v/o5+b/6Ofm/+jn5v/o5+b/6Ofm/+jn5v/o5+b/6Ofm/+jn5v/o5+b/6Ofm/+jn5v+YjdT/NR6//zkiwP+lm9f/6Ofm/+jn5v/o5+b/6Ofm/+jn5v/o5+b/6Ofm/+jn5v/o5+b/6Ofm/+jn5v/o5+b/6Ofm/+jn5v/o5+b/6Ofm/+fn5rwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5+fmvOjn5v/o5+b/6Ofm/+jn5v/o5+b/6Ofm/+jn5v/o5+b/6Ofm/+jn5v/o5+b/6Ofm/+jn5v/o5+b/6Ofm/+jn5v+YjdT/NR6//zkiwP+kmtf/6Ofm/+jn5v/o5+b/6Ofm/+jn5v/o5+b/6Ofm/+jn5v/o5+b/6Ofm/+jn5v/o5+b/6Ofm/+jn5v/o5+b/6Ofm/+fn5rwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5+fmvOjn5v/o5+b/6Ofm/+jn5v/o5+b/6Ofm/+jn5v/o5+b/6Ofm/+jn5v/o5+b/6Ofm/+jn5v/o5+b/6Ofm/+jn5v+YjdT/NR6//zkiv/+jmtf/6Ofm/+jn5v/o5+b/6Ofm/+jn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5v/o5+b/6Ofm/+jn5v/n5uX+6Oblyf///wUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOTk5DDn5+bC5+fm/Ojn5v/o5+b/6Ofm/+jn5v/o5+b/6Ofm/+jn5v/o5+b/6Ofm/+jn5v/o5+b/6Ofm/+jn5v/o5+b/6Ofm/+jn5v/o5+b/6Ofm/+jn5v/o5+b/6Ofm/+jn5v/o5+b/6Ofm/+jn5v/o5+b/6Ofm/+jn5v/o5+b/6Ofm/+fm5f3o5+bD7unpLwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wHs7Owc5+fleOfl5Nbn5uX56Ofm/+jn5v/o5+b/6Ofm/+jn5v/o5+b/6Ofm/+jn5v/o5+b/6Ofm/+jn5v/o5+b/6Ofm/+jn5v/o5+b/6Ofm/+jn5v/o5+b/6Ofm/+jn5v/o5+b/6Ofm/+jn5v/o5+b/6Ofm/+jn5v/n5uX56Ofl1efl5Xnj4+Mcf39/AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==// @resource js_vue https://cdn.staticfile.org/vue/3.3.7/vue.global.min.js// @resource js_elementplus https://cdn.staticfile.org/element-plus/2.4.1/index.full.min.js// @resource js_layui https://cdn.staticfile.org/layui/2.8.17/layui.min.js// @resource js_cryptojs https://cdn.staticfile.org/crypto-js/4.1.1/crypto-js.min.js// @resource js_immutable https://cdn.staticfile.org/immutable/4.3.4/immutable.min.js// @resource js_localforage https://cdn.staticfile.org/localforage/1.10.0/localforage.min.js// @resource js_qunit https://cdn.staticfile.org/qunit/2.20.0/qunit.min.js// @resource css_elementplus https://cdn.staticfile.org/element-plus/2.4.1/index.min.css// @resource css_layui https://cdn.staticfile.org/layui/2.8.17/css/layui.min.css// @resource css_fontAwesome https://cdn.staticfile.org/font-awesome/6.4.2/css/all.min.css// @match *://mjai.ekyu.moe/*// @grant GM_info// @grant unsafeWindow// @grant GM_getResourceText// @grant GM_getResourceURL// @grant GM_setClipboard// @run-at document-start// @license BSD-3-Clause// ==/UserScript==((g_window)=>{'use strict';//兼容浏览器翻译插件,比如谷歌翻译插件会更改lang属性(主要针对设置为自动翻译的语言)const g_origLang = document.scrollingElement.lang.toLocaleLowerCase();let g_currLang = g_origLang;const oHtml = document.getElementsByTagName("html")[0];const observer = new window.MutationObserver((mutations) => {g_currLang = oHtml.getAttribute("lang").toLocaleLowerCase();Console.orig.log(`原始语言:${g_origLang}, 当前被翻译为: ${g_currLang}`);});observer.observe(oHtml, { attributes: true, attributeFilter: ["lang"] });// ---------------------------- 全局变量: App变量 ----------------------------let globalSettings = { //存储永久数据Base: {appName: "Mortal",cssName: ".ui",curVersion: GM_info.script.version,},Config: {badMoveUpperLimit: 5, //恶手率badMoveUpperLimitCustom: 10, //恶手率},AppSettings: {global: {enableScrollAnimation: false, //启用滚动动画},loading: {isShowLoadingAnimation: true, //是否显示加载动画isShowLoadingProgress: true, //是否显示加载进度条},},UISettings: {basic: {global: {fontColor: "#000", //字体颜色currentTheme: "", //当前主题},pageExtend: {},MainApp: {settingUI:{window: {x: 0, //posXy: 0, //posYz: 0, //z-indexwidth: "auto",height: "auto",},cssStyle: "",},catalogUI: {window: {x: 0, //posXy: 0, //posYz: 0, //z-indexwidth: "auto",height: "auto",},cssStyle: "",},},},theme: [{name: "暗色模式",},]},Other: {debugMode: { //调试模式enable: false,trackCall: {enable: false,trackFunction: true,trackTimestamp: true,},logOut: {enable: true,filter: "debug",}},safeMode: false, //安全模式 (禁用程序所有功能) //false},};const matchRule = {isInit: false,isRon3: true, //是否允许三家荣和};let globalStatus = { //存储临时数据globalExitConfirm: false, //全局退出确认 (比如更改了设置没有保存等情况下)openSettingUI: false,};const pageLanguage = g_origLang;const localization = {__proto__: null,zh_cn: Object.defineProperties({__proto__: null,badMove: "恶手",badMoveRatio: "恶手率",matchRatio: "AI 一致率",metaData: "元数据",//seatTypeA0: "东起",seatTypeA1: "南起",seatTypeA2: "西起",seatTypeA3: "北起",//seatTypeB0: "东家",seatTypeB1: "南家",seatTypeB2: "西家",seatTypeB3: "北家",//seatTypeC0: "自家",seatTypeC1: "下家",seatTypeC2: "对家",seatTypeC3: "上家",//seatStart: "亲家", //庄家//Ron: "荣和",Tsumo: '自摸',Ryuukyoku: '流局',RyuukyokuTsumo: "流局满贯",RyuukyokuType1: "九种九牌",//四风连打//四杠散了//四家立直//三家和了//badMoveError: "(恶手率统计只支持Mortal 3.1及更高版本,当前版本生成结果不可靠)",//badMoveUp: ` (严重错误 权重0~${globalSettings.Config.badMoveUpperLimit}%)`,badMoveDown: ` (普通错误 权重${globalSettings.Config.badMoveUpperLimit}~${globalSettings.Config.badMoveUpperLimitCustom}%)`,badMoveDiffer: "差值: ",//badMoveDiffer1: "微差(0~5): ",badMoveDiffer2: "小幅差距(5~10): ",badMoveDiffer3: "低等差距(10~20): ",badMoveDiffer4: "中等差距(20~40): ",badMoveDiffer5: "高等差距(40~60): ",badMoveDiffer6: "大幅度差距(60~80): ",badMoveDiffer7: "压倒性差距(80~100): ",//settingUIPanel: "设置面板",//kyokuSelector: "对局选择器",badChooseSelector: "恶手选择器",differSelector: "不一致选择器",openSetting: "打开设置 (等待完善)",},{getName: { //默认 不可修改,不可重新定义或者删除,不可枚举value : "zh-cn",},}),ja: Object.defineProperties({__proto__: null,badMove: "Bad move",badMoveRatio: "bad moves/total",matchRatio: "AI一致率",metaData: "メタデータ",//seatTypeA0: "東", //トンseatTypeA1: "南", //ナンseatTypeA2: "西", //シャーseatTypeA3: "北", //ペイ//seatTypeB0: "東家",seatTypeB1: "南家",seatTypeB2: "西家",seatTypeB3: "北家",//seatTypeC0: "自家",seatTypeC1: "下家",seatTypeC2: "対面",seatTypeC3: "上家",//seatStart: "親",//Ron: "ロン", //栄Tsumo: 'ツモ', //自摸Ryuukyoku: '流局',RyuukyokuTsumo: "流し満貫",RyuukyokuType1: "九種九牌",//四風連打//四開槓//四家立直//三家和 //トリロン},{getName: { //默认 不可修改,不可重新定义或者删除,不可枚举value : "ja",},}),ko: Object.defineProperties({__proto__: null,badMove: "Bad move",badMoveRatio: "bad moves/total",matchRatio: "matches/total",metaData: "메타데이터",//seatTypeA0: "동",seatTypeA1: "남",seatTypeA2: "서",seatTypeA3: "북",//seatTypeB0: "동",seatTypeB1: "남",seatTypeB2: "서",seatTypeB3: "북",//seatTypeC0: "나",seatTypeC1: "하가", //시모챠seatTypeC2: "대가", //대면 //또이멘seatTypeC3: "상가", //샹차//seatStart: "쫭찌아",//Ron: "론",Tsumo: '쯔모',Ryuukyoku: '유국',RyuukyokuTsumo: "유국만관",RyuukyokuType1: "구종구패",//사풍연타//사개깡//사가리치//삼가화},{getName: { //默认 不可修改,不可重新定义或者删除,不可枚举value : "ko",},}),en: Object.defineProperties({__proto__: null,badMove: "Bad move", //Bad ChoosebadMoveRatio: "bad moves/total", //Bad Choose RatematchRatio: "matches/total",metaData: "Metadata",//modelTag: "model tag",//seatTypeA0: "East",seatTypeA1: "South",seatTypeA2: "West",seatTypeA3: "North",//seatTypeB0: "East",seatTypeB1: "South",seatTypeB2: "West",seatTypeB3: "North",//seatTypeC0: "Self", //OwnseatTypeC1: "Shimocha", //Next playerseatTypeC2: "Toimen", //Opposite playerseatTypeC3: "Kamicha", //Last player//seatStart: "Oya", //Dealer//Ron: "Ron",Tsumo: 'Tsumo',Ryuukyoku: 'Ryuukyoku', //Exhaustive drawRyuukyokuTsumo: "Nagashi mangan", //Mangan at DrawRyuukyokuType1: "Kyuushu kyuuhai", //Nine Different Terminals and Honors//Suufon renda //Four-Wind Discarded//Suukaikan //Four-Kan Abortion//Suucha riichi //Four-Player Riichi//Sanchahou //Three-Player Ron//badMoveError: "(The Bad Choose Rate statistics are only supported by Mortal version 3.1 and later, the r###lts generated by the current version are unreliable)",//badMoveUp: ` (Fatal Error: 0~${globalSettings.Config.badMoveUpperLimit}%)`,badMoveDown: ` (Error: ${globalSettings.Config.badMoveUpperLimit}~${globalSettings.Config.badMoveUpperLimitCustom}%)`,badMoveDiffer: "Differences: ",//badMoveDiffer1: "Very slight difference(0~5): ",badMoveDiffer2: "Small difference(5~10): ",badMoveDiffer3: "Low difference(10~20): ",badMoveDiffer4: "Medium difference(20~40): ",badMoveDiffer5: "Higher difference(40~60): ",badMoveDiffer6: "Wide difference(60~80): ",badMoveDiffer7: "Overwhelming difference(80~100): ",//settingUIPanel: "Settings Panel",//kyokuSelector: "Kyoku Selector",badChooseSelector: "Bad Choose Selector",differSelector: "Inconsistent Selector",openSetting: "Open Settings(unfinished)",},{getName: { //默认 不可修改,不可重新定义或者删除,不可枚举value: "en",},}),};let i18nText = { ...localization.en }; //复制对象if (pageLanguage == localization.zh_cn.getName) {Object.assign(i18nText, localization.zh_cn);} else if (pageLanguage == localization.ja.getName) {Object.assign(i18nText, localization.ja);} else if (pageLanguage == localization.ko.getName) {Object.assign(i18nText, localization.ko);}const tenhouText = {__proto__: null,Ron: '和了', //荣和Tsumo: '和了', //自摸Ryuukyoku: '流局', //荒牌流局RyuukyokuTsumo: "流し満貫", //流局满贯RyuukyokuType1: "九種九牌", //九种九牌//四風連打、四開槓、四家立直 //三家和};// ---------------------------- 全局变量: 模板 ----------------------------const cssID = globalSettings.Base.cssName;const cssIDNP = cssID.substring(1);const commonCSS ='\.position_re { position: relative; }\.position_ab, .ui.position_ab { position: absolute; }\.position_fi { position: fixed; }\\.left-0 { left: 0px; }\.right-0 { right: 0px; }\.top-0 { top: 0px; }\.bottom-0 { bottom: 0px; }\.left-5 { left: 5px; }\.right-5 { right: 5px; }\.top-5 { top: 5px; }\.bottom-5 { bottom: 5px; }\.left-10 { left: 10px; }\.right-10 { right: 10px; }\.top-10 { top: 10px; }\.bottom-10 { bottom: 10px; }\\.text-left { text-align: left; }\.text-center { text-align: center; }\.text-right { text-align: right; }\\.font_weight_900 { font-weight: 900; }\.font_weight_700 { font-weight: 700; }\.font_weight_400 { font-weight: 400; }\\.w-auto { width: auto; }\.w-100 { width: 100%; }\.w-80 { width: 80%; }\.w-75 { width: 75%; }\.w-60 { width: 60%; }\.w-50 { width: 50%; }\.w-40 { width: 40%; }\.w-25 { width: 25%; }\.w-20 { width: 20%; }\.w-10 { width: 10%; }\.w-5 { width: 5%; }\\.min-w-auto { min-width: auto; }\.min-w-100 { min-width: 100%; }\.min-w-80 { min-width: 80%; }\.min-w-75 { min-width: 75%; }\.min-w-60 { min-width: 60%; }\.min-w-50 { min-width: 50%; }\.min-w-40 { min-width: 40%; }\.min-w-25 { min-width: 25%; }\.min-w-20 { min-width: 20%; }\.min-w-10 { min-width: 10%; }\.min-w-5 { min-width: 5%; }\\.max-w-auto { max-width: auto; }\.max-w-100 { max-width: 100%; }\.max-w-80 { max-width: 80%; }\.max-w-75 { max-width: 75%; }\.max-w-60 { max-width: 60%; }\.max-w-50 { max-width: 50%; }\.max-w-40 { max-width: 40%; }\.max-w-25 { max-width: 25%; }\.max-w-20 { max-width: 20%; }\.max-w-10 { max-width: 10%; }\.max-w-5 { max-width: 5%; }\\.cursor_default { cursor: default; }\.cursor_crosshair { cursor: crosshair; }\.cursor_pointer { cursor: pointer; }\.cursor_move { cursor: move; }\.cursor_text { cursor: text; }\.cursor_wait { cursor: wait; }\.cursor_help { cursor: help; }\\.alpha_1 { opacity: 1; }\.alpha_05 { opacity: 0.5; }\.alpha_0 { opacity: 0; }\\.show { visibility: visible; }\.hide { visibility: hidden; }\\.display_block { display: block; }\.display_inline { display: inline; }\.display_inlineblock { display: inline-block; }\.display_flex { display: flex; }\.display_inlineflex { display: inline-flex; }\.display_grid { display: grid; }\.display_inlinegrid { display: inline-grid; }\.display_none { display: none; }\.display_inherit { display: inherit; }\\.margin-0 { margin: 0px !important; }\.margin-right-2 { margin-right: 2px; }\.margin-right-5 { margin-right: 5px; }\.margin-right-10 { margin-right: 10px; }\.margin-right-20 { margin-right: 20px; }\\.padding-0 { padding: 0px !important; }\.padding-2 { padding: 2px; }\.padding-5 { padding: 5px; }\.padding-10 { padding: 10px; }\.padding-20 { padding: 20px; }\';const pageExtendBaseCSS ='\details.collapseEntryL1 { border: 2px solid #f00; }\details.collapseEntryL2 { border: 2px solid #6600FF; }\.badChoose { font-size: 20px; }\.level1 { color: #f00; font-weight: 900; }\.level2 { color: #6600FF; font-weight: 700; }\.color1 { color: #f00 !important; }\.color2 { color: #6600FF !important; }\.color3 { color: #FF0066 !important; }\.color4 { color: #990000 !important; }\.l-129px { left: 129px; }\.l-130px { left: 130px; }\.l-155px { left: 155px; }\.l-170px { left: 170px; }\.l-190px { left: 190px; }\.l-196px { left: 196px; }\.l-210px { left: 210px; }\.color5 { color: #CC0000 !important; }\.color6 { color: #333 !important; }\.bgColor1 { background: #FF0000A0 !important; }\.bgColor2 { background: #6600FFA0 !important; }\';const MainAppBaseCSS ='\html { scroll-behavior: auto; }\/*details { scroll-margin-top: calc(2em + 4px); }*/\.highlight { animation: shimmer 0.75s infinite; }\@keyframes shimmer {\0% { background: #ffd5d500; }\50% { background: #ffd5d5ff; }\100% { background: #ffd5d500; }\}\.badChoose_border { border: 1px solid #ccc; }\.badChoose_border_first { border-top: 1px solid #ccc; border-left: 1px solid #ccc; border-right: 1px solid #ccc; }\.badChoose_border_middle { border-left: 1px solid #ccc; border-right: 1px solid #ccc; }\.badChoose_border_last { border-left: 1px solid #ccc; border-right: 1px solid #ccc; border-bottom: 1px solid #ccc; }\#loadingProgress { width: 100%; position: fixed; left: 0px; bottom: 0px; z-index: 100; }\';const MainAppCSS ='\.commonDIV { padding: 5px; }\#settingUI { width: 800px; height: 600px; border: 1px solid red; position: fixed; top: 100px; left: 100px; z-index: 10; }\#catalogUI { border: 1px solid blue; padding: 20px 0px 0px 0px; position: fixed; z-index: 10; }\#selectorGroups,#selectorGroups>* { width: 100%; overflow: auto; }\.ui.layui-nav-container>*:first-child { padding: 6px 0px 0px 0px; }\.ui.layui-nav-container>*:last-child { padding: 0px 0px 6px 0px; }\.ui.layui-nav-container>*:not(:first-child):not(:last-child) { padding: 0; }\';//悬浮菜单(悬浮球)const appHoverFloatMenu =`\`;//吸附菜单(贴边)const appAdsorptionMenu =`\`;const loadingUITemplate =`\<div class="${cssIDNP} layui-progress" lay-showPercent="true" id="loadingProgress" lay-filter="loading-filter-progress">\<div class="${cssIDNP} layui-progress-bar" lay-percent="5%"></div>\</div>\`;//右下角 公告/消息 显示区域const bottomRightShowBox =`\<div id="bottomRightShowBox">\</div>\`;const settingUITemplate =`\<div class="${cssIDNP} commonDIV" id="settingUI">\<div class="${cssIDNP} layui-card">\<div class="${cssIDNP} layui-card-header">${i18nText.settingUIPanel}</div>\<div class="${cssIDNP} layui-card-body">\<div class="${cssIDNP} layui-tab layui-tab-brief">\<ul class="${cssIDNP} layui-tab-title">\<li class="${cssIDNP} layui-this">功能设置</li>\<li class="${cssIDNP}">UI设置</li>\<li class="${cssIDNP}">资源包</li>\<li class="${cssIDNP}">常见问题</li>\<li class="${cssIDNP}">关于</li>\</ul>\<div class="${cssIDNP} layui-tab-content">\<div class="${cssIDNP} layui-tab-item layui-show">\<div>功能设置-1</div>\</div>\<div class="${cssIDNP} layui-tab-item">\<div>UI设置-2</div>\</div>\<div class="${cssIDNP} layui-tab-item">\<div>资源包-3</div>\</div>\<div class="${cssIDNP} layui-tab-item">\<div>常见问题-4</div>\</div>\<div class="${cssIDNP} layui-tab-item">\<div>关于-5</div>\</div>\</div>\</div>\</div>\</div>\</div>\`;const catalogUIBase =`\<div class="${cssIDNP} commonDIV layui-bg-gray" id="catalogUI">\</div>\`;const catalogUITemplate =`\<div class="${cssIDNP} layui-btn-container" id="catalogUIBuf">\<button type="button" class="${cssIDNP} layui-btn" id="but_kyoku_prev">\<i class="${cssIDNP} layui-icon layui-icon-prev"></i>\</button>\<button type="button" class="${cssIDNP} layui-btn" id="but_kyoku_next">\<i class="${cssIDNP} layui-icon layui-icon-next"></i>\</button>\<button type="button" class="${cssIDNP} layui-btn" id="but_diff_prev">\<i class="${cssIDNP} layui-icon layui-icon-left"></i>\</button>\<button type="button" class="${cssIDNP} layui-btn" id="but_diff_next">\<i class="${cssIDNP} layui-icon layui-icon-right"></i>\</button>\</div>\<div class="${cssIDNP} layui-nav-container layui-bg-gray" id="selectorGroups" data-scrollbar >\<ul class="${cssIDNP} layui-nav layui-nav-tree layui-bg-gray" lay-filter="selector-filter-nav">\<!-- ${i18nText.kyokuSelector} -->\<li class="${cssIDNP} layui-nav-item layui-nav-itemed">\<a class="${cssIDNP} cursor_pointer" href="javascript:;">${i18nText.kyokuSelector}</a>\<dl class="${cssIDNP} layui-nav-child" class="selector" id="selector1">\{{# layui.each(d.selector1, function(index, item){ }}\<dd class="${cssIDNP} cursor_pointer"><a class="${cssIDNP}" id="{{= item.nameID }}">{{= item.name }}</a></dd>\{{# }); }}\</dl>\</li>\</ul>\<ul class="${cssIDNP} layui-nav layui-nav-tree layui-bg-gray" lay-filter="selector-filter-nav">\<!-- ${i18nText.badChooseSelector} -->\<li class="${cssIDNP} layui-nav-item">\<a class="${cssIDNP} cursor_pointer" href="javascript:;">${i18nText.badChooseSelector}</a>\<dl class="${cssIDNP} layui-nav-child" class="selector" id="selector2">\{{# layui.each(d.selector2, function(index, item){ }}\{{# if(item.badChooseType === 1){ }}<dd class="${cssIDNP} cursor_pointer"><a class="${cssIDNP} color1 font_weight_700" id="{{= item.nameID }}">{{= item.name }}</a></dd>\{{# }else{ }}<dd class="${cssIDNP} cursor_pointer"><a class="${cssIDNP} color2 font_weight_700" id="{{= item.nameID }}">{{= item.name }}</a></dd>\{{# } }}{{# }); }}\</dl>\</li>\</ul>\<ul class="${cssIDNP} layui-nav layui-nav-tree layui-bg-gray" lay-filter="selector-filter-nav">\<!-- ${i18nText.differSelector} -->\<li class="${cssIDNP} layui-nav-item">\<a class="${cssIDNP} cursor_pointer" href="javascript:;">${i18nText.differSelector}</a>\<dl class="${cssIDNP} layui-nav-child" class="selector" id="selector3">\{{# layui.each(d.selector3, function(index, item){ }}\{{# if(item.badChooseType === 1){ }}<dd class="${cssIDNP} cursor_pointer"><a class="${cssIDNP} color1 font_weight_700" name="{{= item.nameID }}">{{= item.name }}</a></dd>\{{# }else if(item.badChooseType === 2){ }}<dd class="${cssIDNP} cursor_pointer"><a class="${cssIDNP} color2 font_weight_700" name="{{= item.nameID }}">{{= item.name }}</a></dd>\{{# }else{ }}<dd class="${cssIDNP} cursor_pointer"><a class="${cssIDNP} font_weight_700" name="{{= item.nameID }}">{{= item.name }}</a></dd>\{{# } }}{{# }); }}\</dl>\</li>\</ul>\<ul class="${cssIDNP} layui-nav layui-nav-tree layui-bg-gray position_ab bottom-0 padding-0" lay-filter="selector-filter-nav">\<li class="${cssIDNP} layui-nav-item text-center" style="border-top: 1px solid #ccc">\<a class="${cssIDNP}" href="javascript:;">\<i class="fa-solid fa-gear fa-lg margin-right-5"></i>${i18nText.openSetting}</a>\</li>\</ul>\</div>\`;// ---------------------------- 其他 ----------------------------// ---------------------------- 基本框架 ----------------------------const FILETYPE = {__proto__: null,JS: "js",CSS: "css",}const JSLOADTYPE = {__proto__: null,ASYNC: {__proto__: null, //异步加载name: "async",bit: 1,},DEFER: {__proto__: null, //延迟加载name: "defer",bit: 1 << 1,},}class TypeUtils {//static objType = '[object'; //对象类型//static undefinedType = '[object Undefined]'; //undefined类型static nullType = '[object Null]'; //null类型//static fnTypeArray = ['[object Function]', '[object AsyncFunction]']; //函数类型static strTypeArray = ['[object String]']; //字符串类型static symbolTypeArray = ['[object Symbol]']; //Symbol类型static arrayTypeArray = ['[object Array]']; //数组类型static numberTypeArray = ['[object Number]', '[object BigInt]']; //数字类型static booleanTypeArray = ['[object Boolean]']; //boolean类型static origObjType = ['[object Object]']; //原始对象类型//static mapType = ['[object Map]']; //Map对象类型static setType = ['[object Set]']; //Set对象类型//static dateType = ['[object Date]']; //Date对象类型static promiseType = ['[object Promise]']; //Promise对象类型static regExpType = ['[object RegExp]']; //RegExp对象类型//static errorType = ['[object Error]']; //Error对象类型 //Error、TypeError、SyntaxError、ReferenceError、RangeError、EvalError、URIError、InternalError//static getType(obj){return Object.prototype.toString.apply(obj);}static isValid(obj){if(obj == undefined || obj == null || Number.isNaN(obj)){return false;}if(TypeUtils.isString(obj)){if(TypeUtils.isUndefinedByType(obj) || TypeUtils.isNullByType(obj)){return false;}return true;}return true;}static isValidValue(obj){if(TypeUtils.isValid(obj)){if(TypeUtils.isString(obj) || TypeUtils.isArray(obj)){return obj.length > 0;}if(TypeUtils.isMap(obj) || TypeUtils.isSet(obj)){return obj.size > 0;}return null;}return false;}// 判断是否为undefinedstatic isUndefined(obj){let type = TypeUtils.getType(obj);return TypeUtils.isUndefinedByType(type);}static isUndefinedByType(obj){if(TypeUtils.undefinedType == obj) {return true;}return false;}// 判断是否为nullstatic isNull(obj){let type = TypeUtils.getType(obj);return TypeUtils.isNullByType(type);}static isNullByType(obj){if(TypeUtils.nullType == obj) {return true;}return false;}// 判断是否为函数static isFunction(obj){let type = TypeUtils.getType(obj);return TypeUtils.isFunctionByType(type);}static isFunctionByType(obj){if(TypeUtils.fnTypeArray.includes(obj)) {return true;}return false;}// 判断是否为字符串static isString(obj){let type = TypeUtils.getType(obj);return TypeUtils.isStringByType(type);}static isStringByType(obj){if(TypeUtils.strTypeArray.includes(obj)) {return true;}return false;}// 判断是否为Symbolstatic isSymbol(obj){let type = TypeUtils.getType(obj);return TypeUtils.isSymbolByType(type);}static isSymbolByType(obj){if(TypeUtils.symbolTypeArray.includes(obj)) {return true;}return false;}//判断是否为数组类型static isArray(obj){let type = TypeUtils.getType(obj);return TypeUtils.isArrayByType(type);}static isArrayByType(obj){if(TypeUtils.arrayTypeArray.includes(obj)) {return true;}return false;}//判断是否为数字类型static isNumber(obj){let type = TypeUtils.getType(obj);return TypeUtils.isNumberByType(type);}static isNumberByType(obj){if(TypeUtils.numberTypeArray.includes(obj)) {return true;}return false;}//判断是否为boolean类型static isBoolean(obj){let type = TypeUtils.getType(obj);return TypeUtils.isBooleanByType(type);}static isBooleanByType(obj){if(TypeUtils.booleanTypeArray.includes(obj)) {return true;}return false;}// 判断是否为原始对象static isOrigObj(obj){let type = TypeUtils.getType(obj);return TypeUtils.isOrigObjByType(type);}static isOrigObjByType(obj){if(TypeUtils.origObjType.includes(obj)) {return true;}return false;}// 判断是否为对象(大范围)static isObject(obj){let type = TypeUtils.getType(obj);return TypeUtils.isObjectByType(type, obj);}static isObjectByType(obj, origObj){if(TypeUtils.isValid(origObj != undefined ? origObj : obj)){if(obj.startsWith(TypeUtils.objType)) {return true;}return false;}return false;}//判断是否为Map类型static isMap(obj){let type = TypeUtils.getType(obj);return TypeUtils.isMapByType(type);}static isMapByType(obj){if(TypeUtils.mapType.includes(obj)) {return true;}return false;}//判断是否为Set类型static isSet(obj){let type = TypeUtils.getType(obj);return TypeUtils.isSetByType(type);}static isSetByType(obj){if(TypeUtils.setType.includes(obj)) {return true;}return false;}//判断是否为Date类型static isDate(obj){let type = TypeUtils.getType(obj);return TypeUtils.isDateByType(type);}static isDateByType(obj){if(TypeUtils.dateType.includes(obj)) {return true;}return false;}//判断是否为Promise类型static isPromise(obj){let type = TypeUtils.getType(obj);return TypeUtils.isPromiseByType(type);}static isPromiseByType(obj){if(TypeUtils.promiseType.includes(obj)) {return true;}return false;}//判断是否为RegExp类型static isRegExp(obj){let type = TypeUtils.getType(obj);return TypeUtils.isRegExpByType(type);}static isRegExpByType(obj){if(TypeUtils.regExpType.includes(obj)) {return true;}return false;}//判断是否为Error类型static isError(obj){let type = TypeUtils.getType(obj);return TypeUtils.isErrorByType(type);}static isErrorByType(obj){if(TypeUtils.errorType.includes(obj)) {return true;}return false;}}class DOMTypeUtils extends TypeUtils {static svgTypeArray = ['[object SVGSVGElement]']; //SVG节点类型static textTypeArray = ['[object Text]']; //TEXT节点类型// 判断是否为SVG节点static isSVGElement(obj){let type = DOMTypeUtils.getType(obj);return DOMTypeUtils.isSVGElementByType(type);}static isSVGElementByType(obj){if(DOMTypeUtils.svgTypeArray.includes(obj)) {return true;}return false;}// 判断是否为Text节点static isTextElement(obj){let type = DOMTypeUtils.getType(obj);return DOMTypeUtils.isTextElementByType(type);}static isTextElementByType(obj){if(DOMTypeUtils.textTypeArray.includes(obj)) {return true;}return false;}}class Utils {static {Utils.setDnsPrefecth(true);Utils.addDnsPrefecthUrl("//cdn.staticfile.org");Utils.addDnsPrefecthUrl("//tenhou.net");Utils.addNewScript("js_immutable", GM_getResourceText("js_immutable"));Utils.addNewScript("js_cryptojs", GM_getResourceText("js_cryptojs"));//Utils.loadjscssFile(GM_getResourceURL("js_immutable"), FILETYPE.JS, JSLOADTYPE.ASYNC.bit | JSLOADTYPE.DEFER.bit);}static loadjscssFile(filePath, fileType, args_bits_LOADTYPE_bit) {let ele = undefined;if (fileType == FILETYPE.JS) {ele = document.createElement('script');ele.setAttribute("src", filePath);if((args_bits_LOADTYPE_bit & (1 << 0)) == 0 ? false : true){ele.setAttribute(JSLOADTYPE.ASYNC.name, true);}else if((args_bits_LOADTYPE_bit & (1 << 1)) == 0 ? false : true){ele.setAttribute(JSLOADTYPE.DEFER.name, true);}} else if (fileType == FILETYPE.CSS) {ele = document.createElement("link");ele.setAttribute("rel", "stylesheet");ele.setAttribute("href", filePath);}else{Console.orig.log(`不支持的文件类型: ${fileType}`);return;}if (ele != undefined) {document.getElementsByTagName("head")[0].appendChild(ele);}}static addNewStyle(id, newStyle) {let styleElement = document.getElementById(id);if (!styleElement) {styleElement = document.createElement('style');styleElement.id = id;document.getElementsByTagName('head')[0].appendChild(styleElement);}styleElement.appendChild(document.createTextNode(newStyle));}static addNewScript(id, newScript) {let scriptElement = document.getElementById(id);if (!scriptElement) {scriptElement = document.createElement('script');scriptElement.id = id;document.getElementsByTagName('head')[0].appendChild(scriptElement);}scriptElement.appendChild(document.createTextNode(newScript));}static addElementsByHTMLTemplateText(htmlTemplate, parentElement) {let ele = document.createElement("div");if(parentElement == undefined)document.body.appendChild(ele);elseparentElement.appendChild(ele);ele.outerHTML = htmlTemplate;}static setDnsPrefecth(isEnable) {if(TypeUtils.isBoolean(isEnable)){if(isEnable){isEnable = "on";}else{isEnable = "off";}}else{isEnable = "on";}let ele = document.createElement("meta");ele.setAttribute("http-equiv", "x-dns-prefetch-control");ele.setAttribute("content", isEnable);document.getElementsByTagName("head")[0].appendChild(ele);}static addDnsPrefecthUrl(url){let ele = document.createElement("link");ele.setAttribute("rel", "dns-prefetch");ele.setAttribute("href", url);document.getElementsByTagName("head")[0].appendChild(ele);}}class CustomUtils {static{// 拓展系统库Math.constructor.roundEx = (nValue, n) => { //保留n位小数return Math.round(nValue*Math.pow(10,n))/Math.pow(10,n);}// 为JSON序列化添加对Map、Set类型的支持const { stringify, parse } = JSONJSON.stringify = function (value, replacer, space) {const _replacer =typeof replacer === 'function'? replacer: function (_, value) {return value}replacer = function (key, value) {value = _replacer(key, value)if (value instanceof Set) value = `Set{${stringify([...value])}}`else if (value instanceof Map) value = `Map{${stringify([...value])}}`return value}return stringify(value, replacer, space)}JSON.parse = function (value, reviver) {if (!reviver)reviver = function (key, value) {if (/Set\{\[.*\]\}/.test(value))value = new Set(parse(value.replace(/Set\{\[(.*)\]\}/, '[$1]')))else if (/Map\{\[.*\]\}/.test(value))value = new Map(parse(value.replace(/Map\{\[(.*)\]\}/, '[$1]')))return value}return parse(value, reviver)}}static handleCSSCompatibility(idPrefix, cssText, ruleSet) { //处理css兼容性, 防止外部css库修改原始css样式, 并应用自定义规则let handleMode = 3; //默认为后置处理let retCssText = cssText;if(idPrefix != undefined && idPrefix != null && idPrefix != ""){ //安全检查: 是否处理名称空间let splitStrArray = cssText.split(/\{[\s\S]+?\}/g); //提取每一段的css选择器let startIndex = 0, endIndex = 0;splitStrArray.forEach((item,index)=> {//从css选择器匹配中过滤不需要的元素if(item.indexOf("@charset") != -1){let start = item.indexOf("@charset");let end = item.indexOf(';');let array = item.split("");array.splice(start, end-start +1);item = array.join("");}else if(item.indexOf("@import") != -1){let start = item.indexOf("@import");let end = item.indexOf(';');let array = item.split("");array.splice(start, end-start +1);item = array.join("");}let cssSelectArray = item.match(/[A-Za-z0-9\:\-\_\@\.\#\+\>\<\*\?\;\!\[\]\=\"\'\`\/\\\(\)\{\}\~\^\$\|\%\&]+/g); //提取单个css选择器 //排除了,//Console.orig.log("cssSelectArray: " + cssSelectArray);if(cssSelectArray != null) {for (let i = 0; i < cssSelectArray.length; i++) {const cssSelectStr = cssSelectArray[i];handleMode = 3;if(cssSelectStr.indexOf("@") != -1){ //需要排除的内容break;}if(cssSelectStr == "from" || cssSelectStr == "to") //需要排除的内容break;if(cssSelectStr.indexOf("%") != -1) //需要排除的内容break;if(cssSelectStr.indexOf(":root") != -1){handleMode = 2;}else if(cssSelectStr.indexOf(":") != -1){ //需要排除的内容 (伪类元素选择器)break;}//Console.orig.log("cssSelectArray: " + cssSelectArray);startIndex = retCssText.indexOf(cssSelectStr, startIndex);endIndex = startIndex + cssSelectStr.length;if(handleMode == 1){ //前置处理, 如.ui.layuiretCssText = retCssText.substring(0, startIndex) + idPrefix + cssSelectStr + retCssText.substring(endIndex);}else if(handleMode == 2){ //位于伪类之前处理let pointIndex = cssSelectStr.indexOf(':');let newCssSelectStr = cssSelectStr.substring(0, pointIndex -1) + idPrefix + cssSelectStr.substring(pointIndex);retCssText = retCssText.substring(0, startIndex) + newCssSelectStr + retCssText.substring(endIndex);}else{ //后置处理, 如p.layui.uiretCssText = retCssText.substring(0, startIndex) + cssSelectStr + idPrefix + retCssText.substring(endIndex);}startIndex += idPrefix.length + 1 + cssSelectStr.length; //重新设置startIndex,防止重复查找}startIndex = retCssText.indexOf("}", startIndex) + 1; //重新设置startIndex,防止从错误的位置开始查找//Console.orig.log("\n" + retCssText);}});}//根据规则进行替换if(ruleSet != undefined && ruleSet != null) {for (const [key, value] of ruleSet.entries()) {retCssText = retCssText.replaceAll(key, value);}}//Console.orig.log("\n" + retCssText);return retCssText;}static handleJSCompatibility(idPrefix, jsText, ruleSet) { //处理js兼容性, 应用自定义规则//根据规则进行替换if(ruleSet != undefined && ruleSet != null) {for (const [key, value] of ruleSet.entries()) {jsText = jsText.replaceAll(key, value);}}// Console.orig.log("\n" + jsText);return jsText;}static getTextLineNumByOffsetHeight(ele) {let styles = getComputedStyle(ele, null);let lineHeight = parseFloat(styles.lineHeight);let offsetHeight = parseFloat(ele.offsetHeight);let lineNum = offsetHeight / lineHeight;return Math.round(lineNum);}static getTextLineNum(ele) {let styles = getComputedStyle(ele, null);let lineHeight = parseFloat(styles.lineHeight);let height = parseFloat(styles.height);let offsetHeight = parseFloat(ele.offsetHeight);let lineNum = (height || offsetHeight) / lineHeight;return Math.round(lineNum);}static getStyleOfLineHeight(ele) {let styles = getComputedStyle(ele, null);let lineHeight = parseFloat(styles.lineHeight);return lineHeight;}static isShowInClientWindow(ele) {const scrollTop = document.scrollingElement.scrollTop;const clientHeight = document.scrollingElement.clientHeight;const scrollEndTop = scrollTop + clientHeight;if(ele.offsetTop + ele.offsetHeight >= scrollTop && ele.offsetTop <= scrollEndTop){return true;}else{return false;}}static isShowInClientWindowByApi(ele) {const clientHeight = document.scrollingElement.clientHeight;const {top, bottom} = ele.getBoundingClientRect();return bottom > 0 && top < clientHeight;}static isShowInClientWindowOfPositionMode(ele) {const positionMode = ["static","relative","absolute","sticky","fixed",];const scrollTop = document.scrollingElement.scrollTop;const clientHeight = document.scrollingElement.clientHeight;const scrollEndTop = scrollTop + clientHeight;let parentEle = ele;let styles = getComputedStyle(parentEle, null);if(positionMode.includes(styles.position)){while(styles.position != "absolute" && styles.position != "fixed") { //循坏向上查找设置了绝对定位或固定定位的父节点parentEle = parentEle.parentElement;styles = getComputedStyle(parentEle, null);}}//以父节点为准const realTop = scrollTop + parentEle.offsetTop;const realBottom = realTop + parentEle.offsetHeight;if(realTop + parentEle.offsetHeight >= scrollTop && realTop <= scrollEndTop){return true;}else{return false;}}static isShowInClientWindowOfPositionModeAndScrollbar(ele) {// const positionMode = [// "static",// "relative",// "absolute",// "sticky",// "fixed",// ];// // const scrollTop = document.scrollingElement.scrollTop;// // const clientHeight = document.scrollingElement.clientHeight;// // const scrollEndTop = scrollTop + clientHeight;// let parentEle = ele;// let styles = getComputedStyle(parentEle, null);// if(positionMode.includes(styles.position)){// while(styles.position != "absolute" && styles.position != "fixed") { //循坏向上查找设置了绝对定位或固定定位的父节点// parentEle = parentEle.parentElement;// styles = getComputedStyle(parentEle, null);// }// }// //以父节点为准// debugger// let scrollbarEle = parentEle.querySelector("[data-scrollbar]");// let scrollbarStyles = getComputedStyle(scrollbarEle, null);// const scrollTop = parentEle.offsetTop + scrollbarEle.offsetTop + scrollbarEle.scrollTop; //60+48=108// const clientHeight = scrollbarEle.clientHeight;// const scrollEndTop = scrollTop + clientHeight;// // const realTop = scrollTop + scrollbarEle.offsetTop;// // const realEndTop = realTop + scrollbarEle.offsetHeight;// //ele.offsetTop + ele.offsetHeight >= scrollTop && ele.offsetTop <= scrollEndTop// if(ele.parentElement.offsetTop + ele.parentElement.offsetHeight >= scrollTop && ele.parentElement.offsetTop <= scrollEndTop){// return true;// }else{// return false;// }return false;}static isShowInClientWindowOfNodeArray(eleArray, startIndex, endIndex) {if(eleArray == undefined || eleArray == null || eleArray.length == 0){return false;}if(eleArray.length == 1) {return CustomUtils.isShowInClientWindow(eleArray[0]);}if(startIndex == undefined || startIndex == null){startIndex = 0;}if(endIndex == undefined || endIndex == null){endIndex = eleArray.length - 1;}const scrollTop = document.scrollingElement.scrollTop;const clientHeight = document.scrollingElement.clientHeight;const scrollEndTop = scrollTop + clientHeight;if(eleArray[startIndex].offsetTop + ((eleArray[endIndex].offsetTop - eleArray[startIndex].offsetTop) + eleArray[endIndex].offsetHeight) >= scrollTop && eleArray[startIndex].offsetTop <= scrollEndTop){return true;}return false;}static getScrollTopByTargetEleOfHide(ele){ //2797return CustomUtils.getScrollTopByTargetEleOfShow(ele) + 1;}static getScrollTopByTargetEleOfShow(ele){ //显示 //2796return ele.offsetTop + ele.offsetHeight;}static getScrollBottomByTargetEleOfHide(ele){ //1523const clientHeight = document.scrollingElement.clientHeight;return ele.offsetTop - clientHeight;}static getScrollBottomByTargetEleOfShow(ele){ //显示 //1524return CustomUtils.getScrollBottomByTargetEleOfHide(ele) + 1;}static setScrollToTargetNode(targetNode, verticalAlign){// document.scrollingElement.scrollTop = targetNode.offsetTop;if(globalSettings.AppSettings.global.enableScrollAnimation){// 页面动画滚动过度 A标签高亮targetNode.scrollIntoView({behavior: "smooth",block: verticalAlign || "start",inline: "start",});}else{//页面直接滚动不过度 A标签高亮targetNode.scrollIntoView({behavior: "instant", //动画block: verticalAlign || "start", //垂直对齐方式inline: "start", //水平对齐方式});}}static setHighlightShow(targetNode, showTime_ms, EndCallBack = null, ...args){targetNode.classList.add("highlight");setTimeout(()=>{targetNode.classList.remove("highlight");if(EndCallBack != undefined && EndCallBack != null)EndCallBack(args);}, showTime_ms);}//函数节流: 减少代码执行频率 (在一个单位时间内,只能触发一次函数)static throttle(fn, interval = 500) {let run = true;return function () {if (!run) return;run = false;setTimeout(() => {fn.apply(this, arguments);run = true;}, interval);};}//函数防抖: 判断某个动作结束,如滚动结束、input输入结束等 (在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时) (在规定时间内,只让最后一次生效,前面的不生效)static debounce(fn, interval = 500) {let timeout = null;return function () {clearTimeout(timeout);timeout = setTimeout(() => {fn.apply(this, arguments);}, interval);};}static compareVersion(version1, version2) { //比较字符串,如果有非数字则忽略大小写if(version1 === version2){return 0; // 版本相同}const ver1Array = version1.match(/\d+/g).map(i => parseInt(i));const ver2Array = version2.match(/\d+/g).map(i => parseInt(i));const ver1Leng = ver1Array.length, ver2Leng = ver2Array.length;for (let i = 0; i < Math.max(ver1Leng, ver2Leng); i++) {const value1 = i < ver1Leng ? ver1Array[i] : 0;const value2 = i < ver2Leng ? ver2Array[i] : 0;if (value1 != value2) {return value1 > value2 ? 1 : -1;}}//处理特殊版本, 比如带alpha、Beta标识的版本const newVersion1 = version1.replaceAll(/[(\d+)|(\.\-\_\\\/\|)|(\s)]/g, "").toLocaleLowerCase();const newVersion2 = version2.replaceAll(/[(\d+)|(\.\-\_\\\/\|)|(\s)]/g, "").toLocaleLowerCase();if(newVersion1 === newVersion2){return 0; // 版本相同}else{return -1; // 版本不同, 为了避免后续发生逻辑错误, 返回版本降级结果是最好的}}}class Config {static readConfig(){}static SaveConfig(){}static clear(){}}class DBConfig extends Config {static async readConfig(name){try {return await localforage.getItem(name);} catch (err) {Console.orig.error("获取数据时发生错误: \n" + err);}}static async SaveConfig(name, obj){const jsonStr = JSON.stringify(obj);try {await localforage.setItem(name, jsonStr);} catch (err) {Console.orig.error("存储数据时发生错误: \n" + err);}}static async clear(){ //重置数据库await localforage.clear();}}const windowInfo = {__proto__: null,x: 0,y: 0,l: 0,t: 0,isMouseDown: false,}class DomUtils {static windowInfoMap = new Map();static ObserverEleSetMap = new Map();static setMoveable(targetElement, isMove) {let id = targetElement.id;let winInfo = DomUtils.windowInfoMap.get(id);if(winInfo == undefined){winInfo = { ...windowInfo }; //复制对象}targetElement.addEventListener('mousedown', function(e){ //鼠标按下事件let idStr = e.target.id;//获取x坐标和y坐标winInfo.x = e.clientX;winInfo.y = e.clientY;//获取左部和顶部的偏移量winInfo.l = targetElement.offsetLeft;winInfo.t = targetElement.offsetTop;//开关打开winInfo.isMouseDown = true;//设置样式targetElement.style.cursor = 'move';});targetElement.addEventListener('mousemove', function(e){ //鼠标移动if (winInfo.isMouseDown == false) {return;}//获取x和yvar nx = e.clientX;var ny = e.clientY;//计算移动后的左偏移量和顶部的偏移量var nl = nx - (winInfo.x - winInfo.l);var nt = ny - (winInfo.y - winInfo.t);targetElement.style.left = nl + 'px';targetElement.style.top = nt + 'px';});targetElement.addEventListener('mouseup', function(e){ //鼠标抬起事件//开关关闭winInfo.isMouseDown = false;targetElement.style.cursor = 'default';});DomUtils.windowInfoMap.set(id, winInfo);}static setNewActivateEle(targetEle, className) {if(targetEle != null && targetEle != undefined) {let groupID = targetEle.parentElement.parentElement.id;let ObserverEleArray = DomUtils.ObserverEleSetMap.get(groupID); //if(ObserverEleArray == undefined){ObserverEleArray = [];}//清除旧数据for (let j = 0; j < ObserverEleArray.length; j++) {const element = ObserverEleArray[j];element.parentElement.classList.remove(className);}ObserverEleArray.length = 0; //清空数组//处理新数据targetEle.parentElement.classList.add(className);ObserverEleArray.push(targetEle);DomUtils.ObserverEleSetMap.set(groupID, ObserverEleArray); //}}}class MJCommonUtils {//标准局数 转 东南西场 (1=东 2=南 3=西)static normalKyokuToKyokuMode(curKyoku){let curkyokuMode; //东南西场 (1=东 2=南 3=西)if(curKyoku <= 3)curkyokuMode = 1; //东场else if(curKyoku <= 7)curkyokuMode = 2; //南场else if(curKyoku <= 11)curkyokuMode = 3; //西场return curkyokuMode;}//标准局数 转 文本局数 (0=>1: 东一) (6=>3: 南3)static normalKyokuToTextKyoku(curKyoku, curkyokuMode){let textKyoku = parseInt(curKyoku) + 1;if(curkyokuMode == 2) //南场textKyoku -= 4;else if(curkyokuMode == 3) //西场textKyoku -= 8;return textKyoku;}static getTextByOutStyle(outStyle, index) {if(outStyle == OUTSTYLE.A)return eval("i18nText.seatTypeA" + index); //seatTypeA0 //东起else if(outStyle == OUTSTYLE.B)return eval("i18nText.seatTypeB" + index); //seatTypeB0 //东家else if(outStyle == OUTSTYLE.C)return eval("i18nText.seatTypeC" + index); //seatTypeC0 //自家else{Console.orig.warn("outStyle是无效的!");}}static getSelfViewPlayerNameByTargetPlayerIndex(selfIndex, targetPlayerIndex) {const viewMap = new Map();let viewArray = [{ value: -3, name: i18nText.seatTypeC1 }, //下家{ value: -2, name: i18nText.seatTypeC2 }, //对家{ value: -1, name: i18nText.seatTypeC3 }, //上家{ value: 0, name: i18nText.seatTypeC0 }, //自家{ value: 1, name: i18nText.seatTypeC1 }, //下家{ value: 2, name: i18nText.seatTypeC2 }, //对家{ value: 3, name: i18nText.seatTypeC3 }, //上家];viewArray.forEach((item,index)=> {viewMap.set(item.value, item.name);});const getValue = (offset) => {if(offset > 0)return offset -4;elsereturn offset +4;}let offset = targetPlayerIndex - selfIndex;let r###lt;do {r###lt = viewMap.get(offset);} while (offset = getValue(offset), r###lt == undefined);return r###lt;}static getPlayerIndexByPlayerSeatName(playerSeatName, outStyle) {let seatArray;if(outStyle == OUTSTYLE.A)seatArray = [i18nText.seatTypeA0, i18nText.seatTypeA1, i18nText.seatTypeA2, i18nText.seatTypeA3]; //东起, 南起, 西起, 北起else if(outStyle == OUTSTYLE.B)seatArray = [i18nText.seatTypeB0, i18nText.seatTypeB1, i18nText.seatTypeB2, i18nText.seatTypeB3]; //东家, 南家, 西家, 北家else{Console.orig.warn("outStyle是无效的!");seatArray = [i18nText.seatTypeA0, i18nText.seatTypeA1, i18nText.seatTypeA2, i18nText.seatTypeA3]; //东起, 南起, 西起, 北起}const seatMap = new Map();seatArray.forEach((item,index)=> {seatMap.set(item, index);});return seatMap.get(playerSeatName);}static getPlayerSeatNameByPlayerIndex(playerIndex, kyoku, outStyle) {let seatArray;if(outStyle == OUTSTYLE.A)seatArray = [i18nText.seatTypeA0, i18nText.seatTypeA3, i18nText.seatTypeA2, i18nText.seatTypeA1]; //东起, 北起, 西起, 南起else if(outStyle == OUTSTYLE.B)seatArray = [i18nText.seatTypeB0, i18nText.seatTypeB3, i18nText.seatTypeB2, i18nText.seatTypeB1]; //东家, 北家, 西家, 南家else{Console.orig.warn("outStyle是无效的!");seatArray = [i18nText.seatTypeA0, i18nText.seatTypeA3, i18nText.seatTypeA2, i18nText.seatTypeA1]; //东起, 北起, 西起, 南起}const seatMap = new Map();seatArray.forEach((item,index)=> {seatMap.set(item, index);});const getValueByEachArray = (array, startIndex, eachCount) => {let length = array.length;let targetIndex = startIndex;for (let i = eachCount; i > 0; i--) {if(++targetIndex >= length)targetIndex = 0;}return array[targetIndex];}const getStart = (playerIndex, kyoku, outStyle) => {switch (playerIndex + kyoku) {case 0:return MJCommonUtils.getTextByOutStyle(outStyle, 0); //东起case 1:return MJCommonUtils.getTextByOutStyle(outStyle, 1); //南起case 2:return MJCommonUtils.getTextByOutStyle(outStyle, 2); //西起case 3:return MJCommonUtils.getTextByOutStyle(outStyle, 3); //北起default:return getStart(playerIndex, kyoku -4, outStyle);}}const get = (playerIndex, kyoku, outStyle) => {let startIndex = seatMap.get(getStart(playerIndex, 0, outStyle));return getValueByEachArray(seatArray, startIndex, kyoku);}if(kyoku == 0)return getStart(playerIndex, kyoku, outStyle);elsereturn get(playerIndex, kyoku, outStyle);}}class MJUtils {}class MessageQueue {}class Popup {}class Api {static currApi;static commonApi = {name: "Tampermonkey",id: "",map: new Map([["setValue", "GM_setValue"], //["getValue", "GM_getValue"], //["addStyle", "GM_addStyle"],["deleteValue", "GM_deleteValue"], //["listValues", "GM_listValues"], //["addValueChangeListener", "GM_addValueChangeListener"],["removeValueChangeListener", "GM_removeValueChangeListener"],["log", "GM_log"], //["getResourceText", "GM_getResourceText"], //["getResourceURL", "GM_getResourceURL"], //["registerMenuCommand", "GM_registerMenuCommand"],["unregisterMenuCommand", "GM_unregisterMenuCommand"],["openInTab", "GM_openInTab"], //["xmlhttpRequest", "GM_xmlhttpRequest"], //["download", "GM_download"], //["getTab", "GM_getTab"],["saveTab", "GM_saveTab"],["getTabs", "GM_getTabs"],["notification", "GM_notification"],["setClipboard", "GM_setClipboard"], //["info", "GM_info"],]),};// static viaApi = {// name: "via",// id: "via_gm",// map: new Map([// ["deleteValue", "deleteValue"], //// ["download", "download"], //// ["getResourceText", "getResourceText"], //// ["getResourceURL", "getResourceURL"], //// ["getValue", "getValue"], //// ["isInstalled", "isInstalled"],// ["listValues", "listValues"], //// ["log", "log"], //// ["openInTab", "openInTab"], //// ["openOptions", "openOptions"],// ["setClipboard", "setClipboard"], //// ["setValue", "setValue"], //// ["xmlHttpRequest", "xmlHttpRequest"], //// ]),// };static{// if(Api.isDefine(Api.viaApi.id)){// Api.currApi = Api.viaApi;// }else{Api.currApi = Api.commonApi;// }}static isDefine(id) {return eval(`typeof ${id} != 'undefined'`);}static getByMap(str) {let r###lt = Api.currApi.map.get(str);return r###lt != undefined ? r###lt : Api.commonApi.map.get(str);}static commonCall(fnNameStr, ...args) {if(fnNameStr.startsWith("GM_"))return eval(fnNameStr + ".apply(this, args)");else if(Api.currApi.id == "")return eval(fnNameStr + ".apply(this, args)");elsereturn eval(`${Api.currApi.id}.${fnNameStr}` + ".apply(this, args)");}//static GM_setValue(...args){ return Api.commonCall(Api.getByMap("setValue"), args); }static GM_getValue(...args){ return Api.commonCall(Api.getByMap("getValue"), args); }static GM_addStyle(...args){ return Api.commonCall(Api.getByMap("addStyle"), args); }static GM_deleteValue(...args){ return Api.commonCall(Api.getByMap("deleteValue"), args); }static GM_listValues(...args){ return Api.commonCall(Api.getByMap("listValues"), args); }static GM_addValueChangeListener(...args){ return Api.commonCall(Api.getByMap("addValueChangeListener"), args); }static GM_removeValueChangeListener(...args){ return Api.commonCall(Api.getByMap("removeValueChangeListener"), args); }static GM_log(...args){ return Api.commonCall(Api.getByMap("log"), args); }static GM_getResourceText(...args){ return Api.commonCall(Api.getByMap("getResourceText"), args); }static GM_getResourceURL(...args){ return Api.commonCall(Api.getByMap("getResourceURL"), args); }static GM_registerMenuCommand(...args){ return Api.commonCall(Api.getByMap("registerMenuCommand"), args); }static GM_unregisterMenuCommand(...args){ return Api.commonCall(Api.getByMap("unregisterMenuCommand"), args); }static GM_openInTab(...args){ return Api.commonCall(Api.getByMap("openInTab"), args); }static GM_xmlhttpRequest(...args){ return Api.commonCall(Api.getByMap("xmlhttpRequest"), args); }static GM_download(...args){ return Api.commonCall(Api.getByMap("download"), args); }static GM_getTab(...args){ return Api.commonCall(Api.getByMap("getTab"), args); }static GM_saveTab(...args){ return Api.commonCall(Api.getByMap("saveTab"), args); }static GM_getTabs(...args){ return Api.commonCall(Api.getByMap("getTabs"), args); }static GM_notification(...args){ return Api.commonCall(Api.getByMap("notification"), args); }static GM_setClipboard(...args){ return Api.commonCall(Api.getByMap("setClipboard"), args); }static GM_info(...args){ return Api.commonCall(Api.getByMap("info"), args); }}class Debug {static #bDebug = true; //调试模式static #bPublicApi = true;static {if(Debug.#bDebug && Debug.#bPublicApi){let execute = "";for (const [key, value] of Api.commonApi.map.entries()) {execute += `if(typeof ${value} != 'undefined') g_window.${value} = ${value};\n`;}eval(execute);}}static set setDebug(debug){Debug.#bDebug = debug;}static get getDebug(){return Debug.#bDebug;}//static globalErrorHandle(errObj) {ElementPlus.ElNotification({title: errObj.name || 'Error',message: Vue.h('i', { style: 'color: red' }, errObj.toString()),type: 'error',duration: 4500,});Console.orig.error(errObj);}static globalOutputErrorHandle(errName, errStr) {ElementPlus.ElNotification({title: errName || 'Error',message: Vue.h('i', { style: 'color: red' }, errStr),type: 'error',duration: 4500,});Console.orig.error(errStr);}//}class Console {static orig = window.console; //保存原始对象static clear(){clear();}static log(...args){Debug.getDebug &&Console.orig.log('%c[log]', 'background: #ffa500; padding: 1px; color: #fff;', args);}static warn(...args){Debug.getDebug &&Console.orig.log('%c[warn]', 'background: #ffa500; padding: 1px; color: #fff;', args);}static error(...args){Debug.getDebug &&Console.orig.log('%c[error]', 'background: red; padding: 1px; color: #fff;', args);}static info(...args){Debug.getDebug &&Console.orig.log('%c[info]', 'background: #ffa500; padding: 1px; color: #fff;', args);}static table(...args){Debug.getDebug &&Console.orig.table(args);}}g_window.Console = Console;class Performance {constructor(name){if(name != undefined)this._name = name;elsethis._name = '';this.nTimeStart = 0;this.nTimeEnd = 0;}setStartTime() {this.nTimeStart = performance.now();}setEndTime() {this.nTimeEnd = performance.now();}getEndTime(fnName) {this.nTimeEnd = performance.now();let executionTime = this.nTimeEnd - this.nTimeStart;if(fnName == undefined || fnName == null){fnName = "";}else{fnName = `.${fnName}()`;}Console.orig.log(`${this._name}${fnName}代码执行时间: ${executionTime.toFixed(3)} 毫秒`); //return executionTime;}}const CallType = {__proto__: null,Positive: "Positive",Reverse: "Reverse",}const HookType = {__proto__: null,Single: "Single",Multi: "Multi",}class ProxyGenerator { //代理生成器: 生成代理方法proxyInfo = [CryptoJS.SHA1(this.generateSingle().toString().replace(/\s+/g, "")).toString(),CryptoJS.SHA1(this.generateMulti_ChainCall().toString().replace(/\s+/g, "")).toString(),CryptoJS.SHA1(this.generateMultiReverse_ChainCall().toString().replace(/\s+/g, "")).toString(),];generateSingle(fcName_, origFcAddr_, newFcAddr_, newFcAddrLastCall_) { //单hookfunction _proxy_s(...args){let prototype = _proxy_s.prototype;if(prototype.hasOwnProperty("_fcData")) {let fcData = _proxy_s.prototype._fcData;let origArgs = [...args]; //复制数组args.unshift(origFcAddr_); //添加到数组开头let lastCallArgs = [...args]; //复制数组if(fcData.newFcAddr.length == 2) //根据模板函数实际参数个数,传入匹配的参数args.splice(1, 0, undefined); //将undefined插入到数组第二个元素let interceptor = fcData.newFcAddr.apply(this, args); //调用newFcAddrif(interceptor == true || interceptor == undefined){let ret = fcData.origFcAddr.apply(this, origArgs); //调用原函数 //this是调用方传递的,也一并传递if(fcData.newFcAddrLastCall && fcData.newFcAddrLastCall.length == 2) //根据模板函数实际参数个数,传入匹配的参数lastCallArgs.splice(1, 0, ret); //将ret插入到数组第二个元素if(fcData.newFcAddrLastCall)fcData.newFcAddrLastCall.apply(this, lastCallArgs); //调用newFcAddrLastCallreturn ret;}else{return; //否则就拦截}}}_proxy_s.prototype._fcData = {fcName: fcName_ || "",origFcAddr: origFcAddr_ || null,newFcAddr: newFcAddr_ || null,newFcAddrLastCall: newFcAddrLastCall_ || null,};return _proxy_s;}generateMulti_OneToOneCall(This, key_) { //多层hook-正序-一对一调用function _proxy_m(...args){let prototype = _proxy_m.prototype;if(prototype.hasOwnProperty("_hk")) {let hk = _proxy_m.prototype._hk;let key = hk.key;let hook = hk.obj;let hookDataArray = hook.fcHookMap.get(key);let ret;let origArgs = [...args]; //复制数组for (const [index, item] of hookDataArray.entries()) { //遍历多层hookargs.unshift(item.origFcAddr); //添加到数组开头let lastCallArgs = [...args]; //复制数组if(item.newFcAddr.length == 2) //根据模板函数实际参数个数,传入匹配的参数args.splice(1, 0, undefined); //将undefined插入到数组第二个元素let interceptor = item.newFcAddr.apply(this, args); //调用newFcAddrif(interceptor == true || interceptor == undefined){ret = item.origFcAddr.apply(this, origArgs); //调用原函数 //this是调用方传递的,也一并传递if(item.newFcAddrLastCall && item.newFcAddrLastCall.length == 2) //根据模板函数实际参数个数,传入匹配的参数lastCallArgs.splice(1, 0, ret); //将ret插入到数组第二个元素if(item.newFcAddrLastCall)item.newFcAddrLastCall.apply(this, lastCallArgs); //调用newFcAddrLastCallif(index == hookDataArray.length -1){ //在多层hook结尾, 返回返回值return ret;}}else{return; //否则就拦截}}}}_proxy_m.prototype._hk = {key: key_ || "",obj: This,};return _proxy_m;}generateMulti_ChainCall(This, key_) { //多层hook-正序-链式调用function _proxy_m(...args){let prototype = _proxy_m.prototype;if(prototype.hasOwnProperty("_hk")) {let hk = _proxy_m.prototype._hk;let key = hk.key;let hook = hk.obj;let hookDataArray = hook.fcHookMap.get(key);let ret;let origArgs = [...args]; //复制数组let interceptor;for (const [index, item] of hookDataArray.entries()) { //遍历多层hooklet args = [...origArgs]; //复制数组args.unshift(item.origFcAddr); //添加到数组开头let lastCallArgs = [...args]; //复制数组if(item.newFcAddr.length == 2) //根据模板函数实际参数个数,传入匹配的参数args.splice(1, 0, undefined); //将undefined插入到数组第二个元素interceptor = item.newFcAddr.apply(this, args); //调用newFcAddrif(interceptor == true || interceptor == undefined){if(index < hookDataArray.length -1){ //如果没有遍历完成,则继续链式调用continue;}else{ret = item.origFcAddr.apply(this, origArgs); //调用原函数 //this是调用方传递的,也一并传递if(item.newFcAddrLastCall && item.newFcAddrLastCall.length == 2) //根据模板函数实际参数个数,传入匹配的参数lastCallArgs.splice(1, 0, ret); //将ret插入到数组第二个元素if(item.newFcAddrLastCall)item.newFcAddrLastCall.apply(this, lastCallArgs); //调用newFcAddrLastCallif(index == hookDataArray.length -1){ //在多层hook结尾, 返回返回值return ret;}}}else{return; //否则就拦截}}}}_proxy_m.prototype._hk = {key: key_ || "",obj: This,};return _proxy_m;}generateMultiReverse_OneToOneCall(This, key_) { //多层hook-逆序-一对一调用function _proxy_mr(...args){let prototype = _proxy_mr.prototype;if(prototype.hasOwnProperty("_hk")) {let hk = _proxy_mr.prototype._hk;let key = hk.key;let hook = hk.obj;let hookDataArray = hook.fcHookMap.get(key);let ret;let origArgs = [...args]; //复制数组for (let i = hookDataArray.length -1; i >= 0; i--) { //遍历多层hookconst item = hookDataArray[i];args.unshift(item.origFcAddr); //添加到数组开头let lastCallArgs = [...args]; //复制数组if(item.newFcAddr.length == 2) //根据模板函数实际参数个数,传入匹配的参数args.splice(1, 0, undefined); //将undefined插入到数组第二个元素let interceptor = item.newFcAddr.apply(this, args); //调用newFcAddrif(interceptor == true || interceptor == undefined){ret = item.origFcAddr.apply(this, origArgs); //调用原函数 //this是调用方传递的,也一并传递if(item.newFcAddrLastCall && item.newFcAddrLastCall.length == 2) //根据模板函数实际参数个数,传入匹配的参数lastCallArgs.splice(1, 0, ret); //将ret插入到数组第二个元素if(item.newFcAddrLastCall)item.newFcAddrLastCall.apply(this, lastCallArgs); //调用newFcAddrLastCallif(i == 0){ //在多层hook开头, 返回返回值return ret;}}else{return; //否则就拦截}}}}_proxy_mr.prototype._hk = {key: key_ || "",obj: This,};return _proxy_mr;}generateMultiReverse_ChainCall(This, key_) { //多层hook-逆序-链式调用function _proxy_mr(...args){let prototype = _proxy_mr.prototype;if(prototype.hasOwnProperty("_hk")) {let hk = _proxy_mr.prototype._hk;let key = hk.key;let hook = hk.obj;let hookDataArray = hook.fcHookMap.get(key);let ret;let origArgs = [...args]; //复制数组for (let i = hookDataArray.length -1; i >= 0; i--) { //遍历多层hooklet args = [...origArgs]; //复制数组const item = hookDataArray[i];args.unshift(item.origFcAddr); //添加到数组开头let lastCallArgs = [...args]; //复制数组if(item.newFcAddr.length == 2) //根据模板函数实际参数个数,传入匹配的参数args.splice(1, 0, undefined);; //将undefined插入到数组第二个元素let interceptor = item.newFcAddr.apply(this, args); //调用newFcAddrif(interceptor == true || interceptor == undefined){if(i > 0){ //如果没有遍历完成,则继续链式调用continue;}else{ret = item.origFcAddr.apply(this, origArgs); //调用原函数 //this是调用方传递的,也一并传递if(item.newFcAddrLastCall && item.newFcAddrLastCall.length == 2) //根据模板函数实际参数个数,传入匹配的参数lastCallArgs.splice(1, 0, ret); //将ret插入到数组第二个元素if(item.newFcAddrLastCall)item.newFcAddrLastCall.apply(this, lastCallArgs); //调用newFcAddrLastCallif(i == 0){ //在多层hook开头, 返回返回值return ret;}}}else{return; //否则就拦截}}}}_proxy_mr.prototype._hk = {key: key_ || "",obj: This,};return _proxy_mr;}}class Hook {#proxyGenerator = new ProxyGenerator();#callType = CallType.Positive;constructor(proxyGenerator){if(proxyGenerator != undefined)this.setProxyGenerator = proxyGenerator;this.fcHookMap = new Map();//this.objHookMap = new Map();}set setProxyGenerator(proxyGenerator) {this.#proxyGenerator = proxyGenerator;}setCallType(callType) {this.#callType = callType;for (const [key, item] of this.fcHookMap) { //遍历mapif(item.length > 1){ //排除单hook,因为单hook没有顺序区别if(callType == CallType.Positive){eval(`${key}=this.#proxyGenerator.generateMulti_ChainCall(this, '${key}')`); //替换return true;}else if(callType == CallType.Reverse){eval(`${key}=this.#proxyGenerator.generateMultiReverse_ChainCall(this, '${key}')`); //替换return true;}else{return false;}}}}get getCallType(){return this.#callType;}//设置hook时,设置的是代理函数,然后在代理函数里调用newFcAddr//流程: 其他代码调用 fcAddr =>(实际调用) 代理函数 ->(里面先拿到原函数) 再调用newFcAddr ->(结束后根据返回值如果为true则调用原函数,并返回返回值,false就拦截)//如果newFcAddr没有返回值 (说明不使用拦截功能),就正常调用原函数,返回返回值//代理函数是动态生成的,并且代理函数里不能调用被hook的函数,不然会无限递归调用setSingleHook(fcAddrStr, newFcAddr, newFcAddrLastCall){if(!TypeUtils.isString(fcAddrStr)){Console.orig.log("setSingleHook()失败, fcAddrStr参数错误, 应该是字符串! 实际是: " + fcAddrStr);return false;}let fcAddr = eval(fcAddrStr);let type = TypeUtils.getType(fcAddr);let name = fcAddr.name;let key = fcAddrStr;if(TypeUtils.isFunction(fcAddr)){if(!this.fcHookMap.has(key)){Console.orig.log("新fcHook: " + key);this.fcHookMap.set(key, [{fcName: name,origFcAddr: fcAddr,newFcAddr: newFcAddr,newFcAddrLastCall: newFcAddrLastCall,}]); //保存原始eval(`${fcAddrStr}=this.#proxyGenerator.generateSingle('${name}', fcAddr, newFcAddr, newFcAddrLastCall)`); //替换return true;}else{//先取消hook,再重新单hooklet hookDataArray = this.fcHookMap.get(key);this.unHookByNameOfSpecifyOrAll(fcAddrStr, hookDataArray[0].newFcAddr);return this.setSingleHook(fcAddrStr, newFcAddr, newFcAddrLastCall);}}else{Console.orig.log("setSingleHook()失败, 不支持的type: " + type);return false;}}setHook(fcAddrStr, newFcAddr, newFcAddrLastCall){if(!TypeUtils.isString(fcAddrStr)){Console.orig.log("setHook()失败, fcAddrStr参数错误, 应该是字符串! 实际是: " + fcAddrStr);return false;}let fcAddr = eval(fcAddrStr);let type = TypeUtils.getType(fcAddr);let name = fcAddr.name;let key = fcAddrStr;if(TypeUtils.isFunction(type)){if(!this.fcHookMap.has(key)){Console.orig.log("新fcHook: " + key);this.fcHookMap.set(key, [{fcName: name,origFcAddr: fcAddr,newFcAddr: newFcAddr,newFcAddrLastCall: newFcAddrLastCall,}]); //保存原始eval(`${fcAddrStr}=this.#proxyGenerator.generateSingle('${name}', fcAddr, newFcAddr, newFcAddrLastCall)`); //替换return true;}else{ //多层HookConsole.orig.log("多层fcHook: " + key);let hookDataArray = this.fcHookMap.get(key);hookDataArray.push({fcName: name,origFcAddr: hookDataArray[0].origFcAddr,newFcAddr: newFcAddr,newFcAddrLastCall: newFcAddrLastCall,}); //保存原始this.fcHookMap.set(key, hookDataArray);if(this.#callType == CallType.Positive){eval(`${fcAddrStr}=this.#proxyGenerator.generateMulti_ChainCall(this, '${key}')`); //替换return true;}else if(this.#callType == CallType.Reverse){eval(`${fcAddrStr}=this.#proxyGenerator.generateMultiReverse_ChainCall(this, '${key}')`); //替换return true;}}}else{Console.orig.log("setHook()失败, 不支持的type: " + type);return false;}}isHook(fcAddrStr){if(!TypeUtils.isString(fcAddrStr)){Console.orig.log("isHook()失败, fcAddrStr参数错误, 应该是字符串! 实际是: " + fcAddrStr);return false;}if(this.fcHookMap.has(fcAddrStr)){return true;}else{let fcAddr = eval(fcAddrStr);if(fcAddr == undefined){return false;}let hash = CryptoJS.SHA1(fcAddr.toString().replace(/\s+/g, "")).toString();for (const [index, item] of this.#proxyGenerator.proxyInfo.entries()) {if(item == hash) {return true;}}return false;}}getHookType(fcAddrStr){if(!TypeUtils.isString(fcAddrStr)){Console.orig.log("getHookType()失败, fcAddrStr参数错误, 应该是字符串! 实际是: " + fcAddrStr);return false;}const hookDataArray = this.fcHookMap.get(fcAddrStr);if(hookDataArray != undefined) {return hookDataArray.length == 1 ? HookType.Single : HookType.Multi;}else{return null;}}#unHookCommon_unload(fcAddrStr, hookData, hookDataArray, i){if(hookDataArray.length == 1){ //单hook 或 只有1个元素eval(`${fcAddrStr}=hookData.origFcAddr`); //还原this.fcHookMap.delete(fcAddrStr);return true;}else{ //多层hookDataArray.splice(i, 1); //删除当前记录this.fcHookMap.set(fcAddrStr, hookDataArray);return true;}}#unHookCommon_mode(fcAddrStr, newFcAddr, hookData, hookDataArray, i){if(newFcAddr != undefined && hookData.newFcAddr == newFcAddr){ //unHookByName //取消指定key某一层hookreturn this.#unHookCommon_unload(fcAddrStr, hookData, hookDataArray, i);}else if(newFcAddr == undefined){ //unAllHookByName //取消指定key下的所有hookreturn this.#unHookCommon_unload(fcAddrStr, hookData, hookDataArray, i);}}//取消hook, 主要针对多层hook//unHook(key, 新函数地址)//unHook(key)//如果指定newFcAddr, 则取消指定key某一层hook//如果只指定fcAddrStr, 则取消指定key下的所有hookunHookByNameOfSpecifyOrAll(fcAddrStr, newFcAddr){if(!TypeUtils.isString(fcAddrStr)){Console.orig.log("unHookByNameOfSpecifyOrAll()失败, fcAddrStr参数错误, 应该是字符串! 实际是: " + fcAddrStr);return false;}if(this.isHook(fcAddrStr)){let fcAddr = eval(fcAddrStr);let type = TypeUtils.getType(fcAddr);let key = fcAddrStr;if(TypeUtils.isFunction(type)){if(this.fcHookMap.has(key)){let hookDataArray = this.fcHookMap.get(key);for (let i = hookDataArray.length -1; i >= 0; i--) { //逆序遍历const hookData = hookDataArray[i];let ret = this.#unHookCommon_mode(fcAddrStr, newFcAddr, hookData, hookDataArray, i);if(newFcAddr != undefined && ret){ //单unHook, 并且执行成功, 则直接返回return true;}else if(newFcAddr == undefined && i == 0){ //否则是 取消指定key下的所有hook, 就判断是否循坏完成, 循坏完成后返回return true;}}Console.orig.log("unHook()失败, 找到hook, 但是实参newFcAddr有误: " + newFcAddr.name);return false;}else{Console.orig.log("unHook()失败, 找到hook, 但是实参key有误: " + key);return false;}}else{Console.orig.log("unHook()失败, 不支持的type: " + type);return false;}}else{Console.orig.log("unHook()失败, 找不到指定的hook!");return false;}}unAllHook(){ //取消所有for (const [key, hookDataArray] of this.fcHookMap) { //遍历mapfor (let i = hookDataArray.length -1; i >= 0; i--) { //逆序遍历const hookData = hookDataArray[i];this.#unHookCommon_mode(key, undefined, hookData, hookDataArray, i);}}return true;}}class DynamicProxy {//DynamicProxy.apply(mainObj.perfor, "getEndTime"); //间接调用static apply(obj, fcName, ...args) { //执行目标对象的方法(对象, 方法名, 方法参数)let prototype = Object.getPrototypeOf(obj);if(prototype.hasOwnProperty(fcName)) {return eval("prototype." + fcName).apply(obj, args);}}static autoWired(mainObj, targetClass, CallBack) { //依赖注入for (const [name, value] of Object.entries(mainObj)) { //遍历对象获取指定类型的属性, 属性的类型通过参数传入if(value instanceof targetClass){return CallBack(name, value); //CallBack(属性名, 属性值)}}return null;}}class CodeTemplate {static hook = new Hook();static autoWiredProxy(mainObj, executeFcName){ //依赖注入代理return DynamicProxy.autoWired(mainObj, Performance, (name, value)=>{//使用这个属性调用apply实现具体的功能DynamicProxy.apply(value, executeFcName); //apply(属性值(对象), 要执行的方法)return true;});}static autoWired_Performance(fcAddrStr, mainObj){if(CodeTemplate.hook.isHook(fcAddrStr)){Console.warn(`${fcAddrStr}已注入${mainObj.name}, 请勿重复调用!`);return false;}const fcStartStub = (origFcAddr, ...args)=>{if(CodeTemplate.autoWiredProxy(mainObj, "setStartTime") == null){ //依赖注入Debug.globalOutputErrorHandle("autoWiredProxy()失败!", "返回值为null.");}};const fcEndStub = (origFcAddr, ret, ...args)=>{if(CodeTemplate.autoWiredProxy(mainObj, "getEndTime") == null){ //依赖注入Debug.globalOutputErrorHandle("autoWiredProxy()失败!", "返回值为null.");}};CodeTemplate.hook.setSingleHook(fcAddrStr, fcStartStub, fcEndStub);}}const URLTYPE = {__proto__: null,LINK: 1, //链接FUZZYMATCH: 1 << 1, //模糊匹配RegExp: 1 << 2, //正则表达式}class URL {static test(testURL, urlTYPE, urlRule) {if(urlTYPE == URLTYPE.LINK){return urlRule.includes(testURL); //子串}else if(urlTYPE == URLTYPE.FUZZYMATCH){let targetURL = urlRule;//转义可能的特殊字符 //\和/这种字符必须首先执行 //*和?这种通配符放在后面单独处理let specharsArray = ['\\', '/', '[', ']', '(', ')', '{', '}', '^', '$', '-', '.', '+', '|', ',', ':', '=', '!', '<', '%'];specharsArray.forEach((item,index)=> {targetURL = targetURL.replaceAll(item, '\\' + item);});//替换通配符 //?必须比*先执行targetURL = targetURL.replaceAll("?", "[\\s\\S]?");targetURL = targetURL.replaceAll("*", "([\\s\\S]+)?");//测试URLlet regExp = new RegExp(targetURL);return regExp.test(testURL);}else if(urlTYPE == URLTYPE.RegExp){return urlRule.test(testURL);}}}class PageCallBack {static #css_layui_ruleSet = new Map([//重定向字体文件["url(../", "url(https://cdn.staticfile.org/layui/2.8.17/"]]);static #css_fontAwesome_ruleSet = new Map([//重定向字体文件(这里有些带",有些不带")['url("../', 'url("https://cdn.staticfile.org/font-awesome/6.4.2/'],['url(../', 'url(https://cdn.staticfile.org/font-awesome/6.4.2/']]);static #js_layui_ruleSet = new Map([//解决动态添加的节点与css兼容性的问题['class="', `class="${cssIDNP} `]]);//---------- 通用 ----------static init(){//加载库-cssUtils.addNewStyle("css_fontAwesome", CustomUtils.handleCSSCompatibility(null, GM_getResourceText("css_fontAwesome"), PageCallBack.#css_fontAwesome_ruleSet));Utils.addNewStyle("css_layui", CustomUtils.handleCSSCompatibility(cssID, GM_getResourceText("css_layui"), PageCallBack.#css_layui_ruleSet));Utils.addNewStyle("css_elementplus", GM_getResourceText("css_elementplus"));//加载自定义-cssUtils.addNewStyle("commonCSS", commonCSS);Utils.addNewStyle("pageExtendBaseCSS", pageExtendBaseCSS);Utils.addNewStyle("MainAppBaseCSS", MainAppBaseCSS);Utils.addNewStyle("MainAppCSS", MainAppCSS);//加载库-jsUtils.addNewScript("js_localforage", GM_getResourceText("js_localforage"));Utils.addNewScript("js_layui", CustomUtils.handleJSCompatibility(cssID, GM_getResourceText("js_layui"), PageCallBack.#js_layui_ruleSet));Utils.addNewScript("js_vue", GM_getResourceText("js_vue"));Utils.addNewScript("js_elementplus", GM_getResourceText("js_elementplus"));//预初始化UI.init();}static async ready() {//执行初始化localforage.config({name: 'MortalApp',storeName: 'AppDB',version: '1.0',description: 'default database',});//读取设置let jsonStr = await DBConfig.readConfig("globalSettings"); //从数据库中读取配置if(jsonStr != null) {let obj = JSON.parse(jsonStr);if(TypeUtils.isOrigObjByType(obj)){let r###lt = CustomUtils.compareVersion(globalSettings.BaseInfo.curVersion, obj.BaseInfo.curVersion);if(r###lt == 1){ //如果更新了新版本//优先迁移数据库Console.orig.log("版本更新, 尝试迁移数据库...");//迁移失败时, 重置数据库Console.orig.log("版本更新, 数据库迁移失败! 需要重置数据库...");await DBConfig.clear(); //重置数据库}else if(r###lt == -1){ //如果回退了旧版本//优先迁移数据库Console.orig.log("版本回退, 尝试迁移数据库...");//迁移失败时, 重置数据库Console.orig.log("版本回退, 数据库迁移失败! 需要重置数据库...");await DBConfig.clear(); //重置数据库}else{globalSettings = obj;Console.orig.log("已读取设置: globalSettings");}}else{Console.orig.error("无法识别数据库中的数据: globalSettings, 需要重置数据库...");await DBConfig.clear(); //重置数据库}}else{Console.orig.log("数据库中没有 globalSettings 数据!");}}static async end() {UI.setValueByLoadingProgress(100);UI.setShowLoadingProgress(false);UI.setShowLoadingAnimation(false);}}class ReportCallBack extends PageCallBack {//---------- 详细页 ----------static async init() {super.init();//加载模板// Utils.addElementsByHTMLTemplateText(bottomRightShowBox);// Utils.addElementsByHTMLTemplateText(settingUITemplate);Utils.addElementsByHTMLTemplateText(catalogUIBase);return await ReportCallBack.ready();}static async ready() {await super.ready();PageExtend.init();MainApp.init();UI.setValueByLoadingProgress(10);return true;}static async run() {if(!globalSettings.Other.safeMode) {await PageExtend.run();await MainApp.run();}return await ReportCallBack.end();}static async end() {await super.end();return true;}}class MainPageCallBack extends PageCallBack {//---------- 主页 ----------static async init() {super.init();//加载模板return await MainPageCallBack.ready();}static async ready() {await super.ready();UI.setValueByLoadingProgress(10);return true;}static async run() {if(!globalSettings.Other.safeMode) {//修复原页面bug: 在主页输入一番街牌谱或自定义牌谱后进入牌谱解析页面,再后退回到主页时,不显示对应的输入框let radioEle = document.querySelectorAll('[type="radio"][name="input-method"]');for (let i = 0; i < radioEle.length; i++) {const ele = radioEle[i];if(ele.checked){ele.onchange();break;}}document.getElementsByName("show-rating").forEach((ele)=>{ele.checked = true}); //默认勾选 显示Ratingconst map = new Map();let childEle = document.getElementById("mortal-model-tag").children;for (let i = 0; i < childEle.length; i++) {const ele = childEle[i];map.set(ele.value, ele.innerText); //将数据保存到map}const jsonStr = JSON.stringify(Object.fromEntries(map));try {// 存储不同语言的 Mortal 版本const pageLanguage = g_origLang;let allMap;let allJsonStr = await localforage.getItem("Mortal_All");if(allJsonStr == null) {allMap = new Map();} else{allMap = new Map(Object.entries(JSON.parse(allJsonStr)));}allMap.set(pageLanguage, map);allJsonStr = JSON.stringify(Object.fromEntries(allMap));//牌谱解析页面,默认使用最新的Mortalawait localforage.setItem("Mortal_New", childEle[0].value);await localforage.setItem("Mortal_Type", jsonStr);await localforage.setItem("Mortal_All", allJsonStr);} catch (err) {Console.orig.error("存储数据时发生错误: \n" + err);}}return await MainPageCallBack.end();}static async end() {await super.end();return true;}}class App {static perfor = new Performance("dom");static URLRuleSet = [{name: "牌谱解析页面",url: /^https?:\/\/mjai.ekyu.moe\/report\/[A-Za-z0-9-_]+.html/,urlType: URLTYPE.RegExp,exclude: [],usabilityTest: ["document.body.children.length <= 5 || document.body.firstChild.tagName == 'PRE'",],init: ReportCallBack.init,entry: ReportCallBack.run,},{name: "主页",url: /^https?:\/\/mjai.ekyu.moe\/?([A-Za-z0-9-_]+.html)?/,urlType: URLTYPE.RegExp,exclude: [//排除主动访问非页面URL的内容,比如图片等/^https?:\/\/mjai.ekyu.moe\/?([A-Za-z0-9-_]+)?\/?[A-Za-z0-9-_]+.(?!.*(html|htm|jsp|php|asp))/,],usabilityTest: [],init: MainPageCallBack.init,entry: MainPageCallBack.run,},]; //URL规则集constructor(){this.isRun = false;this.init();}async init() {document.addEventListener('readystatechange', async (event) => {if (event.target.readyState === 'interactive') {App.perfor.setStartTime();//Console.orig.log('interactive');await this.yun(); //chrome第一次在这里触发//initLoader();}else if (event.target.readyState === 'complete') {App.perfor.getEndTime();//Console.orig.log('complete');await this.yun(); //firefox第一次在这里触发//initApp();}});if(document.readyState == 'complete'){ //如果页面已经加载完成 (正常情况下不会触发)await this.yun();}window.addEventListener("beforeunload", async (event) => { //在页面关闭或刷新之前//保存设置// await DBConfig.SaveConfig("globalSettings", globalSettings); //保存配置到数据库//判断是否需要提示确认退出if(globalStatus.globalExitConfirm) {event.preventDefault();event.returnValue = "";}});return true;}async yun(){if(this.isRun) //只能运行一次return;this.isRun = true;let isExclude = false, isUnavailable = false;for (const [index, item] of App.URLRuleSet.entries()) {let currentURL = window.location.origin + window.location.pathname;if(URL.test(currentURL, item.urlType, item.url)){ //测试URL是否匹配if(item.exclude.length > 0){for (const [i, excludeItem] of item.exclude.entries()) {if(URL.test(currentURL, item.urlType, excludeItem)){ //测试URL是否被排除isExclude = true;break;}}}if(item.usabilityTest.length > 0){for (const [i, testItem] of item.usabilityTest.entries()) { //测试可用性if(eval(testItem)){isUnavailable = true;break;}}}if(!isExclude && !isUnavailable){if(await item.init.apply())await item.entry.apply();}return;}}}}class UI {static loadingAnimation = {isShowLoadingAnimation: true,loadIndex: 0,}static loadingProgress = {isShowLoadingProgress: true,value: 0,}static init(){if(globalSettings.AppSettings.loading.isShowLoadingProgress){//加载模板Utils.addElementsByHTMLTemplateText(loadingUITemplate);//渲染进度条组件layui.element.render('progress', 'loading-filter-progress');}if(globalSettings.AppSettings.loading.isShowLoadingAnimation){UI.setShowLoadingAnimation(true);}}static setShowLoadingAnimation(isShow) {UI.loadingAnimation.isShowLoadingAnimation = isShow;layui.use(function(){let layer = layui.layer;if(isShow){UI.loadingAnimation.loadIndex = layer.load(2); //显示加载动画}else{layer.close(UI.loadingAnimation.loadIndex); //关闭加载动画}});}static setShowLoadingProgress(isShow) {UI.loadingProgress.isShowLoadingProgress = isShow;let loadingProgress = document.getElementById("loadingProgress");if(isShow){loadingProgress.style.display = "block";}else{setTimeout(()=>{loadingProgress.style.display = "none";}, 500);}}static setValueByLoadingProgress(value) {if(value>100)value=100;else if(value<0)value=0;UI.loadingProgress.value = value;layui.element.progress('loading-filter-progress', `${value}%`); // 设置进度值}static addValueByLoadingProgress(value) {if(value>100)value=100;else if(value<0)value=0;layui.element.progress('loading-filter-progress', (UI.loadingProgress.value + value) + '%'); // 设置进度值}}// ---------------------------- 功能定义 ----------------------------class MortalBase {constructor(){}static init(){return true;}static run(){}static stop(){}static clean(){}}const OUTSTYLE = {__proto__: null,A: 1,B: 1 << 1,C: 1 << 2,}let strArray2 = []; //恶手选择器let strArray3 = []; //不一致选择器class PageExtend extends MortalBase {static perfor = new Performance("PageExtend");//static badChooseNum = 0;static badChooseNumCustom = 0;constructor(){//super();}static init(){//Console.orig.log("PageExtend.init()");if(Debug.getDebug){CodeTemplate.autoWired_Performance("PageExtend.run", PageExtend);}return true;}static async run(){//Console.orig.log("PageExtend.run()");const [r###lt1, r###lt2, r###lt3] =//显示恶手 (收集数据) //起始信息详细化 (独立) (重要) //列出选择权重 (独立)(await Promise.all([PageExtend.showBadChoose(), PageExtend.showStartInfo(), PageExtend.showChooseWeight()].map(item => item.catch(err => Debug.globalErrorHandle(err))) //每个并发请求使用catch进行对应异常的处理,防止某个请求失败后,影响到其他函数执行));//Console.orig.log(r###lt1, r###lt2, r###lt3);//修改 元数据 选项卡 (数据统计需要在showBadChoose()之后执行)await PageExtend.alterMetaData().catch(err => Debug.globalErrorHandle(err));}static stop(){}static clean(){}//显示恶手static async showBadChoose() {//Console.orig.log("MainApp.showBadChoose()");const getData = (strArray3, collapseEntry, kyokuTitleEle, summary, badChooseType) => {//收集数据2-不一致let nameID = "discord-" + strArray3.length;collapseEntry.setAttribute("name", nameID + "-main"); //设置子项namestrArray3.push({name: kyokuTitleEle.textContent + " " + summary.textContent,parentHref: kyokuTitleEle.href.match(/#[\S\s]+$/)[0],badChooseType: badChooseType,//name: nameID + "-main", //nametype: "name",nameID: nameID});};const orderLossEleArray = document.getElementsByClassName("order-loss");for (let i = 0; i < orderLossEleArray.length; i++) {const orderLoss = orderLossEleArray[i];const nChooseIndex = parseInt(orderLoss.innerText.match(/[\d]+/)[0]);const nChoos###m = parseInt(orderLoss.nextSibling.textContent.match(/[\d]+/)[0]);const turnInfo = orderLoss.parentElement;const summary = turnInfo.parentElement;const collapseEntry = summary.parentElement;const table = collapseEntry.lastChild.firstChild;const tbody = table.lastChild;const nChooseTR = tbody.childNodes[nChooseIndex -1];const nChooseWeightTD = nChooseTR.lastChild;const chosenWeight = parseFloat(nChooseWeightTD.innerHTML.replace(/<.*?>/g, "")); //过滤html标签, 只保留文字内容const kyokuTitleEle = collapseEntry.parentElement.parentElement.firstChild.getElementsByTagName("a")[0];if (chosenWeight <= parseFloat(globalSettings.Config.badMoveUpperLimit)) { //严重恶手const badChooseNode = document.createElement("span");badChooseNode.classList.add("badChoose");badChooseNode.classList.add("level1");badChooseNode.innerHTML = ` \u00A0\u00A0\u00A0${i18nText.badMove}${i18nText.badMoveUp}`;turnInfo.appendChild(badChooseNode);collapseEntry.classList.add("collapseEntryL1");PageExtend.badChooseNum++;//收集数据3-恶手let nameID = "badChoose-1-" + strArray2.length;collapseEntry.id = nameID + "-main"; //设置子项idstrArray2.push({name: kyokuTitleEle.textContent + " " + summary.textContent,parentHref: kyokuTitleEle.href.match(/#[\S\s]+$/)[0],badChooseType: 1,//id: nameID + "-main", //idtype: "id",nameID: nameID});getData(strArray3, collapseEntry, kyokuTitleEle, summary, 1);}else if (chosenWeight <= parseFloat(globalSettings.Config.badMoveUpperLimitCustom)) { //普通恶手const badChooseNode = document.createElement("span");badChooseNode.classList.add("badChoose");badChooseNode.classList.add("level2");badChooseNode.innerHTML = ` \u00A0\u00A0\u00A0${i18nText.badMove}${i18nText.badMoveDown}`;turnInfo.appendChild(badChooseNode);collapseEntry.classList.add("collapseEntryL2");PageExtend.badChooseNumCustom++;//收集数据3-恶手let nameID = "badChoose-2-" + strArray2.length;collapseEntry.id = nameID + "-main"; //设置子项idstrArray2.push({name: kyokuTitleEle.textContent + " " + summary.textContent,parentHref: kyokuTitleEle.href.match(/#[\S\s]+$/)[0],badChooseType: 2,//id: nameID + "-main", //idtype: "id",nameID: nameID});getData(strArray3, collapseEntry, kyokuTitleEle, summary, 2);}else{getData(strArray3, collapseEntry, kyokuTitleEle, summary);}} //for//Console.orig.log("end MainApp.showBadChoose()");UI.addValueByLoadingProgress(20);}//修改 元数据 选项卡static async alterMetaData() {//Console.orig.log("MainApp.alterMetaData()");let mortalMap = null;let mortal_New = null;let allMap = null;try {// 获取不同语言的 Mortal 版本let allJsonStr = await localforage.getItem("Mortal_All");if(allJsonStr != null) {allMap = new Map(Object.entries(JSON.parse(allJsonStr)));}// 新增 显示 Mortal 版本const jsonStr = await localforage.getItem("Mortal_Type");mortal_New = await localforage.getItem("Mortal_New");if(jsonStr != null) {let obj = Object.entries(JSON.parse(jsonStr));mortalMap = new Map(obj);}} catch (err) {Console.orig.error("获取数据时发生错误: \n" + err);}// 修改 元数据 选项卡let metaData = null;const detailsElements = document.getElementsByTagName("details");for (let i = 0; i < detailsElements.length; i++) {const details = detailsElements[i];const summary = details.firstChild;if (summary.firstChild.textContent == i18nText.metaData) {metaData = details;metaData.toggleAttribute("open", true); //打开 元数据 选项卡break;}}const metaDataDL = metaData.lastChild;let matchRatioDD = null;let version = null;for (let i = 0; i < metaDataDL.childNodes.length; i++) {const metaDataChild = metaDataDL.childNodes[i];if(metaDataChild.nodeName == "DT" && metaDataChild.textContent == i18nText.modelTag) {let ele = metaDataDL.childNodes[i + 1];//判断当前是否是最新版本的mortalif(mortalMap != null) {let mortalValue = mortalMap.get(ele.innerText);if(mortalValue != undefined) {let currVer = mortalValue.match('(?<=").*?(?=")')[0]; //匹配引号里的内容if(CustomUtils.compareVersion(currVer, "3.1") == -1) {let aiEle = metaDataDL.childNodes[i - 1];aiEle.classList.add("color1");aiEle.innerText = aiEle.innerText + ` \u00A0\u00A0\u00A0${i18nText.badMoveError}`;}}}//处理当前版本const oldHandle = ()=>{ //设置为上次访问主页记录的数据if(mortalMap != null) {let mortalValue = mortalMap.get(ele.innerText);if(mortalValue != undefined) {ele.innerText = mortalValue;}}}//设置为之前访问主页记录的多语言数据const pageLanguage = g_origLang;if(allMap != null) {let targetMortalMap = allMap.get(pageLanguage);if(targetMortalMap != undefined){let mortalValue = targetMortalMap.get(ele.innerText);if(mortalValue != undefined) {ele.innerText = mortalValue;}}else{ //目标数据中没有目标语言oldHandle();}}else{ //没有目标数据oldHandle();}}if (metaDataChild.nodeName == "DT" && metaDataChild.textContent == i18nText.matchRatio) {matchRatioDD = metaDataDL.childNodes[i + 1];version = metaDataDL.childNodes[i + 2];//metaDataDL.childNodes[i-2].classList.add("color2"); //ratingmetaDataDL.childNodes[i-1].classList.add("color2");metaDataDL.childNodes[i].classList.add("color2"); //AI 一致率metaDataDL.childNodes[i+1].classList.add("color2");break;}}const matchRatioText = matchRatioDD.textContent;const chooseNumStr = matchRatioText.substring(matchRatioText.indexOf("/") + 1);const chooseNum = parseInt(chooseNumStr);const badChooseRatioDT = document.createElement("dt");badChooseRatioDT.classList.add("color3");badChooseRatioDT.innerHTML = `${i18nText.badMoveRatio} ${globalSettings.Config.badMoveUpperLimit}%`;const badChooseRatioDD = document.createElement("dd");badChooseRatioDD.classList.add("color3");badChooseRatioDD.innerHTML = `${PageExtend.badChooseNum}/${chooseNum} = ${(100 * PageExtend.badChooseNum / chooseNum).toFixed(3)}%`;metaDataDL.insertBefore(badChooseRatioDD, version);metaDataDL.insertBefore(badChooseRatioDT, badChooseRatioDD);/* 新增 计算总恶手数 */PageExtend.badChooseNumCustom += PageExtend.badChooseNum; //计算总恶手数const badChooseRatioDT2 = document.createElement("dt");badChooseRatioDT2.classList.add("color3");badChooseRatioDT2.innerText = `${i18nText.badMoveRatio} ${globalSettings.Config.badMoveUpperLimitCustom}%`;const badChooseRatioDD2 = document.createElement("dd");badChooseRatioDD2.classList.add("color3");badChooseRatioDD2.innerHTML = `${PageExtend.badChooseNumCustom}/${chooseNum} = ${(100 * PageExtend.badChooseNumCustom / chooseNum).toFixed(3)}%`;metaDataDL.insertBefore(badChooseRatioDD2, version);metaDataDL.insertBefore(badChooseRatioDT2, badChooseRatioDD2);//Console.orig.log("end MainApp.alterMetaData()");UI.addValueByLoadingProgress(10);}//起始信息详细化static async showStartInfo() {//Console.orig.log("MainApp.showStartInfo()");/* 起始信息详细化 */function parmeHandle(eastScoreChange, southScoreChange, westScoreChange, northScoreChange) {let scoreArray = [{sc: eastScoreChange, i: 0}, {sc: southScoreChange, i: 1}, {sc: westScoreChange, i: 2}, {sc: northScoreChange, i: 3}];let newScoreArray = scoreArray.filter((obj) => {return obj.sc != 0;});let scoreAddArray = newScoreArray.filter((obj) => { //荣和的玩家return obj.sc > 0;});let scor###bArray = newScoreArray.filter((obj) => { //放铳的玩家return obj.sc < 0;});scoreAddArray.sort((a,b)=>{return b.sc-a.sc});return {scoreArray: scoreArray, newScoreArray: newScoreArray, scoreAddArray: scoreAddArray, scor###bArray: scor###bArray};}function handleRon(kyoku, startPlayerIndex, eastScoreChange, southScoreChange, westScoreChange, northScoreChange) { //处理荣和let obj = parmeHandle(eastScoreChange, southScoreChange, westScoreChange, northScoreChange);let scoreAddArray = obj.scoreAddArray;let scor###bArray = obj.scor###bArray;let selfPlayerIndex = MJCommonUtils.getPlayerIndexByPlayerSeatName(MJCommonUtils.getPlayerSeatNameByPlayerIndex(startPlayerIndex, kyoku, OUTSTYLE.B), OUTSTYLE.B);//let str = "";for (let i = 0; i < scoreAddArray.length; i++) {const scoreAdd = scoreAddArray[i];let scAddPlayerSeatName = MJCommonUtils.getPlayerSeatNameByPlayerIndex(scoreAdd.i, kyoku, OUTSTYLE.B); //荣和的玩家let scAddPlayerViewName = MJCommonUtils.getSelfViewPlayerNameByTargetPlayerIndex(selfPlayerIndex, MJCommonUtils.getPlayerIndexByPlayerSeatName(scAddPlayerSeatName, OUTSTYLE.B));let scSubPlayerSeatName = MJCommonUtils.getPlayerSeatNameByPlayerIndex(scor###bArray[0].i, kyoku, OUTSTYLE.B); //放铳的玩家let scSubPlayerViewName = MJCommonUtils.getSelfViewPlayerNameByTargetPlayerIndex(selfPlayerIndex, MJCommonUtils.getPlayerIndexByPlayerSeatName(scSubPlayerSeatName, OUTSTYLE.B));str += `${scAddPlayerSeatName} (${scAddPlayerViewName}) ` +`<span class="color4">${i18nText.Ron}</span> ${scSubPlayerSeatName} (${scSubPlayerViewName}) +${scoreAdd.sc} ${scor###bArray[0].sc}`;}return str;}function handleTsumo(kyoku, startPlayerIndex, eastScoreChange, southScoreChange, westScoreChange, northScoreChange) { //处理自摸}function handleRyuukyoku(kyoku, startPlayerIndex, eastScoreChange, southScoreChange, westScoreChange, northScoreChange) { //处理流局}const summaryEle = document.getElementsByClassName("kyoku-toc")[0];if(summaryEle == undefined)return;summaryEle.classList.add("position_re");for (let j = 0; j < summaryEle.children.length; j++) {const summary = summaryEle.children[j];summary.classList.add("min-w-20"); //}summaryEle.children[1].classList.add("position_ab");summaryEle.children[1].classList.add("l-196px"); //const kyokuEle = summaryEle.getElementsByTagName("a");const endInfoEle = summaryEle.getElementsByClassName("end-status");//const section = document.getElementsByTagName("section");for (let i = 0, length = section.length; i != length; ++i) {const titleEle = section[i].children[0];const titleKyokuEle = titleEle.getElementsByTagName("a"); //只有1个元素const titleEndInfoEle = titleEle.getElementsByClassName("end-status"); //只有1个元素const tenhouData = section[i].getElementsByTagName("iframe")[0].src;const playerIndexStr = tenhouData.match(/tw=[0-3]/)[0];const startPlayerIndex = parseInt(playerIndexStr.substring(playerIndexStr.length -1)); //起始玩家索引const json = JSON.parse(decodeURI(tenhouData.substring(tenhouData.indexOf("{")))); //天凤对局数据const kyoku = json.log[0][0][0]; //局数const count = json.log[0][0][1]; //本场数const currScore = json.log[0][1]; //当前点数const scoreChange = json.log[0][json.log[0].length -1]; //点数变动const endMode = scoreChange[0];//解析规则if(matchRule.isInit == false) {const disp = json.rule.disp;if(disp.indexOf("間") != -1) { //雀魂matchRule.isRon3 = true;//Console.orig.log("雀魂牌谱");}else if(disp.indexOf("Player") != -1) { //一番街matchRule.isRon3 = false;//Console.orig.log("一番街牌谱");}else{ //默认为天凤matchRule.isRon3 = false;//Console.orig.log("默认为天凤牌谱(包括自定义牌谱)");}matchRule.isInit = true;}//四家当前分数const eastScore = currScore[0]; //东const southScore = currScore[1]; //南const westScore = currScore[2]; //西const northScore = currScore[3]; //北//四家分数变化(直接) //送棒的-1000没有显示let eastScoreChange = [];let southScoreChange = [];let westScoreChange = [];let northScoreChange = [];let ronCount = (scoreChange.length -1) / 2;if(scoreChange.length > 1) { //比如九种九牌, 是没有分数变化的数据的//是否有多家荣和for (let j = 0; j < ronCount; j++) { //处理可能的多家荣和eastScoreChange.push(scoreChange[1+ j*2][0]); //东southScoreChange.push(scoreChange[1+ j*2][1]); //南westScoreChange.push(scoreChange[1+ j*2][2]); //西northScoreChange.push(scoreChange[1+ j*2][3]); //北}}//判断模式if(endMode == tenhouText.Ron) { //自摸、荣和let str = "";for (let j = 0; j < ronCount; j++) { //处理可能的多家荣和if(j>0) //处理多家str += ", ";let obj = parmeHandle(eastScoreChange[j], southScoreChange[j], westScoreChange[j], northScoreChange[j]);if(eastScoreChange[j] == 0 ||southScoreChange[j] == 0 ||westScoreChange[j] == 0 ||northScoreChange[j] == 0) { //如果有任何一家分数变动为0, 则为荣和str += handleRon(kyoku, startPlayerIndex, eastScoreChange[j], southScoreChange[j], westScoreChange[j], northScoreChange[j]);}if(obj.scoreAddArray.length == 3) { //判断是否是三家荣和if(matchRule.isRon3) //如果启用了三家和了的规则 //? 可能是没有必要的判断? 等待使用三种游戏牌谱分别进行查证str += handleRon(kyoku, startPlayerIndex, eastScoreChange[j], southScoreChange[j], westScoreChange[j], northScoreChange[j]);else{ //流局 //? 可能是没有必要的判断? 等待使用三种游戏牌谱分别进行查证// let str = handleRyuukyoku(kyoku, startPlayerIndex, eastScoreChange[j], southScoreChange[j], westScoreChange[j], northScoreChange[j]);// Console.orig.log(str);}}else{ //否则都是自摸// str += handleTsumo(kyoku, startPlayerIndex, eastScoreChange[j], southScoreChange[j], westScoreChange[j], northScoreChange[j]);}}//forif(str.length > 0) {const span = document.createElement("span");span.innerHTML = ` \u00A0\u00A0\u00A0` + str;endInfoEle[i].parentElement.appendChild(span);let lineNum = CustomUtils.getTextLineNum(span.parentNode);if(lineNum > 1){ //如果新添加的文字有多行, 则进行对齐for (let j = 1; j < lineNum; j++) {summaryEle.children[0].insertBefore(document.createElement("br"), kyokuEle[i].parentElement.nextElementSibling);}}}}else if(endMode == tenhouText.Ryuukyoku){ //荒牌流局 //流局, 如果有分数改变则处理// let str = handleRyuukyoku(kyoku, startPlayerIndex, eastScoreChange[j], southScoreChange[j], westScoreChange[j], northScoreChange[j]);// Console.orig.log(str);}else if(endMode == tenhouText.RyuukyokuTsumo) { //流局满贯 (等同于自摸8000)// let str = handleRyuukyoku(kyoku, startPlayerIndex, eastScoreChange[j], southScoreChange[j], westScoreChange[j], northScoreChange[j]);// Console.orig.log(str);}else if(endMode == tenhouText.RyuukyokuType1) { //九种九牌 //流局, 如果有分数改变则处理// let str = handleRyuukyoku(kyoku, startPlayerIndex, eastScoreChange[j], southScoreChange[j], westScoreChange[j], northScoreChange[j]);// Console.orig.log(str);}else{ //四风连打、四杠散了、四家立直 //三家和了 //流局, 如果有分数改变则处理// let str = handleRyuukyoku(kyoku, startPlayerIndex, eastScoreChange[j], southScoreChange[j], westScoreChange[j], northScoreChange[j]);// Console.orig.log(str);}const span = document.createElement("span");let str = MJCommonUtils.getPlayerSeatNameByPlayerIndex(startPlayerIndex, kyoku, OUTSTYLE.A);span.innerText = ` \u00A0\u00A0\u00A0` + str;span.classList.add("position_ab");span.classList.add("l-130px");if(str == i18nText.seatTypeA0) //东起span.classList.add("color5");elsespan.classList.add("color6");kyokuEle[i].parentElement.appendChild(span);titleKyokuEle[0].innerText += `\u00A0\u00A0` + str;titleKyokuEle[0].id = titleKyokuEle[0].href.match(/#[\S\s]+$/)[0] + "-main"; //设置父项id}//for//Console.orig.log("end MainApp.showStartInfo()");UI.addValueByLoadingProgress(20);}//列出选择权重static async showChooseWeight() {//Console.orig.log("MainApp.showChooseWeight()");/* 列出选择权重 */const defaultHandleFunc = (newNode, differData, index, colorStr) => {newNode.style.color = colorStr; //设置为目标颜色newNode.innerHTML = ` \u00A0\u00A0\u00A0` + eval("i18nText.badMoveDiffer" + index) + differData;}const map = new Map(); //使用map保证重置循坏后的唯一性let targClassName;if(g_origLang == "en"){targClassName = "l-210px";}else{targClassName = "l-170px";}const boxObj = {__proto__: null,left:0,top:0};const entry = document.getElementsByClassName("collapse entry");let spanSelf, spanMortal;for (let i = 0, length = entry.length; i != length; ++i) {entry[i].classList.add("position_re");const roleEle = entry[i].getElementsByClassName("role");let selfPai = roleEle[0].parentElement;let mortalPai = roleEle[1].nextElementSibling;if(mortalPai.tagName.toLocaleLowerCase() == 'details') {mortalPai = roleEle[1].nextSibling;}if (DOMTypeUtils.isSVGElement(selfPai.childNodes[selfPai.childNodes.length -2])) {if(selfPai.childNodes[selfPai.childNodes.length -2].tagName.toLocaleLowerCase() == 'svg') { //如果有多张牌图片,就使用最后一张牌图片selfPai = selfPai.childNodes[selfPai.childNodes.length -2];}}if(mortalPai.nextElementSibling.tagName.toLocaleLowerCase() == 'svg') { //如果有多张牌图片,就使用最后一张牌图片mortalPai = mortalPai.nextElementSibling;}const dataEle = entry[i].getElementsByTagName("tbody")[0].childNodes;let selfPaiData = 0;let mortalPaiData = 0;let selfBoxObj = { ...boxObj }, mortalBoxObj = { ...boxObj }; //复制对象map.clear(); //清除maplet j = 0, size = dataEle.length;while (j != size) {let selfPaiStr = null;let mortalPaiStr = null;let isResetLoop = false; //是否重置循坏if(selfPai != null){if(DOMTypeUtils.isTextElement(selfPai)) {selfPaiStr = selfPai.data;}else{let obj = selfPai.getElementsByClassName("face");if(obj[0] != null){selfPaiStr = obj[0].href.baseVal;}else{ //选择跳过的情况selfPaiStr = selfPai.childNodes[selfPai.childNodes.length -1].data;}}}if(mortalPai != null){if(DOMTypeUtils.isTextElement(mortalPai)) {mortalPaiStr = mortalPai.data;}else{let obj = mortalPai.getElementsByClassName("face");if(obj[0] != null){mortalPaiStr = obj[0].href.baseVal;}else{ //选择跳过的情况mortalPaiStr = mortalPai.childNodes[mortalPai.childNodes.length -1].data;}}}let data = dataEle[j].childNodes[2].innerHTML.replace(/<.*?>/g, ""); //过滤html标签, 只保留文字内容let obj1 = dataEle[j].childNodes[0].getElementsByClassName("face");let dataPaiStr;if(obj1[obj1.length - 1] != null) { // 如果有多张牌, 则选择最后一张牌作为对比牌 (主要用于吃的情况、碰杠这些牌都是一样的)dataPaiStr = obj1[obj1.length - 1].href.baseVal;}else{dataPaiStr = dataEle[j].childNodes[0].innerHTML;if(map.has(j) == false) {map.set(j, true); //保存当前j的值,防止重复开始循坏j = 0; //如果 有 选择跳过的情况, 则重新开始循坏, 以找到正确的数据isResetLoop = true;}}if(selfPaiStr == dataPaiStr) { //如果目标操作是自己的操作selfPaiData = data;spanSelf = document.createElement("span");spanSelf.innerText = ` \u00A0\u00A0\u00A0` + data;spanSelf.classList.add("position_ab");if(selfBoxObj.top == 0) {if(!isNaN(selfPai.offsetTop)) {selfBoxObj.top = (selfPai.offsetTop + selfPai.offsetHeight / 2 - 10);}else if(!isNaN(selfPai.parentElement.offsetTop)) {selfBoxObj.top = (selfPai.parentElement.offsetTop + selfPai.parentElement.offsetHeight / 2 - 10);}else{Console.orig.error("[dom struct inconsistency] source:", "selfPai");Console.orig.log("[debug]", `i: ${i}`);}spanSelf.style.top = selfBoxObj.top + 2 + "px";}spanSelf.classList.add(targClassName); //l-170px //l-210pxentry[i].insertBefore(spanSelf, entry[i].childNodes[3].nextSibling);selfPai = null; //置null, 防止继续计算}else if(mortalPaiStr == dataPaiStr) { //如果目标操作是Mortal的操作mortalPaiData = data;spanMortal = document.createElement("span");spanMortal.innerText = ` \u00A0\u00A0\u00A0` + data;spanMortal.classList.add("position_ab");if(mortalBoxObj.top == 0) {if(!isNaN(mortalPai.offsetTop)) {mortalBoxObj.top = (mortalPai.offsetTop + mortalPai.offsetHeight / 2 - 10);}else if(!isNaN(mortalPai.previousElementSibling.offsetTop)) {mortalBoxObj.top = (mortalPai.previousElementSibling.offsetTop + mortalPai.previousElementSibling.offsetHeight / 2 - 10);}else if(!isNaN(mortalPai.previousElementSibling.previousElementSibling.offsetTop)) {mortalBoxObj.top = (mortalPai.previousElementSibling.previousElementSibling.offsetTop + mortalPai.previousElementSibling.previousElementSibling.offsetHeight / 2 - 10);}else{Console.orig.error("[dom struct inconsistency] source:", "mortalPai");Console.orig.log("[debug]", `i: ${i}`);}spanMortal.style.top = mortalBoxObj.top + 1 + "px";}spanMortal.classList.add(targClassName); //l-170px //l-210pxif(DOMTypeUtils.isTextElement(mortalPai.nextSibling)) { //如果有多张牌图片,就使用最后一张牌图片后面的文字的位置mortalPai = mortalPai.nextSibling;}entry[i].insertBefore(spanMortal, mortalPai.nextSibling);mortalPai = null; //置null, 防止继续计算}if(selfPaiStr == mortalPaiStr) { //如果自己选择打出的牌与mortal选择打出的牌相同if(map.has(j) == false) {map.set(j, true); //保存当前j的值,防止重复开始循坏j = 0; //如果 有 选择跳过的情况, 则重新开始循坏, 以找到正确的数据isResetLoop = true;}}if(selfPai == null && mortalPai == null) { //是否处理完毕break; //跳出循坏}if(isResetLoop == false){ //不重置循坏时, index++++j;}}//for/* 计算自己的选择与mortal选择的差值 */if(mortalPaiData == selfPaiData) //忽略自己和mortal打出的牌一样的结果continue;const differData = Math.constructor.roundEx(Math.abs(mortalPaiData - selfPaiData), 5); //保留5位小数const turnInfo = entry[i].children[0].children[0];const newNode = document.createElement("span");newNode.classList.add("font_weight_400");if (differData < 5) { //微差defaultHandleFunc(newNode, differData, 1, "#000"); //黑色}else if (differData < 10) { //小幅差距defaultHandleFunc(newNode, differData, 2, "#996633"); //褐色}else if (differData < 20) { //低等差距defaultHandleFunc(newNode, differData, 3, "#009966"); //淡绿}else if (differData < 40) { //中等差距defaultHandleFunc(newNode, differData, 4, "#3399FF"); //淡蓝}else if (differData < 60) { //高等差距defaultHandleFunc(newNode, differData, 5, "#3333CC"); //深蓝}else if (differData < 80) { //大幅度差距defaultHandleFunc(newNode, differData, 6, "#CC0099"); //淡红}else{ //压倒性差距defaultHandleFunc(newNode, differData, 7, "#f00"); //红色}turnInfo.appendChild(newNode);let lineNum = CustomUtils.getTextLineNum(turnInfo);let offsetTopDiffValue = Math.abs(roleEle[0].offsetTop - spanSelf.offsetTop);if(lineNum > 1 && offsetTopDiffValue >= 5){ //如果新添加的文字有多行, 则进行对齐 //移动设备x轴分辨率较小,并且各种设备分辨率不同,就会导致可能不需要对齐let lineHeight = CustomUtils.getStyleOfLineHeight(turnInfo);let origTopSelf = parseFloat(spanSelf.style.top);let origTopMortal = parseFloat(spanMortal.style.top);spanSelf.style.top = origTopSelf + lineHeight + "px";spanMortal.style.top = origTopMortal + lineHeight + "px";}}//for//Console.orig.log("end MainApp.showChooseWeight()");UI.addValueByLoadingProgress(20);}}class MainApp extends MortalBase {static perfor = new Performance("MainApp");static badChooseMap = new Map(); //恶手选择器static disable_kyokuChoose = false;static disable_badChoose = false;static disable_diffChoose = false;constructor(){//super();}static init(){//Console.orig.log("MainApp.init()");if(Debug.getDebug){CodeTemplate.autoWired_Performance("MainApp.run", MainApp);}return true;}static async run(){//Console.orig.log("MainApp.run()");const [r###lt1, r###lt2] =//显示恶手 (收集数据) //起始信息详细化 (独立) (重要) //列出选择权重 (独立)(await Promise.all([MainApp.createCatalogUI(), MainApp.createSettingUI()].map(item => item.catch(err => Debug.globalErrorHandle(err))) //每个并发请求使用catch进行对应异常的处理,防止某个请求失败后,影响到其他函数执行));//Console.orig.log(r###lt1, r###lt2);await MainApp.end();}static async end() {//Console.orig.log("MainApp.end()");/* 将页面滚动导致的元素可见性改变与选择器绑定 (全局滚动) */let section = document.getElementsByTagName("section");let curKyoku; //局数let curCount; //本场数const globalScrollHandle = (e) => {const scrollTop = document.scrollingElement.scrollTop;const heightOffset = document.scrollingElement.clientHeight / 2;for (let i = 0; i < section.length; i++) {if ((section.length == 1) || //比如自定义牌谱,只有1局,就跳过后续的可见性测试(i == 0 && scrollTop +heightOffset < section[1].offsetTop) || //第一个节点高度范围 //0~第二个元素之间的高度 //小于第二个元素的高度(i == section.length -1 && scrollTop >= section[section.length -2].offsetTop) || //最后一个节点高度范围 //大于倒数第二个元素的高度(scrollTop +heightOffset >= section[i].offsetTop && scrollTop +heightOffset < section[i+1].offsetTop)) { //中间的节点高度范围 //相邻的两个节点之间const titleEle = section[i].children[0];const titleKyokuEle = titleEle.getElementsByTagName("a"); //只有1个元素//Console.orig.log("可见: " + titleKyokuEle[0].innerText, titleKyokuEle[0].id);if(!MainApp.disable_kyokuChoose){let targetEle = document.getElementById(titleKyokuEle[0].id.replace("-main", ""));DomUtils.setNewActivateEle(targetEle, "layui-this");}[curKyoku, curCount] = titleKyokuEle[0].id.split("-").filter((item) => {return item.match(/\d+/); //过滤非数字});let curkyokuMode = MJCommonUtils.normalKyokuToKyokuMode(curKyoku); //标准局数 转 东南西场 (1=东 2=南 3=西)let textKyoku = MJCommonUtils.normalKyokuToTextKyoku(curKyoku, curkyokuMode); //标准局数 转 文本局数 (0=>1: 东一) (6=>3: 南3)//恶手选择器if(!MainApp.disable_badChoose){let badChooseArray = MainApp.badChooseMap.get(`${curkyokuMode}${textKyoku}${curCount}`);const handleBadChoose = (badChooseArray)=>{if(badChooseArray != undefined){for (let j = 0; j < badChooseArray.length; j++) {const badChooseEle = document.getElementById(badChooseArray[j]);if(j==0){ //第一个元素DomUtils.setNewActivateEle(badChooseEle, "layui-this");// if(badChooseArray.length == 1){ //只有1个元素// badChooseEle.classList.add("badChoose_border");// }// else{ //多个元素// badChooseEle.classList.add("badChoose_border_first");// }}else if(j == badChooseArray.length -1){ //最后一个// badChooseEle.classList.add("badChoose_border_last");}else{ //中间的// badChooseEle.classList.add("badChoose_border_middle");}}}else{Console.orig.error("badChooseArray is undefined");}};if(badChooseArray == undefined){ //如果当前的对局,比如东一,没有恶手,那么就是undefined //则继续向下查找有恶手的对局for (let index = i + 1; index < section.length; index++) {const titleEle = section[index].children[0];const titleKyokuEle = titleEle.getElementsByTagName("a"); //只有1个元素let targetEle = document.getElementById(titleKyokuEle[0].id.replace("-main", ""));[curKyoku, curCount] = titleKyokuEle[0].id.split("-").filter((item) => {return item.match(/\d+/); //过滤非数字});let curkyokuMode = MJCommonUtils.normalKyokuToKyokuMode(curKyoku); //标准局数 转 东南西场 (1=东 2=南 3=西)let textKyoku = MJCommonUtils.normalKyokuToTextKyoku(curKyoku, curkyokuMode); //标准局数 转 文本局数 (0=>1: 东一) (6=>3: 南3)badChooseArray = MainApp.badChooseMap.get(`${curkyokuMode}${textKyoku}${curCount}`); //循坏获取,直到获取到有恶手的对局if(badChooseArray != undefined){ //找到了就跳出break;}}}if(badChooseArray != undefined){// Console.orig.log("当前局数没有恶手,向后查找并设置!");handleBadChoose(badChooseArray);}else{// Console.orig.log("当前查看的对局没有恶手!");let ele = document.querySelector(".ui #selector2");if(ele.children.length == 0){// Console.orig.log("当前牌谱没有恶手!");}else{// Console.orig.log("设置为第一个!");DomUtils.setNewActivateEle(ele.children[0].children[0], "layui-this"); //设置为第一个}}}//不一致选择器if(!MainApp.disable_diffChoose){}MainApp.disable_kyokuChoose = false;MainApp.disable_badChoose = false;MainApp.disable_diffChoose = false;return;}//if}//for};window.addEventListener("scroll", CustomUtils.throttle(globalScrollHandle, 100));globalScrollHandle();/* 绑定按钮事件 */const Mode = {prev: "prev",next: "next",}const commonButClickEvent = (selectorStr, butName, mode)=>{let nextEle = null;let ele = document.querySelector(selectorStr);if(ele == undefined){Console.orig.warn("click", butName, "获取到的目标节点为空!"); return;}if(mode == Mode.prev){nextEle = ele.previousElementSibling;}else if(mode == Mode.next){nextEle = ele.nextElementSibling;}if(nextEle != null){nextEle = nextEle.children[0];DomUtils.setNewActivateEle(nextEle, "layui-this");nextEle.click();}};document.getElementById("but_kyoku_prev").addEventListener("click", function(e){let selectorName = ".ui #selector1 .layui-this";let ele = document.querySelector(selectorName);if(ele == undefined){Console.orig.warn("click", "but_kyoku_prev", "获取到的目标节点为空!"); return;}ele = ele.children[0];let idStr = ele.id; //let targetNode = document.getElementById(idStr + "-main");if(targetNode == null){Console.orig.error("idSelectorHandle", "获取到的目标节点为空!"); return;}let parentEle = targetNode.parentElement.parentElement.parentElement;let collapseEntry = parentEle.children[3].querySelectorAll(".collapse.entry");let liEle = ele.parentElement.parentElement.parentElement;if(!liEle.classList.contains("layui-nav-itemed")){ //自动打开选项卡liEle.classList.add("layui-nav-itemed");}if(!CustomUtils.isShowInClientWindowOfPositionModeAndScrollbar(ele)){ //如果没有显示出来,则定位到目标元素CustomUtils.setScrollToTargetNode(ele, "center");}if(!CustomUtils.isShowInClientWindowOfNodeArray(collapseEntry)){ //如果没有显示出来,则先定位到目标元素,再执行切换操作let changeEle = parentEle.getElementsByClassName("sticky")[0];changeEle.style.background = "transparent";CustomUtils.setHighlightShow(parentEle, 750, ()=>{changeEle.style.background = "";}); //高亮显示CustomUtils.setScrollToTargetNode(parentEle, "start");}else{commonButClickEvent(selectorName, "#but_kyoku_prev", Mode.prev);}}, false);document.getElementById("but_kyoku_next").addEventListener("click", function(e){let selectorName = ".ui #selector1 .layui-this";let ele = document.querySelector(selectorName);if(ele == undefined){Console.orig.warn("click", "but_kyoku_next", "获取到的目标节点为空!"); return;}ele = ele.children[0];let idStr = ele.id; //let targetNode = document.getElementById(idStr + "-main");if(targetNode == null){Console.orig.error("idSelectorHandle", "获取到的目标节点为空!"); return;}let parentEle = targetNode.parentElement.parentElement.parentElement;let collapseEntry = parentEle.children[3].querySelectorAll(".collapse.entry");let liEle = ele.parentElement.parentElement.parentElement;if(!liEle.classList.contains("layui-nav-itemed")){ //自动打开选项卡liEle.classList.add("layui-nav-itemed");}if(!CustomUtils.isShowInClientWindowOfPositionModeAndScrollbar(ele)){ //如果没有显示出来,则定位到目标元素CustomUtils.setScrollToTargetNode(ele, "center");}if(!CustomUtils.isShowInClientWindowOfNodeArray(collapseEntry)){ //如果没有显示出来,则先定位到目标元素,再执行切换操作let changeEle = parentEle.getElementsByClassName("sticky")[0];changeEle.style.background = "transparent";CustomUtils.setHighlightShow(parentEle, 750, ()=>{changeEle.style.background = "";}); //高亮显示CustomUtils.setScrollToTargetNode(parentEle, "start");}else{commonButClickEvent(selectorName, "#but_kyoku_next", Mode.next);}}, false);document.getElementById("but_diff_prev").addEventListener("click", function(e){let selectorName = ".ui #selector2 .layui-this";let ele = document.querySelector(selectorName);if(ele == undefined){Console.orig.warn("click", "but_diff_prev", "获取到的目标节点为空!"); return;}ele = ele.children[0];let idStr = ele.id; //let targetNode = document.getElementById(idStr + "-main");if(targetNode == null){Console.orig.error("idSelectorHandle", "获取到的目标节点为空!"); return;}let liEle = ele.parentElement.parentElement.parentElement;if(!liEle.classList.contains("layui-nav-itemed")){ //自动打开选项卡liEle.classList.add("layui-nav-itemed");}if(!CustomUtils.isShowInClientWindowOfPositionModeAndScrollbar(ele)){ //如果没有显示出来,则定位到目标元素CustomUtils.setScrollToTargetNode(ele, "center");}if(!CustomUtils.isShowInClientWindow(targetNode)){ //如果没有显示出来,则先定位到目标元素,再执行切换操作CustomUtils.setHighlightShow(targetNode, 750); //高亮显示CustomUtils.setScrollToTargetNode(targetNode, "center");}else{commonButClickEvent(selectorName, "#but_diff_prev", Mode.prev);}}, false);document.getElementById("but_diff_next").addEventListener("click", function(e){let selectorName = ".ui #selector2 .layui-this";let ele = document.querySelector(selectorName);if(ele == undefined){Console.orig.warn("click", "but_diff_next", "获取到的目标节点为空!"); return;}ele = ele.children[0];let idStr = ele.id; //let targetNode = document.getElementById(idStr + "-main");if(targetNode == null){Console.orig.error("idSelectorHandle", "获取到的目标节点为空!"); return;}let liEle = ele.parentElement.parentElement.parentElement;if(!liEle.classList.contains("layui-nav-itemed")){ //自动打开选项卡liEle.classList.add("layui-nav-itemed");}if(!CustomUtils.isShowInClientWindowOfPositionModeAndScrollbar(ele)){ //如果没有显示出来,则定位到目标元素CustomUtils.setScrollToTargetNode(ele, "center");}if(!CustomUtils.isShowInClientWindow(targetNode)){ //如果没有显示出来,则先定位到目标元素,再执行切换操作CustomUtils.setHighlightShow(targetNode, 750); //高亮显示CustomUtils.setScrollToTargetNode(targetNode, "center");}else{commonButClickEvent(selectorName, "#but_diff_next", Mode.next);}}, false);UI.addValueByLoadingProgress(10);//Console.orig.log("end MainApp.end()");}static stop(){}static clean(){}//static async createCatalogUI() {//Console.orig.log("MainApp.createCatalogUI()");const summaryEle = document.getElementsByClassName("kyoku-toc")[0];//数据处理let catalogUI = document.getElementById("catalogUI");DomUtils.setMoveable(catalogUI, true);layui.use(function(){let element = layui.element;let laytpl = layui.laytpl;let $ = layui.$;//收集数据1-对局let strArray1 = [];let array = summaryEle.children[0].children;let length = summaryEle.children[0].children.length;for (let j = 0; j < length; j++) {const ele = array[j];if(ele.children.length == 0)continue; //过滤<br>等没有子节点的单标签strArray1.push({name: ele.children[0].innerText + (ele.children[1] != undefined ? (" " + ele.children[1].innerText.trim()) : ""),type: "id",nameID: ele.children[0].href.match(/#[\S\s]+$/)[0],});}let data = { //模板数据selector1: strArray1, //对局选择器selector2: strArray2, //恶手选择器selector3: strArray3, //不一致选择器};//使用模板进行解析let compile = laytpl(catalogUITemplate); // 模板解析compile.render(data, (htmlStr)=>{$('#catalogUI').html(htmlStr);}); // 模板渲染// 渲染导航组件element.render('nav', 'selector-filter-nav');/* 新增右置对局选择器和切换器(固定/可滑动呼出) */let catalogUIBuf = document.getElementById("catalogUIBuf");let selectorGroups = document.getElementById("selectorGroups");let section = document.getElementsByTagName("section");let eleSection = section[0];selectorGroups.style.height = (document.scrollingElement.clientHeight - 100 - catalogUIBuf.offsetHeight -46) + "px"; //滚动条selectorGroups.style.paddingBottom = "46px";catalogUI.style.left = (eleSection.offsetLeft + eleSection.offsetWidth + 60) + "px"; //x位置let vTop = (eleSection.offsetTop - catalogUI.offsetHeight);catalogUI.style.top = 60 + "px"; //y位置/* 绑定选择器事件 */let selector1_a = document.querySelectorAll("#selector1 a"); //对局选择器 //name->idlet selector2_a = document.querySelectorAll("#selector2 a"); //恶手选择器 //idlet selector3_a = document.querySelectorAll("#selector3 a"); //不一致选择器 //namelet differMap = new Map(); //不一致选择器let nameIdSelectorHandle = function(e) {let idStr = e.target.name;let targetNode = document.getElementById(idStr + "-main");if(targetNode == null){Console.orig.error("nameIdSelectorHandle", "获取到的目标节点为空!"); return;}if(targetNode.id.indexOf("kyoku") != -1){let parentEle = targetNode.parentElement.parentElement.parentElement;let changeEle = parentEle.getElementsByClassName("sticky")[0];changeEle.style.background = "transparent";CustomUtils.setHighlightShow(parentEle, 750, ()=>{changeEle.style.background = "";}); //高亮显示CustomUtils.setScrollToTargetNode(targetNode, "start");}else{CustomUtils.setHighlightShow(targetNode, 750); //高亮显示CustomUtils.setScrollToTargetNode(targetNode, "center");}};let idSelectorHandle = function(e) {let idStr = e.target.id;let targetNode = document.getElementById(idStr + "-main");if(targetNode == null){Console.orig.error("idSelectorHandle", "获取到的目标节点为空!"); return;}if(targetNode.id.indexOf("kyoku") != -1){let parentEle = targetNode.parentElement.parentElement.parentElement;let changeEle = parentEle.getElementsByClassName("sticky")[0];changeEle.style.background = "transparent";CustomUtils.setHighlightShow(parentEle, 750, ()=>{changeEle.style.background = "";}); //高亮显示CustomUtils.setScrollToTargetNode(targetNode, "start");}else{CustomUtils.setHighlightShow(targetNode, 750); //高亮显示CustomUtils.setScrollToTargetNode(targetNode, "center");}};let nameSelectorHandle = function(e) {let idStr = e.target.name;let targetNode = document.getElementsByName(idStr + "-main");if(targetNode == null){Console.orig.error("nameSelectorHandle", "获取到的目标节点为空!"); return;}if(targetNode[0].id.indexOf("kyoku") != -1){let parentEle = targetNode.parentElement.parentElement.parentElement;let changeEle = parentEle.getElementsByClassName("sticky")[0];changeEle.style.background = "transparent";CustomUtils.setHighlightShow(parentEle, 750, ()=>{changeEle.style.background = "";}); //高亮显示CustomUtils.setScrollToTargetNode(targetNode[0], "start");}else{CustomUtils.setHighlightShow(targetNode[0], 750); //高亮显示CustomUtils.setScrollToTargetNode(targetNode[0], "center");}};//绑定 对局选择器for (let i = 0; i < selector1_a.length; i++) {const ele = selector1_a[i];ele.addEventListener('click', function(e){MainApp.disable_kyokuChoose = true;return idSelectorHandle(e);});}//绑定 恶手选择器for (let i = 0; i < selector2_a.length; i++) {const ele = selector2_a[i];//let idStr = ele.id; //let targetNode = document.getElementById(idStr + "-main");if(targetNode == null){Console.orig.error("idSelectorHandle", "获取到的目标节点为空!"); break;}let parentEle = targetNode.parentElement.parentElement;const tenhouData = parentEle.getElementsByTagName("iframe")[0].src;const json = JSON.parse(decodeURI(tenhouData.substring(tenhouData.indexOf("{")))); //天凤对局数据const kyoku = json.log[0][0][0]; //局数const count = json.log[0][0][1]; //本场数//东南西场 (1=东 2=南 3=西)let curkyokuMode = MJCommonUtils.normalKyokuToKyokuMode(kyoku); //标准局数 转 东南西场 (1=东 2=南 3=西)let textKyoku = MJCommonUtils.normalKyokuToTextKyoku(kyoku, curkyokuMode); //标准局数 转 文本局数 (0=>1: 东一) (6=>3: 南3)let key = `${curkyokuMode}${textKyoku}${count}`;let badChooseArray = MainApp.badChooseMap.get(key);if(badChooseArray == undefined)badChooseArray = [];badChooseArray.push(ele.id);MainApp.badChooseMap.set(key, badChooseArray);//ele.addEventListener('click', function(e){MainApp.disable_badChoose = true;return idSelectorHandle(e);});}//绑定 不一致选择器for (let i = 0; i < selector3_a.length; i++) {const ele = selector3_a[i];//let idStr = ele.name; //let targetNode = document.getElementsByName(idStr + "-main");if(targetNode == null){Console.orig.error("nameSelectorHandle", "获取到的目标节点为空!"); break;}let parentEle = targetNode[0].parentElement.parentElement;const tenhouData = parentEle.getElementsByTagName("iframe")[0].src;const json = JSON.parse(decodeURI(tenhouData.substring(tenhouData.indexOf("{")))); //天凤对局数据const kyoku = json.log[0][0][0]; //局数const count = json.log[0][0][1]; //本场数//东南西场 (1=东 2=南 3=西)let curkyokuMode = MJCommonUtils.normalKyokuToKyokuMode(kyoku); //标准局数 转 东南西场 (1=东 2=南 3=西)let textKyoku = MJCommonUtils.normalKyokuToTextKyoku(kyoku, curkyokuMode); //标准局数 转 文本局数 (0=>1: 东一) (6=>3: 南3)let key = `${curkyokuMode}${textKyoku}${count}`;let differArray = differMap.get(key);if(differArray == undefined)differArray = [];differArray.push(ele.name);differMap.set(key, differArray);//ele.addEventListener('click', function(e){MainApp.disable_diffChoose = true;return nameSelectorHandle(e);});}/* 对局选择器和切换器 详细化 (添加格外标记) *///收集数据-恶手选择器let strArrayMap = new Map();const countObject = {__proto__: null,badChooseNum: 0,badChooseNumCustom: 0,differNum: 0,};let badChooseType1 = strArray2.filter((obj) => {return obj.badChooseType == 1;});let badChooseType2 = strArray2.filter((obj) => {return obj.badChooseType == 2;});for (let i = 0; i < badChooseType1.length; i++) {const ele = badChooseType1[i];let idStr = ele.parentHref; //let parentEle = document.getElementById(idStr + "-main");if(parentEle == null){Console.orig.error("idSelectorHandle", "获取到的目标节点为空!"); return;}let obj = strArrayMap.get(idStr + "-main");if(obj == undefined){obj = { ...countObject }; //复制对象}obj.badChooseNum++;strArrayMap.set(idStr + "-main", obj);}for (let i = 0; i < badChooseType2.length; i++) {const ele = badChooseType2[i];let idStr = ele.parentHref; //let parentEle = document.getElementById(idStr + "-main");if(parentEle == null){Console.orig.error("idSelectorHandle", "获取到的目标节点为空!"); return;}let obj = strArrayMap.get(idStr + "-main");if(obj == undefined){obj = { ...countObject }; //复制对象}obj.badChooseNumCustom++;strArrayMap.set(idStr + "-main", obj);}//收集数据-不一致选择器for (let i = 0; i < strArray3.length; i++) {const ele = strArray3[i];let idStr = ele.parentHref; //let parentEle = document.getElementById(idStr + "-main");if(parentEle == null){Console.orig.error("idSelectorHandle", "获取到的目标节点为空!"); return;}let obj = strArrayMap.get(idStr + "-main");if(obj == undefined){obj = { ...countObject }; //复制对象}obj.differNum++;strArrayMap.set(idStr + "-main", obj);}// 处理数据for (let i = 0; i < strArray1.length; i++) {const ele = strArray1[i];let idStr = ele.nameID; //let targetEle = document.getElementById(idStr);if(targetEle == null){Console.orig.error("idSelectorHandle", "获取到的目标节点为空!"); return;}let obj = strArrayMap.get(idStr + "-main");if(obj != undefined){ //如果为undefined, 说明整局打的都与ai一致,无需处理if(obj.differNum > 0){Utils.addElementsByHTMLTemplateText(`<span class="${cssIDNP} layui-badge-dot l-129px"></span>`, targetEle);}if(obj.badChooseNum > 0){Utils.addElementsByHTMLTemplateText(`<span class="${cssIDNP} layui-badge l-155px bgColor1">${obj.badChooseNum}</span>`, targetEle);}if(obj.badChooseNumCustom > 0){Utils.addElementsByHTMLTemplateText(`<span class="${cssIDNP} layui-badge l-190px bgColor2">${obj.badChooseNumCustom}</span>`, targetEle);}}}//Console.orig.log("end MainApp.createCatalogUI()");});}//static async createSettingUI() {}}const app = new App();})((typeof unsafeWindow != 'undefined' ? unsafeWindow : window));