User:實驗性:無用論廢人/js/VocaDB-VOCALOID.js

萌娘百科,萬物皆可萌的百科全書!轉載請標註來源頁面的網頁連結,並聲明引自萌娘百科。內容不可商用。
貢獻者:

注意:在您儲存之後您必須清除瀏覽器快取才可看到最新的變動。

  1. /** 利用[https://vocadb.net/ VocaDB]的數據,生成moegirl上的模板。
  2. * 採用JSONP獲取數據。
  3. * 代碼堆放至github:https://github.com/VOCALOID-lower/Wiki-JS/blob/main/src/VocaDB-VOCALOID.js
  4. * ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
  5. *
  6. * 加載該腳本後,請在頁面[[Special:VocaDB]]進行操作。
  7. * 注意一天內不要使用太多次“獲取P主歌曲列表”,容易超過VocaDB API請求上限。
  8. * 由於正則包含負向預查,可能不支持Safari等瀏覽器。
  9. *
  10. * ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
  11. */
  12. "use strict";
  13. const action = () => {
  14. let ps_1 = '<p><del>若獲取有誤,建議自行到VocaDB修改</del></p>';
  15. let ps_2 = '<p>補充:該獲取最多獲取500首,且只包含niconico的投稿,其餘手動補充;</p><p>排除了recover等作品,但在VocaDB大部分專輯曲重新投稿是算recover的,記得檢查。</p>';
  16. let context = `${ps_1}
  17. <hr>
  18. <div id="change" style="
  19. overflow: scroll;
  20. user-modify: read-write;
  21. -webkit-user-modify: read-write;
  22. -moz-user-modify: read-write;
  23. max-height: 500px;
  24. ">生成於此處</div>
  25. <hr>
  26. <!--
  27. -->
  28. <h2>獲取歌曲</h2>
  29. <form id="templeform_song" class="getid">
  30. <input class="getid" type="number" name="sid" placeholder="vocadb歌曲頁面ID">
  31. </form>
  32. <br>
  33. <div id="song" class="getbuttom">獲得請求</div>
  34. <br>
  35. <!--
  36. -->
  37. <h2>獲取P主歌曲列表</h2>
  38. ${ps_2}
  39. <form id="templeform_ar" class="getid">
  40. <input class="getid" type="number" name="arid" placeholder="vocadbP主頁面ID">
  41. </form>
  42. <br>
  43. <div id="ar" class="getbuttom">獲得請求</div>
  44. <!--
  45. -->
  46. <h2>獲取專輯曲目列表</h2>
  47. <form id="templeform_album" class="getid">
  48. <input class="getid" type="number" name="alid" placeholder="vocadb專輯頁面ID">
  49. </form>
  50. <br>
  51. <div id="album" class="getbuttom">獲得請求</div>
  52. <br>`;
  53. document.getElementsByTagName("style")[0].append(`
  54. div.getbuttom{
  55. background-color: #5fa7f3;
  56. width: 140px;
  57. line-height: 38px;
  58. text-align: center;
  59. font-weight: bold;
  60. color: #fff;
  61. text-shadow:1px 1px 1px #333;
  62. border-radius: 5px;
  63. margin:0 20px 20px 0;
  64. position: relative;
  65. overflow: hidden;
  66. }
  67. input.getid{
  68. border-color: #000;
  69. font-size: 12px;
  70. height:30px;
  71. border-radius:4px;
  72. border:1px solid #c8cccf;
  73. color:#986655;
  74. outline:0;
  75. text-align:left;
  76. padding-left: 10px;
  77. display: block;
  78. cursor: pointer;
  79. box-shadow: 2px 2px 5px 1px #ccc;
  80. }`);
  81. if (mw.config.get("skin") === "moeskin") {
  82. $('#moe-body-content').html(context);
  83. } else {
  84. $('#bodyContent').html(context);
  85. }
  86. if (mw.config.get("skin") === "minerva") {
  87. $('h1#section_0').text('Special:VocaDB');
  88. } else {
  89. document.getElementsByTagName("h1")[0].textContent = "Special:VocaDB";
  90. }
  91. document.title = 'Special:VocaDB';
  92. }
  93. const change = () => {
  94. document.getElementById('change').innerHTML = 'loading...';
  95. }
  96. //action
  97. //預備函數
  98. const api = new mw.Api();
  99. const ja = /[\u2E80-\u2FDF\u3040-\u318F\u31A0-\u31BF\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FFF\uA960-\uA97F\uAC00-\uD7FF]/i;
  100. const IsInIt = (a, b, c) => {
  101. if (b.search(ja) != -1) {
  102. b = `{{lj|${b}}}`;
  103. }
  104. if (a.indexOf(b) == -1) {
  105. var d = a + b + c;
  106. } else {
  107. var d = a;
  108. }
  109. return d
  110. }
  111. const _IsInIt = (a, b, c) => {
  112. if (a.indexOf(b) == -1) {
  113. var d = a + b + c;
  114. } else {
  115. var d = a;
  116. }
  117. return d
  118. }
  119. const second_change = time => {
  120. let m = parseInt(time / 60)
  121. let s = parseInt(time % 60)
  122. s = s < 10 ? '0' + s : s
  123. return `${m}:${s}`
  124. }
  125. const lj = (name, links) => {
  126. //內鏈
  127. if (links) {
  128. if (links.indexOf('NicoNicoDouga') != -1) {
  129. name = `[[${name}]]`;
  130. }
  131. }
  132. //語言
  133. if (name) {
  134. if (name.search(ja) != -1) {
  135. name = `{{lj|${name}}}`;
  136. }
  137. return name
  138. } else if (name == undefined) {
  139. name = '';
  140. return name
  141. } else {
  142. return name
  143. }
  144. }
  145. const isAllEqual = array => {
  146. return !array.some(value => {
  147. return value !== array[0];
  148. });
  149. }
  150. const tran = async (template) => {
  151. let obj = {};
  152. let params = {
  153. 'format': 'json',
  154. 'action': 'expandtemplates',
  155. 'text': `{{:Template:${template}}}`,
  156. }
  157. try {
  158. var response = await api.postWithToken('csrf', params) || '';
  159. } catch (err) { // 防止502
  160. return tran(template)
  161. }
  162. if (response) {
  163. let data = response;
  164. (data['expandtemplates']['*']).replace(/\[\[(.+?)(\|(.+?))?\]\]/ig, (a, b, c, d) => {
  165. obj[d ? d.replace(/^(<(?<a>.+?)( .+)?>)?(.+?)(<\/(\k<a>)>)?$/ig, "$4").replace(/^(-\{)?(.+?)(\}-)?$/ig/* JS沒有平衡組只能出此下策 */, "$2") : b] = b;
  166. })
  167. return obj;
  168. } else { // 防止502
  169. return tran(template);
  170. }
  171. };
  172. //star
  173. const getting_A_ = async (data, pageid) => {
  174. let br = '<br>';
  175. let Lyricist = '';
  176. let Composer = '';
  177. let Illustrator = '';
  178. let Animator = '';
  179. let Vocalist = '';
  180. data.artists.map(this_artists => {
  181. let artistsname = this_artists.name || '';
  182. let categories_split = this_artists.categories.split(', ');
  183. let effectiveRoles_split = this_artists.effectiveRoles.split(', ');
  184. for (let j = 0; j < effectiveRoles_split.length || j < categories_split.length; j++) {
  185. switch (effectiveRoles_split[j]) {
  186. case 'Producer':
  187. Lyricist = IsInIt(Lyricist, artistsname, br);
  188. Composer = IsInIt(Composer, artistsname, br);
  189. break;
  190. case 'Lyricist':
  191. Lyricist = IsInIt(Lyricist, artistsname, br);
  192. break;
  193. case 'Composer':
  194. Composer = IsInIt(Composer, artistsname, br);
  195. break;
  196. case 'Vocalist':
  197. Vocalist = IsInIt(Vocalist, artistsname, br);
  198. break;
  199. case 'Animator':
  200. Animator = IsInIt(Animator, artistsname, br);
  201. break;
  202. case 'Illustrator':
  203. Illustrator = IsInIt(Illustrator, artistsname, br);
  204. break;
  205. default:
  206. break;
  207. }
  208. switch (categories_split[j]) {
  209. case 'Lyricist':
  210. Lyricist = IsInIt(Lyricist, artistsname, br);
  211. break;
  212. case 'Composer':
  213. Composer = IsInIt(Composer, artistsname, br);
  214. break;
  215. case 'Vocalist':
  216. Vocalist = IsInIt(Vocalist, artistsname, br);
  217. break;
  218. case 'Animator':
  219. Animator = IsInIt(Animator, artistsname, br);
  220. break;
  221. case 'Illustrator':
  222. Illustrator = IsInIt(Illustrator, artistsname, br);
  223. break;
  224. default:
  225. break;
  226. }
  227. }
  228. })
  229. if (Composer == '' || Lyricist == '') {
  230. for (let i = 0; i < data.artists.length; i++) {
  231. if (data.artists[i].categories.indexOf('Producer') != -1) {
  232. Composer = IsInIt(Composer, data.artists[i].name, br);
  233. Lyricist = IsInIt(Lyricist, data.artists[i].name, br);
  234. break;
  235. }
  236. }
  237. }
  238. let x_a =
  239. '|作曲 = ' + Composer.substring(0, Composer.length - 4) + '\n' +
  240. '|填詞 = ' + Lyricist.substring(0, Lyricist.length - 4) + '\n' +
  241. '|視頻製作 = ' + Animator.substring(0, Animator.length - 4) + '\n' +
  242. '|畫師 = ' + Illustrator.substring(0, Illustrator.length - 4) + '\n' +
  243. '|演唱者 = ' + Vocalist.substring(0, Vocalist.length - 4).replace(/<br>/g, '、') + '\n';
  244. return getting_B(x_a, pageid);
  245. }
  246. const getting_B_ = async (x_a, data) => {
  247. let YouTube = '';
  248. let NicoNico = '';
  249. let Bilibili = '';
  250. let thumbUrl = '';
  251. let publishtime = '';
  252. if (data.pvServices.indexOf('NicoNicoDouga') != -1) {
  253. for (let i = 0; i < data.pvs.length; i++) {
  254. if (data.pvs[i].service == 'NicoNicoDouga' && data.pvs[i].pvType == 'Original') {
  255. NicoNico = data.pvs[i].url.replace(/.+watch\//i, '');
  256. thumbUrl = data.pvs[i].thumbUrl;
  257. publishtime = data.pvs[i].publishDate;
  258. if (data.pvs[i].url.replace(/.+watch\/sm/i, '') > 23648995) {
  259. thumbUrl += '.M';
  260. }
  261. break;
  262. }
  263. }
  264. }
  265. if (data.pvServices.indexOf('Youtube') != -1) {
  266. for (let i = 0; i < data.pvs.length; i++) {
  267. if (data.pvs[i].service == 'Youtube' && data.pvs[i].pvType == 'Original') {
  268. YouTube = data.pvs[i].url.replace(/.+youtu\.be\//i, '');
  269. break;
  270. }
  271. }
  272. }
  273. if (data.pvServices.indexOf('Bilibili') != -1) {
  274. for (let i = 0; i < data.pvs.length; i++) {
  275. if (data.pvs[i].service == 'Bilibili' && data.pvs[i].pvType == 'Original') {
  276. Bilibili = data.pvs[i].url.replace(/.+video\//i, '');
  277. break;
  278. }
  279. }
  280. }
  281. if (!(publishtime)) {
  282. publishtime = data.pvs[0].publishDate;
  283. }
  284. let x_b =
  285. '|nnd_id = ' + NicoNico + '\n' +
  286. '|yt_id = ' + YouTube + '\n' +
  287. '|bb_id = ' + Bilibili + '\n';
  288. let x_c =
  289. '|image = ' + thumbUrl + '\n';
  290. let x_d =
  291. '|投稿日期 = ' + (publishtime || '').replace(/(\d{4})-(\d{2})-(\d{2}).+/i, '$1年$2月$3日') + '\n';
  292. if (data.name.search(ja) != -1) {
  293. var x_e =
  294. '|標題 = ' + `{{lj|${data.name}}}` + '\n';
  295. } else {
  296. var x_e =
  297. '|標題 = ' + data.name + '\n';
  298. }
  299. let ararr = [];
  300. for (let i of data.artistString.replace(/^(.+?)( feat.+)?$/, "$1").split(', ')) {
  301. ararr.push(await tran(i))
  302. }
  303. let tran_table = ararr.reduce((a, b) => Object.assign(a, b), {})
  304. let x_f = tran_table[data.name] || '';
  305. let y =
  306. '{{Producer_Song' + '\n' +
  307. x_b +
  308. x_a.replaceAll('<br>','&lt;br&gt;') +
  309. '|歌曲描述 = ' + '\n' +
  310. x_d +
  311. '|條目 = ' + x_f + '\n' +
  312. x_e +
  313. x_c + '}}' + '\n\n';
  314. console.log('finish');
  315. return y;
  316. }
  317. const getting_C_ = async (data) => {
  318. //step1
  319. let return_data = [];
  320. for (let b in data["items"]) {
  321. if (data["items"][b]["songType"] !== 'Original') continue;
  322. return_data.push(await getting_A(data["items"][b]["id"]));
  323. }
  324. return return_data;
  325. }
  326. const getting_D_ = async (data) => {
  327. let tracklist = '';
  328. //let artistString = '';
  329. let a = 1;
  330. let b = 0;
  331. let br = '、';
  332. data.map(this_data => {
  333. let singer = '';
  334. let music = '';
  335. let lyrics = '';
  336. for (let j = 0; j < this_data.song.artists.length; j++) {
  337. let artistsname = this_data.song.artists[j].name || '';
  338. let categories_split = this_data.song.artists[j].categories.split(', ');
  339. let effectiveRoles_split = this_data.song.artists[j].effectiveRoles.split(', ');
  340. for (let k = 0; k < effectiveRoles_split.length || k < categories_split.length; k++) {
  341. switch (effectiveRoles_split[k]) {
  342. case 'Producer':
  343. lyrics = _IsInIt(lyrics, artistsname, br);
  344. music = _IsInIt(music, artistsname, br);
  345. break;
  346. case 'Lyricist':
  347. lyrics = _IsInIt(lyrics, artistsname, br);
  348. break;
  349. case 'Composer':
  350. music = _IsInIt(music, artistsname, br);
  351. break;
  352. case 'Vocalist':
  353. singer = _IsInIt(singer, `[[${artistsname}]]`, br);
  354. break;
  355. default:
  356. break;
  357. }
  358. switch (categories_split[k]) {
  359. case 'Lyricist':
  360. lyrics = _IsInIt(lyrics, artistsname, br);
  361. break;
  362. case 'Composer':
  363. music = _IsInIt(music, artistsname, br);
  364. break;
  365. case 'Vocalist':
  366. singer = _IsInIt(singer, `[[${artistsname}]]`, br);
  367. break;
  368. default:
  369. break;
  370. }
  371. }
  372. if (music == '' || lyrics == '') {
  373. for (let k = 0; k < this_data.song.artists.length; k++) {
  374. if (this_data.song.artists[k].categories.indexOf('Producer') != -1) {
  375. music = _IsInIt(music, this_data.song.artists[k].name, br);
  376. lyrics = _IsInIt(lyrics, this_data.song.artists[k].name, br);
  377. break;
  378. }
  379. }
  380. }
  381. }
  382. //去多餘頓號
  383. singer = singer.substring(0, singer.length - 1);
  384. music = music.substring(0, music.length - 1);
  385. lyrics = lyrics.substring(0, lyrics.length - 1);
  386. music = lj(music);
  387. lyrics = lj(lyrics);
  388. singer = lj(singer);
  389. if (this_data.discNumber == b) {
  390. a++;
  391. tracklist = tracklist +
  392. `| title${a} = ` + lj(this_data.name, this_data.song.pvServices) + '\n' +
  393. `| singer${a} = ` + singer + '\n' +
  394. `| music${a} = ` + music + '\n' +
  395. `| lyrics${a} = ` + lyrics + '\n' +
  396. `| length${a} = ` + second_change(this_data.song.lengthSeconds) + '\n\n'
  397. } else {
  398. a = 1;
  399. b++;
  400. if (tracklist != '') {
  401. tracklist += '}}' + '\n' + '¥這是一個偽分割符¥';
  402. }
  403. tracklist = tracklist + '\n' +
  404. `==== Disc ${b} ====` + '\n\n' +
  405. '{{tracklist' + '\n' +
  406. '| headline = ' + 'Disc' + ' ' + b + '\n' +
  407. '| singer_credits = yes' + '\n' +
  408. '| music_credits = yes' + '\n' +
  409. '| lyrics_credits = yes' + '\n\n' +
  410. /*
  411. * 便於查看
  412. */
  413. `| title${a} = ` + lj(this_data.name, this_data.song.pvServices) + '\n' +
  414. `| singer${a} = ` + singer + '\n' +
  415. `| music${a} = ` + music + '\n' +
  416. `| lyrics${a} = ` + lyrics + '\n' +
  417. `| length${a} = ` + second_change(this_data.song.lengthSeconds) + '\n\n';
  418. }
  419. })
  420. tracklist += '}}' + '\n\n' + '{{-}}';
  421. // part 2
  422. let disc = tracklist.split('¥這是一個偽分割符¥');
  423. for (let i = 0; i < disc.length; i++) {
  424. let music = disc[i].match(/(?<=music\d+ ?= ?).+/ig);
  425. if (isAllEqual(music)) {
  426. disc[i] = disc[i].replace(/\n\| ?music\d+ ?= ?.+/ig, '');
  427. disc[i] = disc[i].replace(/(\| ?headline.+?\n)/ig, '$1| all_music =' + music[0] + '\n');
  428. disc[i] = disc[i].replace('music_credits = yes', 'music_credits = no')
  429. }
  430. }
  431. for (let i = 0; i < disc.length; i++) {
  432. let lyrics = disc[i].match(/(?<=lyrics\d+ ?= ?).+/ig);
  433. if (isAllEqual(lyrics)) {
  434. disc[i] = disc[i].replace(/\n\| ?lyrics\d+ ?= ?.+/ig, '');
  435. disc[i] = disc[i].replace(/(\| ?headline.+?\n)/ig, '$1| all_lyrics =' + lyrics[0] + '\n');
  436. disc[i] = disc[i].replace('lyrics_credits = yes', 'lyrics_credits = no')
  437. }
  438. }
  439. for (let i = 0; i < disc.length; i++) {
  440. let singer = disc[i].match(/(?<=singer\d+ ?= ?).+/ig);
  441. if (isAllEqual(singer)) {
  442. disc[i] = disc[i].replace(/\n\| ?singer\d+ ?= ?.+/ig, '');
  443. disc[i] = disc[i].replace(/(\| ?headline.+?\n)/ig, '$1| all_singer =' + singer[0] + '\n');
  444. disc[i] = disc[i].replace('singer_credits = yes', 'singer_credits = no')
  445. }
  446. }
  447. if (disc.length == 1) {
  448. disc[0] = disc[0].replace('==== Disc 1 ====' + '\n\n', '');
  449. }
  450. return disc.join('');
  451. }
  452. const getting_A = async (pageid) => {
  453. //step1
  454. let return_data = await $.getJSON(`https://vocadb.net/api/songs/${pageid}?fields=Artists`);
  455. return getting_A_(return_data, pageid);
  456. }
  457. const getting_B = async (x_a, pageid) => {
  458. //step2
  459. let return_data = await $.getJSON(`https://vocadb.net/api/songs/${pageid}?fields=PVs`);
  460. return getting_B_(x_a, return_data);
  461. }
  462. const getting_C = async (arid) => {
  463. let return_data = await $.getJSON(`https://vocadb.net/api/songs?artistId%5B%5D=${arid}&artistParticipationStatus=OnlyMainAlbums&pvServices=NicoNicoDouga&maxResults=500`);
  464. /*
  465. let return_data = await $.getJSON("https://vocadb.net/api/songs", {
  466. params: {
  467. "artistId[]": arid,
  468. "artistParticipationStatus": "OnlyMainAlbums",
  469. "maxResults": 500,
  470. "pvServices": "NicoNicoDouga",
  471. }
  472. });
  473. */
  474. return getting_C_(return_data);
  475. }
  476. const getting_D = async (alid) => {
  477. let return_data = await $.getJSON(`https://vocadb.net/api/albums/${alid}/tracks?fields=Artists`);
  478. return getting_D_(return_data);
  479. }
  480. //頁面操作
  481. $(() => {
  482. $('#song').click(() => { //防WAF
  483. star_song();
  484. });
  485. $('#ar').click(() => { //防WAF
  486. star_ar();
  487. });
  488. $('#album').click(() => { //防WAF
  489. star_album();
  490. });
  491. });
  492. const star_song = async () => {
  493. change();
  494. var fms = document.getElementById('templeform_song');
  495. let a = await getting_A(fms.elements.sid.value);
  496. document.getElementById('change').innerHTML = `<pre>${a}</pre>`;
  497. }
  498. const star_ar = async () => {
  499. change();
  500. var fma = document.getElementById('templeform_ar');
  501. let c = await getting_C(fma.elements.arid.value);
  502. let _c = c.sort((a, b) => {
  503. let __re = /\d{4}年\d{2}月\d{2}日/ig, _re = /[年月日]/ig;
  504. let _a = (a.match(__re) || ['9999年99月99日'])[0].replace(_re, "");
  505. let _b = (b.match(__re) || ['9999年99月99日'])[0].replace(_re, "");
  506. if (_a < _b) return -1;
  507. else if (_a > _b) return 1;
  508. return 0;
  509. }).join("");
  510. document.getElementById('change').innerHTML = `<pre>${_c}</pre>`;
  511. }
  512. const star_album = async () => {
  513. change();
  514. var fml = document.getElementById('templeform_album');
  515. let d = await getting_D(fml.elements.alid.value);
  516. document.getElementById('change').innerHTML = `<pre>${d}</pre>`;
  517. }
  518. if (mw.config.get('wgPageName').toLowerCase() === "special:vocadb") action()
此頁面最後編輯於 2024年7月26日 (週五) 16:53。
搜尋萌娘百科 (按"/"快速搜尋)
有新的未讀公告