// ==UserScript==
// @name 【kick】コメントスクロール
// @namespace http://tampermonkey.net/
// @version 0.7.2
// @description try to take over the world!
// @author You
// @match https://kick.com*
// @match https://kick.com/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=kick.com
// @grant none
// @license MIT
// ==/UserScript==
//メソッドが同頑張っても呼び出せないバグ発生中のためresetボタンは使えません
const setting = {
CommentColor : "white", //コメントの色
FontSize : 35, //コメントのサイズ おすすめ 30 ~ 45
FontFamily : "'Roboto', sans-serif", //フォント
Alpha : 0.7, //透過率 0 ~ 1 0.7 → 70%
StrokeColor : "black", //縁取りの色
LineSize : 1, //縁取りの幅
BoostRate : [0.1, 5], //コメント加速率 [0.1, 5] → 5文字ごとに10%増加
ScrollSpeed : 3.5, //基礎速度
};
///////////////////////////* DANGER *//////////////////////////////////
const HTML = {
CanvasHTML : `<div class="absolute left-0 top-0 h-full w-full" style="pointer-events: none;">
<canvas id="comment_canvas"></canvas>
</div>`,
ResetBtnHTML : `<button id="resetBTN">reset</button>`,
}
class CommentScroll {
constructor(setting, HTML) {
this.initSettings(setting); //設定読み込み
this.initFlg(); //フラグ
this.initCanvasProperties();
this.initHTML(HTML);//this.をしても何故かthis.canvasHTML等に入らず、からの状態になっている。HTMLObjectから直接呼び出すと動く
this.videoElement = '';
this.lastTime = performance.now();
this.fps = 60;
this.interval = 1000 / this.fps;
this.observer;
}
initSettings(setting) {
this.color = setting["CommentColor"];
this.commentPX = setting["FontSize"];
this.fontSize = setting["FontSize"];
this.fontFamily = setting["FontFamily"];
this.Alpha = setting["Alpha"];
this.strokeColor = setting["StrokeColor"];
this.lineW = setting["LineSize"];
this.boostRate = setting["BoostRate"];
this.scrollSpeed = setting["ScrollSpeed"];
}
initHTML(HTML){
this.canvasHTML = HTML["CanvasHTML"];
this.resetBtnHTML = HTML["ResetBtnHTML"];
}
initCanvasProperties(){
this.canvas = '';
this.ctx = '';
this.canvasWid = 800;
this.commentLane = [50, 100, 150, 200, 250, 300, 350, 400, 450];
this.laneCnt = 0;
this.id = 0;
this.commentObj = {};
}
initFlg(){
this.setup_flg = false;
this.observer_flg = false;
this.animationRunning = false;
}
setup(){
this.commentObj = {};
if (this.setup_flg) return;
if (document.getElementById('video-player') && location.href !=='https://kick.com/') {
console.log(85);
this.addCanvas();
this.canvasSizeSet(); //キャンバスのサイズをセット
this.commentget_observer(); //コメント監視開始
this.canvasAnimation();
this.videoSkipEvent();
this.setup_flg = true;
}
}
firstEvent() {
window.addEventListener('resize', ()=> this.canvasSizeSet());
window.addEventListener("keydown", (e) => {
if (e.code === 'KeyT') this.canvasSizeSet();
})
setTimeout(()=> this.setup(), 4000); //たまに人間が操作しているかの確認が入るので遅めに設定 (短いと引っかかる)
setTimeout(()=>this.resetBtn(), 2000);
}
resetBtn(){
if(document.getElementById('resetBTN') === null){
document.getElementById('channel-chatroom').querySelector('div:first-child > div').insertAdjacentHTML('afterend', this.resetBtnHTML);
document.getElementById('resetBTN').addEventListener("click", this.reset);
}
}
reset(){
if (this.observer) {
this.observer.disconnect();
};
// リセット処理
this.initFlg();
this.initCanvasProperties();
//this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
this.videoElement = '';
// 再セットアップ
this.setup();
this.canvasSizeSet();
}
commentget_observer() {
this.commentObj = {};
console.log("鬱");
this.observer = new MutationObserver(records => {
records.forEach((r) => {
const rNode = r.addedNodes;
if (rNode.length != 0) {
const commentElement = rNode[0].querySelectorAll('div > div > span')[2]
if (!commentElement) return;
const com = commentElement.textContent
if (com.length != 0 && com != ': ') {
//スタンプコメ除外 返信が:だけになってしまうので返信削除
this.commentPushToObj(com);
}
}
})
})
const chatDivCheck = () => {
const channelChatroom = document.getElementById('channel-chatroom');
const chatDiv = channelChatroom.querySelector('.no-scrollbar');
if(chatDiv === null){
setTimeout(chatDivCheck, 500);
}else{
this.observer.disconnect();
this.observer.observe(chatDiv, {
childList: true
})
}
}
chatDivCheck();
}
commentPushToObj(text) {
this.commentObj[this.id] = {
TEXT: text,
w: this.canvasWid + ((Object.keys(this.commentObj).length / this.commentLane.length) * 30) ,//レーン数を込めるコメントが読み込まれた場合ずらす
s: Math.ceil(this.scrollSpeed * (1 * (1 + this.boostRate[0] * Math.floor(text.length /
this.boostRate[1])))),
y: this.laneCnt,
}
this.laneCnt = (this.laneCnt >= this.commentLane.length) ? 0 : this.laneCnt + 1;
this.id++;
}
addCanvas() {
this.videoElement = document.getElementById('video-player');
this.videoElement.insertAdjacentHTML('afterend', this.canvasHTML);
const getCanvas = () =>{
this.canvas = document.getElementById('comment_canvas');
if(this.canvas == ''){
setTimeout(getCanvas,200);
}else{
console.log(this.canvas);
this.ctx = this.canvas.getContext('2d');
this.ctx.font = `${this.fontSize}px ${this.fontFamily}`;
this.ctx.fillStyle = this.color;
this.videoElement.parentElement.addEventListener("click", ()=> this.canvasSizeSet());
return;
}
}
getCanvas();
}
canvasSizeSet() {
setTimeout(() => {
if(!this.videoElement) return;
const rect = this.videoElement.getBoundingClientRect();
this.canvas.width = rect.width;
this.canvas.height = rect.height;
this.canvasWid = this.canvas.width;
}, 1000);
}
canvasAnimation() {
if (this.animationRunning) return; // 既にアニメーションが動いている場合は何もしない
this.animationRunning = true;
const animate = (currentTime) => {
const deltaTime = currentTime - this.lastTime;
if (deltaTime >= this.interval) {
this.lastTime = currentTime - (deltaTime % this.interval);
anime();
}
requestAnimationFrame(animate);
}
const anime = () => {
if(!this.ctx) return;
this.ctx.font = `${this.fontSize}px ${this.fontFamily}`;
this.ctx.fillStyle = this.color;
this.ctx.globalAlpha = this.Alpha;
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
this.ctx.lineWidth = this.lineW;
this.ctx.strokeStyle = this.strokeColor;
Object.entries(this.commentObj).forEach(([id, c]) => {
c["w"] -= c["s"]
if (c["w"] < 0 - this.commentPX * c["TEXT"].length) {
delete this.commentObj[id]; // ここでidを使って削除
if (Object.keys(this.commentObj).length < 5) {
this.laneCnt = 0;
}
} else {
this.ctx.strokeText(c["TEXT"], c["w"], this.commentLane[c["y"]]);
this.ctx.fillText(c["TEXT"], c["w"], this.commentLane[c["y"]]); // コメを描画
}
});
}
animate();
}
videoSkipEvent(){
this.seeked = this.videoElement.addEventListener('seeked',()=>{
this.observer.disconnect();
this.commentget_observer();
});
this.skip = document.querySelector('body:not(input)').addEventListener('keyup',(e)=>{
if(e.code === 'ArrowRight' || e.code === 'ArrowLeft'){
setTimeout(()=>{
this.observer.disconnect();
this.commentget_observer();
},500)
}
});
}
}
const comeScroll = new CommentScroll(setting, HTML);
let oldUrl = '';
setInterval(()=>{
if (oldUrl !== location.href) {
oldUrl = location.href; // oldUrlを更新
if (oldUrl.match(/https:\/\/kick\.com\/*/) !== null) {
window.dispatchEvent(new CustomEvent('urlChange')); // イベントを発火
}
}
},3000)
window.addEventListener('urlChange', () => {
comeScroll.firstEvent();
});