您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
try to take over the world!
// ==UserScript== // @name [YouTube-Chat] Words Typing // @namespace http://tampermonkey.net/ // @version 0.9 // @description try to take over the world! // @author You // @include https://www.youtube.com/live_chat* // @include https://studio.youtube.com/live_chat?is_popout* // @icon data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw== // @license MIT // @grant unsafeWindow // @require https://greasyfork.org/scripts/455494-micromodal-min-js/code/micromodal-min%20js.js?version=1121802 // @grant none // ==/UserScript== const chat = document.querySelector("#input > #input") if (window.trustedTypes && window.trustedTypes.createPolicy) { window.trustedTypes.createPolicy('default', { createHTML: (string) => string }); } let wordSearch let typingCheck let htmlSetUp let separator = " " let searchTime = new Date().getTime() let JpWords let typeCountEvents /** *@Description 単語集をロードする。 */ class loadJpWords { constructor(searchBox) { this.txtUrls = [ "https://dl.dropboxusercontent.com/s/hme8y6jx87fe0ri/3letter.txt?dl=0", "https://dl.dropboxusercontent.com/s/tyd629mownzv009/4letter.txt?dl=0", "https://dl.dropboxusercontent.com/s/g5zuaz6wjj4itmf/5letter.txt?dl=0", "https://dl.dropboxusercontent.com/s/4xfr7p4un9r38eh/6letter.txt?dl=0" ] } async loadWords(text){ document.getElementById("panel-pages").insertAdjacentHTML('afterend', window.trustedTypes.defaultPolicy.createHTML(` <div id="load-words">loading words...</div> <style> #load-words{ margin-left: 48px; white-space: nowrap; } </style>`)); //here our function should be implemented for(let i=0;i<this.txtUrls.length;i++){ text = await fetch(this.txtUrls[i]) text = await text.text() this[(i+3)+"words"] = text.split("\r\n") } document.getElementById("load-words").style.display = "none" if(!htmlSetUp){ htmlSetUp = new HtmlSetUp() htmlSetUp.setUp() } return; } } class HtmlSetUp { constructor(){ this.wordArea this.addTextOption } setUp(){ const fontSizeFlag = localStorage.getItem('words-font-size') == null && !isNaN(+localStorage.getItem('words-font-size')) const shortcutKeys = ` Esc or Space: Word skip Tab: Switch Text Box ` wordSearch = new WordSearch(); document.getElementById("top").insertAdjacentHTML("afterend",window.trustedTypes.defaultPolicy.createHTML(` <div id="minimum-dictionary"> <div id="word-table" class='words-typing-mode'>#<span id="wordarea">${wordSearch.result.join(separator).toLowerCase()}</span></div> <div id="word-tools"> <input class='words-typing-mode' id="word-search-box" maxlength="6" autocomplete="off" value="" placeholder="[?] Search"> <span class='words-typing-mode'><span id="word-match">0</span> word</span> <span data-micromodal-trigger="modal-1" id="shortcut-keys" title="${shortcutKeys}">⌨</span> <span><span id="typing-count">${+sessionStorage.getItem('liveTypingCount') ? +sessionStorage.getItem('liveTypingCount') : 0}</span> types</span> <span class='words-typing-mode'><span id="typing-speed">0.0</span> k/s</span> </div> </div> <style> #minimum-dictionary{ margin-left: 48px; white-space: nowrap; } #word-search-box{ background: rgb(0 0 0 / 10%); border: #000000 thin; border-top: none; outline: solid thin #ffffffa6; color: rgb(255 255 255 / 87%); width: 8rem; } #minimum-dictionary > div { margin-bottom: 1rem; } #word-tools > *{ margin-right: 0.9rem; } #shortcut-keys:hover{ text-decoration: underline; cursor: help; } </style> <style id="words-typing"> .words-typing-mode{ display:${localStorage.getItem('enable-words-typing') != 'false' ? '' : 'none'}; } #shortcut-keys{ color:${localStorage.getItem('enable-words-typing') != 'false' ? 'gold' : ''}; } #word-tools{ margin-top:${localStorage.getItem('enable-words-typing') != 'false' ? '' : '0.5rem'}; } </style> <style id="words-font"> #word-table{ font-size:${fontSizeFlag ? 13 : +localStorage.getItem('words-font-size')}px; } </style> `)) document.body.insertAdjacentHTML("afterend",window.trustedTypes.defaultPolicy.createHTML(` <div id="modal-1" aria-hidden="false" class="is-open"> <div class="modal__overlay" tabindex="-1" data-micromodal-close=""> <div class="modal__container" role="dialog" aria-modal="true" aria-labelledby="modal-1-title"> <header class="modal__header"> <h1 id="modal-1-title" class="modal__title"> Words Typing Option </h1> </header> <div id="modal-1-content" class="modal__content"> <label><input type="checkbox" id="enable-words-typing" ${localStorage.getItem('enable-words-typing') != 'false' ? 'checked' : ''}>Enable Words Typing</label> <label><input type="checkbox" id="miss-word-skip" ${localStorage.getItem('miss-word-skip') != 'true' ? '' : 'checked'}>Miss Word Skip</label> <label>font-size<input type="number" min="13" max="30" id="words-font-size" value='${fontSizeFlag ? 13 : +localStorage.getItem('words-font-size')}'>px</label> <label>Add Text<input type="text" id="add-text" value='${localStorage.getItem('add-text') == null ? '#' : localStorage.getItem('add-text')}'></label> </div> </div> </div> </div> <style> #modal-1 { display: none; } #modal-1.is-open { display: block; color: rgb(255 255 255 / 90%); } .modal__container { background-color: #212121; padding: 30px; margin-right: 20px; margin-left: 20px; max-width: 640px; max-height: 100vh; width: 100%; border-radius: 4px; overflow-y: auto; box-sizing: border-box; } .modal__overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; z-index: 2; background: rgba(0,0,0,0.6); display: flex; justify-content: center; align-items: center; } .modal__content { margin-top: 2rem; margin-bottom: 2rem; line-height: 1.5; font-size: 1.5rem; display: flex; flex-direction: column; } #words-font-size{ width:30px; } </style>`)) this.wordArea = document.getElementById("wordarea") this.addTextOption = document.getElementById("add-text") document.getElementById("modal-1-content").addEventListener('change', event => { switch(event.target.type){ case 'checkbox' : localStorage.setItem(event.target.id,event.target.checked) if(event.target.id == 'enable-words-typing'){ document.getElementById("words-typing").innerText = `.words-typing-mode{ display:${event.target.checked ? '' : 'none'}; } #shortcut-keys{ color:${event.target.checked ? 'gold' : ''}; } #word-tools{ margin-top:${event.target.checked ? '' : '0.5rem'}; }` if(!event.target.checked){ //addText("") }else if(!JpWords){ JpWords = new loadJpWords() JpWords.loadWords() } } break; case 'number' : localStorage.setItem(event.target.id,event.target.value) document.getElementById("words-font").innerText = `#word-table{ font-size:${typeof event.target.value == 'number' ? 13 : +event.target.value}px; }` break; case 'text' : localStorage.setItem(event.target.id,event.target.value) break; } }) MicroModal.init() MicroModal.close() typingCheck = new TypingCheck(isNaN(+sessionStorage.getItem('liveTypingCount')) ? 0 : +sessionStorage.getItem('liveTypingCount')) typeCountEvents = new TypeCountEvents(); window.addEventListener('beforeunload',typeCountEvents.setSessionStorageTypingCount); chat.addEventListener("input", typingCheck.typeCheck.bind(typingCheck)) chat.addEventListener("keydown", typingCheck.enterSubmitWord.bind(typingCheck)) chat.addEventListener("focus", e => { if(!e.target.textContent && localStorage.getItem('enable-words-typing') != 'false'){ //addText(htmlSetUp.addTextOption.value) }else{ //moveEndCaret(chat) } }) document.getElementById("word-search-box").addEventListener("keydown",wordSearch.Search.bind(wordSearch)) document.getElementById("word-search-box").addEventListener("focus",e => { e.target.value = "" }) setInterval(typeCountEvents.updateTypingSpeed,1000) } } class TypingCheck { constructor(sessionTypeCount) { this.typelog = ""; this.roundTypeCounter = sessionTypeCount this.typeCounter = sessionTypeCount } /** *@Description inputイベントで入力ワードを比較する。 */ typeCheck(event){ const c = new RegExp(`^${chat.textContent}`,"i") const match = wordSearch.result[0] ? wordSearch.result[0].match(c) : "" const matchLength = (match ? match[0].length : 0) if(event.data && /insertCompositionText|insertText/.test(event.inputType) && this.typelog.length < event.target.textContent.length){ document.getElementById("typing-count").textContent = ++this.typeCounter; typeCountEvents.updateTypingSpeed() } this.typelog = event.target.textContent || ""; if(match){ htmlSetUp.wordArea.textContent = (wordSearch.result[0].slice(matchLength) + separator + wordSearch.result.slice(1,10).join(separator)).toLowerCase() }else if(!chat.textContent){ htmlSetUp.wordArea.textContent = wordSearch.result.slice(0,10).join(separator).toLowerCase() } } /** *@Description チャットテキストボックスのkeydownイベント。 *@note *Enterで送信時、入力したワードを評価。 *Escでワードスキップ *Tabでテキストボックスフォーカス切り替え */ enterSubmitWord(event){ if(event.key == "Enter" && chat.textContent){ if(chat.textContent && wordSearch.result[0].toLowerCase() == chat.textContent.toLowerCase() || localStorage.getItem('miss-word-skip') == 'true'){ wordSearch.result = wordSearch.result.slice(1) document.getElementById("word-match").textContent = wordSearch.result.length }else if(chat.textContent == htmlSetUp.addTextOption.value || !chat.textContent){ wordSkip() } if(chat.textContent != htmlSetUp.addTextOption.value && chat.textContent){ document.getElementById("typing-count").textContent = ++this.typeCounter; typeCountEvents.updateTypingSpeed() } htmlSetUp.wordArea.textContent = wordSearch.result.slice(0,10).join(separator).toLowerCase() }else if(event.key == "Escape" && localStorage.getItem('enable-words-typing') != 'false'){ wordSkip() }else if(event.key == "Tab" && localStorage.getItem('enable-words-typing') != 'false'){ if(document.activeElement.id === "input"){ document.getElementById("word-search-box").focus() }else{ chat.focus() } event.preventDefault() } } } function wordSkip(){ wordSearch.result = wordSearch.result.slice(1) document.getElementById("word-match").textContent = wordSearch.result.length htmlSetUp.wordArea.textContent = wordSearch.result.slice(0,10).join(separator).toLowerCase() } class WordSearch { constructor(searchBox) { this.result = [] } async Search(event){ //検索ボックスにフォーカスしている状態でEnterを押した if(event.key == "Enter"){ //Enterを押した検索ボックスの要素 this.searchBox = event.target if(/?/.test(this.searchBox.value)){ separator = " " //文字列で正規表現を作成 const RegText = `^${this.searchBox.value.replace(/[?]/g, "\\D")}$` //文字列を正規表現に変換 const Reg = new RegExp(RegText ,"i") //正規表現にマッチする単語のみを絞り込む this.result = JpWords[`${this.searchBox.value.length}words`].filter(word => Reg.test(word)).slice(0,100); }else{ separator = " " this.result = await this.getEngWords() } //結果を出力 htmlSetUp.wordArea.textContent = this.result.slice(0,10).join(separator).toLowerCase() //結果件数を表示 document.getElementById("word-match").textContent = this.result.length //チャットにフォーカス chat.focus() //moveEndCaret(chat) //打鍵速度計測用時間・測定用打鍵数を設定 searchTime = new Date().getTime() typingCheck.roundTypeCounter = new Number(typingCheck.typeCounter) document.getElementById("typing-speed").textContent = (0).toFixed(1) }else if(event.key == "Tab"){ chat.focus() event.preventDefault() } } async getEngWords(html){ //here our function should be implemented let result = [] for(let i=1;i<=100;i+=100){ let html = await fetch(`https://www.onelook.com//?w=${this.searchBox.value}&ssbp=1&first=${i}`) html = await html.text() //wordの前後のいちを取得 const start = html.search(/<td width=20% valign=top>/) const end = html.search(/<\/TR><\/TABLE>/) //word要素のみを取り出す const wordsElement = html.slice(start,end) //取得したワードを配列に結合 result = result.concat(wordsElement.match(/(?<=\>)[a-zA-Z]+(?=\<)/g)) } return result; } } class TypeCountEvents { /** *@Description 打鍵速度を更新する */ updateTypingSpeed(){ document.getElementById("typing-speed").textContent = ((typingCheck.typeCounter - typingCheck.roundTypeCounter) / ((new Date().getTime()-searchTime)/1000)).toFixed(1) } /** *@Description ページを離れるときに打件数をSessionStorageに保存する */ setSessionStorageTypingCount(){ sessionStorage.setItem('liveTypingCount',typingCheck.typeCounter); } } if(localStorage.getItem('enable-words-typing') != 'false'){ JpWords = new loadJpWords() JpWords.loadWords() }else{ htmlSetUp = new HtmlSetUp() htmlSetUp.setUp() }