PTH Artist Aliases Filter

Add a box on artist page to filter based on aliases

当前为 2016-12-13 提交的版本,查看 最新版本

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