Skip to content

Instantly share code, notes, and snippets.

@castella-cake
Last active March 25, 2025 12:57
Show Gist options
  • Save castella-cake/10a220bc8227adf503fcff9e19085851 to your computer and use it in GitHub Desktop.
Save castella-cake/10a220bc8227adf503fcff9e19085851 to your computer and use it in GitHub Desktop.
MintWatch Songle Integration
// ==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