Twitch User Chat Log

This script can monitor the chat logs of Twitch chat users.

目前為 2021-03-18 提交的版本,檢視 最新版本

// ==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)
    }
})()

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址