您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
AbemaTV のコメントをニコニコ風にスクロールさせます。
当前为
// ==UserScript== // @name AbemaTV Screen Comment Scroller // @namespace knoa.jp // @description AbemaTV のコメントをニコニコ風にスクロールさせます。 // @include https://abema.tv/* // @version 1.3.7 // @grant none // ==/UserScript== // console.log('AbemaTV? => hireMe()'); (function(){ const SCRIPTNAME = 'ScreenCommentScroller'; const DEBUG = false;//アベマの仕様変更に対応しました。 // delete localStorage['ScreenCommentScroller-configs']; if(window === top) console.time(SCRIPTNAME); const CONFIGS = [ /*スクロールコメント*/ {KEY: 'color', DEFAULT: '#ffffff', TYPE: 'string'},/*色*/ {KEY: 'ocolor', DEFAULT: '#000000', TYPE: 'string'},/*縁取り色*/ {KEY: 'owidth', DEFAULT: 0.05, TYPE: 'float' },/*縁取りの太さ(比率)*/ {KEY: 'maxlines', DEFAULT: 10, TYPE: 'int' },/*最大行数*/ {KEY: 'linemargin', DEFAULT: 0.2, TYPE: 'float' },/*行間(比率)*/ {KEY: 'opacity', DEFAULT: 0.50, TYPE: 'float' },/*不透明度*/ {KEY: 'hopacity', DEFAULT: 0.50, TYPE: 'float' },/*不透明度(マウスオーバー時)*/ /*一覧コメント*/ {KEY: 'lt_opacity', DEFAULT: 0.75, TYPE: 'float' },/*文字の不透明度*/ {KEY: 'lt_hopacity', DEFAULT: 1.00, TYPE: 'float' },/*文字の不透明度(マウスオーバー時)*/ {KEY: 'lb_opacity', DEFAULT: 0.25, TYPE: 'float' },/*背景の不透明度*/ {KEY: 'lb_hopacity', DEFAULT: 0.50, TYPE: 'float' },/*背景の不透明度(マウスオーバー時)*/ /*アニメーション*/ {KEY: 'duration', DEFAULT: 5, TYPE: 'float' },/*横断にかける秒数*/ {KEY: 'fps', DEFAULT: 60, TYPE: 'int' },/*秒間コマ数*/ ]; const AINTERVAL = 5;/*AbemaTVのコメント取得間隔の仕様値*/ const ADELAYS = {/*AbemaTVのコメント取得時の投稿時刻を(AINTERVAL)まで用意しておく*/ '今': 0, '1秒前': 1, '2秒前': 2, '3秒前': 3, '4秒前': 4, '5秒前': 5, }; /* サイト定義 */ let site = { targets: [ /* 構造 */ function header(){let header = document.querySelector('body > div > div > header'); return (header) ? site.use(header) : null;}, function footer(){let fullscreen = document.querySelector('button[aria-label="フルスクリーン表示"]'); return (fullscreen) ? site.use(fullscreen.parentNode.parentNode) : null;}, function board(){let board = document.querySelector('div[aria-hidden] form + div > div'); return (board) ? site.use(board) : null;}, function screen(){let loading = document.querySelector('img[src="/images/misc/feed_loading.gif"]'); return (loading) ? site.use(loading.parentNode.parentNode) : null;}, /* ペイン */ function commentPane(){let form = document.querySelector('form:not([role="search"])'); return (form) ? site.use(form.parentNode.parentNode) : null;}, function channelPane(){let list = document.querySelector('use[*|href^="/images/icons/list.svg"]'); return (list) ? site.use(list.parentNode.parentNode.parentNode.parentNode.nextElementSibling) : null;}, function programPane(){let list = document.querySelector('use[*|href^="/images/icons/list.svg"]'); return (list) ? site.use(list.parentNode.parentNode.parentNode.parentNode.nextElementSibling.nextElementSibling) : null;}, /* ボタン */ function channelButtons(){let list = document.querySelector('use[*|href^="/images/icons/list.svg"]'); return (list) ? site.use(list.parentNode.parentNode.parentNode.parentNode) : null;}, function channelButton(){let list = document.querySelector('use[*|href^="/images/icons/list.svg"]'); return (list) ? site.use(list.parentNode.parentNode) : null;}, function commentButton(){let svg = document.querySelector('use[*|href^="/images/icons/comment.svg"]'); return (svg) ? site.use(svg.parentNode.parentNode) : null;}, function programButton(){let button = document.querySelector('button[aria-label="フルスクリーン表示"] + div + div > div > div'); return (button) ? site.use(button) : null;}, function fullscreenButton(){let fullscreen = document.querySelector('button[aria-label="フルスクリーン表示"]'); return (fullscreen) ? site.use(fullscreen) : null;}, function closer(){let commentForm = document.querySelector('form:not([role="search"])'); return (commentForm) ? site.use(commentForm.parentNode.parentNode.nextElementSibling) : null;}, /* 表示要素 */ function audience(){let loading = document.querySelector('img[src="/images/misc/feed_loading.gif"]'); return (loading) ? site.use(loading.parentNode.parentNode.firstChild.firstChild) : null;}, function programName(){let name = document.querySelector('button[aria-label="フルスクリーン表示"] + div + div div > p > span > span:last-child'); return (name) ? site.use(name) : null;}, function notice(){let header = document.querySelector('header'); return (header) ? site.use(header.nextElementSibling) : null;}, ], getComments: function(target){return (target.querySelectorAll) ? target.querySelectorAll('div[aria-hidden] form + div > div > div > div > div > p:first-child') : null}, use: function use(target){ const cid = 'selectorId'/*camelCase*/, sid = 'selector-id'/*snake-case*/; target.dataset[cid] = use.caller.name; selectors[use.caller.name] = `${target.localName}[data-${sid}="${use.caller.name}"]`; elements[use.caller.name] = target; return true; }, }; /* 処理本体 */ let elements = {}, selectors = {}, canvas, context, lines = [], fontsize, interval, configButton, configPanel, configs = {}, style; let core = { /* 初期化 */ initialize: function(){ let previousUrl = ''; /* 一度だけ */ html = document.documentElement; core.config.read(); window.addEventListener('resize', setTimeout.bind(null, core.modify, 1000)); /* URLの変化を見守る */ setInterval(function(){ if(location.href === previousUrl) return;/*URLが変わってない*/ /* テレビ視聴ページ */ if(location.href.startsWith('https://abema.tv/now-on-air/')){ if(previousUrl.startsWith('https://abema.tv/now-on-air/')) return;/*チャンネルを変えただけ*/ html.classList.add(SCRIPTNAME); core.ready(); /* テレビ視聴ページではない */ }else{ html.classList.remove(SCRIPTNAME); } previousUrl = location.href; }, 1000); }, /* URLが変わるたびに呼ぶ */ ready: function(e){ /* 必要な要素が出揃うまで粘る */ for(let i = 0; site.targets[i]; i++) if(site.targets[i]() === null) return setTimeout(core.ready, 1000); core.addStyle(); core.config.createButton(); /* 開けるようになったら自動で開く */ observe(elements.commentButton, function(records){ if(getComputedStyle(this).cursor === 'pointer'){ this.click(); } }, {attributes: true}); /* 設定画面を用意する */ core.config.createButton(); /* コメントをスクロールさせるCanvasの設置 */ /* (描画処理の軽さは HTML5 Canvas, CSS Position Left, CSS Transition の順) */ core.createCanvas(); /* メイン処理 */ core.listenComments(); core.scrollComments(); }, /* canvas作成 */ createCanvas: function(){ if(canvas) return; canvas = document.createElement('canvas'); canvas.id = SCRIPTNAME; elements.screen.appendChild(canvas); context = canvas.getContext('2d'); core.modify(); }, /* スクリーンサイズに変化があればcanvasも変化させる */ modify: function(){ canvas.width = elements.screen.offsetWidth; canvas.height = elements.screen.offsetHeight; fontsize = (canvas.height / configs.maxlines) / (1 + configs.linemargin); context.font = 'bold ' + (fontsize) + 'px sans-serif'; context.fillStyle = configs.color; context.strokeStyle = configs.ocolor; context.lineWidth = fontsize * configs.owidth; }, /* コメントの新規追加を見守る */ listenComments: function(){ if(elements.board.isListening) return; elements.board.isListening = true; elements.board.addEventListener('DOMNodeInserted', function(e){ let comments = site.getComments(e.target); if(!comments || !comments.length) return;/*新着コメントの追加でなければ終了*/ /*投稿経過時間に合わせた時間差を付けることで自然に流す*/ let earliest = ADELAYS[comments[comments.length - 1].nextElementSibling.textContent];/*同時取得の中で最初に投稿されたコメントの経過時間*/ if(earliest === undefined) earliest = AINTERVAL; for(let i = 0; comments[i]; i++){ let current = ADELAYS[comments[i].nextElementSibling.textContent]; if(current === undefined) current = AINTERVAL; window.setTimeout(function(){ core.attachComment(comments[i]); }, 1000 * (earliest - current)); } }); }, /* コメントが追加されるたびにスクロールキューに追加 */ attachComment: function(comment){ let record = {}; record.text = comment.textContent;/*流れる文字列*/ record.width = context.measureText(record.text).width;/*文字列の幅*/ record.ppms = (canvas.width + record.width) / (configs.duration * 1000);/*ミリ秒あたり移動距離*/ record.start = Date.now();/*開始時刻*/ record.reveal = record.start + (record.width / record.ppms);/*文字列が右端から抜ける時刻*/ record.touch = record.start + (canvas.width / record.ppms);/*文字列が左端に触れる時刻*/ record.end = record.start + (configs.duration * 1000);/*終了時刻*/ record.left = canvas.width;/*左端からの距離(描画位置)*/ /* 追加されたコメントをどの行に流すかを決定する */ for(let i=0; i < configs.maxlines; i++){ let length = lines[i] ? lines[i].length : 0;/*同じ行に詰め込まれているコメント数*/ switch(true){ /* 行がなければ行を追加して流す */ case(length === 0): lines[i] = []; /* ひとつ先行するコメントより遅い(短い)文字列なら、現時点で先行コメントがすでに右端から抜けていれば流す */ case(record.ppms < lines[i][length - 1].ppms && lines[i][length - 1].reveal < record.start): /* ひとつ先行するコメントより速い(長い)文字列なら、左端に触れる瞬間までに先行コメントが終了するなら流す */ case(lines[i][length - 1].ppms < record.ppms && lines[i][length - 1].end < record.touch): break;/*条件に当てはまればswitch文を抜けて行に追加*/ default: continue;/*条件に当てはまらなければforループを回して次の行に入れられるかの判定へ*/ } record.top = ((canvas.height / configs.maxlines) * i) + fontsize; lines[i].push(record); break; } }, /* FPSタイマー駆動 */ scrollComments: function(){ if(interval) clearInterval(interval); interval = window.setInterval(function(){ context.clearRect(0, 0, canvas.width, canvas.height); /* Canvas描画 */ let now = Date.now(); for(let i=0; lines[i]; i++){ for(let j=0; lines[i][j]; j++){ /* 視認性を向上させるスクロール文字の縁取りは、幸いにもパフォーマンスにほぼ影響しない */ context.strokeText(lines[i][j].text, lines[i][j].left, lines[i][j].top); context.fillText(lines[i][j].text, lines[i][j].left, lines[i][j].top); /* 次の描画位置を計算 */ lines[i][j].left = canvas.width - ((now - lines[i][j].start) * lines[i][j].ppms); } if(lines[i][0] && lines[i][0].end < now) lines[i].shift(); } }, 1000 / configs.fps); }, /* 設定 */ config: { read: function(){ /* 保存済みの設定を読む */ let ls = localStorage[SCRIPTNAME + '-configs']; if(ls) configs = JSON.parse(ls); /* 未定義項目をデフォルト値で上書きしていく */ for(let i = 0; CONFIGS[i]; i++) if(configs[CONFIGS[i].KEY] === undefined) configs[CONFIGS[i].KEY] = CONFIGS[i].DEFAULT; }, save: function(new_config){ /* CONFIGSを元に文字列を型評価して値を格納していく */ for(let i = 0; CONFIGS[i]; i++){ /* 値がなければデフォルト値 */ if(new_config[CONFIGS[i].KEY] === ""){ configs[CONFIGS[i].KEY] = CONFIGS[i].DEFAULT; continue; } switch(CONFIGS[i].TYPE){ case 'int': configs[CONFIGS[i].KEY] = parseInt(new_config[CONFIGS[i].KEY]); break; case 'float': configs[CONFIGS[i].KEY] = parseFloat(new_config[CONFIGS[i].KEY]); break; case 'string': default: configs[CONFIGS[i].KEY] = new_config[CONFIGS[i].KEY]; break; } } localStorage[SCRIPTNAME + '-configs'] = JSON.stringify(configs); }, createButton: function(){ if(configButton) return; /* フルスクリーンボタンを元に設定ボタンを追加する */ configButton = document.createElement('button'); configButton.className = elements.fullscreenButton.className; configButton.classList.add('hidden'); configButton.id = SCRIPTNAME + '-config-button'; configButton.innerHTML = core.config.buttonHtml();/*歯車*/ configButton.setAttribute('title', SCRIPTNAME + '設定'); configButton.addEventListener('click', core.config.togglePanel, true); elements.fullscreenButton.parentNode.insertBefore(configButton, elements.fullscreenButton); animate(function(){configButton.classList.remove('hidden')}); }, togglePanel: function(){ if(configPanel) return core.config.closePanel(); configPanel = document.createElement('div'); configPanel.id = SCRIPTNAME + '-config-panel'; configPanel.classList.add('hidden'); configPanel.innerHTML = core.config.panelHtml(); configPanel.querySelector('button.cancel').addEventListener('click', core.config.closePanel, true); configPanel.querySelector('button.save').addEventListener('click', function(){ let inputs = configPanel.querySelectorAll('input'), new_configs = {}; for(let i = 0; inputs[i]; i++) new_configs[inputs[i].name] = inputs[i].value; core.config.save(new_configs); /* 新しい設定値で再スタイリング */ core.modify(); core.addStyle(); core.scrollComments(); core.config.closePanel(); }, true); document.body.appendChild(configPanel); animate(function(){configPanel.classList.remove('hidden')}); }, closePanel: function(){ configPanel.classList.add('hidden'); configPanel.addEventListener('transitionend', function(){ document.body.removeChild(configPanel); configPanel = null; }, {once: true}); }, buttonHtml: function(){ /* https://www.onlinewebfonts.com/icon/347 */ return innerHTML = `<!-- iCon by oNlineWebFonts.Com --> <img src="data:image/svg+xml;base64,CjxzdmcgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB4PSIwcHgiIHk9IjBweCIgdmlld0JveD0iMCAwIDEwMDAgMTAwMCIgZW5hYmxlLWJhY2tncm91bmQ9Im5ldyAwIDAgMTAwMCAxMDAwIiB4bWw6c3BhY2U9InByZXNlcnZlIj4KPG1ldGFkYXRhPiBTdmcgVmVjdG9yIEljb25zIDogaHR0cDovL3d3dy5vbmxpbmV3ZWJmb250cy5jb20vaWNvbiA8L21ldGFkYXRhPgogIDxnPjxnPjxwYXRoIGQ9Ik00OTkuOSwzMjIuOGMtOTcuOCwwLTE3Ny4xLDc5LjMtMTc3LjEsMTc3LjJjMCw5Ny44LDc5LjMsMTc3LjMsMTc3LjEsMTc3LjNjOTcuOCwwLDE3Ni42LTc5LjUsMTc2LjYtMTc3LjNDNjc2LjUsNDAyLjEsNTk3LjgsMzIyLjgsNDk5LjksMzIyLjh6IE04NTUuMSw2MDEuOGwtMzEuOSw3Ni45bDY0LjUsMTI2LjZsLTc5LDc5bC0xMjkuNS02MS4ybC03Ni45LDMxLjZsLTM5LDExOS41bC01LDE1LjlINDQ2LjZsLTQ4LjMtMTM0LjlsLTc2LjktMzEuN2wtMTI2LjgsNjQuMmwtNzguOS03OC45bDYxLjEtMTI5LjZsLTMxLjctNzYuOEwxMCw1NTguMlY0NDYuNmwxMzUtNDguNGwzMS43LTc2LjhsLTU2LjgtMTEyLjFsLTcuNS0xNC43bDc4LjgtNzguOEwzMjAuOSwxNzdsNzYuOC0zMS44bDM5LTExOS40bDUtMTUuOGgxMTEuNmw0OC4zLDEzNWw3Ni43LDMxLjhsMTI2LjktNjQuM2w3OC45LDc4LjhsLTYxLjEsMTI5LjVsMzEuNiw3Ni45bDEzNS40LDQ0djExMS41TDg1NS4xLDYwMS44TDg1NS4xLDYwMS44eiIgc3R5bGU9ImZpbGw6I0ZGRkZGRiI+PC9wYXRoPjwvZz48L2c+PC9zdmc+CiAg" width="22" height="22">`; }, panelHtml: function(){ return innerHTML = ` <h1>${SCRIPTNAME}設定</h1> <fieldset> <legend>スクロールコメント</legend> <p><label>色: <input type="color" name="color" value="${configs.color}"></label></p> <p><label>縁取り色: <input type="color" name="ocolor" value="${configs.ocolor}"></label></p> <p><label>縁取りの太さ(比率): <input type="number" name="owidth" value="${configs.owidth}" min="0" max="0.2" step="0.01"></label></p> <p><label>最大行数: <input type="number" name="maxlines" value="${configs.maxlines}" min="1" max="25" step="1"></label></p> <p><label>行間(比率): <input type="number" name="linemargin" value="${configs.linemargin}" min="0" max="1" step="0.05"></label></p> <p><label>不透明度: <input type="number" name="opacity" value="${configs.opacity}" min="0" max="1" step="0.05"></label></p> <p><label>不透明度(マウスオーバー時): <input type="number" name="hopacity" value="${configs.hopacity}" min="0" max="1" step="0.05"></label></p> </fieldset> <fieldset> <legend>一覧コメント</legend> <p><label>文字の不透明度: <input type="number" name="lt_opacity" value="${configs.lt_opacity}" min="0" max="1" step="0.05"></label></p> <p><label>文字の不透明度(マウスオーバー時): <input type="number" name="lt_hopacity" value="${configs.lt_hopacity}" min="0" max="1" step="0.05"></label></p> <p><label>背景の不透明度: <input type="number" name="lb_opacity" value="${configs.lb_opacity}" min="0" max="1" step="0.05"></label></p> <p><label>背景の不透明度(マウスオーバー時): <input type="number" name="lb_hopacity" value="${configs.lb_hopacity}" min="0" max="1" step="0.05"></label></p> </fieldset> <fieldset> <legend>アニメーション</legend> <p><label>横断にかける秒数: <input type="number" name="duration" value="${configs.duration}" min="1" max="10" step="1"></label></p> <p><label>秒間コマ数: <input type="number" name="fps" value="${configs.fps}" min="1" max="240" step="1"></label></p> </fieldset> <p class="buttons"><button class="cancel">キャンセル</button><button class="save">保存</button></p> <p class="license">Icon made from <a href="http://www.onlinewebfonts.com/icon">Icon Fonts</a> is licensed by CC BY 3.0</p> `; }, }, addStyle: function(){ if(style) document.head.removeChild(style); (function(css){ style = document.createElement('style'); style.type = 'text/css'; style.textContent = css.replace(/^<style>([^]*)<\/style>$/, '$1'); document.head.appendChild(style); })(innerHTML = `<style> /* スクロールコメント */ canvas#${SCRIPTNAME}{ pointer-events: none; position: absolute; top: 0; left: 0; width: 100%; height: 100%; opacity: ${configs.opacity}; transition: 500ms ease 0ms; } body:hover canvas#${SCRIPTNAME}{ opacity: ${configs.hopacity}; } /* コメントを表示させても映像を画面いっぱいに */ ${selectors.screen}, ${selectors.screen} > div{ width: 100% !important; height: 100% !important; } /* 右コメント一覧を透明に */ ${selectors.commentPane}{ mix-blend-mode: hard-light;/*https://stackoverflow.com/questions/15597167/css3-opacity-gradient*/ background: rgba(0,0,0,${configs.lb_opacity}); transition: 500ms ease 0ms; z-index: 9;/*右側に表示される番組情報や右下のコントローラより下層に*/ } ${selectors.commentPane}:hover{ background: rgba(0,0,0,${configs.lb_hopacity}); } ${selectors.commentPane}::after{ pointer-events: none; position: absolute; content: ""; left: 0px; top: 0px; height: 100%; width: 100%; background: linear-gradient(transparent 50%, gray); } ${selectors.commentPane} *{ background: transparent; color: rgba(255,255,255,${configs.lt_opacity}); } ${selectors.commentPane}:hover *{ color: rgba(255,255,255,${configs.lt_hopacity}); } /* 右コメント一覧のスクロールバーを美しく */ ${selectors.commentPane} > div > div{ overflow-y: hidden; } ${selectors.commentPane}:hover > div > div{ overflow-y: auto; } ${selectors.commentPane} > div > div::-webkit-scrollbar{ background: rgba(255,255,255,0); } ${selectors.commentPane} > div > div::-webkit-scrollbar-thumb{ background: rgba(255,255,255,${configs.lt_hopacity/2}); } /* マウスオーバー時だけナビゲーションを表示させる */ body ${selectors.footer}{ transform: translateY(200%); } body:hover ${selectors.footer}{ transform: translateY(0%); visibility: visible; } /* 設定 */ #${SCRIPTNAME}-config-button{ right: 125px; transition: 500ms ease 0ms; } #${SCRIPTNAME}-config-button.hidden, div[aria-hidden="false"] #${SCRIPTNAME}-config-button/*コメント非表示の時*/{ bottom: -22px; } #${SCRIPTNAME}-config-panel{ position: fixed; width: 360px; left: 50%; bottom: 50%; transform: translate(-50%, 50%); z-index: 100; background: rgba(0,0,0,.75); transition: 500ms ease 0ms; padding: 5px 0; } #${SCRIPTNAME}-config-panel.hidden{ bottom: 0; transform: translate(-50%, 100%); } #${SCRIPTNAME}-config-panel h1, #${SCRIPTNAME}-config-panel legend, #${SCRIPTNAME}-config-panel p{ color: rgba(255,255,255,1); font-size: 14px; padding: 4px 10px; line-height:20px; } #${SCRIPTNAME}-config-panel fieldset p{ padding-left: 30px; } #${SCRIPTNAME}-config-panel fieldset p:hover{ background: rgba(255,255,255,.25); } #${SCRIPTNAME}-config-panel input{ width: 80px; height: 20px; position: absolute; right: 10px; } #${SCRIPTNAME}-config-panel p.buttons{ text-align: right; } #${SCRIPTNAME}-config-panel button{ width: 120px; padding: 5px 10px; margin-left: 10px; border-radius: 5px; color: rgba(255,255,255,1); background: rgba(64,64,64,1); border: 1px solid rgba(255,255,255,1); } #${SCRIPTNAME}-config-panel button.save{ font-weight: bold; background: rgba(0,0,0,1); } #${SCRIPTNAME}-config-panel button:hover{ background: rgba(128,128,128,1); } #${SCRIPTNAME}-config-panel p.license, #${SCRIPTNAME}-config-panel p.license a{ font-size: 10px; color: rgba(255,255,255,.25); } </style>`); }, }; let animate = function(callback, ...params){requestAnimationFrame(() => requestAnimationFrame(() => callback(...params)))}; let innerHTML = '';/*trick for syntax highlighting, waiting js engines support html template*/ let observe = function(element, callback, config = {childList: true}){ let observer = new MutationObserver(callback.bind(element)); observer.observe(element, config); return observer; }; let log = (DEBUG) ? function(){ let l = log.last = log.now || new Date(), n = log.now = new Date(); console.log( SCRIPTNAME + ':', /* 00:00:00.000 */ n.toLocaleTimeString() + '.' + n.getTime().toString().slice(-3), /* +0.000s */ '+' + ((n-l)/1000).toFixed(3) + 's', /* :00 */ ':' + new Error().stack.match(/:[0-9]+:[0-9]+/g)[1].split(':')[1],/*LINE*/ /* caller */ log.caller ? log.caller.name : '', ...arguments ); if(arguments.length === 1) return arguments[0]; } : function(){}; core.initialize(); if(window === top) console.timeEnd(SCRIPTNAME); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址