您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
This script can monitor the chat logs of Twitch chat users.
当前为
// ==UserScript== // @name Twitch User Chat Log // @namespace TwitchUserChatLogScript // @version 0.1 // @description This script can monitor the chat logs of Twitch chat users. // @author Emble // @match https://www.twitch.tv/* // @require https://code.jquery.com/jquery-3.3.1.min.js // @noframes // @grant none // ==/UserScript== (function() { var intervalID2 let retryCounter2 const Page = { isStreaming: (location.pathname.indexOf('video') !== -1)? false : true, getChatArea: ()=>{return (location.pathname.indexOf('video') !== -1)?document.getElementsByClassName('tw-align-items-end tw-flex tw-flex-wrap tw-full-width')[0]:document.getElementsByClassName('chat-scrollable-area__message-container tw-flex-grow-1 tw-pd-b-1')[0]}, waitUserCardLoading: ()=>{ clearInterval(intervalID2) intervalID2 = setInterval(()=>{ let cardElement = document.getElementsByClassName('tw-flex tw-flex-grow-1 tw-flex-wrap tw-mg-b-05 tw-mg-l-1') if(cardElement !== undefined ){ Page.putButton() clearInterval(intervalID2) } if(retryCounter2 > 10){ clearInterval(intervalID2) } },500) }, putButton: ()=>{ if(document.getElementById('TUCL_Button') !== null)return const parent = document.getElementsByClassName('tw-flex tw-flex-grow-1 tw-flex-wrap tw-mg-b-05 tw-mg-l-1')[0] const button = document.createElement('div') button.className = 'tw-mg-r-05 tw-mg-t-05' button.innerHTML = `<div class="tw-inline-flex viewer-card-drag-cancel" id="TUCL_Button"> <button class="ScCoreButton-sc-1qn4ixc-0 ScCoreButtonPrimary-sc-1qn4ixc-1 jeBpig tw-core-button" data-a-target="usercard-whisper-button" data-test-selector="whisper-button"> <div class="ScCoreButtonLabel-lh1yxp-0 bUTtZU tw-core-button-label"> <div class="tw-align-items-center tw-flex tw-mg-r-05"> <div class="ScCoreButtonIcon-khv8ri-0 fVWBSS tw-core-button-icon"> <div class="ScIconLayout-sc-1bgeryd-0 kbOjdP tw-icon" data-a-selector="tw-core-button-icon"> <div class="ScAspectRatio-sc-1sw3lwy-1 dNNaBC tw-aspect"> <div class="ScAspectSpacer-sc-1sw3lwy-0 gkBhyN"> </div> <svg width="100%" height="100%" version="1.1" viewBox="0 0 20 20" x="0px" y="0px" class="ScIconSVG-sc-1bgeryd-1 cMQeyU"><g><path fill-rule="evenodd" d="M7.828 13L10 15.172 12.172 13H15V5H5v8h2.828zM10 18l-3-3H5a2 2 0 01-2-2V5a2 2 0 012-2h10a2 2 0 012 2v8a2 2 0 01-2 2h-2l-3 3z" clip-rule="evenodd"></path></g></svg> </div> </div> </div> </div> <div data-a-target="tw-core-button-label-text" class="tw-align-items-center tw-flex tw-flex-grow-0 tw-justify-content-start"> 履歴 </div> </div> </button> </div>` parent.appendChild(button) }, observeUserCard: new MutationObserver((ms)=>{ ms.forEach((e) => { const addedElements = e.addedNodes for(let i = 0; i < addedElements.length; i++){ console.log(addedElements) if(addedElements[i].getAttribute('data-a-target') === 'viewer-card-positioner' || addedElements[i].className==='tw-border-radius-medium tw-c-background-base tw-elevation-2 tw-flex tw-flex-column viewer-card'){ Page.waitUserCardLoading() } } }) }) } const HTML = { logButton: `<div class="tw-inline-flex viewer-card-drag-cancel" id="TUCL_Button"> <button class="ScCoreButton-sc-1qn4ixc-0 ScCoreButtonPrimary-sc-1qn4ixc-1 jeBpig tw-core-button" data-a-target="usercard-whisper-button" data-test-selector="whisper-button"> <div class="ScCoreButtonLabel-lh1yxp-0 bUTtZU tw-core-button-label"> <div class="tw-align-items-center tw-flex tw-mg-r-05"> <div class="ScCoreButtonIcon-khv8ri-0 fVWBSS tw-core-button-icon"> <div class="ScIconLayout-sc-1bgeryd-0 kbOjdP tw-icon" data-a-selector="tw-core-button-icon"> <div class="ScAspectRatio-sc-1sw3lwy-1 dNNaBC tw-aspect"> <div class="ScAspectSpacer-sc-1sw3lwy-0 gkBhyN"> </div> <svg width="100%" height="100%" version="1.1" viewBox="0 0 20 20" x="0px" y="0px" class="ScIconSVG-sc-1bgeryd-1 cMQeyU"><g><path fill-rule="evenodd" d="M7.828 13L10 15.172 12.172 13H15V5H5v8h2.828zM10 18l-3-3H5a2 2 0 01-2-2V5a2 2 0 012-2h10a2 2 0 012 2v8a2 2 0 01-2 2h-2l-3 3z" clip-rule="evenodd"></path></g></svg> </div> </div> </div> </div> <div data-a-target="tw-core-button-label-text" class="tw-align-items-center tw-flex tw-flex-grow-0 tw-justify-content-start"> 履歴 </div> </div> </button> </div>` } const Chat = { Observer: new MutationObserver((ms) => { ms.forEach((e) => { const addedChats = e.addedNodes for(let i = 0; i < addedChats.length; i++){ if(addedChats[i].className === 'tw-accent-region'){ continue } const chatData = Chat.convertChat(addedChats[i]) addStorage(chatData.userName, chatData.chat) //console.log(getStorage(chatData.userName)) } }) }), convertChat: (chat) =>{ let Data = { chat: null, userName: null } const getUserName = () => { if(chat.getElementsByClassName('chat-author__intl-login').length > 0){ const name = chat.getElementsByClassName('chat-author__intl-login')[0].innerHTML return name.match(/(?<=\().*?(?=\))/)[0] } if(chat.getElementsByClassName('chat-author__display-name').length > 0){ return chat.getElementsByClassName('chat-author__display-name')[0].innerHTML } return null } //Data.chat = (Page.isStreaming)?chat.getElementsByClassName('chat-line__no-background tw-inline'):chat.getElementsByClassName('tw-flex-grow-1') Data.chat = (chat.getElementsByClassName('text-fragment')[0])?chat.getElementsByClassName('text-fragment')[0].innerHTML:null Data.userName = getUserName() return Data } } var intervalID let retryCounter = 0 const findChatArea = (c) => { intervalID = setInterval(()=>{ let chatAreaElement = Page.getChatArea()/*(Page.isStreaming)? document.getElementsByClassName('chat-scrollable-area__message-container tw-flex-grow-1 tw-pd-b-1') :document.getElementsByClassName('tw-align-items-end tw-flex tw-flex-wrap tw-full-width')*/ if(chatAreaElement !== undefined ){ log('Found element: ') initialize() clearInterval(intervalID) } if(retryCounter > c){ log('Element not found.') clearInterval(intervalID) } retryCounter++ },1000) } const runObserver = () => { if(Page.getChatArea() === undefined || null)return if(document.getElementsByClassName('tw-full-height tw-full-width tw-relative tw-z-above viewer-card-layer').length <= 0)return Page.observeUserCard.disconnect() Page.observeUserCard.observe(document.getElementsByClassName('tw-full-height tw-full-width tw-relative tw-z-above viewer-card-layer')[0], {childList: true, characterData: true, subtree: true}) Chat.Observer.disconnect() Chat.Observer.observe(Page.getChatArea(), {childList: true}) } const initialize = () => { runObserver() } const addStorage = (name, chat) =>{ /* * let array = { * "user1": ["chat1", "chat2"], * "user2": ["chatA", "chatB", "chatC"] * . * . * . * } */ //名前の検索 let array = JSON.parse(sessionStorage.getItem('TUCL_Log')) for(let key of Object.keys(array)){ if(key == name){ let val = array[key] val.push(chat) array[key] = val sessionStorage.setItem('TUCL_Log', JSON.stringify(array)) return } } //見つからなければ新規追加 array[name] = [] let val = array[name] val.push(chat) array[name] = val sessionStorage.setItem('TUCL_Log', JSON.stringify(array)) } const getStorage = (n) => { return JSON.parse(sessionStorage.getItem('TUCL_Log')) } const openLogWindow = () => { } let storedHref = location.href; const URLObserver = new MutationObserver(function(ms){ ms.forEach(function(m){ if(storedHref !== location.href){ storedHref = location.href log('URL Changed', storedHref, location.href) Chat.Observer.disconnect() clearInterval(intervalID) findChatArea(60) } }) }) const log = (m) => console.log('[TUCL] '+m) window.onload = () => { if(!sessionStorage){ alert('セッションストレージ非対応ブラウザです。') return } //console.log(sessionStorage.getItem('TUCL_Log')) if(!sessionStorage.getItem('TUCL_Log')){ let array = {} sessionStorage.setItem('TUCL_Log', JSON.stringify(array)) } URLObserver.observe(document, {childList: true, subtree: true}) findChatArea(60) } })()