NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ // ==UserScript== // @name Metal-Archives PassTheHeadphones // @description Search PassTheHeadphones from Metal-Archives // @updateURL https://openuserjs.org/meta/maleficus032/Metal-Archives_PassTheHeadphones.meta.js // @version 1.0.1 // @require http://code.jquery.com/jquery-3.1.0.min.js // @include /^http:\/\/www\.metal-archives\.com\/search\?.*\&type\=(band_name|album_title)/ // @include /^http:\/\/www\.metal-archives\.com\/bands\/.*/ // @grant GM_xmlhttpRequest // @license MPL 2.0;http://mozilla.org/MPL/2.0/ // ==/UserScript== /*jshint esversion: 6 */ // Use noConflict mode since Metal-Archives includes its own copy of jQuery this.$ = this.jQuery = jQuery.noConflict(true); // Check if user is logged in to Metal-Archives because an extra column will be // displayed var maLoggedIn = $(".member_logout").length > 0; /* Constants */ const PTH_BASE_URL = 'https://passtheheadphones.me/'; const PTH_GROUP_ID = 'torrents.php?id='; const PTH_ADV_QUERY = 'torrents.php?artistname=AR_REPLACE' + '&groupname=AL_REPLACE&order_by=time&order_way=desc' + '&group_results=1&action=advanced&searchsubmit=1'; const PTH_ARTIST_API = 'ajax.php?action=artist&artistname=AR_REPLACE' + '&artistreleases=true'; const ARTIST_NAME = 'artistname'; const COLLAGE_LABEL_QUERY = 'https://passtheheadphones.me/collages.php?action=search' + '&search=REPLACE&tags=&tags_type=1&cats%5B4%5D=1' + '&type=c.name&order_by=Time&order_way=Descending'; /** * The `elements` module defines functions for creating common DOM elements * used by the view controllers. */ var elements = { /** * Return a table header object for PassTheHeadphones columns. * * @returns {jQuery} */ pthHeader: function() { return $('<th>', { id: 'pth-header', text: 'PTH' }).css('width', '50px'); }, /** * Return a PassTheHeadphones search link for a given artist and album. * * @param String artist name of artist * @param String album name of album * @returns {jQuery} */ searchLink: function(artist, album) { return $('<a>', { text: 'Search', href: PTH_BASE_URL + PTH_ADV_QUERY .replace('AR_REPLACE', encodeURIComponent(artist)) .replace('AL_REPLACE', encodeURIComponent(album)), target: '_blank' }); }, /** * Return a PassTheHeadphones link to view a given group * * @param String groupId groupId of torrent * @returns {jQuery} */ viewLink: function(groupId) { return $('<a>', { text: 'View', href: PTH_BASE_URL + PTH_GROUP_ID + groupId, target: '_blank' }); }, /** * Get the value passed as a query parameter to the URL. * * @param String param query search key * @returns {String|null} */ queryString: function(param) { var string = new RegExp('[\?&]' + param + '=([^&#]*)') .exec(window.location.href); return string ? string[1] : null; } }; /** * The `searchResultsViewController` module defines functions for manipulating * the /search view on Metal-Archives. */ var searchResultsViewController = { /** * Add search links to results in 'Artist' and 'Album' searches. */ addLinksToSearchResults: function(queryType) { // Return if there's only two rows in the table, which are the "no matches // found" row and a hidden row if ($('#searchResults tr').length == 2) return; // Add PassTheHeadphones header if it isn't already on the page if (!$('#pth-header').length) { elements.pthHeader().insertBefore($("#searchResults th").first()); } // Add links to search results, which are stored in rows with class names // "even" and "odd" $('#searchResults .even, .odd').each(function() { var artistCell = $(this).children().first(); var artist = artistCell.children().first().text(); var album = ""; if (queryType == 'album_title') { album = $(this).children("td:eq(1)").text().trim(); } var link = elements.searchLink(artist, album); var cell = $('<td>'); cell.append(link); cell.insertBefore(artistCell); }); } }; /** * The `artistViewController` module defines functions for manipulating the * /bands view on Metal-Archives. */ var artistViewController = { /** * Add all search links to artist view. */ addLinksToArtistView: function() { // Check if table is fully loaded and return if it isn't if (!$('.releaseCol').length) return; var artist = $('.band_name').children().first().text(); artistViewController.addCollageLabelSearch(); pthApiController.getReleasesForArtist(artist, artistViewController.addLinksToArtistReleases ); }, /** * Add search links to releases listed on an artist's page. * * @param Array pthGroups array of groupName/groupId pairs */ addLinksToArtistReleases: function(pthGroups) { // Add PassTheHeadphones header if it isn't already on the page, and return if it // already exists (e.g., user switched tabs) if ($('#pth-header').length) { return; } else { elements.pthHeader().insertBefore($(".releaseCol").first()); } var artist = $('.band_name').children().first().text(); // Add links to 'Complete Discography', skipping the first header row $('.discog tr').slice(1).each(function() { var releaseCell = maLoggedIn ? $(this).children().first().next() : $(this).children().first(); var release = releaseCell.children().first().text(); var link = null; // Attempt to match torrents on PassTheHeadphones with releases on Metal-Archives if (pthGroups) { for (var i = 0; i < pthGroups.length; i++) { // Use a faked text area to decode the HTML entities provided by // PassTheHeadphones's API (breaks foreign characters) var pthRelease = $('<textarea />') .html(pthGroups[i].groupName).text(); if (pthRelease.toLowerCase() == release.toLowerCase()) { link = elements.viewLink(pthGroups[i].groupId.toString()); break; } } } // Add a "Search" link if the album wasn't found--maybe someone uploaded // it with a slightly different name if (!link) { link = elements.searchLink(artist, release); } var cell = $('<td>'); cell.append(link); cell.insertBefore(releaseCell); }); }, /** * Add 'Search Collages' link to an artist's page to search PassTheHeadphones Collages * for their current label. */ addCollageLabelSearch: function() { // Return if the DOM already has this element, as switching tabs will cause // the function to trigger again if ($('#collage-label-search').length) return; var label = $('#band_stats') .find('.float_right') .first() .children("dd") .last(); // Return if there's no link to a label in the description, meaning // "unsigned and/or independent" if (!label.children("a").length) return; labelLink = $('<a>', { text: 'Search Collages', href: COLLAGE_LABEL_QUERY .replace('REPLACE', label.children("a").first().text()), target: '_blank' }); labelLink.attr('id', 'collage-label-search'); label.append(document.createTextNode(" | "), labelLink); } }; /** * The `pthApiController` module defines functions for searching the PassTheHeadphones * API. */ var pthApiController = { /** * Get the releases available on PassTheHeadphones for the given artist. * * @param String artist artist to search * @returns {Array} */ getReleasesForArtist: function(artist, callback) { var url = PTH_BASE_URL + PTH_ARTIST_API.replace('AR_REPLACE', artist); return GM_xmlhttpRequest({ method: "GET", url: url, onload: function(data) { if (data.status == 200) { // Get groupNames and groupIds and add links to results var payload = $.parseJSON(data.response); var groups = payload.response.torrentgroup.map(function(release) { return { 'groupName': release.groupName, 'groupId': release.groupId }; }); callback(groups); } else { // Bad response, probably not logged in. Add generic "Search" links // to results callback(null); } }, onerror: function() { console.log('Metal-Archives PassTheHeadphones: An error occurred ' + 'searching PassTheHeadphones for ' + artist); } }); } }; /** * Main logic */ (function() { var callback = null; var target = null; var config = { childList: true }; var queryType = elements.queryString('type'); var obs = null; if (queryType !== null) { callback = searchResultsViewController.addLinksToSearchResults; target = document.getElementById('searchResults'); } else { // No query in URL, must be looking at an artist page callback = artistViewController.addLinksToArtistView; target = document.getElementById('band_tab_discography'); config.subtree = true; } // Register a mutation observer for results because the tables are // populated via an AJAX request if (target !== null) { obs = new MutationObserver(function(mutations) { callback(queryType); }); obs.observe(target, config); } else { console.log("Metal-Archives PassTheHeadphones: Couldn't find target"); } })();