RED Artist Aliases Filter

Add a box on artist page to filter based on aliases

当前为 2017-04-04 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name RED Artist Aliases Filter
  3. // @namespace PTH Artist Aliases Filter
  4. // @description Add a box on artist page to filter based on aliases
  5. // @include https://passtheheadphones.me/artist.php?id=*
  6. // @include https://redacted.ch/artist.php?id=*
  7. // @version 1.3.0
  8. // @grant none
  9. // ==/UserScript==
  10.  
  11. /* Avoid using jQuery in this userscript, prioritize vanilla javascript as a matter of performance on big pages */
  12.  
  13. "use strict";
  14.  
  15. function Storage(alias_id) {
  16. this.key = "red.artists_aliases_filter." + alias_id;
  17.  
  18. this.save = function(data) {
  19. if (typeof data !== 'string') {
  20. data = JSON.stringify(data);
  21. }
  22. sessionStorage.setItem(this.key, data);
  23. };
  24.  
  25. this.load = function() {
  26. let storage = sessionStorage.getItem(this.key) || "{}";
  27. return JSON.parse(storage);
  28. };
  29. };
  30.  
  31. function Builder() {
  32. this.make_box_aliases = function() {
  33. let box_aliases =
  34. "<div class='box box_aliases'>" +
  35. "<div class='head'><strong>Aliases</strong></div>" +
  36. "<ul class='stats nobullet'></ul>" +
  37. "</div>";
  38. return box_aliases;
  39. };
  40.  
  41. this.make_alias_release = function(alias_id, alias_name) {
  42. let alias_release =
  43. "<text class='alias_id'>" +
  44. " <i>as</i> " +
  45. "<a href='#content' alias_id='" + alias_id + "'>" +
  46. alias_name +
  47. "</a>" +
  48. "</text>";
  49. return alias_release;
  50. }
  51.  
  52. this.make_alias_li = function(alias_id, alias_name) {
  53. let alias_li =
  54. "<li>" +
  55. "<a href='#' alias_id='" + alias_id.toString() + "'>" + alias_name + "</a>" +
  56. "</li>";
  57. return alias_li;
  58. };
  59.  
  60. this.make_alias_title = function(artist_name) {
  61. let main = "<a id='main_title' href='#' alias_id='-1'>" + artist_name + "</a>";
  62. let alias = "<span id='alias_title'></span>";
  63. let title =
  64. "<h2 id='title_filtering'>" +
  65. main + " " + alias +
  66. "</h2>";
  67. return title;
  68. };
  69. };
  70.  
  71. function Manager() {
  72. this.builder = new Builder();
  73. this.current_alias_id = "-1";
  74.  
  75. this.try_catch = function(func) {
  76. let self = this;
  77. func = func.bind(this);
  78.  
  79. function wrapped() {
  80. try {
  81. func();
  82. } catch(err) {
  83. let err_msg = err.message + " (line " + err.lineNumber + ")";
  84. console.log("Error in RED AAF: '" + err_msg + "'.");
  85. self.set_error_message(err_msg);
  86. }
  87. }
  88.  
  89. return wrapped;
  90. };
  91.  
  92. this.get_aliases_list = function() {
  93. let aliases_list = document.getElementById("aliases_list");
  94. return aliases_list;
  95. };
  96.  
  97. this.set_error_message = function(msg) {
  98. let error_msg =
  99. "<li>" +
  100. "<strong>An error occured.</strong></br>" +
  101. msg +
  102. "</li>";
  103. let aliases_list = this.get_aliases_list();
  104. aliases_list.innerHTML = error_msg;
  105. };
  106.  
  107. this.proceed = function() {
  108. let start = this.try_catch(this.start);
  109. start();
  110. };
  111.  
  112. this.start = function() {
  113. this.set_box_aliases();
  114. this.set_loading_message();
  115.  
  116. let artist_id = this.get_artist_id();
  117.  
  118. let storage = new Storage(artist_id);
  119. let storage_data = storage.load();
  120.  
  121. this.set_style_node();
  122. this.reset_style();
  123.  
  124. let hash = this.compute_hash();
  125.  
  126. let self = this;
  127.  
  128. // If cache is not yet set or if it is no longer valid, query the API
  129. if (storage_data["hash"] !== hash) {
  130. this.query_api(artist_id, function(json_data) {
  131. let data = self.parse_json_data(json_data);
  132. data["hash"] = hash;
  133. storage.save(data);
  134. self.set_aliases(data);
  135. });
  136. } else {
  137. this.set_aliases(storage_data);
  138. }
  139. };
  140.  
  141. this.set_box_aliases = function() {
  142. let box_search = document.getElementsByClassName("box_search")[0];
  143. let box_aliases = this.builder.make_box_aliases();
  144. box_search.insertAdjacentHTML('beforebegin', box_aliases);
  145. box_aliases = box_search.parentNode.getElementsByClassName("box_aliases")[0];
  146. box_aliases.getElementsByClassName("stats")[0].id = "aliases_list";
  147. };
  148.  
  149. this.set_loading_message = function() {
  150. let aliases_list = this.get_aliases_list();
  151. aliases_list.innerHTML = "<li>Loading...</li>";
  152. };
  153.  
  154. this.get_artist_id = function() {
  155. let artist_id = window.location.href.match(/id=(\d+)/)[1];
  156. return artist_id;
  157. };
  158.  
  159. this.set_style_node = function() {
  160. let head = document.getElementsByTagName('head')[0];
  161. let style = document.createElement('style');
  162. style.type = 'text/css';
  163. style.id = "artist_alias_filter_css";
  164. head.appendChild(style);
  165. };
  166.  
  167. this.set_style = function(css) {
  168. let style = document.getElementById("artist_alias_filter_css");
  169. style.innerHTML = css;
  170. };
  171.  
  172. this.reset_style = function() {
  173. let style = "#title_filtering { display: none; }";
  174. this.set_style(style);
  175. };
  176.  
  177. this.filter_style = function(alias_id) {
  178. let style =
  179. "#default_title { display: none; } " +
  180. ".alias_id:not(.alias_id_" + alias_id.toString() + ") { display: none; }";
  181. this.set_style(style);
  182. };
  183.  
  184. // Set an array `groups_ids` of all groupid on the current artist page
  185. // to ensure that cache is still valid (no new group since last visit)
  186. this.compute_hash = function() {
  187. let elements = document.querySelectorAll("[id^='showimg_']");
  188. let groups_ids = [];
  189. for (let i = 0, len = elements.length; i < len; i++) {
  190. let group_id = elements[i].id.split("_")[1];
  191. groups_ids.push(group_id);
  192. }
  193. groups_ids.sort();
  194.  
  195. let version = GM_info.script.version;
  196. groups_ids.unshift("version:" + version);
  197.  
  198. let hash = groups_ids.toString();
  199. return hash;
  200. };
  201.  
  202. // Parse JSON response after having queried the API and extract
  203. // main_alias_id, main_name, aliases and groups
  204. this.parse_json_data = function(json_data) {
  205. json_data = json_data.response;
  206. let main_name = json_data.name;
  207. let main_alias_id = undefined;
  208. let aliases = {};
  209. let groups = {};
  210.  
  211. let main_id = json_data["id"]
  212.  
  213. // Iterate through each artists of each group to find those correct (`id` === `main_id`)
  214. let torrentgroup = json_data.torrentgroup;
  215. for (let i = 0, len = torrentgroup.length; i < len; i++) {
  216. let group = torrentgroup[i];
  217. let extendedArtists = group["extendedArtists"];
  218. let found = false;
  219.  
  220. let alias_id = -1;
  221. let group_id = group["groupId"].toString();
  222.  
  223. for (let id in extendedArtists) {
  224. let artists = extendedArtists[id];
  225. if (artists) {
  226. for (let j = 0, len_ = artists.length; j < len_; j++) {
  227. let artist = artists[j];
  228. if (artist["id"] === main_id) {
  229. // This is not perfect:
  230. // If a release contains references to multiple aliases of the same artist, it keeps only the first one
  231. // For example, see group 72607761 of Snoop Dogg
  232. // However, it is better for performance not to have to iterate through an array
  233. // So let's say 1 group release => 1 artist alias
  234. alias_id = artist["aliasid"].toString();
  235. aliases[alias_id] = artist["name"];
  236.  
  237. if ((main_alias_id === undefined) && (artist["name"] === main_name)) {
  238. // Sometimes, the alias_id associated with the artist main id differs, see artist 24926
  239. // But we need it to not display "as Alias" besides releases of main artist name
  240. main_alias_id = alias_id;
  241. }
  242. found = true;
  243. break;
  244. }
  245. }
  246. }
  247. if (found) break;
  248. }
  249. // Sometimes, release does not contain any artist because of an issue with the API
  250. // See: https://what.cd/forums.php?action=viewthread&threadid=192517&postid=5290204
  251. // In such a case (aliasid == -1), the release is not linked to any alias, just the default "[Show All]"
  252. groups[group_id] = alias_id;
  253. }
  254.  
  255. let data = {
  256. "main_name": main_name,
  257. "main_alias_id": main_alias_id,
  258. "aliases": aliases,
  259. "groups": groups
  260. };
  261. return data;
  262. };
  263.  
  264. this.query_api = function(artist_id, callback) {
  265. let self = this;
  266. let url = "/ajax.php?action=artist&id=" + artist_id;
  267.  
  268. let xhr = new XMLHttpRequest();
  269. xhr.timeout = 20000;
  270.  
  271. xhr.ontimeout = this.try_catch(
  272. function() {
  273. self.set_error_message("The API query timed out.");
  274. }
  275. );
  276.  
  277. xhr.onerror = this.try_catch(
  278. function() {
  279. self.set_error_message("The API query failed.\n" + xhr.statusText);
  280. }
  281. );
  282.  
  283. xhr.onload = this.try_catch(
  284. function() {
  285. if (xhr.status === 200) {
  286. let data = JSON.parse(xhr.responseText);
  287. callback(data);
  288. } else {
  289. self.set_error_message("The API query returned an error.\n" + xhr.statusText);
  290. }
  291. }
  292. );
  293.  
  294. xhr.open("GET", url, true);
  295. xhr.send(null);
  296. };
  297.  
  298. this.set_alias_title = function(alias_name) {
  299. document.getElementById("alias_title").innerHTML = "[" + alias_name + "]";
  300. };
  301.  
  302. this.append_alias_filter = function(alias_id, alias_name) {
  303. let li = this.builder.make_alias_li(alias_id, alias_name);
  304. let aliases_list = this.get_aliases_list();
  305. aliases_list.insertAdjacentHTML('beforeend', li);
  306. };
  307.  
  308. this.set_aliases = function(data) {
  309. if (Object.keys(data["aliases"]).length < 2) {
  310. this.cancel_process();
  311. return;
  312. }
  313. this.init_alias_title(data["main_name"]);
  314. this.fill_aliases_list(data["aliases"]);
  315. this.classify_releases(data["aliases"], data["groups"], data["main_alias_id"]);
  316. this.bind_filter(data["aliases"]);
  317. };
  318.  
  319. this.cancel_process = function() {
  320. let box_aliases = document.getElementsByClassName("box_aliases")[0];
  321. box_aliases.style.display = "none";
  322. };
  323.  
  324. this.init_alias_title = function(main_name) {
  325. let content = document.getElementById("content");
  326. let header = content.getElementsByClassName("header")[0];
  327. let h2 = header.getElementsByTagName("h2")[0];
  328. h2.id = "default_title";
  329. let title = this.builder.make_alias_title(main_name);
  330.  
  331. h2.insertAdjacentHTML("afterend", title);
  332. };
  333.  
  334. this.fill_aliases_list = function(aliases) {
  335. let aliases_list = this.get_aliases_list();
  336. aliases_list.innerHTML = "";
  337. this.append_alias_filter(-1, "[Show All]");
  338. let first = aliases_list.getElementsByTagName("a")[0];
  339. first.style.fontSize = "80%";
  340. first.style.fontWeight = "bold";
  341. for (let alias_id in aliases) {
  342. let name = aliases[alias_id];
  343. this.append_alias_filter(alias_id, name);
  344. }
  345. };
  346.  
  347. this.classify_releases = function(aliases, groups, main_alias_id) {
  348. let torrent_tables = document.getElementsByClassName("torrent_table");
  349. let categories = document.getElementById("discog_table").getElementsByClassName("box")[0];
  350.  
  351. for (let i = 0, len = torrent_tables.length; i < len; i++) {
  352. let table = torrent_tables[i];
  353. let id = table.getAttribute("id");
  354. let aliases_in_this_category = {};
  355.  
  356. let discogs = table.getElementsByClassName("discog");
  357. let alias_id = undefined;
  358.  
  359. for (let j = 0, len_ = discogs.length; j < len_; j++) {
  360. let discog = discogs[j];
  361. // The groupid of each torrent row is the same that the previous encountered main release row
  362. // This avoid having to extract groupid value at each iteration
  363. if (discog.classList.contains("group")) {
  364. let group_id = discog.querySelector("[id^='showimg_']").id.split("_")[1];
  365. alias_id = groups[group_id];
  366. aliases_in_this_category[alias_id] = 1;
  367.  
  368. if ((alias_id !== main_alias_id) && (alias_id != -1)) {
  369. let group_info = discog.getElementsByClassName("group_info")[0];
  370. let strong = group_info.getElementsByTagName("strong")[0];
  371. let name = aliases[alias_id];
  372.  
  373. let alias_text = this.builder.make_alias_release(alias_id, name);
  374.  
  375. strong.insertAdjacentHTML("beforeend", alias_text);
  376. }
  377. }
  378.  
  379. discog.className += " alias_id alias_id_" + alias_id;
  380. }
  381.  
  382. let category_aliases = " alias_id";
  383. for (let alias in aliases_in_this_category) {
  384. category_aliases += " alias_id_" + alias;
  385. }
  386. table.className += category_aliases;
  387. categories.querySelector("[href='#" + id + "']").className += category_aliases;
  388. }
  389. };
  390.  
  391. this.bind_filter = function(aliases) {
  392. let self = this;
  393. let filters = document.querySelectorAll("[alias_id]");
  394.  
  395. function callback(event) {
  396. let call = self.try_catch(
  397. function() {
  398. let clicked = event.target;
  399. if (clicked.getAttribute("href") === "#") {
  400. event.preventDefault();
  401. }
  402. let alias_id = clicked.getAttribute("alias_id");
  403. self.filter_releases(alias_id, aliases);
  404. }
  405. );
  406. call();
  407. }
  408.  
  409. for (let i = 0, len = filters.length; i < len; i++) {
  410. let filter = filters[i];
  411. filter.addEventListener("click", callback);
  412. }
  413. };
  414.  
  415. this.filter_releases = function(alias_id, aliases) {
  416. let current_alias_id = this.current_alias_id;
  417. if (alias_id === current_alias_id) return;
  418.  
  419. let aliases_list = this.get_aliases_list();
  420. let current_link = aliases_list.querySelector("[alias_id='" + current_alias_id + "']");
  421. let new_link = aliases_list.querySelector("[alias_id='" + alias_id + "']");
  422.  
  423. current_link.style.fontWeight = "";
  424. new_link.style.fontWeight = "bold";
  425.  
  426. if (alias_id === "-1") {
  427. this.reset_style();
  428. } else {
  429. this.set_alias_title(aliases[alias_id]);
  430. this.filter_style(alias_id);
  431. }
  432.  
  433. this.current_alias_id = alias_id;
  434. };
  435. };
  436.  
  437. let manager = new Manager();
  438. manager.proceed();