您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Display detailed information on the side panel of the song.
// ==UserScript== // @name AMQ Detailed Song Info // @namespace https://github.com/SlashNephy // @version 0.7.2 // @author SlashNephy // @description Display detailed information on the side panel of the song. // @description:ja 曲のサイドパネルに詳細な情報を表示します。 // @homepage https://scrapbox.io/slashnephy/AMQ_%E3%81%A7%E6%9B%B2%E3%81%AE%E3%82%B5%E3%82%A4%E3%83%89%E3%83%91%E3%83%8D%E3%83%AB%E3%81%AB%E8%A9%B3%E7%B4%B0%E6%83%85%E5%A0%B1%E3%82%92%E8%A1%A8%E7%A4%BA%E3%81%99%E3%82%8B_UserScript // @homepageURL https://scrapbox.io/slashnephy/AMQ_%E3%81%A7%E6%9B%B2%E3%81%AE%E3%82%B5%E3%82%A4%E3%83%89%E3%83%91%E3%83%8D%E3%83%AB%E3%81%AB%E8%A9%B3%E7%B4%B0%E6%83%85%E5%A0%B1%E3%82%92%E8%A1%A8%E7%A4%BA%E3%81%99%E3%82%8B_UserScript // @icon https://animemusicquiz.com/favicon-32x32.png // @supportURL https://github.com/SlashNephy/userscripts/issues // @match https://animemusicquiz.com/* // @require https://cdn.jsdelivr.net/gh/TheJoseph98/AMQ-Scripts@b97377730c4e8553d2dcdda7fba00f6e83d5a18a/common/amqScriptInfo.js // @connect api.jikan.moe // @connect api.myanimelist.net // @grant unsafeWindow // @grant GM_xmlhttpRequest // @grant GM_getValue // @grant GM_setValue // @license MIT license // ==/UserScript== (function () { 'use strict'; const awaitFor = async (predicate, timeout) => new Promise((resolve, reject) => { let timer; const interval = window.setInterval(() => { if (predicate()) { clearInterval(interval); clearTimeout(timer); resolve(); } }, 500); if (timeout !== undefined) { timer = window.setTimeout(() => { clearInterval(interval); clearTimeout(timer); reject(new Error('timeout')); }, timeout); } }); const onReady = (callback) => { if (document.getElementById('startPage')) { return; } awaitFor(() => document.getElementById('loadingScreen')?.classList.contains('hidden') === true) .then(callback) .catch(console.error); }; async function fetchJikanAnimeById(id) { const response = await fetch(`https://api.jikan.moe/v4/anime/${id}`); return response.json(); } const MalClientId = '6b13c8a22ad3a5e16dd52f548ba7d545'; async function fetchMalAnimeScoreById(id) { const response = await fetch(`https://api.myanimelist.net/v2/anime/${id}?fields=mean`, { headers: { 'X-MAL-CLIENT-ID': MalClientId, }, }); return response.json(); } class GM_Value { key; defaultValue; constructor(key, defaultValue, initialize = true) { this.key = key; this.defaultValue = defaultValue; const value = GM_getValue(key, null); if (initialize && value === null) { GM_setValue(key, defaultValue); } } get() { return GM_getValue(this.key, this.defaultValue); } set(value) { GM_setValue(this.key, value); } delete() { GM_deleteValue(this.key); } pop() { const value = this.get(); this.delete(); return value; } } const scoreCache = new Map(); const titleCache = new Map(); const showDifficultyRow = new GM_Value('SHOW_DIFFICULTY_ROW', true); const showVintageRow = new GM_Value('SHOW_VINTAGE_ROW', true); const showFormatRow = new GM_Value('SHOW_FORMAT_ROW', true); const showRatingRow = new GM_Value('SHOW_RATING_ROW', true); const showSpotifyLink = new GM_Value('SHOW_SPOTIFY_LINK', true); const showYouTubeLink = new GM_Value('SHOW_YOUTUBE_LINK', true); const showAnnLink = new GM_Value('SHOW_ANN_LINK', true); const rows = [ { id: 'difficulty-row', title: 'Difficulty', isEnabled() { return showDifficultyRow.get(); }, content(event) { return `${event.songInfo.animeDifficulty.toFixed(1)} / 100`; }, }, { id: 'vintage-row', title: 'Vintage', isEnabled() { return showVintageRow.get(); }, content(event) { return event.songInfo.vintage; }, }, { id: 'format-row', title: 'Format', isEnabled() { return showFormatRow.get(); }, content(event) { return event.songInfo.animeType; }, }, { id: 'rating-row', title: 'Rating', isEnabled() { return showRatingRow.get(); }, async content(event) { const { malId } = event.songInfo.siteIds; let score = scoreCache.get(malId); let title = titleCache.get(malId); if (score === undefined || title === undefined) { try { const result = await fetchJikanAnimeById(malId); score = result.data.score; title = result.data.title_japanese; } catch { try { const result = await fetchMalAnimeScoreById(malId); score = result.mean; title = result.alternative_titles.ja; } catch { score = null; title = null; } } scoreCache.set(malId, score); titleCache.set(malId, title); } if (title !== null && navigator.language === 'ja') { const element = document.getElementById('qpAnimeName'); if (element !== null && title !== element.textContent?.trim()) { element.innerHTML = `${title}<br/>(${element.textContent})`; unsafeWindow.quiz.infoContainer.fitTextToContainer(); } } if (score === null) { return `${event.songInfo.animeScore.toFixed(2)} / 10`; } return `${score.toFixed(2)} / 10 (MAL)`; }, }, ]; const links = [ { id: 'spotify-link', title: 'Spotify', target: '_blank', isEnabled() { return showSpotifyLink.get(); }, href(event) { return `spotify://search/${encodeURIComponent(event.songInfo.songName)}%20${encodeURIComponent(event.songInfo.artist)}/tracks`; }, }, { id: 'youtube-link', title: 'YouTube', target: '_blank', isEnabled() { return showYouTubeLink.get(); }, href(event) { return `https://www.youtube.com/results?search_query=${encodeURIComponent(event.songInfo.songName)}+${encodeURIComponent(event.songInfo.artist)}`; }, }, { id: 'ann-link', title: 'ANN', target: '_blank', isEnabled() { return showAnnLink.get(); }, href(event) { return `https://www.animenewsnetwork.com/encyclopedia/anime.php?id=${event.songInfo.annId}`; }, }, ]; const handle = (event) => { const container = document.querySelector('#qpAnimeContainer #qpSongInfoContainer'); if (!container) { throw new Error('container is not found.'); } for (const row of rows) { if (row.isEnabled !== undefined && !row.isEnabled()) { continue; } const element = getOrCreateRow(container, row.id); const contentElement = element.querySelector('.row-content'); if (contentElement !== null) { const content = row.content(event); if (content !== null && typeof content !== 'string') { contentElement.textContent = 'Loading...'; } Promise.resolve(content) .then((c) => { contentElement.textContent = c; }) .catch(console.error); } else { const content = row.content(event); Promise.resolve(content) .then((c) => { renderRow(element, { ...row, content: c, }); }) .catch(console.error); } } const element = getOrCreateLinkContainer(container, 'link-container'); renderLinks(element, links .filter((link) => link.isEnabled === undefined || link.isEnabled()) .map((link) => { const href = link.href(event); if (href === null) { return null; } return { ...link, href, }; }) .filter((x) => x !== null)); }; const getOrCreateRow = (container, id) => { const existing = document.getElementById(id); if (existing !== null) { return existing; } const element = document.createElement('div'); element.id = id; const hider = container.querySelector('div#qpInfoHider'); if (hider === null) { throw new Error('div#qpInfoHider is not found.'); } if (!hider.classList.contains('custom-hider')) { hider.classList.add('custom-hider'); } container.insertBefore(element, hider.previousElementSibling); return element; }; const renderRow = (element, row) => { const h5 = document.createElement('h5'); const b = document.createElement('b'); const p = document.createElement('p'); h5.append(b); element.append(h5); element.append(p); element.classList.add('row'); p.classList.add('row-content'); b.textContent = row.title; p.textContent = row.content; }; const getOrCreateLinkContainer = (container, id) => { const existing = document.getElementById(id); if (existing !== null) { while (existing.lastElementChild !== null) { existing.removeChild(existing.lastElementChild); } return existing; } const element = document.createElement('div'); element.id = id; const hider = container.querySelector('div#qpInfoHider'); if (hider === null) { throw new Error('div#qpInfoHider is not found.'); } if (!hider.classList.contains('custom-hider')) { hider.classList.add('custom-hider'); } container.insertBefore(element, hider); return element; }; const renderLinks = (element, ls) => { const b = document.createElement('b'); element.append(b); const lastIndex = ls.length - 1; for (const [index, link] of ls.entries()) { const a = document.createElement('a'); b.append(a); a.href = link.href; a.textContent = link.title; if (link.target !== undefined) { a.target = link.target; } if (index !== lastIndex) { b.append(' - '); } } }; unsafeWindow.detailedSongInfo = { register(item) { const container = 'content' in item ? rows : links; if (container.some((x) => x.id === item.id)) { return; } container.push(item); }, unregister(item) { const container = 'content' in item ? rows : links; const index = container.findIndex((x) => x.id === item.id); if (index >= 0) { container.splice(index, 1); } }, get rows() { return rows; }, get links() { return links; }, }; onReady(() => { new Listener('answer results', handle).bindListener(); AMQ_addScriptData({ name: 'Detailed Song Info', author: 'SlashNephy <[email protected]>', description: 'Display detailed information on the side panel of the song.', }); AMQ_addStyle(` .custom-hider { padding: 50% 0; } `); }); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址