[GMT] Flexible Search Links

Appends versatile search links bar to linkbar

  1. // ==UserScript==
  2. // @name [GMT] Flexible Search Links
  3. // @namespace https://greasyfork.org/users/321857-anakunda
  4. // @version 1.62.0
  5. // @description Appends versatile search links bar to linkbar
  6. // @author Anakunda
  7. // @copyright © 2025 Anakunda (https://greasyfork.org/users/321857)
  8. // @license GPL-3.0-or-later
  9. // @match https://*/torrents.php?id=*
  10. // @match https://*/artist.php?id=*
  11. // @match https://*/requests.php?action=view&id=*
  12. // @grant GM_getValue
  13. // @grant GM_setValue
  14. // @grant GM_deleteValue
  15. // @grant GM_listValues
  16. // @grant GM_registerMenuCommand
  17. // @require https://openuserjs.org/src/libs/Anakunda/libCtxtMenu.min.js
  18. // ==/UserScript==
  19.  
  20. 'use strict';
  21.  
  22. if (document.querySelector('div.sidebar > div.box_artists') == null) return; // not a music category
  23. const header = document.querySelector('div#content div.header');
  24. if (header == null) throw 'Unexpected page structure';
  25. for (let fn of ['GM_getValue', 'GM_setValue'/*, 'GM_listValues'*/])
  26. if (!(fn in window)) throw 'GM extensions not available';
  27.  
  28. if (typeof GM_deleteValue == 'function') {
  29. var menu = document.createElement('menu');
  30. menu.type = 'context';
  31. menu.id = 'context-1d19ca90-5242-418a-b6d3-d9a9fd5e5cfc';
  32. menu.innerHTML = '<menuitem label="Remove this link" icon="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAAsSAAALEgHS3X78AAADaElEQVQ4y0XTT0xcVRQG8O+ce+fNAOUN7QiFFoHSNq2SIDQ2QGfQ0hYtSatpuvFPjK0mxtDWRF24MG7UbVeasDGmiTrWgG2NjRlaUyAsirrQoCZVF5QZhsKUP/NmKMwM993jwlG/zfdtT3J+IGYCwPE3zye+f//d6UrmMAAwkUI5/+6qilD4q7Z90x9tjyQIYAIIAHjo1ZfjMn5dNm9+LdfeOjdZQeSCCEzETMQAUBl03C/6j0wmD0Vl7kCnfFgXiQNgFT/32ndnTp84lVnK+A+8rHTubW7piNT2Xfvp5xEDFABIheLwZ6dPjR4u+odWvKxvQ0Hpcd32RrFdvD1cXV9az8Ffz4sqFdT8fNr0P76/O372hYQDbHFEtnz+4vOJJ6C7M/fmTSjoKBYrOuAgEnDqqZI5HH/97LcDPe29CytLJmCtLpV8U1dVo6+M3R6DdnCirr4vOTFhKiortEBMrVJ6ODU3eSGVOkkAUKmU++WZ50aPdu7pvp/NmwAcbQzZ6mA12VUPmbFb4hAYzCbCrK+m01ODs8mni0BOMZEqiRS++eX3kQ5365PtO3c059dLVvuai+tFyf34A3SxwBxQ9iHF6mo6fXtwNjlQBHIMKCWAMBEbSOHqb3fie0NVsUdrd+za9MWaP+6wZBZJBQO2CsSXU+mJ88nUUwZYY4AtYDUAQAQgghWB2SiCfIGZT8OfS0E7DhiAIoEAsOXfkHIrJlIWZEMk4Uv9x248+9iBWHZ12dq//mSGWMUkmsDCsNFtNbsaoY6MetkRHyj8d0KI4H7S1zc68Ehbz8pazmBmRmFjw1bpAAUJJAxRAi5AzMEat7mBAodveN6wDxRUiBAeisWun9zfFl1ZXzOcWdT+6rKJBIPqcjI9/msufze6raa1ADEk0EWI6drqNkVEx2553hX9aTQ6fnTPvo7lnGf0xgNtlpZMJODo4dT81Nup5DMA4LC6+VJTQ3ceviGBzlnfvNJc3yuWxzmTzy+YnAdd3CB/6b4fUUoPp+9NXZi9e9yA1gyw9sbszPFLyYUpl5UWgh9gULZgsVjaXAAB/F7r7ngyGpPF7i75uKll0gFcACiLYwAIgNyLD7dOer09MnuwSwYjO+MAGGWS/EFdQ2KosWU6RPQPZ+B/zuUdJApfbNw9/U5dUwJlzn8DiOOFPhubkGUAAAAASUVORK5CYII=" /><menuitem label="-" />';
  33. menu.deleter = function(searchLinks, branch) {
  34. if (typeof searchLinks != 'object') throw 'Invalid argument (searchLinks)';
  35. if (!branch) throw 'Invalid argument (branch)';
  36. if (!(this.invoker instanceof HTMLAnchorElement)) throw 'Invoker not set';
  37. if (!(this.invoker.textContent in searchLinks)) {
  38. console.debug('searchLinks:', Object.keys(searchLinks), this.invoker.textContent);
  39. throw '"' + this.invoker.textContent + '" not a key of searchLinks';
  40. }
  41. if (!confirm('Are you sure to remove ' + this.invoker.textContent + ' from search bar?')) return false;
  42. delete searchLinks[this.invoker.textContent];
  43. if (this.invoker.nextSibling != null && this.invoker.nextSibling.nodeType == 3) this.invoker.nextSibling.remove();
  44. else if (this.invoker.previousSibling.textContent == divisor) this.invoker.previousSibling.remove();
  45. this.invoker.remove();
  46. GM_setValue(branch, searchLinks);
  47. alert('Site was removed. To restore it, use reset command from script\'s submenu');
  48. }.bind(menu);
  49. menu.callerSetter = function(evt) { this.invoker = evt.currentTarget }.bind(menu);
  50. document.body.append(menu);
  51. }
  52.  
  53. const searchBox = document.createElement('div'), divisor = ' | ';
  54. searchBox.className = 'searchbox';
  55. searchBox.style = 'text-align: center; padding-bottom: 5px; margin-top: -1px;';
  56. const fileName = document.location.pathname.replace(/^.*\//, '').toLowerCase();
  57. let searchLinks = GM_getValue(fileName);
  58.  
  59. String.prototype.toASCII = function() { return this.normalize('NFKD').replace(/[\x00-\x1F\u0300-\u036F]/gu, '') };
  60.  
  61. switch (fileName) {
  62. case 'torrents.php':
  63. case 'requests.php': {
  64. const defaultSearchLinks = fileName == 'torrents.php' ? {
  65. 'Orpheus': 'https://orpheus.network/torrents.php?action=advanced&artistname=${real_artists}&groupname=${album}',
  66. 'RuTracker': 'https://rutracker.org/forum/tracker.php?nm=${real_artists_quoted}+${album_quoted}',
  67. 'Google': 'https://www.google.com/search?q=${artists_quoted}+${album_quoted}+${year}',
  68. 'Google (Images)': 'https://www.google.com/search?q=${artists_quoted}+${album_quoted}+${year}&tbm=isch',
  69. 'Wikipedia': 'https://www.wikipedia.org/w/index.php?search=${artists}+${album}',
  70. 'Discogs': 'https://www.discogs.com/search/?title=${album}&artist=${real_artists}&type=all&layout=med',
  71. 'Discogs (title/year)': 'https://www.discogs.com/search/?title=${album}&year=${year}&type=all&layout=med',
  72. 'MusicBrainz': 'https://musicbrainz.org/search?query=artistname:${artist_quoted} AND release:${album_quoted}&type=release_group&method=advanced',
  73. 'AllMusic': 'https://www.allmusic.com/search/all/${real_artists_quoted}%20${album_quoted}',
  74. 'Rate Your Music': 'https://rateyourmusic.com/search?searchterm=${real_artists_quoted}+${album_quoted}&searchtype=l',
  75. 'Album of the Year': 'https://www.albumoftheyear.org/search/?q=${real_artists}+${album}',
  76. 'Apple Music': 'https://music.apple.com/search?term=${artists}+${album}',
  77. 'Deezer': 'https://www.deezer.com/search/${artists_quoted}%20${album_quoted}/album',
  78. 'Spotify': 'https://open.spotify.com/search/${artists_quoted}%20${album_quoted}',
  79. 'Tidal': 'https://listen.tidal.com/search/albums?q=${real_artists_quoted}+${album_quoted}',
  80. 'Qobuz': 'https://www.qobuz.com/search?q=${real_artist}+${album}&i=boutique',
  81. 'HighResAudio': 'https://www.highresaudio.com/en/search/?artist=${real_artist_quoted}&album=${album_quoted}&sort=-releaseDate',
  82. 'Bandcamp': 'https://bandcamp.com/search?q=${real_artist_quoted}+${album_quoted}&item_type=a',
  83. 'Mora': 'https://mora.jp/search/top?keyWord=${real_artists_quoted}+${album_quoted}',
  84. 'e-onkyo': 'https://www.e-onkyo.com/search/search.aspx?q=${real_artists_quoted}+${album_quoted}',
  85. '7digital': 'https://uk.7digital.com/search?q=${real_artists_quoted}+${album_quoted}',
  86. 'Boomkat': 'https://boomkat.com/products?q[keywords]=${album_quoted}',
  87. 'Bleep': 'https://bleep.com/search/query?q=${album}',
  88. 'SoundCloud': 'https://soundcloud.com/search/albums?q=${real_artists_quoted}+${album_quoted}',
  89. 'Amazon Music': 'https://music.amazon.com/search/${real_artists_quoted}%20${album_quoted}',
  90. 'YouTube Music': 'https://music.youtube.com/search?q=${real_artists_quoted}%20${album_quoted}',
  91. 'Presto Jazz': 'https://www.prestomusic.com/jazz/search?search_query=${real_artists_quoted}%20${album_quoted}',
  92. 'Presto Classical': 'https://www.prestomusic.com/classical/search?search_query=${real_artists_quoted}%20${album_quoted}',
  93. 'ProStudioMasters': 'https://www.prostudiomasters.com/search?cs=1&q=${real_artists_quoted}+${album_quoted}',
  94. 'Acoustic Sounds': 'https://store.acousticsounds.com/index.cfm?get=results&Artist=${real_artists}&Album=${album}',
  95. 'Beatport': 'https://www.beatport.com/search/releases?q=${real_artists_quoted}+${album_quoted}',
  96. 'Beatsource': 'https://www.beatsource.com/search?q=${real_artists_quoted}+${album_quoted}',
  97. 'Juno Download': 'https://www.junodownload.com/search/?solrorder=relevancy&q%5Ball%5D%5B%5D=${real_artists}%20${album}',
  98. 'Traxsource': 'https://www.traxsource.com/search/titles?term=${real_artists_quoted}+${album_quoted}',
  99. 'Last.fm': 'https://www.last.fm/search?q=${real_artists_quoted}+${album_quoted}',
  100. 'OTOTOY': 'https://ototoy.jp/find/?q=${album_quoted}',
  101. 'Recochoku (レコチョク)': 'https://recochoku.jp/search/all?q=${real_artist}+${album}',
  102. 'NetEase': 'https://music.163.com/#/search/m/?s=${real_artists_quoted}%20${album_quoted}&type=10',
  103. 'QQ音乐': 'https://y.qq.com/portal/search.html#t=album&w=${real_artists_quoted}%20${album_quoted}',
  104. } : {
  105. 'Orpheus': 'https://orpheus.network/torrents.php?action=advanced&artistname=${real_artists}&groupname=${album}',
  106. 'RuTracker': 'https://rutracker.org/forum/tracker.php?nm=${real_artists_quoted}+${album_quoted}',
  107. 'Google': 'https://www.google.com/search?q=${artists_quoted}+${album_quoted}+${year}',
  108. 'Google (Images)': 'https://www.google.com/search?q=${artists_quoted}+${album_quoted}+${year}&tbm=isch',
  109. 'Wikipedia': 'https://www.wikipedia.org/w/index.php?search=${artists}+${album}',
  110. 'Discogs': 'https://www.discogs.com/search/?title=${album}&artist=${real_artists}&type=all&layout=med',
  111. 'Discogs (title/year)': 'https://www.discogs.com/search/?title=${album}&year=${year}&type=all&layout=med',
  112. 'Discogs (label/cat№)': 'https://www.discogs.com/search/?label=${label}&catno=${cat_no}&type=all&layout=med',
  113. 'MusicBrainz': 'https://musicbrainz.org/search?query=artistname:${artist_quoted} AND release:${album_quoted}&type=release_group&method=advanced',
  114. 'AllMusic': 'https://www.allmusic.com/search/all/${real_artists_quoted}%20${album_quoted}',
  115. 'Rate Your Music': 'https://rateyourmusic.com/search?searchterm=${real_artists_quoted}+${album_quoted}&searchtype=l',
  116. 'Album of the Year': 'https://www.albumoftheyear.org/search/?q=${real_artists_quoted}+${album_quoted}',
  117. 'Apple Music': 'https://music.apple.com/search?term=${artists}+${album}',
  118. 'Deezer': 'https://www.deezer.com/search/${artists_quoted}%20${album_quoted}/album',
  119. 'Spotify': 'https://open.spotify.com/search/${artists_quoted}%20${album_quoted}',
  120. 'Tidal': 'https://listen.tidal.com/search/albums?q=${real_artists_quoted}+${album_quoted}',
  121. 'Qobuz': 'https://www.qobuz.com/search?q=${real_artist}+${album}&i=boutique',
  122. 'HighResAudio': 'https://www.highresaudio.com/en/search/?artist=${real_artist_quoted}&album=${album_quoted}&sort=-releaseDate',
  123. 'Bandcamp': 'https://bandcamp.com/search?q=${real_artist_quoted}+${album_quoted}&item_type=a',
  124. 'Mora': 'https://mora.jp/search/top?keyWord=${real_artists_quoted}+${album_quoted}',
  125. 'e-onkyo': 'https://www.e-onkyo.com/search/search.aspx?q=${real_artists_quoted}+${album_quoted}',
  126. '7digital': 'https://uk.7digital.com/search?q=${real_artists_quoted}+${album_quoted}',
  127. 'Boomkat': 'https://boomkat.com/products?q[keywords]=${album_quoted}',
  128. 'Bleep': 'https://bleep.com/search/query?q=${album}',
  129. 'SoundCloud': 'https://soundcloud.com/search/albums?q=${real_artists_quoted}+${album_quoted}',
  130. 'Amazon Music': 'https://music.amazon.com/search/${real_artists_quoted}%20${album_quoted}',
  131. 'YouTube Music': 'https://music.youtube.com/search?q=${real_artists_quoted}%20${album_quoted}',
  132. 'Presto Jazz': 'https://www.prestomusic.com/jazz/search?search_query=${real_artists_quoted}%20${album_quoted}',
  133. 'Presto Classical': 'https://www.prestomusic.com/classical/search?search_query=${real_artists_quoted}%20${album_quoted}',
  134. 'ProStudioMasters': 'https://www.prostudiomasters.com/search?cs=1&q=${real_artists_quoted}+${album_quoted}',
  135. 'Acoustic Sounds': 'https://store.acousticsounds.com/index.cfm?get=results&Artist=${real_artists}&Album=${album}',
  136. 'Beatport': 'https://www.beatport.com/search/releases?q=${real_artists_quoted}+${album_quoted}',
  137. 'Beatsource': 'https://www.beatsource.com/search?q=${real_artists_quoted}+${album_quoted}',
  138. 'Juno Download': 'https://www.junodownload.com/search/?solrorder=relevancy&q%5Ball%5D%5B%5D=${real_artists}%20${album}',
  139. 'Traxsource': 'https://www.traxsource.com/search/titles?term=${real_artists_quoted}+${album_quoted}',
  140. 'Last.fm': 'https://www.last.fm/search?q=${real_artists_quoted}+${album_quoted}',
  141. 'OTOTOY': 'https://ototoy.jp/find/?q=${album_quoted}',
  142. 'Recochoku (レコチョク)': 'https://recochoku.jp/search/all?q=${real_artist}+${album}',
  143. 'NetEase': 'https://music.163.com/#/search/m/?s=${real_artists_quoted}%20${album_quoted}&type=10',
  144. 'QQ音乐': 'https://y.qq.com/portal/search.html#t=album&w=${real_artists_quoted}%20${album_quoted}',
  145. };
  146. if (typeof searchLinks != 'object') GM_setValue(fileName, searchLinks = defaultSearchLinks);
  147. //console.debug('searchLinks:', searchLinks);
  148. if (typeof GM_registerMenuCommand == 'function' && typeof GM_deleteValue == 'function')
  149. GM_registerMenuCommand('Reset links to default', function() {
  150. if (!confirm('Are you sure to discard current configuration?')) return;
  151. GM_setValue(fileName, searchLinks = defaultSearchLinks);
  152. if (header.querySelector('div.searchbox') == null) header.append(searchBox);
  153. searchBox.build();
  154. }, 'R');
  155. if (Object.keys(searchLinks) <= 0) return;
  156. menu.onclick = evt => menu.deleter(searchLinks, fileName);
  157. let full_title = header.querySelector('h2 > span:last-of-type');
  158. if (full_title != null) {full_title = full_title.textContent.trim()}else{
  159. const opsTitle = header.querySelector('h2 >a:last-of-type');
  160. if (opsTitle) {full_title = opsTitle.textContent.trim()} else throw 'Unexpected page structure';}
  161. const title = full_title.replace(/\s+(?:EP|E\.\s?P\.|\(EP\)|\(E\.\s?P\.\)|-\s*EP|-\s*E\.\s?P\.|\(Live\)|- Live)$/, '');
  162. let albumArtist = header.querySelector('div.header > h2 > a:first-of-type');
  163. if (albumArtist != null) albumArtist = albumArtist.textContent.trim();
  164. let releaseType, label, cat_no;
  165. switch (fileName) {
  166. case 'torrents.php':
  167. releaseType = header.querySelector('div.header > h2');
  168. if (releaseType != null) releaseType = /\[([^\[\]]*)\]$/.exec(releaseType.textContent.trim());
  169. releaseType = releaseType != null && releaseType[1] || undefined;
  170. if (/^\d{4}/.test(releaseType)) releaseType = releaseType.slice(5);
  171. label = cat_no = '';
  172. break;
  173. case 'requests.php': {
  174. function getValue(label) {
  175. if (label) for (let tr of document.body.querySelectorAll('div.main_column > table > tbody > tr'))
  176. if ([0, 1].every(ndx => tr.children[ndx] != null)
  177. && tr.children[0].textContent.trim().toLowerCase() == label.toLowerCase())
  178. return tr.children[1].textContent.trim();
  179. return '';
  180. }
  181. releaseType = getValue('Release type');
  182. label = getValue('Record label');
  183. cat_no = getValue('Catalogue number');
  184. break;
  185. }
  186. }
  187. const isComp = releaseType == 'Compilation', VA = 'Various Artists';
  188. let year = header.querySelector('h2');
  189. if (year != null) year = /\[(\d{4})\]/.exec(year.lastChild.textContent);
  190. year = year != null ? parseInt(year[1]) : '????';
  191. console.assert(year >= 1900 && year < 1e4, 'year >= 1900 && year < 1e4');
  192. const mainArtists = Array.from(document.querySelectorAll((fileName == 'torrents.php' ?
  193. 'ul#artist_list > li.artist_main' : 'ul > li.artists_main') + ' > a[href]')).map(a => a.textContent.trim());
  194. const metaNames = {
  195. artist: isComp ? VA : mainArtists.length > 0 ? mainArtists[0] : '',
  196. real_artist: isComp ? '' : mainArtists.length > 0 ? mainArtists[0] : '',
  197. artists: isComp ? VA : mainArtists.slice(0, 3).join(' '),
  198. real_artists: isComp ? '' : mainArtists.slice(0, 3).join(' '),
  199. all_artists: isComp ? VA : mainArtists.join(' '),
  200. album_artist: isComp ? VA : albumArtist ? albumArtist : '',
  201. real_album_artist: isComp ? '' : albumArtist ? albumArtist : '',
  202. album: title,
  203. release_type: releaseType ? releaseType : '',
  204. };
  205. for (let quoted of [false, true]) for (let asc of [false, true])
  206. for (let raw of [false, true]) for (let key in metaNames) {
  207. let value = metaNames[key];
  208. if (asc) { value = value.toASCII(); key += '_asc'; }
  209. if (quoted) { value = '"' + value + '"'; key += '_quoted'; }
  210. if (raw) key = 'raw_' + key; else value = encodeURIComponent(value);
  211. //console.debug('Metaname:', key, 'Value:', value);
  212. window.eval(`${key} = unescape('${escape(value)}')`);
  213. }
  214. (searchBox.build = function() {
  215. this.textContent = 'Lookup on: ';
  216. for (let key in searchLinks) {
  217. if (this.lastChild.nodeName == 'A') this.append(divisor);
  218. let a = document.createElement('A');
  219. a.textContent = key;
  220. try { a.href = eval('`' + searchLinks[key] + '`') } catch(e) {
  221. console.error('Invalid URL format for', key, searchLinks[key], e);
  222. continue;
  223. }
  224. a.target = '_blank';
  225. if (typeof GM_deleteValue == 'function' && menu instanceof HTMLElement) {
  226. a.setAttribute('contextmenu', menu.id);
  227. a.oncontextmenu = menu.callerSetter;
  228. }
  229. a.style = 'white-space: nowrap;';
  230. this.append(a);
  231. }
  232. }.bind(searchBox))();
  233. header.append(searchBox);
  234. break;
  235. }
  236. case 'artist.php': {
  237. const defaultSearchLinks = {
  238. 'Orpheus': 'https://orpheus.network/artist.php?artistname=${artist}',
  239. 'RuTracker': 'https://rutracker.org/forum/tracker.php?nm=${artist_quoted}',
  240. 'Google': 'https://www.google.com/search?q=${artist_quoted}',
  241. 'Google (Images)': 'https://www.google.com/search?q=${artist_quoted}&tbm=isch',
  242. 'Wikipedia': 'https://www.wikipedia.org/w/index.php?search=${artist_quoted}',
  243. 'Discogs': 'https://www.discogs.com/search/?q=${artist}&type=artist&layout=med',
  244. 'MusicBrainz': 'https://musicbrainz.org/search?query=${artist_quoted}&type=artist',
  245. 'AllMusic': 'https://www.allmusic.com/search/artists/${artist}',
  246. 'Rate Your Music': 'https://rateyourmusic.com/search?searchterm=${artist_quoted}&searchtype=a',
  247. 'Album of the Year': 'https://www.albumoftheyear.org/search/?q=${artist}',
  248. 'Artist Info': 'https://music.metason.net/artistinfo?name=${artist}',
  249. 'Apple Music': 'https://music.apple.com/search?term=${artist_quoted}',
  250. 'Deezer': 'https://www.deezer.com/search/${artist}/artist',
  251. 'Spotify': 'https://open.spotify.com/search/${artist_quoted}',
  252. 'Tidal': 'https://listen.tidal.com/search/artists?q=${artist_quoted}',
  253. 'Qobuz': 'https://www.qobuz.com/search?q=${artist}&i=boutique',
  254. 'HighResAudio': 'https://www.highresaudio.com/en/search/?artist=${artist_quoted}',
  255. 'Bandcamp': 'https://bandcamp.com/search?q=${artist_quoted}&item_type=b',
  256. 'Mora': 'https://mora.jp/search/top?keyWord=${artist_quoted}',
  257. 'e-onkyo': 'https://www.e-onkyo.com/search/search.aspx?q=${artist_quoted}',
  258. '7digital': 'https://uk.7digital.com/search?q=${artist_quoted}',
  259. 'Boomkat': 'https://boomkat.com/products?q[keywords]=${artist_quoted}',
  260. 'Bleep': 'https://bleep.com/search/query?q=${artist}',
  261. 'SoundCloud': 'https://soundcloud.com/search/people?q=${artist_quoted}',
  262. 'Amazon Music': 'https://music.amazon.com/search/${artist_quoted}',
  263. 'YouTube Music': 'https://music.youtube.com/search?q=${artist_quoted}',
  264. 'Presto Jazz': 'https://www.prestomusic.com/jazz/search?search_query=${artist_quoted}',
  265. 'Presto Classical': 'https://www.prestomusic.com/classical/search?search_query=${artist_quoted}',
  266. 'ProStudioMasters': 'https://www.prostudiomasters.com/search?cs=1&q=${artist_quoted}',
  267. 'Acoustic Sounds': 'https://store.acousticsounds.com/index.cfm?get=results&Artist=${artist}',
  268. 'Beatport': 'https://www.beatport.com/search/artists?q=${artist_quoted}',
  269. 'Beatsource': 'https://www.beatsource.com/search?q=${artist_quoted}',
  270. 'Juno Download': 'https://www.junodownload.com/search/?solrorder=relevancy&q%5Ball%5D%5B%5D=${artist}',
  271. 'Traxsource': 'https://www.traxsource.com/search/artists?term=${artist_quoted}',
  272. 'Last.fm': 'https://www.last.fm/search/artists?q=${artist_quoted}',
  273. 'OTOTOY': 'https://ototoy.jp/find/?q=${artist_quoted}',
  274. 'Recochoku (レコチョク)': 'https://recochoku.jp/search/artist?q=${artist}',
  275. 'NetEase': 'https://music.163.com/#/search/m/?s=${artist_quoted}&type=100',
  276. 'QQ音乐': 'https://y.qq.com/portal/search.html#t=artist&w=${artist_quoted}',
  277. };
  278. if (typeof searchLinks != 'object') GM_setValue(fileName, searchLinks = defaultSearchLinks);
  279. //console.debug('searchLinks:', searchLinks);
  280. if (typeof GM_registerMenuCommand == 'function' && typeof GM_deleteValue == 'function')
  281. GM_registerMenuCommand('Reset links to default', function() {
  282. if (!confirm('Are you sure to discard current configuration?')) return;
  283. GM_setValue(fileName, searchLinks = defaultSearchLinks);
  284. if (header.querySelector('div.searchbox') == null) header.append(searchBox);
  285. searchBox.build();
  286. }, 'R');
  287. if (Object.keys(searchLinks) <= 0) return;
  288. menu.onclick = evt => menu.deleter(searchLinks, fileName);
  289. let h2 = header.querySelector('h2');
  290. if (h2 == null) throw 'Unexpected page structure';
  291. const artist = encodeURIComponent(h2.textContent.trim()),
  292. artist_asc = encodeURIComponent(h2.textContent.trim().toASCII()),
  293. artist_quoted = encodeURIComponent('"' + h2.textContent.trim() + '"'),
  294. artist_asc_quoted = encodeURIComponent('"' + h2.textContent.trim().toASCII() + '"');
  295. searchBox.style.marginBottom = '1em';
  296. (searchBox.build = function() {
  297. this.textContent = 'Lookup on: ';
  298. for (let key in searchLinks) {
  299. if (this.lastChild.nodeName == 'A') this.append(divisor);
  300. let a = document.createElement('A');
  301. a.textContent = key;
  302. try { a.href = eval('`' + searchLinks[key] + '`') } catch(e) {
  303. console.error('Invalid URL format for', key, searchLinks[key], e);
  304. continue;
  305. }
  306. a.target = '_blank';
  307. if (typeof GM_deleteValue == 'function' && menu) {
  308. a.setAttribute('contextmenu', menu.id);
  309. a.oncontextmenu = menu.callerSetter;
  310. }
  311. a.style = 'white-space: nowrap;';
  312. this.append(a);
  313. }
  314. }.bind(searchBox))();
  315. header.append(searchBox);
  316. break;
  317. }
  318. }