您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
立ち見に飛ばされても気分はアリーナ。簡易的なNG機能あり。
// ==UserScript== // @name ニコ生立ち見開放コメビュ // @namespace https://gf.qytechs.cn/ja/users/292779-kinako // @version 1.5 // @description 立ち見に飛ばされても気分はアリーナ。簡易的なNG機能あり。 // @author kinako // @match https://live2.nicovideo.jp/watch/* // @grant none // @run-at document-start // @compatible chrome // ==/UserScript== (function() { 'use strict'; class Controller { constructor(model) { this._model = model; } router(flag = null, data = null) { switch (flag) { case 'comment_post': this._model.postComment(data); break; case 'ng_comment_user': this._model.addNgUser(data); break; default: this._model.openSocket(); break; } } } class Model { constructor() { this.ng_comment_users = []; this.ng_comment_keyword = new RegExp( '[まマ].*[こコ古].*[じジ事].*[きキ記]|' + '[mMmM].*[aAaA].*[kKkKcCcC].*[oOoO].*[jJjJ].*[iIiI].*[kKkK].*[iIiI]'); this.liveOpenTime = null; this.liveId = null; this.commentAPI = null; this.defaultComment = null; this.isHook = null; } dispatchEvent(eventType, target, regArray=[]) { if (target) { let result = false; for (const reg of regArray) { switch (reg) { case 'comment_user': result = this.commentUserRegEx(target); break; case 'comment_keyword': result = this.commentKeywordRegEx(target); break; } if (result) break; } if (result) { eventType += 'FilterMatched'; } target.dispatchEvent(new Event(eventType, {"bubbles":true})); } } commentUserRegEx(target) { const data = JSON.parse(target.getAttribute('data')); return (this.ng_comment_users.includes(data.user_id))? true: false; } commentKeywordRegEx(target) { const data = JSON.parse(target.getAttribute('data')); return (this.ng_comment_keyword.test(data.content))? true: false; } addNgUser(data) { if (/\w+/.test(data)) this.ng_comment_users.push(data); } openSocket() { const programId = /lv[0-9]+/.exec(location.href); if (programId) { const xhr = new XMLHttpRequest(); xhr.onreadystatechange = (e)=> { if (xhr.readyState == 4) { if (xhr.status == 200) { const res = JSON.parse(xhr.responseText); //console.log(res); this.liveOpenTime = Date.parse(res.data.onAirTime.beginAt) / 1000; this.liveId = programId; this.commentAPI = {messageServer: res.data.messageServer, threads: res.data.threads}; if (res.data.liveCycle == 'on_air') { document.addEventListener("DOMContentLoaded", (e)=>{ this.dispatchEvent('commentDefault', document); }); this.initComment(); window.WebSocket = new Proxy(WebSocket, this.openSocketHandler()); } } else { console.log("status = " + xhr.status); } } }; // 番組情報取得 xhr.open("GET", `https://api.cas.nicovideo.jp/v1/services/live/programs/${programId}`); xhr.withCredentials = true; xhr.send(); } } openSocketHandler() { return { construct: function(target, args) { const ws = new target(...args); ws.onmessage = (e)=>{ if (Object.prototype.toString.call(e.data) == '[object String]') { const data = JSON.parse(e.data); if (data.ping && data.ping.content == 'rf:0') { ws.close(); } else if (data.thread){ if (data.thread.resultcode !== 0) this.dispatchEvent('openSocketError', document); } else if (data.chat) { this.chatEvent(data.chat); } } }; ws.onclose = (e)=> { window.WebSocket = new Proxy(WebSocket,this.commentApiHandler()); }; //ws.onerror = function(e) {console.log(e);}; return ws; }.bind(this) } } commentApiHandler() { return { construct: function(target, args) { const ms = this.commentAPI.messageServer; const td = this.commentAPI.threads; if (!this.isHook) { this.isHook = true; args = [ms.wss, "msg.nicovideo.jp#json"]; const ws = new target(...args); ws.onopen = (e) => { const req = [{ thread: { version:ms.version, thread:td.chat, service:ms.service }}, { thread: { version:ms.version, thread:td.store, service:ms.service }}]; ws.send(JSON.stringify(req)); setInterval((e)=>{ws.send('')}, 50000); }; ws.onmessage = (e)=> { if (Object.prototype.toString.call(e.data) == '[object String]') { const data = JSON.parse(e.data); if (data.length == 2 && data[0].thread){ if (data[0].thread.resultcode !== 0) this.dispatchEvent('commentApiError', document); } else if (data.chat) { this.chatEvent(data.chat); } } }; //ws.onerror = function(e) {console.log('error', e);}; //ws.onclose = function(e) {console.log('close', e)}; } }.bind(this) }; } chatEvent(data) { const chatData = {no:data.no, content:data.content, user_id:data.user_id, premium:data.premium }; const chatContainer = document.getElementById('commentdata'); chatContainer.setAttribute('data', JSON.stringify(chatData)); this.dispatchEvent('chat', chatContainer, ['comment_user', 'comment_keyword']); } initComment() { const mo_option = {childList: true, subtree: true}, mo = new MutationObserver((mr, mo)=>{ // 通信エラーダイアログ const button = document.querySelector('div[class^="___dialog-layer-old___"] div[class^="___body___"] button'); if (button){ mo.disconnect(); button.click(); } // 部屋名 const room = document.querySelector('p[class^="___room-name___"]:not([loaded])'); if (room && !/\-|(co|ch)[0-9]+|アリーナ($|[\s ]?最前列)/.test(room.textContent)) { room.setAttribute('loaded', ''); this.dispatchEvent('roomName', room); } }); mo.observe(document, mo_option); let mo2 = new MutationObserver((mr2, mo2)=>{ const parent = document.querySelector('div[id^="root"]'); if (parent) { mo2.disconnect(); mo2 = new MutationObserver((_mr, _mo)=>{ for (const r of _mr) { if (/^___leo-player___/.test(r.target.className) && r.addedNodes.length > 0 && /^___player-status___/.test(r.addedNodes[0].className)) { this.dispatchEvent('chatVisibley', document); } } }); mo2.observe(parent, mo_option); } }); mo2.observe(document, mo_option); } postComment(data) { if (data.message) { let cmmd = (data.command.length== 0)? []: data.command.split(' '); if (data.anonymous) cmmd.unshift('184'); cmmd = cmmd.join(' '); const vpos = (Math.floor(new Date().getTime() / 1000) - this.liveOpenTime) * 100; const xhr = new XMLHttpRequest(); const req = { message: data.message, command: cmmd, vpos: vpos }; //console.log(req); xhr.open('POST', `https://api.cas.nicovideo.jp/v1/services/live/programs/${this.liveId}/comments`); xhr.withCredentials = true; xhr.setRequestHeader('Content-type', 'application/json'); xhr.send(JSON.stringify(req)); xhr.onreadystatechange = (e)=> { if(xhr.readyState === 4 && xhr.status === 200) { this.dispatchEvent('commentPosted', document.querySelector('form[class^="___comment-post-form___"]')); } else if(xhr.status !== 200){ console.log("status = " +xhr.status); } } } } } class View { constructor(controller) { this._controller = controller; this.css_prefix = 'sacv'; this.setStyle(); this.comment(); } setStyle() { let css = document.createElement('style') let rule = document.createTextNode(` #show-all-comment-outer { height:100%; overflow-y: auto; } #show-all-comment-viewer { margin: 0px; padding: 0 0 0 1em; display: table; list-style: none; } #show-all-comment-viewer li { margin-top: 1em; } #show-all-comment-viewer .number { display: table-cell; vertical-align: middle; color: #b1b1b1; font-size: smaller; } #show-all-comment-viewer .content, #show-all-comment-viewer .nh-content { display: table-cell; padding-left:1em; word-break: break-all; } #show-all-comment-viewer .nh-content { padding-left:0em; } #show-all-comment-viewer span[class$="-icon"] { color: #fff; text-decoration: none; margin-right: 1em; border-radius: 4px; padding: 0 0.3em; font-size: 0.8em; } #show-all-comment-viewer .nicoad { color: #d0bda0; } #show-all-comment-viewer .nicoad-icon { background-color: #d0bda0; padding: 0 0.5em !important; } #show-all-comment-viewer .quote { color: #76a5ce; } #show-all-comment-viewer .quote-icon { background-color: #76a5ce; } #show-all-comment-viewer .cruise { color: #76a5ce; } #show-all-comment-viewer .cruise-icon { background-color: #76a5ce; } #show-all-comment-viewer .info { color: #96C79A; } #show-all-comment-viewer .info-icon { background-color: #96C79A; } #show-all-comment-viewer .spi { color: #002856; } #show-all-comment-viewer .spi-icon { background-color: #002856; } #show-all-comment-viewer .author { color: #e40074; } #show-all-comment-viewer .author-icon { background-color: #e40074; } #show-all-comment-viewer .gift { color: #002bff; } #show-all-comment-viewer .gift-icon { background-color: #002bff; } /* コメントリンク */ a.${this.css_prefix}-comment-url{ background-color: #3194da; border-radius: 5px; color: #fff; width: 3em; display: inline-block; text-align: center; text-decoration: none; margin-left: 0.5em; } a.${this.css_prefix}-comment-url:hover { background-color: #000; } #${this.css_prefix}-error { color: #fff; text-align: center; background-color: #a71846; } `); css.media = 'screen'; css.type = 'text/css'; if (css.styleSheet) { css.styleSheet.cssText = rule.nodeValue; } else { css.appendChild(rule); }; document.getElementsByTagName('head')[0].appendChild(css); } comment() { document.addEventListener('roomName', (e)=>{ e.target.textContent = `\${e.target.textContent}開放中/`; }); document.addEventListener('commentDefault', (e)=>{ // 184状態 let anonymousToggle = document.querySelector('div[class^="___anonymous-comment-post-toggle-button-field____"] button'); let anonymous; if (!anonymousToggle) { const setiingButton = document.querySelector('button[class^="___setting-button___"]'); setiingButton.click(); setiingButton.click(); anonymousToggle = document.querySelector('div[class^="___anonymous-comment-post-toggle-button-field____"] button'); } // コメント投稿 const commentButton = document.querySelector('form[class^="___comment-post-form___"] button'); commentButton.addEventListener('click', (_e)=> { const data = {command:_e.target.parentNode.querySelector('input[class^="___command-text-box___"]').value, message:_e.target.parentNode.querySelector('input[class^="___comment-text-box___"]').value, anonymous: (anonymousToggle.getAttribute('data-toggle-state') == 'true')? true: false }; this._controller.router('comment_post', data); }); // リロードボタン const reloadButton = document.querySelector('button[class^="___reload-button___"]'); reloadButton.addEventListener('click', (_e)=> { location.reload(); }); // デフォルトコメント非表示 const parent = document.querySelector('div[class^="___comment-data-grid___"]') parent.querySelector('div[class^="___body___"]').style.display = 'none'; // 新規コメビュ用 const outer = document.createElement('div'); outer.id = 'show-all-comment-outer'; const viewer = document.createElement('ul'); viewer.id = 'show-all-comment-viewer'; outer.appendChild(viewer); parent.appendChild(outer); const chat = document.createElement('script'); chat.id = 'commentdata'; document.body.appendChild(chat); // 詳細設定画面を表示時に退避 const settingButton = document.querySelector('button[class^="___detail-setting-button___"]'); settingButton.addEventListener('click', (e)=>{ outer.style.display = 'none'; document.body.appendChild(outer); }); // フルスクリーン時に退避 const fullScreenButton = document.querySelector('button[class^="___fullscreen-button___"]'); fullScreenButton.addEventListener('click', (e)=>{ if (outer.parentNode.localName !== 'body') { outer.style.display = 'none'; document.body.appendChild(outer); } }); }); document.addEventListener('commentPosted', (e)=>{ e.target.parentNode.querySelector('input[class^="___command-text-box___"]').value = ''; e.target.parentNode.querySelector('input[class^="___comment-text-box___"]').value = ''; }); document.addEventListener('chat', (e)=>{ const data = JSON.parse(e.target.getAttribute('data')); if (this.ignoreCommand(data)) return; const parent = document.getElementById('show-all-comment-viewer'), li = document.createElement('li'), head = document.createElement('span'), content = document.createElement('span'); li.setAttribute('user', data.user_id); const uc = this.convertCommand(data); if(uc) { head.setAttribute('class', uc.class+'-icon'); head.textContent = uc.icon; content.setAttribute('class', uc.class); content.textContent = uc.message; li.appendChild(head); } else { head.setAttribute('class', 'number'); head.textContent = data.no; content.setAttribute('class', 'content'); content.textContent = data.content; if (head.textContent !== '') { this.setNgEvent(head); li.appendChild(head); } else { content.setAttribute('class', 'nh-content'); this.setNgEvent(content); } } this.link(content); li.appendChild(content); parent.appendChild(li); const limit = 50; if (parent.children.length > limit) { const count = parent.children.length; for (let i = 0; i < count-limit; i++) { parent.children[i].remove(); } } const root = parent.parentNode; const rCR = root.getBoundingClientRect(); const over = parent.querySelector('span[over]'); //最後の子要素の一つ手前 if (parent.lastElementChild.previousElementSibling && !over) { const lCR = parent.lastElementChild.previousElementSibling.getBoundingClientRect(); if(lCR.bottom < rCR.bottom) root.scrollTop =root.scrollHeight; } }); document.addEventListener('chatFilterMatched', (e)=>{ }); document.addEventListener('chatVisibley', (e)=>{ const parent = document.querySelector('div[class^="___comment-data-grid___"]'); if (!parent.querySelector('#show-all-comment-outer')) { parent.querySelector('div[class^="___body___"]').style.display = 'none'; const outer = document.getElementById('show-all-comment-outer'); outer.removeAttribute('style'); parent.appendChild(outer); outer.scrollTop = outer.scrollHeight; } }); document.addEventListener('openSocketError', (e)=>{ const outer = document.getElementById('show-all-comment-outer'), parent = document.querySelector('#show-all-comment-viewer'), error = document.createElement('li'); error.textContent = '⚠ 初期コメントサーバーエラー'; error.id = `${this.css_prefix}-error`; parent.appendChild(error); outer.scrollTop = outer.scrollHeight; }); document.addEventListener('commentApiError', (e)=>{ const outer = document.getElementById('show-all-comment-outer'), parent = document.querySelector('#show-all-comment-viewer'), error = document.createElement('li'); error.textContent = '⚠ コメントAPIサーバーエラー'; error.id = `${this.css_prefix}-error`; parent.appendChild(error); outer.scrollTop = outer.scrollHeight; }); } setNgEvent(node) { node.addEventListener('click', (e)=>{ const user = e.target.parentNode.getAttribute('user'); this._controller.router('ng_comment_user', user); const result = document.querySelectorAll(`#show-all-comment-viewer li[user="${user}"]`); for (const r of result) r.remove(); }); node.addEventListener('mouseover', (e)=>{ e.target.setAttribute('over', ''); const user = e.target.parentNode.getAttribute('user'); const className = e.target.getAttribute('class'); const result = document.querySelectorAll(`#show-all-comment-viewer li[user="${user}"] span[class="${className}"]`); for (const r of result) { r.style.backgroundColor = '#B3B3B3'; r.style.borderRadius = '4px'; r.style.color = '#fff'; } }); node.addEventListener('mouseout', (e)=>{ e.target.removeAttribute('over'); const user = e.target.parentNode.getAttribute('user'); const className = e.target.getAttribute('class'); const result = document.querySelectorAll(`#show-all-comment-viewer li[user="${user}"] span[class="${className}"]`); for (const r of result) r.removeAttribute('style'); }); } ignoreCommand(data) { return (/^\/(uadpoint|hb|coe|clear)\s?/.test(data.content))? true: false; } convertCommand(data) { const text = data.content let match = /\/(nicoad|quote|cruise|info|spi|gift|perm) (.*)/.exec(text); const messageTrim = (str)=>{return str.replace(/^"|"$/g, '')}; let result; if (match) { switch (match[1]) { case 'nicoad': result = {class:'nicoad', message:JSON.parse(match[2]).message, icon:'広告'}; break; case 'gift': match = match[2].split(' '); match = {name: messageTrim(match[2])+'さん', point: match[3]+'pt', gift: messageTrim(match[5])}; match = `${match.gift}(${match.point}) by${match.name}`; result = {class:'gift', message:match, icon:'ギフト'}; break; case 'quote': match = messageTrim(match[2]).replace('(生放送クルーズさんの番組)', ''); result = {class:'quote', message:match, icon:'クルーズ'}; break; case 'cruise': result = {class:'cruise', message:messageTrim(match[2]), icon:'クルーズ'}; break; case 'info': result = {class:'info', message:match[2].replace(/[0-9]+ /, ''), icon:'運コメ'}; break; case 'spi': result = {class:'spi', message:messageTrim(match[2]), icon:'アイテム'}; break; case 'perm': result = {class:'author', message:match[2], icon:'主コメ'}; break; default: break; } } else if (data.premium == 3) { result = {class:'author', message:text, icon:'主コメ'}; } return result; } link(node) { const pttn = /http(s)?:\/\/(([\w-]+\.)+)([\w-]+)(\/[\w-./?%&=#]*)?/; const link = pttn.exec(node.textContent); if (link) { node.innerHTML += `<a href="${new URL(link[0])}" target="_blank" class="${this.css_prefix}-comment-url">開く</a>`; } } } const model = new Model(); const con = new Controller(model); const view = new View(con); con.router(); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址