Last active
March 25, 2025 12:57
-
-
Save castella-cake/10a220bc8227adf503fcff9e19085851 to your computer and use it in GitHub Desktop.
MintWatch Songle Integration
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==UserScript== | |
// @name MintWatch Songle Integration | |
// @namespace cyaki_mintwatch_songle | |
// @version 2025-03-25-a | |
// @description try not to take over the world! | |
// @author CYaki | |
// @match https://www.nicovideo.jp/watch/* | |
// @icon https://www.google.com/s2/favicons?sz=64&domain=nicovideo.jp | |
// @grant none | |
// @run-at document-start | |
// ==/UserScript== | |
(function () { | |
'use strict'; | |
const elementId = "pmwp-cyaki-songle" | |
const seekbarContainerId = "pmwp-cyaki-songle-seekbar" | |
const styleId = "pmwp-cyaki-songle-style" | |
const buttonId = "pmwp-cyaki-songle-button" | |
const videoElementId = "pmw-element-video" | |
//console.log(document) | |
document.addEventListener("pmw_playerReady", (e) => { | |
// すでにプラグインリストに表示がある場合は何もしない | |
if (!document.getElementById(elementId)) { | |
// プラグインリストにプラグインを表示する(推奨) | |
const itemElem = document.createElement("div"); | |
itemElem.id = elementId; | |
itemElem.className = "plugin-list-item" | |
itemElem.innerHTML = ` | |
<div class="plugin-list-item-title"> | |
Songle 非公式統合プラグイン | |
</div> | |
<div class="plugin-list-item-desc"> | |
Songle API を用いて、サビやメロディーをシークバーに表示します。 | |
</div> | |
` | |
const pluginListElement = document.getElementById("pmw-plugin-list") | |
pluginListElement.appendChild(itemElem) | |
console.log("PMW Plugin: Songle 非公式統合プラグイン") | |
} | |
// スタイルを用意 | |
if (!document.getElementById(styleId)) { | |
const styleElem = document.createElement("style"); | |
styleElem.id = styleId | |
styleElem.innerHTML = ` | |
.pmwp-cyaki-songle_chorus-segment { | |
position: absolute; | |
height: 0.5rem; | |
width: 1rem; | |
background: #a0f5; | |
transform: translate(0%, -50%); | |
border-radius: 2px; | |
&[data-ischorus="true"] { | |
background: #f50a; | |
z-index: 1; | |
} | |
} | |
div#pmwp-cyaki-songle-seekbar { | |
position: absolute; | |
left:0; | |
right: 0; | |
} | |
.seekbar-thumb { | |
z-index: 2; | |
} | |
` | |
document.body.appendChild(styleElem) | |
} | |
// シークバーに置く用のコンテナーを準備 | |
if (!document.getElementById(seekbarContainerId)) { | |
const containerElem = document.createElement("div"); | |
containerElem.id = seekbarContainerId; | |
containerElem.className = "pmwp-cyaki-songle_chorus-container" | |
const seekbarElement = document.getElementsByClassName("seekbar")[0] | |
seekbarElement.appendChild(containerElem) | |
} | |
// ボタンを準備 | |
let customPlayerControlButton, chorusData | |
function toChorusPoint() { | |
const videoElem = document.getElementById(videoElementId) | |
//console.log(videoElem) | |
const currentTime = videoElem.currentTime | |
//alert(`Hello! currenttime is ${currentTime}`) | |
// chorusDataは後で入れてもらう | |
if (chorusData && chorusData.repeats && chorusData.repeats.length > 0) { | |
// サビ区間の再生前の部分 | |
console.log(chorusData) | |
const filteredChorus = chorusData.repeats.filter(repeat => currentTime < (repeat.start / 1000)).sort((a, b) => a.start > b.start) | |
console.log(filteredChorus) | |
if (filteredChorus.length < 1) return | |
videoElem.currentTime = filteredChorus[0].start / 1000 | |
} | |
} | |
if (!document.getElementById(buttonId)) { | |
// 要素を作成 | |
customPlayerControlButton = document.createElement("button") | |
customPlayerControlButton.id = buttonId | |
customPlayerControlButton.type = "button" | |
customPlayerControlButton.innerHTML = `次のサビへ` | |
// プレイヤーコントローラーの右サイドをピックアップしてprepend | |
const playerControlLeftElement = document.getElementsByClassName("playercontroller-container-right")[0] | |
playerControlLeftElement.prepend(customPlayerControlButton) | |
// ボタン処理の作成 | |
customPlayerControlButton.addEventListener("click", toChorusPoint) | |
} else { | |
customPlayerControlButton = document.getElementById(buttonId) | |
} | |
// ここは毎回実行 | |
// イベントのdetailから動画IDとdurationを取得できる | |
const thisVideoId = JSON.parse(e.detail).videoInfo.data.response.video.id | |
const thisVideoDuration = JSON.parse(e.detail).videoInfo.data.response.video.duration | |
async function updateSeekbar() { | |
// 前の物が残らないようにまずクリア | |
const containerElem = document.getElementById(seekbarContainerId) | |
containerElem.innerHTML = "" | |
customPlayerControlButton.removeEventListener("click", toChorusPoint) | |
// コーラスAPIをフェッチしてJSONで取得 | |
const songleResult = await fetch(`https://widget.songle.jp/api/v1/song/chorus.json?url=${encodeURIComponent(`https://www.nicovideo.jp/watch/${thisVideoId}`)}`) | |
const json = await songleResult.json() | |
// 求めている物がないなら多分まだ未生成なので何もしない | |
if (!json.repeatSegments) return; | |
// セグメント表示を作成する | |
json.repeatSegments.forEach(seg => { | |
seg.repeats.forEach(rep => { | |
const segmentElem = document.createElement("div"); | |
segmentElem.className = "pmwp-cyaki-songle_chorus-segment" | |
segmentElem.setAttribute("data-ischorus", seg.isChorus) | |
segmentElem.setAttribute("style", `left: ${(rep.start / 1000) / thisVideoDuration * 100}%; width: ${(rep.duration / 1000) / thisVideoDuration * 100}%`); | |
containerElem.appendChild(segmentElem) | |
}) | |
}) | |
// ボタン用にデータを入れる | |
chorusData = json.chorusSegments[0] | |
console.log("chorusData updated", chorusData) | |
customPlayerControlButton.addEventListener("click", toChorusPoint) | |
} | |
updateSeekbar() | |
}) | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment