// ==UserScript== // @name Flow Youtube Chat // @namespace FlowYoutubeChatScript // @version 1.12.2.emoji_fixed // @description Youtubeのチャットをニコニコ風に画面上へ流すスクリプトです(再アップ,絵文字バグ修正済み) // @author Emubure // @name:en Flow Youtube Chat // @description:en Flow the chat on Youtube // @match https://www.youtube.com/* // @require https://code.jquery.com/jquery-3.3.1.min.js // @require https://cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/js/toastr.min.js // @resource toastrCSS https://cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/css/toastr.min.css // @grant GM_setValue // @grant GM_getValue // @grant GM_addStyle // @grant GM_getResourceText // @noframes // @downloadURL none // ==/UserScript== (function(){ GM_addStyle(GM_getResourceText('toastrCSS')) //---ユーザー設定||デフォルトの数値--- //---User Settings||Default Value--- let USER_CONFIG = { Lang: GM_getValue('FYC_LANG')||'FYC_EN', Font: GM_getValue('FYC_FONT')||'', Opacity: GM_getValue('FYC_OPACITY')||1, Color: GM_getValue('FYC_COLOR')||'#FFFFFF', ColorOwner: GM_getValue('FYC_COLOR_OWNER')||'#ffd600', ColorModerator: GM_getValue('FYC_COLOR_MODERATOR')||'#5e84f1', ColorMember: GM_getValue('FYC_COLOR_MEMBER')||'#2ba640', Size: GM_getValue('FYC_SIZE')||1, SizeInChatField: GM_getValue('FYC_SIZE_IN_CHATFIELD')||13, Weight: GM_getValue('FYC_WEIGHT')||730, WeightShadow: GM_getValue('FYC_WEIGHT_SHADOW')||1, Limit: GM_getValue('FYC_LIMIT')||25, Speed: GM_getValue('FYC_SPEED')||18, MaxLength: GM_getValue('FYC_MAX')||100, LaneNum: GM_getValue('FYC_LANE_DIV')||12, NGWords: GM_getValue('FYC_NG_WORDS')||'', NGRegWords: GM_getValue('FYC_NG_REG_WORDS')||'', NGUsers: GM_getValue('FYC_NG_USERS')||'' } //ページの要素関連 let LIVE_PAGE = { getPlayer: ()=>{return document.getElementById('movie_player')}, getMainVideo: ()=>{return document.getElementsByClassName('video-stream html5-main-video')[0]}, getUserIcon: ()=>{return document.getElementsByClassName('style-scope ytd-topbar-menu-button-renderer no-transition')[0].lastElementChild}, getChatField: ()=>{ //現在アカウントによってはchatframeが見つからないバグが有り let chatField if(document.getElementById('chatframe')!==null){ chatField = document.getElementById('chatframe').contentDocument.querySelector("#items.style-scope.yt-live-chat-item-list-renderer") }else{ chatField = document.querySelector("#items.style-scope.yt-live-chat-item-list-renderer") } return chatField } } //コメント関連 let COMMENTS = { getAll: ()=>{return document.getElementsByClassName('fyc_comment')}, Visibility: 'visible', AnimState: 'running' } //設定関連 let SETTINGS = { CreateComments: true, CreateNGButtons: true, SimpleChatField: false, DisplayOwner: true, DisplayModerator: true, DisplayMember: true, DisplayModeratorName: true, DisplaySettingPanel: false, NGWords: [], NGRegWords: [], NGUsers: [], UserID: '', } //-----------立ち上がり------------ //$(window).on('load', () => { $(document).ready(() => { //チャット欄とプレイヤーが出るまで待つ findChatField() }) //URL変更検知オブザーバー let storedHref = location.href; const URLObserver = new MutationObserver(function(mutations){ mutations.forEach(function(mutation){ if(storedHref !== location.href){ findChatField() storedHref = location.href log('URL Changed', storedHref, location.href) } }) }) //URL監視 URLObserver.disconnect() URLObserver.observe(document, {childList: true, subtree: true}) //リサイズ検知オブザーバー function playerResizeObserve(){ clearInterval(playerResizeInterval_id) let storedSize = LIVE_PAGE.getPlayer().clientWidth + LIVE_PAGE.getPlayer().clientHeight var playerResizeInterval_id = setInterval(() => { if(LIVE_PAGE.getPlayer().clientWidth + LIVE_PAGE.getPlayer().clientHeight !== storedSize){ clearInterval(playerResizeInterval_id) deleteAllComments() initialize() } },1000) } var findInterval function findChatField(){ let FindCount = 1 clearInterval(findInterval) findInterval = setInterval(function(){ FindCount++ if(FindCount > 180){ log('The element cannot be found') clearInterval(findInterval) FindCount = 0 } if(document.getElementById('chatframe')){ if(LIVE_PAGE.getChatField() !== null && LIVE_PAGE.getPlayer() !== null){// && LIVE_PAGE.getUserIcon() !== null){ log('Found the element: ') console.log(LIVE_PAGE.getChatField()) console.log(LIVE_PAGE.getPlayer()) //console.log(LIVE_PAGE.getUserIcon()) setTimeout(function(){//少し待つ initialize() }, 1000) clearInterval(findInterval) FindCount = 0 } } }, 1000) } function initialize(){ log('initialize...') //変数初期化 initializeParameter() //チャット欄監視 if(LIVE_PAGE.getChatField() !== null){ ChatFieldObserver.disconnect() ChatFieldObserver.observe(LIVE_PAGE.getChatField(), {childList: true}) } //プレイヤーサイズ監視 playerResizeObserve() //CSS配置 createScriptCSS() //コメント描画レイヤー配置 if(!document.getElementById('fyc_comment_screen')){ LIVE_PAGE.getPlayer().insertAdjacentHTML('afterbegin', '
') } //コメント表示切り替えボタン配置 createToggleCommentDisplayButton() //コメント送信フォーム設置 //createCommentSubmitForm() //イベント設置 putEvents() //設定パネル配置 createSettingPanel() } function initializeParameter(){ if(GM_getValue('FYC_TOGGLE_CREATE_COMMENTS') === true || GM_getValue('FYC_TOGGLE_CREATE_COMMENTS') === undefined){ SETTINGS.CreateComments = true }else{ SETTINGS.CreateComments = false } if(GM_getValue('FYC_NG_BUTTON') === true || GM_getValue('FYC_NG_BUTTON') === undefined){ SETTINGS.CreateNGButtons = true }else{ SETTINGS.CreateNGButtons = false } if(GM_getValue('FYC_SIMPLE_CHAT_FIELD') === false || GM_getValue('FYC_SIMPLE_CHAT_FIELD') === undefined){ SETTINGS.SimpleChatField = false }else{ SETTINGS.SimpleChatField = true } if(GM_getValue('FYC_DISPLAY_MODERATOR_NAME') === true || GM_getValue('FYC_DISPLAY_MODERATOR_NAME') === undefined){ SETTINGS.DisplayModeratorName = true }else{ SETTINGS.DisplayModeratorName = false } if(GM_getValue('FYC_DISPLAY_COMMENTS') === true || GM_getValue('FYC_DISPLAY_COMMENTS') === undefined){ COMMENTS.Visibility = 'visible' }else{ COMMENTS.Visibility = 'hidden' } //NG更新 if(GM_getValue('FYC_NG_WORDS') !== undefined){ SETTINGS.NGWords = USER_CONFIG.NGWords.split(/\r\n|\n/) } if(GM_getValue('FYC_NG_REG_WORDS') !== undefined){ SETTINGS.NGRegWords = USER_CONFIG.NGRegWords.split(/\r\n|\n/) } if(GM_getValue('FYC_NG_USERS') !== undefined){ SETTINGS.NGUsers = USER_CONFIG.NGUsers.split(/\r\n|\n/) } } function createScriptCSS(){ //const Player = LIVE_PAGE.getPlayer() const screenWidth = LIVE_PAGE.getPlayer().clientWidth const screenHeight = LIVE_PAGE.getMainVideo().clientHeight const screenY = (LIVE_PAGE.getPlayer().clientHeight - screenHeight)/2 //プレイヤー全体の高さ-実際の動画の高さ const screenWidthLimit = 0 - screenWidth * 4//コメントを 横幅の-4倍 まで流す const ScriptCSS = document.getElementById('fyc_style') //既にCSSがあれば消す if(ScriptCSS){ ScriptCSS.parentNode.removeChild(ScriptCSS) } let ScriptCSS_HTML = '' ScriptCSS_HTML +=`' document.body.insertAdjacentHTML('beforeend', ScriptCSS_HTML) //youtubeのCSSの設定 document.getElementById('chatframe').contentDocument.querySelector('#item-scroller.animated.yt-live-chat-item-list-renderer #item-offset.yt-live-chat-item-list-renderer').style.overflow = 'unset' //toastrの設定 toastr.options = { 'positionClass': 'toast-bottom-left', 'timeOut': '2500', 'progressBar': true, 'newestOnTop': true, 'extendedTimeOut': '1000' } } //--------------コメント関連-------------- const ChatFieldObserver = new MutationObserver(function(mutations){ mutations.forEach(function(e){ let addedChats = e.addedNodes if(addedChats.length <= 0){ return } for(let i = 0; i < addedChats.length; i++){ //.yt-live-chat-placeholder-item-rendererを避ける if(addedChats[i].children.length <= 0){ continue } const commentData = convertChat(addedChats[i]) //const commentLaneNum = judgeLaneNum(commentData) if(checkBannedWords(commentData) || checkBannedRegexpWords(commentData) || checkBannedUsers(commentData)){ addedChats[i].style.display='none' continue }else{ //youtubeのチャットは同じ要素を使い回すので、NG以外のときに表示させる処理も必要 if(addedChats[i].getElementsByClassName('style-scope yt-live-chat-paid-message-renderer').length > 0){ //スパチャの場合 addedChats[i].style.display='block' }else{ //通常の場合 addedChats[i].style.display='flex' } } if(SETTINGS.CreateComments){ findCommentsOutOfScreen() createComment(commentData) } if(SETTINGS.CreateNGButtons&&addedChats[i].getElementsByClassName('owner')[0]===undefined){ createNGButton(addedChats[i], commentData.authorID) } simplificationCommentField(addedChats[i], SETTINGS.SimpleChatField) deleteOldComments() } }) }) function calcSpeed(length){ const MAX_LENGTH = USER_CONFIG.MaxLength let speed = 0 speed = 720/(length+30) //最高文字数以上では速さ固定 if(length >= MAX_LENGTH){ speed = 720/(MAX_LENGTH+30) } //最大速度以上では速さ固定 if(speed < 720/(MAX_LENGTH+30)){ speed = 720/(MAX_LENGTH+30) } //ユーザー設定適用 speed = speed * (20/USER_CONFIG.Speed) return speed } function createComment(commentData){ const screenHeight = LIVE_PAGE.getMainVideo().clientHeight const commentHTML = commentData.html const commentLength = commentData.length const commentColor = commentData.color const commentAuthorID = commentData.authorID const commentIsMine = commentData.isMine const commentIsMember = commentData.isMember const commentSize = Math.round(((USER_CONFIG.Size-0.2) * (screenHeight/USER_CONFIG.LaneNum))*100)/100 const commentSpeed = calcSpeed(commentLength) const commentLaneNum = judgeLaneNum(commentData) let html = '' html += '' if(document.getElementsByClassName('fyc_comment_usable').length > 0){ const element = document.getElementsByClassName('fyc_comment_usable') element[element.length-1].outerHTML = html }else{ document.getElementById('fyc_comment_screen').insertAdjacentHTML('beforeend', html) } } function createNGButton(chat, id){ //既にボタンがあれば無視 if(chat.children['content'] && chat.children['content'].children['fyc_ngbutton'])return //スパチャは無視 if(chat.children['card'])return let button = document.createElement('button') button.className = 'style-scope yt-icon-button fyc_button' button.id = 'fyc_ngbutton' button.style = 'padding: 0px;width: 20px; height: 20px;' button.setAttribute('aria-label','NGに入れる') button.innerHTML = '
'+ ''+ ''+ ''+ ''+ '
' button.onclick = () =>{ try{ console.log('【FYC】Added to Banned Users: '+id) if(USER_CONFIG.NGUsers !== null){ USER_CONFIG.NGUsers += '\n'+id }else{ USER_CONFIG.NGUsers += id } GM_setValue('FYC_NG_USERS', GM_getValue('FYC_NG_USERS')+'\n'+id) document.getElementById('fyc_ngusers').value = USER_CONFIG.NGUsers SETTINGS.NGUsers = USER_CONFIG.NGUsers.split(/\r\n|\n/) chat.style.display='none' toastr.success('Added Banned User: '+id) }catch(e){ toastr.error('Error: '+e.message) } } chat.children['content'].children['message'].appendChild(button) } //チャット欄に追加されたチャットから必要なものを抽出する function convertChat(chat){ const MAX_LENGTH = USER_CONFIG.MaxLength let html = '' let length = 0 let color = USER_CONFIG.Color let isMine = false let isMember = false let authorID = null //オーナーチップの有無 if(chat.getElementsByClassName('owner')[0]){ color = USER_CONFIG.ColorOwner } //モデレーターチップの有無 if(chat.getElementsByClassName('moderator')[0]){ color = USER_CONFIG.ColorModerator } //メンバーチップの有無 if(chat.getElementsByClassName('member')[0]){ isMember = true color = USER_CONFIG.ColorMember } //チャットの子要素を見ていく let children = Array.from(chat.children) children.some(_chat =>{ let childID = _chat.id //テキストの場合 if(childID === 'content'){ //モデレーターの名前表示 if(chat.getElementsByClassName('moderator')[0]){ if(SETTINGS.DisplayModeratorName === true){ html += _chat.querySelector('#author-name').innerText + ': ' } } let text = Array.from(_chat.children).find((v) => v.id === 'message') //正規表現で文字と画像要素を分ける let textChildren = text.innerHTML.split(//g) textChildren.some(_text => { //絵文字の場合 if(_text.match('emoji yt-formatted-string style-scope yt-live-chat-text-message-renderer')){ let src = _text.match(/src="(\\.|[^"\\])*"/g)// let size = Math.round(((USER_CONFIG.Size-0.2) * (LIVE_PAGE.getMainVideo().clientHeight/USER_CONFIG.LaneNum))*100)/100 html += '' length++ }else{//テキストの場合 html += (_text.length >= MAX_LENGTH)? _text.substr(0, MAX_LENGTH) : _text//最大文字数以上なら切り捨て length += _text.length } if(length >= MAX_LENGTH)return true }) } //アイコンの場合(アイコンの画像URLはアカウントによって違うためこれでNGユーザー処理が出来そうだ) if(childID === 'author-photo'){ let str = _chat.lastElementChild.getAttribute('src')||'' let result = str.split('/') //yt3.ggpht.com/【-xxxxxxxxxxx】/AAAAAAAAAAI/AAAAAAAAAAA/【xxxxxxxxxxx】/s32-c-k-no-mo-rj-c0xffffff/photo.jpg authorID = result[3]+result[6] } //スパチャの場合 if(childID === 'card'){ //通常 if(_chat.className === 'style-scope yt-live-chat-paid-message-renderer'){ let header = _chat.children[0] let content = _chat.children[1] let text = content.children[0].innerText let textColor = window.getComputedStyle(header, null).getPropertyValue('background-color');//文字の色 let amountColor = window.getComputedStyle(content, null).getPropertyValue('background-color');//金額の色 let authorName = _chat.querySelector('#author-name') let paidAmount = _chat.querySelector('#purchase-amount')||_chat.querySelector('#purchase-amount-chip') text = (text.length >= MAX_LENGTH)? text.substr(0,MAX_LENGTH) : text html += ''+authorName.innerText+': ' html += ''+text+'' html += ''+paidAmount.innerText+'' length += text.length + paidAmount.innerText.length authorID = null } //ステッカー if(_chat.className === 'style-scope yt-live-chat-paid-sticker-renderer'){ let textColor = window.getComputedStyle(chat, null).getPropertyValue('--yt-live-chat-paid-sticker-chip-background-color') let amountColor = window.getComputedStyle(chat, null).getPropertyValue('--yt-live-chat-paid-sticker-background-color') //本当ならステッカーも表示させたいが、srcが取得出来るのはimg要素が出てから0.1秒遅延があり、無理 //let sticker = _chat.querySelector('#sticker') let authorName = _chat.querySelector('#author-name') let paidAmount = _chat.querySelector('#purchase-amount')||_chat.querySelector('#purchase-amount-chip') html += ''+authorName.innerText+': ' html += ''+paidAmount.innerText+'' length += paidAmount.innerText.length authorID = null } } }) let convertedComment={ html: html, length: length, color: color, isMine: isMine, isMember: isMember, authorID: authorID } return convertedComment } //レーン判定 function judgeLaneNum(commentData){ /* *1. 描画されてるコメントを全部見る *2. コメントの右端がスクリーンからはみ出てたらそのコメントのdata-lane属性のレーン番号をfalseにする そうでないならtrue *(2.5). 一つ前のコメントとの文字数差が10文字以上、かつ、一つ前のコメントのX座標がプレイヤーの横幅*0.4より右にある、という場合、コメントの判定ボックスを画面外まで引き伸ばして強制的にfalseにする(コメントが追い越して被るのを防ぐため) *3. レーンの真偽を頭から順番に見る。falseだったら次のレーンを見て、trueだったらそのレーンに設定してbreak */ const screenWidth = LIVE_PAGE.getPlayer().clientWidth const comments = COMMENTS.getAll() const latestCommentLength = commentData.length let acceptableLane = new Array(USER_CONFIG.LaneNum*2-1).fill(true) //1と2 //console.log('judge: ' + commentData.html) for(let i = 0; i <= comments.length - 1; i++){ const commentLane = comments[i].getAttribute('data-lane') const commentX = comments[i].getBoundingClientRect().x + comments[i].getBoundingClientRect().width//コメントの右端のx座標 const commentLength = comments[i].innerText.length //2.5(なんともアナログな調整法だけど) let commentBoxAdjustment = 0 if(latestCommentLength - commentLength >= 3 && commentX > screenWidth * 0.8 && commentX > 0){ commentBoxAdjustment = screenWidth - (commentX) + 70 } if(latestCommentLength - commentLength >= 10 && commentX > screenWidth * 0.4 && commentX > 0){ commentBoxAdjustment = screenWidth - (commentX) + 70 } //コメントの右端がスクリーンからはみ出ていたらfalse if(acceptableLane[commentLane]!==false){ acceptableLane[commentLane] = commentX + commentBoxAdjustment> screenWidth ? false : true //console.log('acceptableLane['+commentLane+']: '+acceptableLane[commentLane]) //console.log(': '+commentX+' + '+commentBoxAdjustment+' > '+screenWidth) } } //3 let resultLaneNum = 0 let i = 0 while(resultLaneNum === 0){ if(acceptableLane[i]==true){ resultLaneNum = i i = 0 break }else if(i > USER_CONFIG.LaneNum*3-1){//無限ループ対策 resultLaneNum = 0 i = 0 break }else if(acceptableLane[i]==false){ resultLaneNum = 0 } i++ } return resultLaneNum } function checkBannedWords(commentData){ if(!USER_CONFIG.NGWords||!commentData.html){ return false } let target = commentData.html for(let i = 0; i < SETTINGS.NGWords.length; i++){ if(target.indexOf(SETTINGS.NGWords[i]) > -1){ //console.log('Banned Words: '+commentData.html) return true break } } return false } function checkBannedRegexpWords(commentData){ if(!USER_CONFIG.NGRegWords||!commentData.html){ return false } let target = commentData.html for(let i = 0; i < SETTINGS.NGRegWords.length; i++){ if(SETTINGS.NGRegWords[i] === ''){continue} let result = target.match(SETTINGS.NGWords[i]) if(result !== null){ //console.log('Banned Words(Regexp): '+commentData.html) return true break } } return false } function checkBannedUsers(commentData){ if(!USER_CONFIG.NGUsers||!commentData.authorID){ return false } let target = commentData.authorID for(let i = 0; i < SETTINGS.NGUsers.length; i++){ if(SETTINGS.NGUsers[i]!==''){ let result = target.match(SETTINGS.NGUsers[i]) if(result !== null){ //log(commentData.authorID+': '+commentData.html) return true break } } } return false } function findCommentsOutOfScreen(){ const comments = COMMENTS.getAll() for(let i = comments.length-1; i >= 0; i--){ if(comments[i].getBoundingClientRect().x + comments[i].getBoundingClientRect().width <= 0){//なんかしらんけど70足りねえから足してる //再利用 if(comments[i].className !== 'fyc_comment fyc_comment_usable'){ comments[i].className += ' fyc_comment_usable' //console.log(comments[i]) } } } } function deleteOldComments(){ const comments = COMMENTS.getAll() if(comments.length<=USER_CONFIG.Limit)return; while(comments.length>USER_CONFIG.Limit){ comments[0].parentNode.removeChild(comments[0]) } } function deleteAllComments(){ const comments = COMMENTS.getAll() for(let i = comments.length-1; i >= 0; i--){ comments[i].parentNode.removeChild(comments[i]) } } function createCommentSubmitForm(){ //既にあればやらない if(document.getElementById('fyc_input_comment') !== null)return //送信フォーム設置 let parent = document.getElementsByClassName('ytp-chrome-controls')[0] let inputArea = document.createElement('div') inputArea.id = 'fyc_input_comment' inputArea.style = 'display: inline-block;' inputArea.innerHTML = ` ` parent.appendChild(inputArea) //Enterキー押下時 $('#fyc_input_comment_textbox').on('keydown',(e)=>{ if(e.keyCode === 13){ const message = document.querySelector('#fyc_input_comment_textbox').value document.getElementById('chatframe').contentDocument.querySelector('#input.style-scope.yt-live-chat-text-input-field-renderer').innerHTML = message document.getElementById('chatframe').contentDocument.querySelector('#send-button.style-scope.yt-live-chat-message-input-renderer').querySelector('#button.style-scope.yt-icon-button').click() } }) //ショートカットキー無効化 var ignoreShortcut = (e) => { //J,K,L,F,M,C var list = [74,75,76,70,77,67]; if(list.indexOf(e.keyCode) != -1){ e.stopPropagation(); } } document.querySelector('#fyc_input_comment_textbox').onfocus = () => { log('focus') window.addEventListener("keydown", ignoreShortcut, true); } document.querySelector('#fyc_input_comment_textbox').onblur = () => { log('blur') window.removeEventListener("keydown", ignoreShortcut, true) } } function simplificationCommentField(chat, setting){ if(setting === true){ chat.style.borderBottom='1px solid var(--yt-spec-text-secondary)' if(chat.getElementsByClassName('style-scope yt-live-chat-paid-message-renderer').length>0)return if(chat.getElementsByClassName('owner')[0])return chat.children['author-photo'].style.display = 'none' chat.querySelector('yt-live-chat-author-chip.style-scope.yt-live-chat-text-message-renderer').style.display = 'none' }else{ chat.style.borderBottom='none' if(chat.getElementsByClassName('style-scope yt-live-chat-paid-message-renderer').length>0)return if(chat.getElementsByClassName('owner')[0])return chat.children['author-photo'].style.display = 'block' chat.querySelector('yt-live-chat-author-chip.style-scope.yt-live-chat-text-message-renderer').style.display = 'inline-flex' chat.setAttribute('style', 'border-bottom: none;') } } function changeSizeInChatField(size){ document.getElementsByClassName('yt-live-chat-renderer').fontSize = size+'px' } //-----------設定パネル関連----------------- function putEvents(){ const video = document.getElementsByTagName('video')[0] video.addEventListener('playing', changeCommentAnimState) video.addEventListener('pause', changeCommentAnimState) } const changeCommentAnimState = () => { if(document.getElementsByTagName('video')[0].paused){ document.styleSheets.item(document.styleSheets.length-1).cssRules.item(0).style.animationPlayState = 'paused' COMMENTS.AnimState = 'paused' }else{ document.styleSheets.item(document.styleSheets.length-1).cssRules.item(0).style.animationPlayState = 'running' COMMENTS.AnimState = 'running' } } //コメント表示切り替えボタン設置 function createToggleCommentDisplayButton(){ //既にあればやらない if(document.getElementById('fyc_comment_visibility_button') !== null){ return } const parent = document.querySelector('.ytp-right-controls') let button = document.createElement('button') button.className = 'ytp-button fyc-comment-button' button.id = 'fyc_comment_visibility_button' button.type = 'button' if(COMMENTS.Visibility === 'visible'){//コメントが表示設定だったとき button.setAttribute('aria-label', 'コメント非表示') button.setAttribute('title', 'コメント非表示') button.innerHTML = ` ` }else{//コメントが非表示設定だったとき button.setAttribute('aria-label', 'コメント表示') button.setAttribute('title', 'コメント表示') button.innerHTML = ` ` } button.onclick = changeCommentDisplay //挿入 parent.appendChild(button) } //表示切り替え処理 const changeCommentDisplay = () => { const comments = COMMENTS.getAll() if(COMMENTS.Visibility==='visible'){//コメントを非表示にするとき GM_setValue('FYC_DISPLAY_COMMENTS', false) COMMENTS.Visibility='hidden' if(comments.length){ for(let i = comments.length-1; i >= 0; i--){ comments[i].style.visibility = 'hidden' } } document.getElementById('fyc_comment_visibility_button').setAttribute('aria-label', 'コメント表示') document.getElementById('fyc_comment_visibility_button').setAttribute('title', 'コメント表示') document.getElementById('comment_button_path').setAttribute('fill-opacity', '0') }else{//コメントを表示させるとき GM_setValue('FYC_DISPLAY_COMMENTS', true) COMMENTS.Visibility='visible' if(comments.length){ for(let i = comments.length-1; i >= 0; i--){ comments[i].style.visibility = 'visible' } } document.getElementById('fyc_comment_visibility_button').setAttribute('aria-label', 'コメント非表示') document.getElementById('fyc_comment_visibility_button').setAttribute('title', 'コメント非表示') document.getElementById('comment_button_path').setAttribute('fill-opacity', '1') } } //設定パネル function createSettingPanel(){ if(document.getElementsByClassName('fyc_settings')[0]!==undefined)return const HTML_EN = `
Language(Refresh after change)
Font
Aa1あア亜
Color*
Color(Owner)*
Color(Moderator)*
Color(Member)*
* requires [Reload]
Opacity
`+USER_CONFIG.Opacity+`
Size
`+USER_CONFIG.Size+`
Weight
`+USER_CONFIG.Weight+`
Weight(Shadow) *
`+USER_CONFIG.WeightShadow+`
Speed
`+USER_CONFIG.Speed+`
Max number of comments *
`+USER_CONFIG.Limit+`
Max number of characters *
`+USER_CONFIG.MaxLength+`
Number of Lines *
`+USER_CONFIG.LaneNum+`
Banned Words
Banned Words(Regexp)
Banned Users*
` const HTML_JA = `
言語(要ページ再読み込み)
フォント
Aa1あア亜
色(通常)*
色(オーナー)*
色(モデレーター)*
色(メンバー)*
*は要[再読み込み]
透明度
`+USER_CONFIG.Opacity+`
サイズ
`+USER_CONFIG.Size+`
太さ
`+USER_CONFIG.Weight+`
太さ(影) *
`+USER_CONFIG.WeightShadow+`
速度
`+USER_CONFIG.Speed+`
最大表示数 *
`+USER_CONFIG.Limit+`
最大文字数 *
`+USER_CONFIG.MaxLength+`
行数 *
`+USER_CONFIG.LaneNum+`
NGワード
NGワード(正規表現)
NGユーザー*
` //言語設定 let HTML = '' if(USER_CONFIG.Lang === 'FYC_EN'){ HTML = HTML_EN } if(USER_CONFIG.Lang === 'FYC_JA'){ HTML = HTML_JA } else{ HTML = HTML_EN } const menuElement = document.getElementById('menu-container').getElementsByClassName('dropdown-trigger style-scope ytd-menu-renderer')[0] menuElement.insertAdjacentHTML('beforebegin', HTML); //設定ボタン押下時のバルーン開閉 let HIDE_OR_BLOCK_JUDGE_ELEMENT = document.getElementById('fyc-setting-panel-block-or-hide') document.getElementById('fyc-setting-panel-button').onclick = function(){ if(SETTINGS.DisplaySettingPanel==false){ HIDE_OR_BLOCK_JUDGE_ELEMENT.style.visibility = 'visible' SETTINGS.DisplaySettingPanel=true }else if(SETTINGS.DisplaySettingPanel==true){ HIDE_OR_BLOCK_JUDGE_ELEMENT.style.visibility = 'hidden' SETTINGS.DisplaySettingPanel=false } } //同期 document.getElementById('fyc_check_button_to_ban').checked = SETTINGS.CreateNGButtons document.getElementById('fyc_check_display_moderator_name').checked = SETTINGS.DisplayModeratorName document.getElementById('fyc_button_toggle_create_comments').checked = SETTINGS.CreateComments document.getElementById('fyc_toggle_simple_chat_field').checked = SETTINGS.SimpleChatField document.getElementById('fyc_input_font').value = USER_CONFIG.Font document.getElementById('fyc_input_lang').value = USER_CONFIG.Lang //設定保存 document.getElementById('fyc_input_save_button').onclick = function(){ try{ let val = document.getElementById('fyc_input_color').value USER_CONFIG.Color = val GM_setValue('FYC_COLOR', val) val = document.getElementById('fyc_input_color_owner').value USER_CONFIG.ColorOwner = val GM_setValue('FYC_COLOR_OWNER', val) val = document.getElementById('fyc_input_color_moderator').value USER_CONFIG.ColorModerator = val GM_setValue('FYC_COLOR_MODERATOR', val) val = document.getElementById('fyc_input_color_member').value USER_CONFIG.ColorMember = val GM_setValue('FYC_COLOR_MEMBER', val) val = document.getElementById('fyc_ngwords').value USER_CONFIG.NGWords = val GM_setValue('FYC_NG_WORDS', val) val = document.getElementById('fyc_ngwords_reg').value USER_CONFIG.NGRegWords = val GM_setValue('FYC_NG_REG_WORDS', val) val = document.getElementById('fyc_ngusers').value USER_CONFIG.NGUsers = val GM_setValue('FYC_NG_USERS', val) val = document.getElementById('fyc_toggle_simple_chat_field').checked SETTINGS.SimpleChatField = val GM_setValue('FYC_SIMPLE_CHAT_FIELD', val) val = document.getElementById('fyc_check_button_to_ban').checked SETTINGS.CreateNGButtons = val GM_setValue('FYC_NG_BUTTON', val) val = document.getElementById('fyc_check_display_moderator_name').checked SETTINGS.DisplayModeratorName = val GM_setValue('FYC_DISPLAY_MODERATOR_NAME', val) val = document.getElementById('fyc_button_toggle_create_comments').checked SETTINGS.CreateComments = val GM_setValue('FYC_TOGGLE_CREATE_COMMENTS', val) SETTINGS.NGWords = USER_CONFIG.NGWords.split(/\r\n|\n/) SETTINGS.NGRegWords = USER_CONFIG.NGRegWords.split(/\r\n|\n/) SETTINGS.NGUsers = USER_CONFIG.NGUsers.split(/\r\n|\n/) toastr.success('Saved.') }catch(e){ toastr.error('Error: '+e.message) } } document.getElementById('fyc_input_font').onchange = function(){ let val = document.getElementById('fyc_input_font').value if(val === 'Default')val='' document.getElementById('fyc_font_sample_text').style.fontFamily = val USER_CONFIG.Font = val GM_setValue('FYC_FONT', val) } document.getElementById('fyc_input_lang').onchange = function(){ let val = document.getElementById('fyc_input_lang').value USER_CONFIG.Lang = val GM_setValue('FYC_LANG', val) } document.getElementById('fyc_range_opacity').oninput = function(val){ val=this.value/10 document.getElementById('output_opacity').value = val USER_CONFIG.Opacity = val GM_setValue('FYC_OPACITY', val) } document.getElementById('fyc_range_size').oninput = function(val){ val=this.value/10 document.getElementById('output_size').value = val USER_CONFIG.Size = val GM_setValue('FYC_SIZE', val) } document.getElementById('fyc_range_weight').oninput = function(val){ val=this.value*10 document.getElementById('output_weight').value = val USER_CONFIG.Weight = val GM_setValue('FYC_WEIGHT', val) } document.getElementById('fyc_range_weight_shadow').oninput = function(val){ val=this.value/10 document.getElementById('output_weight_shadow').value = val USER_CONFIG.WeightShadow = val GM_setValue('FYC_WEIGHT_SHADOW', val) } document.getElementById('fyc_range_speed').oninput = function(val){ val=this.value document.getElementById('output_speed').value = val USER_CONFIG.Speed = val GM_setValue('FYC_SPEED', val) } document.getElementById('fyc_range_limit').oninput = function(val){ val=this.value*5 document.getElementById('output_limit').value = val USER_CONFIG.Limit = val GM_setValue('FYC_LIMIT', val) } document.getElementById('fyc_range_max').oninput = function(val){ val=this.value*5 document.getElementById('output_max').value = val USER_CONFIG.Max = val GM_setValue('FYC_MAX', val) } document.getElementById('fyc_range_line').oninput = function(val){ val=this.value document.getElementById('output_line').value = val USER_CONFIG.LaneNum = val GM_setValue('FYC_LANE_DIV', val) } document.getElementById('fyc_reload_button').onclick = function(){ try{ console.log(LIVE_PAGE.getChatField()) document.getElementById('fyc_comment_screen').parentNode.removeChild(document.getElementById('fyc_comment_screen')) document.getElementsByClassName('ytp-button fyc-comment-button')[0].parentNode.removeChild(document.getElementsByClassName('ytp-button fyc-comment-button')[0]) initialize() ChatFieldObserver.disconnect() ChatFieldObserver.observe(LIVE_PAGE.getChatField(), {childList: true}) toastr.success('Reloaded.') }catch(e){ toastr.error('Error: '+e.message) } } } //------------------------------------------ function log(mes){console.log('【FYC】'+mes)} })()