PTH Collector

Download multiple torrents by some criteria on PTH

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(У мене вже є менеджер скриптів, дайте мені встановити його!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         PTH Collector
// @version      1.8
// @description  Download multiple torrents by some criteria on PTH
// @author       Chameleon
// @include      http*://redacted.ch/*
// @grant        none
// @require      https://greasyfork.org/scripts/19855-jszip/code/jszip.js?version=126859
// @namespace https://greasyfork.org/users/87476
// ==/UserScript==

(function() {
  'use strict';

  var wH=window.location.href;
  if(wH.indexOf('artist.php?id=') != -1)
  {
    addToX(compileArtist);
  }
  else if(wH.indexOf('threadid=3543') != -1)
    showSettings();
  else if(wH.indexOf('collages.php?id=') != -1)
    addToX(compileCollage);
  else if(wH.indexOf('bookmarks.php?type=torrents') != -1)
    addToX(compileCollage.bind('Bookmarks'));
  //else if(wH.indexOf('torrents.php') != -1 && wH.indexOf('?id=') == -1)
    //addToSearch();
})();

function addToSearch()
{
  var sidebar=document.createElement('div');
  sidebar.setAttribute('class', 'sidebar');
  sidebar.setAttribute('style', 'float: initial; margin: auto;');
  /*var left=(document.body.clientWidth/2)+425;
  sidebar.setAttribute('style', 'position: absolute; left: '+left+'px;');
  var before=document.getElementsByClassName('header')[0].nextElementSibling;
  before.parentNode.insertBefore(sidebar, before);*/
  document.getElementsByClassName('thin')[0].appendChild(sidebar);
  
  addToX(compileCollage.bind('Search'));
}

function showSettings()
{
  var div=document.getElementById('collectorSettings');
  if(!div)
  {
    var before = document.getElementsByClassName('forum_post')[0];
    div = document.createElement('div');
    div.setAttribute('id', 'collectorSettings');
    before.parentNode.insertBefore(div, before);
    div.setAttribute('style', 'width: 100%; text-align: center; padding-bottom: 10px;');
    div.setAttribute('class', 'box');
  }
  div.innerHTML = '<h2>Collector Settings</h2><br />';
  var settings = getSettings();

  var a=document.createElement('a');
  a.href='javascript:void(0);';
  a.innerHTML = 'Sequential torrent download: '+(settings.sequential ? 'On':'Off');
  a.addEventListener('click', changeLink.bind(undefined, a, div), false);
  div.appendChild(a);
  div.appendChild(document.createElement('br'));

  var a=document.createElement('a');
  a.href='javascript:void(0);';
  a.innerHTML = 'Include summary txt in .zip: '+(settings.summary ? 'On':'Off');
  a.addEventListener('click', changeLink.bind(undefined, a, div), false);
  div.appendChild(a);
  div.appendChild(document.createElement('br'));
}

function changeLink(a, div)
{
  if(a.innerHTML.indexOf('On') != -1)
    a.innerHTML = a.innerHTML.replace(/On/, 'Off');
  else
    a.innerHTML = a.innerHTML.replace(/Off/, 'On');

  changeSettings(div);
}

function changeSettings(div)
{
  var settings = getSettings();
  var as=div.getElementsByTagName('a');

  if(as[0].innerHTML.indexOf('On') != -1)
    settings.sequential=true;
  else
    settings.sequential=false;

  if(as[1].innerHTML.indexOf('On') != -1)
    settings.summary=true;
  else
    settings.summary=false;

  setSettings(settings);
  showSettings();
}

function setSettings(settings)
{
  window.localStorage.collectorOperationSettings = JSON.stringify(settings);
}

function getSettings()
{
  var settings = window.localStorage.collectorOperationSettings;
  if(!settings)
  {
    settings = {sequential:false};
  }
  else
  {
    settings = JSON.parse(settings);
  }
  return settings;
}

function addToX(compileFunc)
{
  var style=document.createElement('style');
  style.innerHTML = '.collectorAdded { background-color: #151 !important; } ';
  style.innerHTML+= '.collectorAddedSuboptimal { background-color: #551 !important; } ';
  style.innerHTML+= '.collectorMissing { background: rgba(255, 0, 0, 0.2) !important;} ';
  style.innerHTML+= '.collectorSuboptimal { background: rgba(255, 255, 0, 0.2) !important;}';
  document.head.appendChild(style);

  var settings=getPersistence();

  var sidebar=document.getElementsByClassName('sidebar')[0];
  var box=document.createElement('div');
  box.setAttribute('class', 'box');
  sidebar.appendChild(box);
  //sidebar.insertBefore(box, sidebar.firstElementChild);
  var head=document.createElement('div');
  box.appendChild(head);
  head.setAttribute('class', 'head');
  head.innerHTML = '<strong>Collector</strong>';
  var list=document.createElement('ul');
  box.appendChild(list);
  list.setAttribute('class', 'nobullet');

  var labelStyle='';
  var inputStyle='';

  var li=document.createElement('li');
  list.appendChild(li);
  var label=document.createElement('span');
  label.setAttribute('style', labelStyle);
  label.innerHTML = 'Format [?]: ';
  var options = ["Any", "FLAC", "MP3", "AAC", "AC3", "DTS"];
  li.addEventListener('mouseover', mouseOverLabel.bind(undefined, options), false);
  li.addEventListener('mouseout', mouseOutLabel.bind(undefined, options), false);
  li.appendChild(label);
  var format=document.createElement('input');
  format.setAttribute('style', inputStyle);
  li.appendChild(format);
  format.value = settings.format.join(', ');
  format.placeholder="Format";

  var li=document.createElement('li');
  list.appendChild(li);
  var label=document.createElement('span');
  label.setAttribute('style', labelStyle);
  label.innerHTML = 'Bitrate  [?]: ';
  var options = ["Any", "192", "APS (VBR)", "V2 (VBR)", "V1 (VBR)", "256", "APX", "V0 (VBR)", "320", "Lossless", "24bit Lossless", "Other"];
  li.addEventListener('mouseover', mouseOverLabel.bind(undefined, options), false);
  li.addEventListener('mouseout', mouseOutLabel.bind(undefined, options), false);
  li.appendChild(label);
  var bitrate=document.createElement('input');
  bitrate.setAttribute('style', inputStyle);
  li.appendChild(bitrate);
  bitrate.value = settings.bitrate.join(', ');
  bitrate.placeholder="Bitrate";

  var li=document.createElement('li');
  list.appendChild(li);
  var label=document.createElement('span');
  label.setAttribute('style', labelStyle);
  label.innerHTML = 'Media [?]: ';
  var options = ["Any", "CD", "DVD", "Vinyl", "Soundboard", "SACD", "DAT", "Cassette", "WEB", "Blu-Ray"];
  li.addEventListener('mouseover', mouseOverLabel.bind(undefined, options), false);
  li.addEventListener('mouseout', mouseOutLabel.bind(undefined, options), false);
  li.appendChild(label);
  var media=document.createElement('input');
  media.setAttribute('style', inputStyle);
  li.appendChild(media);
  media.value = settings.media.join(', ');
  media.placeholder="Media";

  var li=document.createElement('li');
  list.appendChild(li);
  var label=document.createElement('span');
  label.setAttribute('style', labelStyle);
  label.innerHTML = 'Type: ';
  li.appendChild(label);
  var type=document.createElement('input');
  type.placeholder = 'Release Type';
  type.value = settings.types.join(', ');
  type.setAttribute('style', inputStyle);
  li.appendChild(type);

  var li=document.createElement('li');
  list.appendChild(li);
  var label=document.createElement('span');
  label.setAttribute('style', labelStyle);
  label.innerHTML = 'Minimum number of seeders: ';
  li.appendChild(label);
  var minSeedCount=document.createElement('input');
  minSeedCount.type='number';
  minSeedCount.value = settings.minSeedCount;
  minSeedCount.setAttribute('style', inputStyle);
  li.appendChild(minSeedCount);

  var messageDiv=document.createElement('div');
  messageDiv.setAttribute('style', 'text-align: center;');

  format.addEventListener('keyup', compileFunc.bind(undefined, {format:format, bitrate:bitrate, media:media, type:type, minSeedCount:minSeedCount}, messageDiv), false);
  bitrate.addEventListener('keyup', compileFunc.bind(undefined, {format:format, bitrate:bitrate, media:media, type:type, minSeedCount:minSeedCount}, messageDiv), false);
  media.addEventListener('keyup', compileFunc.bind(undefined, {format:format, bitrate:bitrate, media:media, type:type, minSeedCount:minSeedCount}, messageDiv), false);
  type.addEventListener('keyup', compileFunc.bind(undefined, {format:format, bitrate:bitrate, media:media, type:type, minSeedCount:minSeedCount}, messageDiv), false);
  minSeedCount.addEventListener('keyup', compileFunc.bind(undefined, {format:format, bitrate:bitrate, media:media, type:type, minSeedCount:minSeedCount}, messageDiv), false);
  minSeedCount.addEventListener('change', compileFunc.bind(undefined, {format:format, bitrate:bitrate, media:media, type:type, minSeedCount:minSeedCount}, messageDiv), false);
  minSeedCount.addEventListener('click', compileFunc.bind(undefined, {format:format, bitrate:bitrate, media:media, type:type, minSeedCount:minSeedCount}, messageDiv), false);

  var div=document.createElement('div');
  div.setAttribute('style', 'text-align: center;');
  box.appendChild(div);
  var a=document.createElement('a');
  div.appendChild(a);
  a.href='javascript:void(0);';
  a.innerHTML = 'Collect';
  a.addEventListener('click', save.bind(undefined, messageDiv), false);

  div.appendChild(document.createTextNode(' | '));
  var a=document.createElement('a');
  div.appendChild(a);
  a.href='javascript:void(0);';
  a.innerHTML = 'Preview';
  a.setAttribute('id', 'collectorPreviewLink');
  a.addEventListener('click', compileFunc.bind(undefined, {format:format, bitrate:bitrate, media:media, type:type, minSeedCount:minSeedCount}, messageDiv, true), false);


  div.appendChild(document.createTextNode(' | '));
  var a=document.createElement('a');
  div.appendChild(a);
  a.href='javascript:void(0);';
  a.innerHTML = 'List';
  a.addEventListener('click', listC.bind(undefined, messageDiv), false);

  var div=document.createElement('div');
  div.setAttribute('style', 'text-align: center; white-space:nowrap; text-overflow:ellipsis; overflow:hidden;');
  box.appendChild(div);
  div.id='collectorList';

  box.appendChild(messageDiv);
}

function listC(messageDiv)
{
  var div=document.getElementById('collectorList');
  div.innerHTML='';

  var torrentList=messageDiv.getAttribute('torrentList');
  if(!torrentList)
    messageDiv.innerHTML = "You need to preview first";
  torrentList=JSON.parse(torrentList);
  for(var i=0; i<torrentList.length; i++)
  {
    var t=torrentList[i];
    var id=parseInt(t.dl.split('id=')[1]);
    var name=t.name.split('.torrent')[0];
    div.innerHTML+='<br /><a href="/torrents.php?torrentid='+id+'" title="'+name+'">'+name+'</a>';
  }
}

function mouseOverLabel(options, event)
{
  var div=document.createElement('div');
  document.body.appendChild(div);
  div.setAttribute('class', 'labelPopups');
  div.setAttribute('style', 'position: fixed; left: '+event.clientX+'px; top: '+event.clientY+'px; border-radius: 10px; padding: 10px; background: rgba(0,0,0,0.8); color: white;');
  for(var i=0; i<options.length; i++)
  {
    div.innerHTML+=options[i]+'<br />';
  }
}

function mouseOutLabel()
{
  var l=document.getElementsByClassName('labelPopups');
  for(var i=0; i<l.length; i++)
  {
    l[i].parentNode.removeChild(l[i]);
  }
}

function compileCollage(filters, messageDiv, hilight)
{
  setPersistence(filters);
  var pers=getPersistence();

  var format = pers.format;
  var bitrate = pers.bitrate;
  var media = pers.media;
  var minSeedCount = pers.minSeedCount;

  var collage=document.getElementsByTagName('h2')[0].textContent;
  if(this == "Bookmarks")
    collage="Bookmarks";

  var title = "Collection Collage - "+collage+".zip";//+" - "+format+"_"+bitrate+"_"+media+".zip";
  messageDiv.setAttribute('zipName', title);

  messageDiv.innerHTML = 'Collecting torrents';
  var torrents=[];

  var missed=[];
  var suboptimal=[];
  var albumCount=0;

  if(hilight)
  {
    var tRows = document.getElementsByClassName('torrent_row');
    for(var i=0; i<tRows.length; i++)
    {
      var t=tRows[i];
      t.setAttribute('class', t.getAttribute('class').replace(' collectorAddedSuboptimal', ''));
      t.setAttribute('class', t.getAttribute('class').replace(' collectorAdded', ''));
      if(t.getAttribute('collectorPreviewing') != "true")
      {
        t.addEventListener('click', addAnywayFunc.bind(undefined, t), false);
        t.setAttribute('collectorPreviewing', "true");
      }
    }
    var groups=document.getElementsByClassName('group');
    for(var i=0; i<groups.length; i++)
    {
      groups[i].setAttribute('class', groups[i].getAttribute('class').replace(' collectorMissing', ''));
      groups[i].setAttribute('class', groups[i].getAttribute('class').replace(' collectorSuboptimal', ''));
    }
  }

  var totalSize=0;

  var groups=document.getElementsByClassName('group');
  for(var i=0; i<groups.length; i++)
  {
    var g=groups[i];
    var album=g.getElementsByTagName('strong');
    if(album.length === 0)
      album=g.getElementsByClassName('group_info')[0].getElementsByTagName('a');
    else
      album = album[0].getElementsByTagName('a');
    var artist=album[0].innerHTML;
    album = album[album.length-1].innerHTML;
    g.setAttribute('artist', artist);
    g.setAttribute('album', album);
  }

  var output=doGroups(groups, torrents, hilight, albumCount, suboptimal, missed);

  torrents=output.torrents;
  totalSize+=output.totalSize;
  albumCount=output.albumCount;
  suboptimal=output.suboptimal;
  missed=output.missed;


  messageDiv.innerHTML = '';

  var summaryText=(title.replace(/.zip$/, ''))+"\n\nDate: "+Date()+"\n\nTorrent groups analyzed: "+albumCount;
  if(missed.length > 0)
    summaryText+="\nTorrent groups filtered: "+missed.length+"\nTorrents downloaded: "+(albumCount-missed.length);
  if(suboptimal.length > 0)
    summaryText+="\nSuboptimal torrents: "+suboptimal.length;
  summaryText+="\n\nFormat filter: "+format.join(', ')+'\nBitrate filter: '+bitrate.join(', ')+'\nMedia filter: '+media.join(', ');
  summaryText+="\n\nTotal size of torrents (ratio hit): "+prettySize(totalSize);
  if(missed.length > 0)
  {
    summaryText+="\n\nAlbums unavailable within your criteria (consider making a request for your desired format):";
    for(var i=0; i<missed.length; i++)
    {
      var m=missed[i];
      summaryText+="\n/torrents.php?id="+m.groupid+" - "+m.artist+" - "+m.album;
    }
  }
  if(suboptimal.length > 0)
  {
    summaryText+="\n\nAlbums suboptimal within your criteria (consider making a request for your optimal format):";
    for(var i=0; i<suboptimal.length; i++)
    {
      var m=suboptimal[i];
      summaryText+="\n/torrents.php?id="+m.groupid+" - "+m.artist+" - "+m.album;
    }
  }

  messageDiv.setAttribute('summaryText', summaryText);

  if(missed.length > 0)
  {
    messageDiv.innerHTML = "Missing "+missed.length+" of "+albumCount+" groups";
  }
  else
  {
    messageDiv.innerHTML = 'Added torrents from all '+albumCount+' groups';
  }
  if(suboptimal.length > 0)
  {
    messageDiv.innerHTML+= '<br />'+suboptimal.length+' of '+(albumCount-missed.length)+' selected are suboptimal';
  }
  messageDiv.innerHTML+="<br />Total size: "+prettySize(totalSize);
  messageDiv.setAttribute('torrentList', JSON.stringify(torrents));
}

function getPersistence()
{
  var settings = window.localStorage.collectorSettings;
  if(!settings)
  {
    settings = {format: [], bitrate: [], media: [], types: [], minSeedCount: 0};
  }
  else
  {
    settings = JSON.parse(settings);
  }
  if(typeof(settings.format) != "object")
    settings = {format: [], bitrate: [], media: [], types: [], minSeedCount: 0};
  return settings;
}

function setPersistence(filters)
{
  var settings = {
    format: filters.format.value.split(', '),
    bitrate: filters.bitrate.value.split(', '),
    media: filters.media.value.split(', '),
    types: filters.type.value.split(', '),
    minSeedCount: parseInt(filters.minSeedCount.value),
  };
  if(isNaN(settings.minSeedCount))
    settings.minSeedCount=0;
  window.localStorage.collectorSettings = JSON.stringify(settings);
}

function compileArtist(filters, messageDiv, hilight)
{
  setPersistence(filters);
  var pers=getPersistence();

  var format = pers.format;
  var bitrate = pers.bitrate;
  var media = pers.media;
  var types = pers.types;

  var artist=document.getElementsByTagName('h2')[0].textContent;
  var title = "Collection Artist - "+artist+".zip";//+" - "+format+"_"+bitrate+"_"+media+".zip";
  messageDiv.setAttribute('zipName', title);

  messageDiv.innerHTML = 'Collecting torrents';
  var torrents=[];

  var missed=[];
  var suboptimal=[];
  var albumCount=0;

  if(hilight)
  {
    var tRows = document.getElementsByClassName('torrent_row');
    for(var i=0; i<tRows.length; i++)
    {
      var t=tRows[i];
      t.setAttribute('class', t.getAttribute('class').replace(' collectorAddedSuboptimal', ''));
      t.setAttribute('class', t.getAttribute('class').replace(' collectorAdded', ''));
      if(t.getAttribute('collectorPreviewing') != "true")
      {
        t.addEventListener('click', addAnywayFunc.bind(undefined, t), false);
        t.setAttribute('collectorPreviewing', "true");
      }
    }
    var groups=document.getElementsByClassName('group');
    for(var i=0; i<groups.length; i++)
    {
      groups[i].setAttribute('class', groups[i].getAttribute('class').replace(' collectorMissing', ''));
      groups[i].setAttribute('class', groups[i].getAttribute('class').replace(' collectorSuboptimal', ''));
    }
  }
  
  for(var i=0; i<groups.length; i++)
  {
    var g=groups[i];
    var album=g.getElementsByClassName('group_info')[0].getElementsByTagName('strong')[0].getElementsByTagName('a');
    album = album[album.length-1].innerHTML;
    g.setAttribute('artist', artist);
    g.setAttribute('album', album);
  }

  var totalSize=0;

  var tables = document.getElementsByClassName('release_table');
  for(var i=0; i<tables.length; i++)
  {
    var t=tables[i];
    var releaseType=t.getElementsByTagName('tr')[0].getElementsByTagName('strong')[0].innerHTML;

    var inTypes=false;
    for(var m=0; m<types.length; m++)
    {
      if(types[m] == releaseType)
      {
        inTypes=true;
        break;
      }
    }
    if(!inTypes && !(types[0] == '' && types.length == 1))
      continue;

    var groups=t.getElementsByClassName('group');

    var output=doGroups(groups, torrents, hilight, albumCount, suboptimal, missed);

    torrents=output.torrents;
    totalSize+=output.totalSize;
    albumCount=output.albumCount;
    suboptimal=output.suboptimal;
    missed=output.missed;
  }

  messageDiv.innerHTML = '';

  var summaryText=(title.replace(/.zip$/, ''))+"\n\nDate: "+Date()+"\n\nTorrent groups analyzed: "+albumCount;
  if(missed.length > 0)
    summaryText+="\nTorrent groups filtered: "+missed.length+"\nTorrents downloaded: "+(albumCount-missed.length);
  if(suboptimal.length > 0)
    summaryText+="\nSuboptimal torrents: "+suboptimal.length;
  summaryText+="\n\nFormat filter: "+format.join(', ')+'\nBitrate filter: '+bitrate.join(', ')+'\nMedia filter: '+media.join(', ')+'\nRelease type filter: '+types.join(', ');
  summaryText+="\n\nTotal size of torrents (ratio hit): "+prettySize(totalSize);
  if(missed.length > 0)
  {
    summaryText+="\n\nAlbums unavailable within your criteria (consider making a request for your desired format):";
    for(var i=0; i<missed.length; i++)
    {
      var m=missed[i];
      summaryText+="\n/torrents.php?id="+m.groupid+" - "+m.artist+" - "+m.album;
    }
  }
  if(suboptimal.length > 0)
  {
    summaryText+="\n\nAlbums suboptimal within your criteria (consider making a request for your optimal format):";
    for(var i=0; i<suboptimal.length; i++)
    {
      var m=suboptimal[i];
      summaryText+="\n/torrents.php?id="+m.groupid+" - "+m.artist+" - "+m.album;
    }
  }

  messageDiv.setAttribute('summaryText', summaryText);

  if(missed.length > 0)
  {
    messageDiv.innerHTML = "Missing "+missed.length+" of "+albumCount+" groups";
  }
  else
  {
    messageDiv.innerHTML = 'Added torrents from all '+albumCount+' groups';
  }
  if(suboptimal.length > 0)
  {
    messageDiv.innerHTML+= '<br />'+suboptimal.length+' of '+(albumCount-missed.length)+' selected are suboptimal';
  }
  messageDiv.innerHTML+="<br />Total size: "+prettySize(totalSize);
  messageDiv.setAttribute('torrentList', JSON.stringify(torrents));
}

function doGroups(groups, torrents, hilight, albumCount, suboptimal, missed)
{
  var searchPage=false;
  if(window.location.href.indexOf('torrents.php') != -1 && window.location.href.indexOf('?id=') == -1)
    searchPage=true;
  var totalSize=0;
  for(var j=0; j<groups.length; j++)
  {
    var g=groups[j];
    var groupId=g.getElementsByTagName('div')[0].getAttribute('id').split('_')[1];
    var artist=g.getAttribute('artist');
    var album=g.getAttribute('album');
    var editions=document.getElementsByClassName('groupid_'+groupId+' edition');
    var added=0;
    var allEditions=[];
    for(var k=0; k<editions.length; k++)
    {
      var editionMeta=editions[k].textContent.split(' / ');
      var editionMedia = editionMeta[editionMeta.length-1].trim();

      var torrentRows = document.getElementsByClassName('torrent_row groupid_'+groupId+' edition_'+(k+1));
      if(searchPage)
      {
        torrentRows = document.getElementsByClassName('groupid_'+groupId+' edition_'+(k+1));
      }
      for(var l=0; l<torrentRows.length; l++)
      {
        var e=torrentRows[l];
        e.setAttribute('media', editionMedia);
        allEditions.push(e);
      }
    }
    var best=getBest(allEditions);
    var addIndexes=[];
    var score=best.score;
    for(var k=0; k<allEditions.length; k++)
    {
      var e=allEditions[k];
      var addAnyway = e.getAttribute('addAnyway');
      if(addAnyway == "true")
      {
        var score1=getBest([e]).score;
        addIndexes.push({index:k, score:score1});
        if(score1 < score)
          score=score1;
      }
      if(addIndexes.length === 0 && best.index != -1 && allEditions[best.index].getAttribute('addAnyway') != "false")
        addIndexes.push(best);
    }
    var secondCount=0;
    for(var l=0; l<addIndexes.length; l++)
    {
      var e=allEditions[addIndexes[l].index];

      var as=e.getElementsByTagName('a');
      var dl=as[0].href;
      var linkCount=1;
      while(dl.indexOf('authkey') == -1)
      {
        dl=as[linkCount].href;
        linkCount++;
      }
      var meta=as[as.length-1].innerHTML.split(' / ');
      var tdIndex=1;
      if(searchPage)
        tdIndex=3;
      var size=unPretty(e.getElementsByTagName('td')[tdIndex].textContent);
      var editionMedia=e.getAttribute('media');
      totalSize+=size;

      var addAnyway = e.getAttribute('addAnyway');
      if(addAnyway == "false")
        continue;

      var torrentId=as[as.length-1].href.split('&torrentid=')[1];
      var torrentName=artist+' - '+album+' - '+meta[0]+"_"+meta[1]+"_"+editionMedia+" - "+torrentId+".torrent";

      torrents.push({dl:dl, name:torrentName});
      if(hilight)
      {
        if(score === 0)
          e.setAttribute('class', e.getAttribute('class')+' collectorAdded');
        else if(addIndexes.length > 0)
          e.setAttribute('class', e.getAttribute('class')+' collectorAddedSuboptimal');
      }
      secondCount++;
    }
    if(score === 0)
      added=1;
    else if(addIndexes.length > 0)
      added=2;
    if(secondCount === 0)
      added=0;
    albumCount++;
    if(added === 0)
    {
      if(hilight)
        g.setAttribute('class', g.getAttribute('class')+' collectorMissing');
      missed.push({groupid:groupId, artist:artist, album:album});
    }
    else if(added == 2)
    {
      if(hilight)
        g.setAttribute('class', g.getAttribute('class')+' collectorSuboptimal');
      suboptimal.push({groupid:groupId, artist:artist, album:album});
    }
  }

  return {totalSize:totalSize, torrents:torrents, albumCount:albumCount, suboptimal:suboptimal, missed:missed};
}

function unPretty(size)
{
  var s=parseFloat(size);
  if(size.indexOf('KB') != -1)
    s = s*Math.pow(2, 10);
  else if(size.indexOf('MB') != -1)
    s = s*Math.pow(2, 20);
  else if(size.indexOf('GB') != -1)
    s = s*Math.pow(2, 30);
  else if(size.indexOf('TB') != -1)
    s = s*Math.pow(2, 40);
  else if(size.indexOf('PB') != -1)
    s = s*Math.pow(2, 50);

  return Math.round(s);
}

function prettySize(size)
{
  var newSize=size;
  var i=0;
  while(newSize > 1)
  {
    i++;
    newSize = size/Math.pow(2, i*10);
  }
  i--;
  if(i<0)
    i=0;
  newSize=size/Math.pow(2, (i)*10);

  var suffixes=["B", "KB", "MB", "GB", "TB", "PB"];

  newSize = Math.round(newSize*100)/100;
  newSize = newSize+' '+suffixes[i];
  return newSize;
}

function getBest(rows)
{
  var pers=getPersistence();

  var best={index:-1, score:100000};

  for(var i=0; i<rows.length; i++)
  {
    var e=rows[i];
    var editionMedia=e.getAttribute('media');
    if(e.getAttribute('addAnyway') == "false")
      continue;

    var seedCount=parseInt(e.getElementsByTagName('td')[3].textContent);
    if(seedCount < pers.minSeedCount)
      continue;

    var meta=e.getElementsByTagName('a');
    meta=meta[meta.length-1].innerHTML.split(' / ');

    if(meta[0] == pers.format[0] && meta[1] == pers.bitrate[0] && editionMedia == pers.media[0])
    {
      best.index=i;
      best.score=0;
      break;
    }

    if(pers.format.length === 0)
      pers.format.push("Any");
    if(pers.bitrate.length === 0)
      pers.format.push("Any");
    if(pers.media.length === 0)
      pers.format.push("Any");


    for(var j=0; j<pers.format.length; j++)
    {
      var f=pers.format[j];
      if(f==='')
        f='Any';
      if(f=="Any" || meta[0]==f)
      {
        for(var k=0; k<pers.bitrate.length; k++)
        {
          var b=pers.bitrate[k];
          if(b==='')
            b='Any';
          if(b=="Any" || meta[1]==b)
          {
            for(var l=0; l<pers.media.length; l++)
            {
              var m=pers.media[l];
              if(m==='')
                m='Any';
              if(m=="Any" || editionMedia==m)
              {
                var score = (j*100)+(k*10)+l;
                if(score < best.score)
                {
                  best.index=i;
                  best.score=score;
                }
              }
            }
          }
        }
      }
    }
  }
  return best;
}

function addAnywayFunc(tr, event)
{
  if(!event.ctrlKey && !event.metaKey)
    return;

  event.preventDefault();

  var addAnyway = tr.getAttribute("addAnyway");
  var adding = tr.getAttribute('class').indexOf('collectorAdded') != -1;
  if(adding)
  {
    tr.setAttribute("addAnyway", false);
    document.getElementById('collectorPreviewLink').click();
    return;
  }
  else
  {
    tr.setAttribute("addAnyway", true);
    document.getElementById('collectorPreviewLink').click();
    return;
  }
}

function save(messageDiv)
{
  var torrentList=messageDiv.getAttribute('torrentList');
  if(!torrentList)
    messageDiv.innerHTML = "You need to preview first";
  torrentList=JSON.parse(torrentList);
  var opSettings = getSettings();
  if(opSettings.sequential)
  {
    messageDiv.setAttribute('stopSequential', "false");
    sequentialGet(torrentList, 0, messageDiv);
  }
  else
  {
    var zip=new JSZip();

    var settings=getSettings();
    if(settings.summary)
    {
      var name=messageDiv.getAttribute('zipName').replace(/zip$/, "txt");
      var text=messageDiv.getAttribute('summaryText');
      zip.file(name.replace(/\//g, '_'), text);
    }

    getTorrent(torrentList, 0, zip, messageDiv);
  }
}

function sequentialGet(list, index, messageDiv)
{
  if(messageDiv.getAttribute('stopSequential') == "true")
  {
    messageDiv.innerHTML = 'Stopped';
    return;
  }
  if(index >= list.length)
  {
    messageDiv.innerHTML = 'Finished';
    return;
  }

  messageDiv.innerHTML = "Downloading torrent "+(index+1)+" of "+list.length+"<br />";
  var a=document.createElement('a');
  messageDiv.appendChild(a);
  a.innerHTML='Stop';
  a.href='javascript:void(0);';
  a.addEventListener('click', stopSequential.bind(undefined, messageDiv), false);

  var a=document.createElement('a');
  a.href=list[index].dl;
  a.download=list[index].name;
  document.body.appendChild(a);
  a.click();

  window.setTimeout(sequentialGet.bind(undefined, list, index+1, messageDiv), 1000);
}

function stopSequential(messageDiv)
{
  messageDiv.setAttribute('stopSequential', "true");
}

function getTorrent(list, index, zip, messageDiv)
{
  if(index >= list.length)
  {
    gotTorrents(zip, messageDiv);
    return;
  }
  messageDiv.innerHTML = "Getting torrent "+(index+1)+" of "+list.length+"<br />"+list[index].name;
  var xhr = new XMLHttpRequest();
  xhr.open('GET', list[index].dl);
  xhr.responseType = "arraybuffer";
  xhr.onreadystatechange = xhr_func.bind(undefined, messageDiv, xhr, gotTorrent.bind(undefined, list, index, zip, messageDiv), getTorrent.bind(undefined, list, index, zip, messageDiv), true);
  xhr.send();
}

function gotTorrent(list, index, zip, messageDiv, response)
{
  zip.file(list[index].name.replace(/\//g, '_'), response);
  window.setTimeout(getTorrent.bind(undefined, list, index+1, zip, messageDiv), 1000);
}

function gotTorrents(zip, messageDiv)
{
  messageDiv.innerHTML = 'Generating zip file of torrents';
  zip.generateAsync({type:"blob"}).then(saveLink.bind(undefined, messageDiv));
}

function saveLink(messageDiv, blob)
{
  messageDiv.innerHTML = '';
  var a=document.createElement('a');
  messageDiv.appendChild(a);
  var name=messageDiv.getAttribute('zipName');
  a.innerHTML = name;
  a.href = URL.createObjectURL(blob);
  a.download = name;
}

function xhr_func(messageDiv, xhr, func, repeatFunc, binary)
{
  if(xhr.readyState == 4)
  {
    if(xhr.status == 200)
    {
      if(binary)
      {
        var byteArray = new Uint8Array(xhr.response);
        func(byteArray);
      }
      else
      {
        func(xhr.responseText);
      }
    }
    else
    {
      messageDiv.innerHTML = 'Error: '+xhr.status+'<br />retrying in 1 second';
      window.setTimeout(repeatFunc, 1000);
    }
  }
}