# 帰ニコ用時間移動スクリプト
帰ニコ用時間移動スクリプト v0.1
# ブックマークレット版
帰ニコ用時間移動スクリプトv0.1
# userscript版
// ==UserScript== // @name kinico_timeseeker // @version 0.1 // @description 帰ニコ用時間移動スクリプト // @match https://www.nicovideo.jp/watch/* // @grant none // @run-at document-start // ==/UserScript== (function () { /** @return {HTMLElement} */ let tag = (tagname = "div", {a, s, e, t} = {})=>{ let dom = document.createElement(tagname); for(let k in a)dom.setAttribute(k, a[k]); for(let k in s)dom.style[k] = s[k]; for(let k in e)dom.addEventListener(k, e[k]); if(t)dom.textContent = t; return dom; }; const buttonStyle = {borderRadius: "5px", padding: "0px 3px 0px 3px", border: "solid 1px #000", background: "#ddd", color: "#000"}; const containerBuild = ( title = [], menu = [], body = [], onRemoveTodo = [], )=>{ let container = tag("div", { s: {border: "2px solid #000", padding: 0, margin: 0}, }); let header = tag("div", { s: {border: "2px solid #666", display: "grid", grid: "auto-flow/auto max-content"}, }); header.append( ...title, tag("button", {t: "\u2026", s: buttonStyle, e: {click: (e)=>{ e.stopPropagation(); menuDom.toggleAttribute("hidden"); }}}), ); let menuDom = tag("div", {a: {hidden: "hidden"}, s: {textAlign: "right"}}); menuDom.append( ...menu, tag("button", {t: "remove", s: buttonStyle, e: {click: ()=>{[...onRemoveTodo].map(v=>v());}}}), ); container.append( header, menuDom, ...body, ); container.remove(); onRemoveTodo.push(()=>{container.remove();}); return container; }; (async()=>{ let endFlag = false; let onRemoveTodo = [()=>{endFlag = true; onRemoveTodo = [];}]; try{ let playerOpp; let containerAdder; const playerOppFinder = ()=>{ let rootDom = document.querySelector("main"); if(!rootDom)return; let root = rootDom[Object.keys(rootDom).find(v=>v.startsWith("__reactFiber"))]; if(!root)return; let pool = [root]; let r; while(pool[0] && !r){ let t = pool.pop(); if(t?.memoizedProps?.playerOperation){ r = t.memoizedProps.playerOperation; } if(t.sibling)pool.push(t.sibling); if(t.child)pool.push(t.child); } return r; }; const containerAddFuncMake = ()=>{ let videoElement = document.querySelector(`main video`); let commentInput = document.querySelector(`main textarea`); if(!videoElement || !commentInput)return; let videoWrapper = videoElement; while(!videoWrapper.parentElement.contains(commentInput)){ videoWrapper = videoWrapper.parentElement; } return (element)=>{ videoWrapper.parentElement.insertBefore(element, videoWrapper.nextSibling); }; }; { // page match let i = 10; while( !(playerOpp ??= playerOppFinder()) || !(containerAdder ??= containerAddFuncMake()) ){ if(i < 0)throw "target page not match"; await new Promise(res=>setTimeout(res, 500)); i--; } } let uiBody = tag("div", {s: {display: "grid", grid: "auto-flow/repeat(5, 1fr) 12ex repeat(5, 1fr)", height: "2lh"}}); { // ui build const seek = (time)=>{ playerOpp.currentTime.set( Math.min(playerOpp.duration, Math.max(0, time)), ); }; const seekButtonBuilder = (str = "", moveVpos)=>tag( "button", { t: str, e: { click: ()=>{ seek((Math.floor(playerOpp.currentTime.get() * 100) + moveVpos + 0.1) / 100); }, }, s: buttonStyle, }); /** @type {HTMLInputElement} */ let timeShow = tag("input", {a: {type: "text"}, s: {color: "#000", backgroundColor: "#ddd"}, e: { change: ()=>{ /** @type {string} */ let valText = timeShow.value ?? ""; let {1:hour = "0", 2:min = "0", 3:sec = "0", 4:subsec = ".0"} = valText.replace(/\s/, "") .match(/(?:(?:([0-9]*):)?([0-9]*):)?([0-9]+)(\.[0-9]+)?/) ?? {}; let targetTime = ((hour - 0) * 60 + (min - 0)) * 60 + ((sec + subsec) - 0); seek(targetTime + (subsec.length < 4 ? 0.001 : 0)); }, }}); const timeShowUpdate = ()=>{ if(endFlag)return; if(document.activeElement != timeShow){ let vpos = Math.floor(playerOpp.currentTime.get() * 100); let subsec = vpos % 100; let sec = (vpos - subsec) % 6000; let min = vpos - sec - subsec; const strMake = (v)=>Math.round(v).toString().padStart(2, "0"); timeShow.value = `${strMake(min / 6000)}:${strMake(sec / 100)}.${strMake(subsec)}`; } window.requestAnimationFrame(timeShowUpdate); }; timeShowUpdate(); uiBody.append( seekButtonBuilder("-10 ", -1000), seekButtonBuilder("-3 ", -300), seekButtonBuilder("-1 ", -100), seekButtonBuilder("-0.1 ", -10), seekButtonBuilder("-0.01", -1), timeShow, seekButtonBuilder("+0.01", +1), seekButtonBuilder("+0.1 ", +10), seekButtonBuilder("+1 ", +100), seekButtonBuilder("+3 ", +300), seekButtonBuilder("+10 ", +1000), ); } console.log(playerOpp); let container = containerBuild( ["KiNico timeseeker 0.1"], [], [uiBody], onRemoveTodo, ); containerAdder(container); }catch(e){ console.log(e); // eslint-disable-line onRemoveTodo.map(v=>v()); } })(); })();