// ==UserScript== // @name What.CD to bB Crossposter // @description Fills the bB upload form with data from WCD // @namespace BlackNullerNS // @include http*://what.cd/torrents.php* // @include http*://what.cd/artist.php?id=* // @version 1.3.3 // @grant GM_xmlhttpRequest // @downloadURL none // ==/UserScript== var span, pos, lnk, announceLink; var bb = document.createElement('a'); bb.textContent = '+bB'; bb.setAttribute('href', '#'); bb.setAttribute('title', 'Crosspost to bB'); var userId = document.getElementById('nav_userinfo').firstElementChild.getAttribute('href').split('?id=')[1]; if (document.location.href.indexOf('type=seeding&userid=' + userId) > -1 || document.location.href.indexOf('type=uploaded&userid=' + userId) > -1 || document.location.href.indexOf('type=leeching&userid=' + userId) > -1 || document.location.href.indexOf('type=snatched&userid=' + userId) > -1) { var t, torrents = document.getElementsByClassName('torrent_links_block'); for (var i = 0, l = torrents.length; i < l; i++) { t = torrents.item(i); pos = t.lastElementChild.nextSibling; t.insertBefore(document.createTextNode(" | "), pos); lnk = bb.cloneNode(true); lnk.onclick = crossPostToBb; t.insertBefore(lnk, pos); } return; } var item, rows = document.querySelectorAll(".tl_snatched, .wcds_seeding, .wcds_snatched, .wcds_uploaded, .wcds_leeching"); if (rows.length > 0) { for (var i = 0, l = rows.length; i < l; i++) { item = rows.item(i); if (item.classList.contains("tl_snatched")) { if (!item.parentNode.className) { span = item.parentNode.parentNode.firstElementChild; } else { continue; } } else { span = item.parentNode.firstElementChild; } pos = span.lastElementChild.nextSibling; span.insertBefore(document.createTextNode(" | "), pos); lnk = bb.cloneNode(true); lnk.onclick = crossPostToBb; span.insertBefore(lnk, pos); } return; } function loadTorrentInfo(a, callback) { var tr = a.closest('tr'); var id = ('id' in tr && tr.id) ? tr.id.replace('torrent', '') : a.parentNode.firstElementChild.getAttribute('href').split('&id=')[1].split('&')[0]; GM_xmlhttpRequest({ url: 'https://what.cd/ajax.php?action=torrent&id=' + id, method: 'GET', onload: function(response){ try { var data = JSON.parse(response.responseText); if (data.status !== 'success') { alert('Request failed!'); return; } } catch (e) { alert('Bad response from What.CD API'); return; } if (!('torrent' in data.response)) { alert('Unexpected response from What.CD API'); return; } callback(data.response); }, onerror: function(response){ alert('API request error!'); }, timeout: 7000, ontimeout: function(response){ alert('API request timed out!'); } }); return false; } function crossPostToBb(data) { if (!('torrent' in data)) { return loadTorrentInfo(this, crossPostToBb); } var form = document.createElement('form'); form.setAttribute('target', '_blank'); form.setAttribute('method', 'post'); form.setAttribute('action', 'https://baconbits.org/upload.php'); input(form, 'type', data.group.categoryName); var artist = ''; var groupName = data.group.name.replace(/&/g, '&'); if (data.group.categoryName === 'Music') { var media = data.torrent.media; if (media === 'WEB') media = 'Web'; if (data.group.musicInfo.artists.length > 2) { artist = 'Various Artists'; } else if (data.group.musicInfo.artists.length === 2) { artist = data.group.musicInfo.artists[0].name + ' & ' + data.group.musicInfo.artists[1].name; } else { artist = data.group.musicInfo.artists[0].name; } artist = artist.replace(/&/g, '&'); input(form, 'artist', artist); input(form, 'media', media); input(form, 'format', data.torrent.format); input(form, 'bitrate', data.torrent.encoding); if (data.torrent.remasterTitle) { input(form, 'remaster', 1); input(form, 'remaster_year', data.torrent.remasterYear); input(form, 'remaster_title', data.torrent.remasterTitle); } } if (data.group.categoryName === 'Music' || data.group.categoryName === 'Audiobooks') { input(form, 'album_desc', strip(data.group.wikiBody)); input(form, 'release_desc', strip(data.torrent.description)); } else { input(form, 'desc', strip(data.group.wikiBody) + "\n" + strip(data.torrent.description)); } input(form, 'submit', 'true'); input(form, 'image', data.group.wikiImage); input(form, 'title', groupName); input(form, 'year', data.group.year); input(form, 'tags', data.group.tags.join(', ')); if (data.torrent.scene) { input(form, 'scene', 1); } var div = document.createElement("div"); div.setAttribute("id","bbConfirm"); div.setAttribute("style", "position:fixed;width:260px;top:50%;left:50%;margin-top:-5%;margin-left:-130px;padding:30px;display:inline-block;border:1px solid #666;border-radius:6px;-moz-box-shadow: 0px 0px 7px #2e2e2e;-webkit-box-shadow: 0px 0px 7px #2e2e2e;box-shadow: 0px 0px 7px #2e2e2e;background:#fff;text-align:center;z-index:200;transition:all 0.5s;transition-delay:0s;"); var dl = document.createElement("a"); dl.style.cursor = "pointer"; dl.appendChild(document.createTextNode("Download modified torrent")); dl.onclick = function(){ var xhr = new XMLHttpRequest(); xhr.mozBackgroundRequest = true; xhr.timeout = 10000; xhr.responseType = "arraybuffer"; xhr.ontimeout = xhr.onerror = function() { alert('Unable to fetch the torrent!'); } xhr.onload = function () { var filename = (artist ? artist + " - " : "") + groupName + (data.group.year ? " (" + data.group.year + ")" : "") + (data.torrent.format ? " [" + data.torrent.format + "]" : "") + " [bB].torrent"; if (announceLink) { downloadTorrent(this.response, filename); return; } var binary = this.response; GM_xmlhttpRequest({ url: "https://baconbits.org/upload.php", method: 'GET', onload: function(response){ announceLink = response.responseText.split('http://tracker.baconbits.org:34000/')[1].split('/')[0]; announceLink = 'http://tracker.baconbits.org:34000/' + announceLink + '/announce'; downloadTorrent(binary, filename); }, timeout: 10000, ontimeout: function() { alert('Unable to fetch announce link!'); }, onerror: function() { alert('Unable to fetch announce link!'); } }); }; xhr.open("GET", "https://what.cd/torrents.php?action=download&id=" + data.torrent.id, true); xhr.send(); }; var dldiv = document.createElement("div"); dldiv.style.marginBottom = '18px'; dldiv.appendChild(dl); var search = document.createElement("div"); search.style.marginBottom = '18px'; search.textContent = 'Searching bB...'; var btnText = "Repost to bB"; var btn = document.createElement("button"); btn.type = "submit"; btn.appendChild(document.createTextNode(btnText)); var a = document.createElement("a"); a.style.cursor = "pointer"; a.appendChild(document.createTextNode("Cancel")); div.appendChild(search); div.appendChild(dldiv); div.appendChild(btn); div.appendChild(document.createElement("br")); div.appendChild(document.createElement("br")); div.appendChild(a); form.appendChild(div); document.body.appendChild(form); if (data.torrent.hasLog) { btn.textContent = 'Loading LOG, please wait...'; btn.setAttribute('disabled', true); var logUrl = 'https://what.cd/torrents.php?action=viewlog&torrentid='+ data.torrent.id +'&groupid=' + data.group.id; GM_xmlhttpRequest({ url: logUrl, method: 'GET', onload: function(response){ if (response.responseText.indexOf('
') === -1) {
                    search.innerHTML = 'No LOG found!';
                    return;
                }

                var logContent = response.responseText.split('
')[1].split('
')[0]; input(form, 'release_desc', (data.torrent.description ? strip(data.torrent.description) + "\n\n" : "") + "[spoiler=LOG][pre]" + logContent + '[/pre][/spoiler]'); btn.removeAttribute('disabled'); btn.textContent = btnText; }, onerror: function(response){ btn.textContent = btnText; btn.removeAttribute('disabled'); alert('LOG request error!'); }, timeout: 7000, ontimeout: function(response){ btn.textContent = btnText; btn.removeAttribute('disabled'); alert('LOG request timed out!'); } }); } form.onsubmit = function(){ setTimeout(function(){ form.parentNode.removeChild(form); }, 1000); }; a.onclick = function(){ form.parentNode.removeChild(form); }; var url = data.group.categoryName === 'Music' ? 'https://baconbits.org/torrents.php?artistname='+ encodeURIComponent(artist) +'&action=advanced&torrentname='+ encodeURIComponent(groupName) +'&format='+ encodeURIComponent(data.torrent.format) +'&disablegrouping=1' : 'https://baconbits.org/torrents.php?disablegrouping=1&searchstr=' + encodeURIComponent(groupName); GM_xmlhttpRequest({ url: url, method: 'GET', onload: function(response){ if (response.responseText.indexOf('action=download') === -1) { search.innerHTML = 'No duplicates found on bB, go ahead!'; return; } div.style.width = '600px'; div.style.marginLeft = '-300px'; var searchLink = document.createElement('a'); searchLink.setAttribute('href', url); searchLink.setAttribute('target', '_blank'); searchLink.style.fontWeight = 'bold'; searchLink.textContent = 'Found on bB:'; search.textContent = ''; search.appendChild(searchLink); search.appendChild(document.createElement('br')); var dom = document.createElement("div"); dom.insertAdjacentHTML("afterbegin", response.responseText.replace(/Show<\/a>/g, ''); html = html.replace(/
([.\s\S]+?)<\/blockquote>/g, "[spoiler]$1[/spoiler]"); html = html.replace(/(.+?)<\/a>/g, "$1"); html = html.replace(/(.+?)<\/a>/g, "[url=$1]$2[/url]"); html = html.replace(//g, "[img]$1[/img]"); html = html.replace(/(.+?)<\/span>/g, "[size=$1]$2[/size]"); html = html.replace(/(.+?)<\/span>/g, "[color=$1]$2[/color]"); html = html.replace(/(.+?)<\/ol>/g, "[list=1]\n$1[/list]"); html = html.replace(/(.+?)<\/ul>/g, "[list]\n$1[/list]"); html = html.replace(/(.+?)<\/li>/g, "[#]$1\n"); html = html.replace(/(.+?)<\/b>/g, "[b]$1[/b]"); html = html.replace(/(.+?)<\/strong>/g, "[b]$1[/b]"); html = html.replace(/(.+?)<\/i>/g, "[i]$1[/i]"); html = html.replace(/(.+?)<\/em>/g, "[i]$1[/i]"); html = html.replace(/(.+?)<\/s>/g, "[s]$1[/s]"); html = html.replace(/(.+?)<\/u>/g, "[u]$1[/u]"); html = html.replace(/\[hide/g, "[spoiler"); html = html.replace(/\[\/hide\]/g, "[/spoiler]"); var tmp = document.createElement("DIV"); tmp.innerHTML = html; return tmp.textContent || tmp.innerText || ""; } function random_string(length) { var text = ""; var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; for( var i=0; i < length; i++ ) text += possible.charAt(Math.floor(Math.random() * possible.length)); return text; } function decodeHTML(str) { var d = document.createElement('div'); d.innerHTML = str; return d.textContent; } // Module for encoding/decoding Bencoded data function bencode() { "use strict"; // Encoding functions var encode = function (value) { // Type var t = typeof(value); // Number if (t == "number") return encode_int(Math.floor(value)); // String if (t == "string") return encode_string(value); // Array if (Array.isArray(value)) return encode_list(value); // Dict return encode_dict(value); }; var encode_int = function (value) { return "i" + value + "e"; }; var encode_string = function (value) { return "" + value.length + ":" + value; }; var encode_list = function (value) { var str = [ "l" ], i; // List values for (i = 0; i < value.length; ++i) { str.push(encode(value[i])); } // End str.push("e"); return str.join(""); }; var encode_dict = function (value) { var str = [ "d" ], keys = [], i; // Get and sort keys for (i in value) keys.push(i); keys.sort(); // Push values for (i = 0; i < keys.length; ++i) { str.push(encode_string(keys[i])); str.push(encode(value[keys[i]])); } // End str.push("e"); return str.join(""); }; // Decoding class var Decoder = function () { this.pos = 0; }; Decoder.prototype = { constructor: Decoder, decode: function (str) { // Errors var k = str[this.pos]; if (!(k in decode_generic)) throw "Invalid format"; // Call return decode_generic[k].call(this, str); }, decode_int: function (str) { // Skip the "i" prefix ++this.pos; var end = str.indexOf("e", this.pos), value; // No end if (end < 0) throw "Invalid format"; // Assume proper number format value = parseInt(str.substr(this.pos, end - this.pos), 10); // Done this.pos = end + 1; return value; }, decode_string: function (str) { var delim = str.indexOf(":", this.pos), length, value; // No end if (delim < 0) throw "Invalid format"; // Assume proper number format length = parseInt(str.substr(this.pos, delim - this.pos), 10); value = str.substr(delim + 1, length); // Done this.pos = delim + length + 1; return value; }, decode_list: function (str) { // Skip the "l" prefix ++this.pos; // Read list var list = [], value; // Loop until end or exception while (str[this.pos] != "e") { value = this.decode(str); // this throws errors if str[this.pos] is out of bounds list.push(value); } // Done; skip "e" suffix ++this.pos; return list; }, decode_dict: function (str) { // Skip the "d" prefix ++this.pos; // Read dict var dict = {}, key, value; // Loop until end or exception while (str[this.pos] != "e") { key = this.decode_string(str); value = this.decode(str); // this throws errors if str[this.pos] is out of bounds dict[key] = value; } // Done; skip "e" suffix ++this.pos; return dict; }, }; // Generic decode functions var decode_generic = { "l": Decoder.prototype.decode_list, "d": Decoder.prototype.decode_dict, "i": Decoder.prototype.decode_int, }, i; for (i = 0; i < 10; ++i) decode_generic[i.toString()] = Decoder.prototype.decode_string; // Encode/decode functions return { /** encode: function (obj) Encodes an object into a Bencode'd string @param obj The object to encode This should only be one of the following: string number (floats are floor'd to integers) array (containing things only from this list) object (containing things only from this list) Strings should be encoded in some way such that each character is in the range [0,255] @return A string representing the object */ encode: encode, /** decode: function (str) Decodes a Bencode'd string back into its original type @param str The string to decode @return The original object represented by str @throws Any one of the following self-explanatory strings "Invalid format" "Invalid string" "Invalid int" */ decode: function (str) { // Create a decoder and call return (new Decoder()).decode(str); }, }; }