Skip to content

Instantly share code, notes, and snippets.

@Shiroizu
Last active November 6, 2025 03:28
Show Gist options
  • Select an option

  • Save Shiroizu/faf59e536fcdd019f66e7d2cf682c082 to your computer and use it in GitHub Desktop.

Select an option

Save Shiroizu/faf59e536fcdd019f66e7d2cf682c082 to your computer and use it in GitHub Desktop.
javascript:void function(){javascript:(async function(){"use strict";const CONFIG={delay:200,filters:{minViews:1,skipTitlesWith:["on vocal","off vocal","\u30CB\u30B3\u30AB\u30E9"]},colors:{add:"#b0e8fc",existing:"#8ef990"},baseUrl:"https://vocadb.net"},sleep=ms=>new Promise(resolve=>setTimeout(resolve,ms)),createButton=(text,color,href)=>{const btn=document.createElement("a");return btn.textContent=text,btn.href=href,btn.target="_blank",btn.addEventListener("click",e=>{e.stopPropagation()}),Object.assign(btn.style,{backgroundColor:color,color:"black",padding:"7px 14px",border:"1px solid black",textDecoration:"none",display:"inline-block",marginLeft:"10px",marginTop:"5 px",width:"70px"}),btn},addButtonToElement=(button,videoId)=>{let position=`a[href="/watch/${videoId}"]`,element=document.querySelector(position);if(!element&&(console.log(`First position attempt failed for video id: ${videoId} and position: ${position}`),position=`a[href="https://www.nicovideo.jp/watch/${videoId}"]`,element=document.querySelector(position),!element&&(console.log(`Second position attempt failed for video id: ${videoId} and position: ${position}`),position=`a[href="watch/${videoId}"]`,element=document.querySelector(position),!element)))throw new Error(`No position found for video id: ${videoId} and position: ${position}`);return element.parentElement.parentElement.appendChild(button),!0},checkSongInDatabase=async(videoId,service)=>{console.log(`Checking ${service} song in database for video id: ${videoId}`);const url=`${CONFIG.baseUrl}/api/songs/byPV?pvService=${service}&pvId=${videoId}`;console.log("URL: ",url);const response=await fetch(url);if(!response.ok)throw new Error(`HTTP ${response.status}`);const result=await response.text();return"null"===result?null:JSON.parse(result)},addButtonsForNicoVideo=async videoId=>{const songData=await checkSongInDatabase(videoId,"NicoNicoDouga"),nicoBase="www.nicolog.jp"===window.location.host?"nicolog":"nicovideo",nicoUrl=`https://www.${nicoBase}.jp/watch/${videoId}`;if(null===songData){console.log("Video not in database");const addBtn=createButton("Add",CONFIG.colors.add,`${CONFIG.baseUrl}/Song/Create?pvUrl=${nicoUrl}`),infoBtn=createButton("Info",CONFIG.colors.add,`http://nicodata.vocaloid.eu/?NicoUrl=${nicoUrl}`);addButtonToElement(addBtn,videoId),addButtonToElement(infoBtn,videoId)}else{console.log("Video already added");const entryBtn=createButton("Song Entry",CONFIG.colors.existing,`${CONFIG.baseUrl}/S/${songData.id}`);addButtonToElement(entryBtn,videoId)}},handleVideoListPage=async()=>{console.log("Processing video list page..."),document.querySelectorAll("a").forEach(a=>{a.onmousedown=null,a.onmouseup=null,a.addEventListener("click",e=>e.stopPropagation(),!0)});let videos=document.querySelectorAll(".grid-area_main .flex-d_column"),gridLayout=!0;if(0===videos.length)return void console.error("No videos found! Layout changed?");1==videos.length&&(console.log("Column layout detected."),videos=videos[0].childNodes,gridLayout=!1);for(const video of videos)video.style.border="1px solid orange";for(const video of videos){if(await sleep(CONFIG.delay),console.log("Video:",video),!video)continue;const title=gridLayout?video.childNodes[1].innerText||"":video.childNodes[1].childNodes[0].innerText,views=gridLayout?parseInt(video.childNodes[2].childNodes[1].innerText):parseInt(video.childNodes[1].childNodes[2].childNodes[1].innerText);let videoId=gridLayout?video.childNodes[1].href.split("/watch/")[1]:video.childNodes[1].childNodes[0].href.split("/watch/")[1];if(videoId=videoId.split("?")[0],console.log("Video ID:",videoId),videoId){if(views<CONFIG.filters.minViews){console.log("Skipping, not enough views:",views);continue}if(CONFIG.filters.skipTitlesWith.some(word=>title.toLowerCase().includes(word.toLowerCase()))){console.log("Skipping title",title);continue}await addButtonsForNicoVideo(videoId)}}},handleNicologUserPage=async()=>{console.log("Processing Nicolog User page...");const videos=document.querySelectorAll("tr");if(console.log("Videos:",videos),0===videos.length)return void console.error("No videos found! Layout changed?");for(let i=1;i<videos.length;i++){await sleep(CONFIG.delay);const videoId=videos[i].childNodes[0].childNodes[0].childNodes[0].href.split("watch/")[1];await addButtonsForNicoVideo(videoId)}},handleNicoUserPage=async()=>{console.log("Processing Nico User page...");const videos=document.querySelectorAll(".VideoMediaObjectList a.NC-MediaObject-contents");if(0===videos.length)return void console.error("No videos found! Layout changed?");for(const video of videos){await sleep(CONFIG.delay);const videoId=video.href.split("?")[0].split("/")[4];await addButtonsForNicoVideo(videoId)}},handleDirectVideoPage=async()=>{console.log("Processing direct video page...");try{let service,videoId,videoUrl=window.location.href;switch(window.location.host){case"www.nicovideo.jp":service="NicoNicoDouga",videoId=videoUrl.split("/watch/")[1];break;case"music.youtube.com":case"www.youtube.com":service="Youtube",videoId=videoUrl.split("?v=")[1].split("&")[0];break;default:throw new Error(`Unsupported host: ${window.location.host}`);}const songData=await checkSongInDatabase(videoId,service);songData?window.location.assign(`${CONFIG.baseUrl}/S/${songData.id}`):window.open(`${CONFIG.baseUrl}/Song/Create?pvUrl=${videoUrl}`)}catch(error){alert(error.message)}},{host,pathname}=window.location;try{"www.nicolog.jp"===host&&pathname.startsWith("/user/")?await handleNicologUserPage():"www.nicovideo.jp"!==host||pathname.startsWith("/watch/")?["www.youtube.com","music.youtube.com","www.nicovideo.jp"].includes(host)?await handleDirectVideoPage():alert("This bookmarklet is not supported on this website."):pathname.startsWith("/tag/")||pathname.startsWith("/search/")?await handleVideoListPage():pathname.startsWith("/user/")&&(await handleNicoUserPage())}catch(error){console.error("Bookmarklet execution failed:",error),alert("An error occurred while processing the page.")}})()}();
javascript: (async function () {
"use strict";
// Configuration
const CONFIG = {
delay: 200,
filters: {
minViews: 1,
skipTitlesWith: ["on vocal", "off vocal", "ニコカラ"]
},
colors: {
add: "#b0e8fc",
existing: "#8ef990"
},
baseUrl: "https://vocadb.net"
};
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
const createButton = (text, color, href) => {
const btn = document.createElement("a");
btn.textContent = text;
btn.href = href;
btn.target = "_blank";
btn.addEventListener("click", e => {
e.stopPropagation();
});
Object.assign(btn.style, {
backgroundColor: color,
color: "black",
padding: "7px 14px",
border: "1px solid black",
textDecoration: "none",
display: "inline-block",
marginLeft: "10px",
marginTop: "5 px",
width: "70px",
});
return btn;
};
const addButtonToElement = (button, videoId) => {
let position = `a[href="/watch/${videoId}"]`;
let element = document.querySelector(position);
if (!element) {
console.log(
`First position attempt failed for video id: ${videoId} and position: ${position}`
);
position = `a[href="https://www.nicovideo.jp/watch/${videoId}"]`;
element = document.querySelector(position);
if (!element) {
console.log(
`Second position attempt failed for video id: ${videoId} and position: ${position}`
);
position = `a[href="watch/${videoId}"]`;
element = document.querySelector(position);
if (!element) {
throw new Error(
`No position found for video id: ${videoId} and position: ${position}`
);
}
}
}
element.parentElement.parentElement.appendChild(button);
return true;
};
const checkSongInDatabase = async (videoId, service) => {
console.log(
`Checking ${service} song in database for video id: ${videoId}`
);
const url = `${CONFIG.baseUrl}/api/songs/byPV?pvService=${service}&pvId=${videoId}`;
console.log("URL: ", url);
const response = await fetch(url);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const result = await response.text();
return result === "null" ? null : JSON.parse(result);
};
const addButtonsForNicoVideo = async videoId => {
const songData = await checkSongInDatabase(videoId, "NicoNicoDouga");
const nicoBase = (window.location.host === "www.nicolog.jp") ? "nicolog" : "nicovideo";
const nicoUrl = `https://www.${nicoBase}.jp/watch/${videoId}`;
if (songData === null) {
// Video not in database - add "Add" and "Info" buttons
console.log("Video not in database");
const addBtn = createButton(
"Add",
CONFIG.colors.add,
`${CONFIG.baseUrl}/Song/Create?pvUrl=${nicoUrl}`
);
const infoBtn = createButton(
"Info",
CONFIG.colors.add,
`http://nicodata.vocaloid.eu/?NicoUrl=${nicoUrl}`
);
addButtonToElement(addBtn, videoId);
addButtonToElement(infoBtn, videoId);
} else {
// Video exists in database - add "Song Entry" button
console.log("Video already added");
const entryBtn = createButton(
"Song Entry",
CONFIG.colors.existing,
`${CONFIG.baseUrl}/S/${songData.id}`
);
addButtonToElement(entryBtn, videoId);
}
};
const handleVideoListPage = async () => {
console.log("Processing video list page...");
document.querySelectorAll("a").forEach(a => {
a.onmousedown = null;
a.onmouseup = null;
a.addEventListener("click", e => e.stopPropagation(), true);
});
let videos = document.querySelectorAll(".grid-area_main .flex-d_column");
let gridLayout = true;
if (videos.length === 0) {
console.error("No videos found! Layout changed?");
return;
}
if (videos.length == 1) {
console.log("Column layout detected.");
videos = videos[0].childNodes;
gridLayout = false;
}
for (const video of videos) {
video.style.border = "1px solid orange";
}
for (const video of videos) {
await sleep(CONFIG.delay);
console.log("Video:", video);
if (!video) continue;
const title = gridLayout
? video.childNodes[1].innerText || ""
: video.childNodes[1].childNodes[0].innerText;
const views = gridLayout
? parseInt(video.childNodes[2].childNodes[1].innerText)
: parseInt(video.childNodes[1].childNodes[2].childNodes[1].innerText);
let videoId = gridLayout
? video.childNodes[1].href.split("/watch/")[1]
: video.childNodes[1].childNodes[0].href.split("/watch/")[1];
videoId = videoId.split("?")[0];
console.log("Video ID:", videoId);
if (!videoId) continue;
if (views < CONFIG.filters.minViews) {
console.log("Skipping, not enough views:", views);
continue;
}
if (
CONFIG.filters.skipTitlesWith.some(word =>
title.toLowerCase().includes(word.toLowerCase())
)
) {
console.log("Skipping title", title);
continue;
}
await addButtonsForNicoVideo(videoId);
}
};
// Page handlers
const handleNicologUserPage = async () => {
console.log("Processing Nicolog User page...");
const videos = document.querySelectorAll("tr");
console.log("Videos:", videos);
if (videos.length === 0) {
console.error("No videos found! Layout changed?");
return;
}
// start from index 1 (skip table header)
for (let i = 1; i < videos.length; i++) {
await sleep(CONFIG.delay);
const videoId =
videos[i].childNodes[0].childNodes[0].childNodes[0].href.split(
"watch/"
)[1];
await addButtonsForNicoVideo(videoId);
}
};
const handleNicoUserPage = async () => {
console.log("Processing Nico User page...");
const videos = document.querySelectorAll(
".VideoMediaObjectList a.NC-MediaObject-contents"
);
if (videos.length === 0) {
console.error("No videos found! Layout changed?");
return;
}
for (const video of videos) {
await sleep(CONFIG.delay);
const videoId = video.href.split("?")[0].split("/")[4];
await addButtonsForNicoVideo(videoId);
}
};
const handleDirectVideoPage = async () => {
console.log("Processing direct video page...");
try {
let videoUrl = window.location.href;
let service, videoId;
switch (window.location.host) {
case "www.nicovideo.jp":
service = "NicoNicoDouga";
videoId = videoUrl.split("/watch/")[1];
break;
case "music.youtube.com":
case "www.youtube.com":
service = "Youtube";
videoId = videoUrl.split("?v=")[1].split("&")[0];
break;
default:
throw new Error(`Unsupported host: ${window.location.host}`);
}
const songData = await checkSongInDatabase(videoId, service);
if (songData) {
// Redirect to existing song entry
window.location.assign(`${CONFIG.baseUrl}/S/${songData.id}`);
} else {
// Open song creation page
window.open(`${CONFIG.baseUrl}/Song/Create?pvUrl=${videoUrl}`);
}
} catch (error) {
alert(error.message);
}
};
// Main execution logic
const { host, pathname } = window.location;
try {
if (host === "www.nicolog.jp" && pathname.startsWith("/user/")) {
await handleNicologUserPage();
} else if (host === "www.nicovideo.jp" && !pathname.startsWith("/watch/")) {
if (pathname.startsWith("/tag/") || pathname.startsWith("/search/")) {
await handleVideoListPage();
} else if (pathname.startsWith("/user/")) {
await handleNicoUserPage();
}
} else if (
[
"www.youtube.com",
"music.youtube.com",
"www.nicovideo.jp"
/* "piapro.jp",
"vimeo.com",
"soundcloud.com",
"www.bilibili.com" */
].includes(host)
) {
await handleDirectVideoPage();
} else {
alert("This bookmarklet is not supported on this website.");
}
} catch (error) {
console.error("Bookmarklet execution failed:", error);
alert("An error occurred while processing the page.");
}
})();
@Shiroizu
Copy link
Author

Shiroizu commented Mar 31, 2020

Bookmarklet script with two purposes (depends on the current URL):

1) Quickly finding the song entry page from the video page (NicoNico, Youtube):

  • If the song entry page already exists on the database, it will be displayed replacing the current tab.
    (if opening in a new tab is preferred, change the line "window.location.assign" to "window.open")

  • If the entry doesn't exist, a song submit page opens as a new tab with pre-filled video URL -field (this requires browser pop-up permissions for the video sites).

2) Helping with browsing the vocaloid-tag (works with any tag) on NicoNicoDouga: https://www.nicovideo.jp/tag/VOCALOID?sort=f&order=d

The script adds buttons below the video links (song entry or add+info) if the view count and the mylist count is more than specified.

pic

3) Also works on the NND search pages, artist mylist pages and artist video pages.

4) Also works on nicolog user pages

@Susko3
Copy link

Susko3 commented Jan 14, 2025

For youtube music support. It's free as the url formats are the same, and vocadb already supports it.

Test URLs:

diff --git "a/.\\old.txt" "b/.\\new.txt"
index 3ea4712..b81402c 100644
--- "a/.\\old.txt"
+++ "b/.\\new.txt"
@@ -182,9 +182,9 @@ javascript:(
             }, delay);
           } checkVideos();
         } 
-      } else if (["piapro.jp", "vimeo.com", "soundcloud.com", "www.youtube.com", "www.nicovideo.jp", "www.bilibili.com", "www.creofuga.net"].indexOf(window.location.host) !== -1) {
+      } else if (["piapro.jp", "vimeo.com", "soundcloud.com", "www.youtube.com", "music.youtube.com", "www.nicovideo.jp", "www.bilibili.com", "www.creofuga.net"].indexOf(window.location.host) !== -1) {
         var videoUrl = ""; 
-        if (window.location.host === "www.youtube.com") {
+        if (window.location.host === "www.youtube.com" || window.location.host === "music.youtube.com") {
           videoUrl = window.location.href.split("&")[0];
         } else {
           videoUrl = window.location.href.split("?")[0];

@Shiroizu
Copy link
Author

2025-08-19:

Cleaned up the code and refactored with the Fetch API.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment