注意:在保存之后,您可能需要清除浏览器缓存才能看到所作出的变更的影响。
?_=1
来访问最新页面。https://zh.moegirl.org.cn/User:%E5%AE%9E%E9%AA%8C%E6%80%A7%EF%BC%9A%E6%97%A0%E7%94%A8%E8%AE%BA%E5%BA%9F%E4%BA%BA/js/VocaDB-VOCALOID.js?_=1
/** 利用[https://vocadb.net/ VocaDB]的数据,生成moegirl上的模板。
* 采用JSONP获取数据。
* 代码堆放至github:https://github.com/VOCALOID-lower/Wiki-JS/blob/main/src/VocaDB-VOCALOID.js
* ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
*
* 加载该脚本后,请在页面[[Special:VocaDB]]进行操作。
* 注意一天内不要使用太多次“获取P主歌曲列表”,容易超过VocaDB API请求上限。
* 由于正则包含负向预查,可能不支持Safari等浏览器。
*
* ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
*/
"use strict";
const action = () => {
let ps_1 = '<p><del>若获取有误,建议自行到VocaDB修改</del></p>';
let ps_2 = '<p>补充:该获取最多获取500首,且只包含niconico的投稿,其余手动补充;</p><p>排除了recover等作品,但在VocaDB大部分专辑曲重新投稿是算recover的,记得检查。</p>';
let context = `${ps_1}
<hr>
<div id="change" style="
overflow: scroll;
user-modify: read-write;
-webkit-user-modify: read-write;
-moz-user-modify: read-write;
max-height: 500px;
">生成于此处</div>
<hr>
<!--
-->
<h2>获取歌曲</h2>
<form id="templeform_song" class="getid">
<input class="getid" type="number" name="sid" placeholder="vocadb歌曲页面ID">
</form>
<br>
<div id="song" class="getbuttom">获得请求</div>
<br>
<!--
-->
<h2>获取P主歌曲列表</h2>
${ps_2}
<form id="templeform_ar" class="getid">
<input class="getid" type="number" name="arid" placeholder="vocadbP主页面ID">
</form>
<br>
<div id="ar" class="getbuttom">获得请求</div>
<!--
-->
<h2>获取专辑曲目列表</h2>
<form id="templeform_album" class="getid">
<input class="getid" type="number" name="alid" placeholder="vocadb专辑页面ID">
</form>
<br>
<div id="album" class="getbuttom">获得请求</div>
<br>`;
document.getElementsByTagName("style")[0].append(`
div.getbuttom{
background-color: #5fa7f3;
width: 140px;
line-height: 38px;
text-align: center;
font-weight: bold;
color: #fff;
text-shadow:1px 1px 1px #333;
border-radius: 5px;
margin:0 20px 20px 0;
position: relative;
overflow: hidden;
}
input.getid{
border-color: #000;
font-size: 12px;
height:30px;
border-radius:4px;
border:1px solid #c8cccf;
color:#986655;
outline:0;
text-align:left;
padding-left: 10px;
display: block;
cursor: pointer;
box-shadow: 2px 2px 5px 1px #ccc;
}`);
if (mw.config.get("skin") === "moeskin") {
$('#moe-body-content').html(context);
} else {
$('#bodyContent').html(context);
}
if (mw.config.get("skin") === "minerva") {
$('h1#section_0').text('Special:VocaDB');
} else {
document.getElementsByTagName("h1")[0].textContent = "Special:VocaDB";
}
document.title = 'Special:VocaDB';
}
const change = () => {
document.getElementById('change').innerHTML = 'loading...';
}
//action
//预备函数
const api = new mw.Api();
const ja = /[\u2E80-\u2FDF\u3040-\u318F\u31A0-\u31BF\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FFF\uA960-\uA97F\uAC00-\uD7FF]/i;
const IsInIt = (a, b, c) => {
if (b.search(ja) != -1) {
b = `{{lj|${b}}}`;
}
if (a.indexOf(b) == -1) {
var d = a + b + c;
} else {
var d = a;
}
return d
}
const _IsInIt = (a, b, c) => {
if (a.indexOf(b) == -1) {
var d = a + b + c;
} else {
var d = a;
}
return d
}
const second_change = time => {
let m = parseInt(time / 60)
let s = parseInt(time % 60)
s = s < 10 ? '0' + s : s
return `${m}:${s}`
}
const lj = (name, links) => {
//内链
if (links) {
if (links.indexOf('NicoNicoDouga') != -1) {
name = `[[${name}]]`;
}
}
//语言
if (name) {
if (name.search(ja) != -1) {
name = `{{lj|${name}}}`;
}
return name
} else if (name == undefined) {
name = '';
return name
} else {
return name
}
}
const isAllEqual = array => {
return !array.some(value => {
return value !== array[0];
});
}
const tran = async (template) => {
let obj = {};
let params = {
'format': 'json',
'action': 'expandtemplates',
'text': `{{:Template:${template}}}`,
}
try {
var response = await api.postWithToken('csrf', params) || '';
} catch (err) { // 防止502
return tran(template)
}
if (response) {
let data = response;
(data['expandtemplates']['*']).replace(/\[\[(.+?)(\|(.+?))?\]\]/ig, (a, b, c, d) => {
obj[d ? d.replace(/^(<(?<a>.+?)( .+)?>)?(.+?)(<\/(\k<a>)>)?$/ig, "$4").replace(/^(-\{)?(.+?)(\}-)?$/ig/* JS没有平衡组只能出此下策 */, "$2") : b] = b;
})
return obj;
} else { // 防止502
return tran(template);
}
};
//star
const getting_A_ = async (data, pageid) => {
let br = '<br>';
let Lyricist = '';
let Composer = '';
let Illustrator = '';
let Animator = '';
let Vocalist = '';
data.artists.map(this_artists => {
let artistsname = this_artists.name || '';
let categories_split = this_artists.categories.split(', ');
let effectiveRoles_split = this_artists.effectiveRoles.split(', ');
for (let j = 0; j < effectiveRoles_split.length || j < categories_split.length; j++) {
switch (effectiveRoles_split[j]) {
case 'Producer':
Lyricist = IsInIt(Lyricist, artistsname, br);
Composer = IsInIt(Composer, artistsname, br);
break;
case 'Lyricist':
Lyricist = IsInIt(Lyricist, artistsname, br);
break;
case 'Composer':
Composer = IsInIt(Composer, artistsname, br);
break;
case 'Vocalist':
Vocalist = IsInIt(Vocalist, artistsname, br);
break;
case 'Animator':
Animator = IsInIt(Animator, artistsname, br);
break;
case 'Illustrator':
Illustrator = IsInIt(Illustrator, artistsname, br);
break;
default:
break;
}
switch (categories_split[j]) {
case 'Lyricist':
Lyricist = IsInIt(Lyricist, artistsname, br);
break;
case 'Composer':
Composer = IsInIt(Composer, artistsname, br);
break;
case 'Vocalist':
Vocalist = IsInIt(Vocalist, artistsname, br);
break;
case 'Animator':
Animator = IsInIt(Animator, artistsname, br);
break;
case 'Illustrator':
Illustrator = IsInIt(Illustrator, artistsname, br);
break;
default:
break;
}
}
})
if (Composer == '' || Lyricist == '') {
for (let i = 0; i < data.artists.length; i++) {
if (data.artists[i].categories.indexOf('Producer') != -1) {
Composer = IsInIt(Composer, data.artists[i].name, br);
Lyricist = IsInIt(Lyricist, data.artists[i].name, br);
break;
}
}
}
let x_a =
'|作曲 = ' + Composer.substring(0, Composer.length - 4) + '\n' +
'|填词 = ' + Lyricist.substring(0, Lyricist.length - 4) + '\n' +
'|视频制作 = ' + Animator.substring(0, Animator.length - 4) + '\n' +
'|画师 = ' + Illustrator.substring(0, Illustrator.length - 4) + '\n' +
'|演唱者 = ' + Vocalist.substring(0, Vocalist.length - 4).replace(/<br>/g, '、') + '\n';
return getting_B(x_a, pageid);
}
const getting_B_ = async (x_a, data) => {
let YouTube = '';
let NicoNico = '';
let Bilibili = '';
let thumbUrl = '';
let publishtime = '';
if (data.pvServices.indexOf('NicoNicoDouga') != -1) {
for (let i = 0; i < data.pvs.length; i++) {
if (data.pvs[i].service == 'NicoNicoDouga' && data.pvs[i].pvType == 'Original') {
NicoNico = data.pvs[i].url.replace(/.+watch\//i, '');
thumbUrl = data.pvs[i].thumbUrl;
publishtime = data.pvs[i].publishDate;
if (data.pvs[i].url.replace(/.+watch\/sm/i, '') > 23648995) {
thumbUrl += '.M';
}
break;
}
}
}
if (data.pvServices.indexOf('Youtube') != -1) {
for (let i = 0; i < data.pvs.length; i++) {
if (data.pvs[i].service == 'Youtube' && data.pvs[i].pvType == 'Original') {
YouTube = data.pvs[i].url.replace(/.+youtu\.be\//i, '');
break;
}
}
}
if (data.pvServices.indexOf('Bilibili') != -1) {
for (let i = 0; i < data.pvs.length; i++) {
if (data.pvs[i].service == 'Bilibili' && data.pvs[i].pvType == 'Original') {
Bilibili = data.pvs[i].url.replace(/.+video\//i, '');
break;
}
}
}
if (!(publishtime)) {
publishtime = data.pvs[0].publishDate;
}
let x_b =
'|nnd_id = ' + NicoNico + '\n' +
'|yt_id = ' + YouTube + '\n' +
'|bb_id = ' + Bilibili + '\n';
let x_c =
'|image = ' + thumbUrl + '\n';
let x_d =
'|投稿日期 = ' + (publishtime || '').replace(/(\d{4})-(\d{2})-(\d{2}).+/i, '$1年$2月$3日') + '\n';
if (data.name.search(ja) != -1) {
var x_e =
'|标题 = ' + `{{lj|${data.name}}}` + '\n';
} else {
var x_e =
'|标题 = ' + data.name + '\n';
}
let ararr = [];
for (let i of data.artistString.replace(/^(.+?)( feat.+)?$/, "$1").split(', ')) {
ararr.push(await tran(i))
}
let tran_table = ararr.reduce((a, b) => Object.assign(a, b), {})
let x_f = tran_table[data.name] || '';
let y =
'{{Producer_Song' + '\n' +
x_b +
x_a.replaceAll('<br>','<br>') +
'|歌曲描述 = ' + '\n' +
x_d +
'|条目 = ' + x_f + '\n' +
x_e +
x_c + '}}' + '\n\n';
console.log('finish');
return y;
}
const getting_C_ = async (data) => {
//step1
let return_data = [];
for (let b in data["items"]) {
if (data["items"][b]["songType"] !== 'Original') continue;
return_data.push(await getting_A(data["items"][b]["id"]));
}
return return_data;
}
const getting_D_ = async (data) => {
let tracklist = '';
//let artistString = '';
let a = 1;
let b = 0;
let br = '、';
data.map(this_data => {
let singer = '';
let music = '';
let lyrics = '';
for (let j = 0; j < this_data.song.artists.length; j++) {
let artistsname = this_data.song.artists[j].name || '';
let categories_split = this_data.song.artists[j].categories.split(', ');
let effectiveRoles_split = this_data.song.artists[j].effectiveRoles.split(', ');
for (let k = 0; k < effectiveRoles_split.length || k < categories_split.length; k++) {
switch (effectiveRoles_split[k]) {
case 'Producer':
lyrics = _IsInIt(lyrics, artistsname, br);
music = _IsInIt(music, artistsname, br);
break;
case 'Lyricist':
lyrics = _IsInIt(lyrics, artistsname, br);
break;
case 'Composer':
music = _IsInIt(music, artistsname, br);
break;
case 'Vocalist':
singer = _IsInIt(singer, `[[${artistsname}]]`, br);
break;
default:
break;
}
switch (categories_split[k]) {
case 'Lyricist':
lyrics = _IsInIt(lyrics, artistsname, br);
break;
case 'Composer':
music = _IsInIt(music, artistsname, br);
break;
case 'Vocalist':
singer = _IsInIt(singer, `[[${artistsname}]]`, br);
break;
default:
break;
}
}
if (music == '' || lyrics == '') {
for (let k = 0; k < this_data.song.artists.length; k++) {
if (this_data.song.artists[k].categories.indexOf('Producer') != -1) {
music = _IsInIt(music, this_data.song.artists[k].name, br);
lyrics = _IsInIt(lyrics, this_data.song.artists[k].name, br);
break;
}
}
}
}
//去多余顿号
singer = singer.substring(0, singer.length - 1);
music = music.substring(0, music.length - 1);
lyrics = lyrics.substring(0, lyrics.length - 1);
music = lj(music);
lyrics = lj(lyrics);
singer = lj(singer);
if (this_data.discNumber == b) {
a++;
tracklist = tracklist +
`| title${a} = ` + lj(this_data.name, this_data.song.pvServices) + '\n' +
`| singer${a} = ` + singer + '\n' +
`| music${a} = ` + music + '\n' +
`| lyrics${a} = ` + lyrics + '\n' +
`| length${a} = ` + second_change(this_data.song.lengthSeconds) + '\n\n'
} else {
a = 1;
b++;
if (tracklist != '') {
tracklist += '}}' + '\n' + '¥这是一个伪分割符¥';
}
tracklist = tracklist + '\n' +
`==== Disc ${b} ====` + '\n\n' +
'{{tracklist' + '\n' +
'| headline = ' + 'Disc' + ' ' + b + '\n' +
'| singer_credits = yes' + '\n' +
'| music_credits = yes' + '\n' +
'| lyrics_credits = yes' + '\n\n' +
/*
* 便于查看
*/
`| title${a} = ` + lj(this_data.name, this_data.song.pvServices) + '\n' +
`| singer${a} = ` + singer + '\n' +
`| music${a} = ` + music + '\n' +
`| lyrics${a} = ` + lyrics + '\n' +
`| length${a} = ` + second_change(this_data.song.lengthSeconds) + '\n\n';
}
})
tracklist += '}}' + '\n\n' + '{{-}}';
// part 2
let disc = tracklist.split('¥这是一个伪分割符¥');
for (let i = 0; i < disc.length; i++) {
let music = disc[i].match(/(?<=music\d+ ?= ?).+/ig);
if (isAllEqual(music)) {
disc[i] = disc[i].replace(/\n\| ?music\d+ ?= ?.+/ig, '');
disc[i] = disc[i].replace(/(\| ?headline.+?\n)/ig, '$1| all_music =' + music[0] + '\n');
disc[i] = disc[i].replace('music_credits = yes', 'music_credits = no')
}
}
for (let i = 0; i < disc.length; i++) {
let lyrics = disc[i].match(/(?<=lyrics\d+ ?= ?).+/ig);
if (isAllEqual(lyrics)) {
disc[i] = disc[i].replace(/\n\| ?lyrics\d+ ?= ?.+/ig, '');
disc[i] = disc[i].replace(/(\| ?headline.+?\n)/ig, '$1| all_lyrics =' + lyrics[0] + '\n');
disc[i] = disc[i].replace('lyrics_credits = yes', 'lyrics_credits = no')
}
}
for (let i = 0; i < disc.length; i++) {
let singer = disc[i].match(/(?<=singer\d+ ?= ?).+/ig);
if (isAllEqual(singer)) {
disc[i] = disc[i].replace(/\n\| ?singer\d+ ?= ?.+/ig, '');
disc[i] = disc[i].replace(/(\| ?headline.+?\n)/ig, '$1| all_singer =' + singer[0] + '\n');
disc[i] = disc[i].replace('singer_credits = yes', 'singer_credits = no')
}
}
if (disc.length == 1) {
disc[0] = disc[0].replace('==== Disc 1 ====' + '\n\n', '');
}
return disc.join('');
}
const getting_A = async (pageid) => {
//step1
let return_data = await $.getJSON(`https://vocadb.net/api/songs/${pageid}?fields=Artists`);
return getting_A_(return_data, pageid);
}
const getting_B = async (x_a, pageid) => {
//step2
let return_data = await $.getJSON(`https://vocadb.net/api/songs/${pageid}?fields=PVs`);
return getting_B_(x_a, return_data);
}
const getting_C = async (arid) => {
let return_data = await $.getJSON(`https://vocadb.net/api/songs?artistId%5B%5D=${arid}&artistParticipationStatus=OnlyMainAlbums&pvServices=NicoNicoDouga&maxResults=500`);
/*
let return_data = await $.getJSON("https://vocadb.net/api/songs", {
params: {
"artistId[]": arid,
"artistParticipationStatus": "OnlyMainAlbums",
"maxResults": 500,
"pvServices": "NicoNicoDouga",
}
});
*/
return getting_C_(return_data);
}
const getting_D = async (alid) => {
let return_data = await $.getJSON(`https://vocadb.net/api/albums/${alid}/tracks?fields=Artists`);
return getting_D_(return_data);
}
//页面操作
$(() => {
$('#song').click(() => { //防WAF
star_song();
});
$('#ar').click(() => { //防WAF
star_ar();
});
$('#album').click(() => { //防WAF
star_album();
});
});
const star_song = async () => {
change();
var fms = document.getElementById('templeform_song');
let a = await getting_A(fms.elements.sid.value);
document.getElementById('change').innerHTML = `<pre>${a}</pre>`;
}
const star_ar = async () => {
change();
var fma = document.getElementById('templeform_ar');
let c = await getting_C(fma.elements.arid.value);
let _c = c.sort((a, b) => {
let __re = /\d{4}年\d{2}月\d{2}日/ig, _re = /[年月日]/ig;
let _a = (a.match(__re) || ['9999年99月99日'])[0].replace(_re, "");
let _b = (b.match(__re) || ['9999年99月99日'])[0].replace(_re, "");
if (_a < _b) return -1;
else if (_a > _b) return 1;
return 0;
}).join("");
document.getElementById('change').innerHTML = `<pre>${_c}</pre>`;
}
const star_album = async () => {
change();
var fml = document.getElementById('templeform_album');
let d = await getting_D(fml.elements.alid.value);
document.getElementById('change').innerHTML = `<pre>${d}</pre>`;
}
if (mw.config.get('wgPageName').toLowerCase() === "special:vocadb") action()