[YouTube-Chat] Words Typing

try to take over the world!

目前為 2022-11-25 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         [YouTube-Chat] Words Typing
// @namespace    http://tampermonkey.net/
// @version      0.4
// @description  try to take over the world!
// @author       You
// @include        https://www.youtube.com/live_chat*
// @icon         data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
// @license MIT
// @require https://greasyfork.org/scripts/455302-youtube-auto-add-text-in-chatbox/code/%5BYouTube%5D%20Auto%20add%20Text%20in%20ChatBox.js?version=1121194
// @grant        none
// ==/UserScript==

let chatTextBox
let wordArea
let setWords = ['test','sample','word']
let inputEvent
let separator = " "
let searchTime = new Date().getTime()
let roundTypeCounter = 0
let typeCounter = 0
let JpWords

/**
 *@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("top").insertAdjacentHTML("afterend",`
<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")

		}

		htmlSetUp()
		return;
	}

}

JpWords = new loadJpWords()
JpWords.loadWords()

function htmlSetUp(){

	const shortcutKeys = `
Esc: Word skip
Tab: Switch Text Box
`
	document.getElementById("load-words").style.display = "none"
	document.getElementById("top").insertAdjacentHTML("afterend",`
<div id="minimum-dictionary">
  <div id="word-table">#<span id="wordarea">${setWords.join(separator).toLowerCase()}</span></div>
  <div id="word-tools">
    <input id="word-search-box" maxlength="6" autocomplete="off" value="" placeholder="[?] Search">
    <span><span id="word-match">0</span> word</span>
    <span id="shortcut-keys" title="${shortcutKeys}">⌨</span>
    <span><span id="typing-count">0</span> types</span>
    <span><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>`)


	chatTextBox = document.querySelector("#input > #input")
	wordArea = document.getElementById("wordarea")
    inputEvent = new typingCheck()

	chatTextBox.addEventListener("input", inputEvent.typeCheck.bind(inputEvent))
	chatTextBox.addEventListener("keydown", inputEvent.enterSubmitWord.bind(inputEvent))
	chatTextBox.addEventListener("focus", e => {
		if(!e.target.textContent){
			addText("#")
		}else{
			moveEndCaret(chat)
		}
	})

	document.getElementById("word-search-box").addEventListener("keydown",wordSearch)
	document.getElementById("word-search-box").addEventListener("focus",e => {
		e.target.value = ""
	})
	setInterval(updateTypingSpeed,1000)
}



class typingCheck {

	constructor() {
		this.typelog = "";
	}


    /**
     *@Description inputイベントで入力ワードを比較する。
    */
	typeCheck(event){

		const c = new RegExp(`^${chatTextBox.textContent.slice(1)}`,"i")
		const match = setWords[0] ? setWords[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 = ++typeCounter;
			updateTypingSpeed()
		}

		this.typelog = event.target.textContent || "";

		if(match){
			wordArea.textContent = (setWords[0].slice(matchLength) + separator + setWords.slice(1).join(separator)).toLowerCase()
        }else if(!chatTextBox.textContent){
            wordArea.textContent = setWords.join(separator).toLowerCase()
        }

	}



    /**
     *@Description チャットテキストボックスのkeydownイベント。

     *@note
     *Enterで送信時、入力したワードを評価。
     *Escでワードスキップ
	 *Tabでテキストボックスフォーカス切り替え
    */
	enterSubmitWord(event){

        if(event.key == "Enter"){

            if(setWords[0].toLowerCase() == chatTextBox.textContent.slice(1).toLowerCase()){
                setWords = setWords.slice(1)
				document.getElementById("word-match").textContent = setWords.length
            }

			if(chatTextBox.textContent != "#" && chatTextBox.textContent){
				document.getElementById("typing-count").textContent = ++typeCounter;
				updateTypingSpeed()
			}

            wordArea.textContent = setWords.join(separator).toLowerCase()

        }else if(event.key == "Escape"){

            setWords = setWords.slice(1)
			document.getElementById("word-match").textContent = setWords.length
            wordArea.textContent = setWords.join(separator).toLowerCase()

        }else if(event.key == "Tab"){

			document.getElementById("word-search-box").focus()
			event.preventDefault()

		}
	}

}




/**
 *@Description ?でワードを検索する。
*/
async function wordSearch(event){

	//検索ボックスにフォーカスしている状態でEnterを押した
	if(event.key == "Enter"){
		//Enterを押した検索ボックスの要素
		const searchBox = event.target

		if(/?/.test(searchBox.value)){
			separator = " "
			//文字列で正規表現を作成
			const RegText = `^${searchBox.value.replace(/[?]/g, "\\D")}$`
			//文字列を正規表現に変換
			const Reg = new RegExp(RegText ,"i")
			//正規表現にマッチする単語のみを絞り込む
			setWords = JpWords[`${searchBox.value.length}words`].filter(word => Reg.test(word)).slice(0,200);

		}else{
			separator = " "
			setWords = await new onelook(searchBox).getSearchWords()
		}


		//結果を出力
		wordArea.textContent = setWords.join(separator).toLowerCase()

		//結果件数を表示
		document.getElementById("word-match").textContent = setWords.length

		//チャットにフォーカス
		chatTextBox.focus()
		moveEndCaret(chat)

		//打鍵速度計測用時間・測定用打鍵数を設定
		searchTime = new Date().getTime()
		roundTypeCounter = new Number(typeCounter)
		document.getElementById("typing-speed").textContent = (0).toFixed(1)

	}else if(event.key == "Tab"){

		chatTextBox.focus()

		event.preventDefault()

	}

}



class onelook {

	constructor(searchBox) {
		this.result = [];
		this.searchBox = searchBox;
	}


	async getSearchWords(html){
		//here our function should be implemented

		for(let i=1;i<=101;i+=100){
			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)

			//取得したワードを配列に結合
			this.result = this.result.concat(wordsElement.match(/(?<=\>)[a-zA-Z]+(?=\<)/g))
		}

		return this.result;

	}

}



/**
 *@Description 打鍵速度を更新する
*/
function updateTypingSpeed(){

	document.getElementById("typing-speed").textContent = ((typeCounter - roundTypeCounter) / ((new Date().getTime()-searchTime)/1000)).toFixed(1)

}