Greasy Fork is available in English.

PTH Collector

Download multiple torrents by some criteria on PTH

// ==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);
    }
  }
}