mirror of
https://github.com/vikstrous/pirate-get
synced 2025-01-09 09:59:51 +01:00
add some configs and local csv db search
This adds support for the new csv database format, closes #113. This also adds the port and auth configs for transmission.
This commit is contained in:
parent
39ad5e7f63
commit
42aa9d8555
24
README.md
24
README.md
@ -18,7 +18,7 @@ Watch [this](http://showterm.io/d6f7a0c2a5de1da9ea317) for an example usage.
|
|||||||
|
|
||||||
|
|
||||||
## Configuration file
|
## Configuration file
|
||||||
You can use a file to override pirate-get's default settings.
|
You can use a file to override pirate-get's default settings.
|
||||||
Default is `$XDG_CONFIG_HOME/pirate-get`.
|
Default is `$XDG_CONFIG_HOME/pirate-get`.
|
||||||
If it does not exist then `$HOME/.config/pirate-get`.
|
If it does not exist then `$HOME/.config/pirate-get`.
|
||||||
|
|
||||||
@ -34,23 +34,27 @@ directory = $PWD
|
|||||||
magnets = false
|
magnets = false
|
||||||
|
|
||||||
; save each selected torrent in a .torrent file
|
; save each selected torrent in a .torrent file
|
||||||
torrents = false
|
torrents = false
|
||||||
|
|
||||||
[LocalDB]
|
[LocalDB]
|
||||||
; use a local copy of the pirate bay database
|
; use a local copy of the csv formatted pirate bay database
|
||||||
enabled = false
|
enabled = false
|
||||||
|
|
||||||
; path of the database
|
; path of the database
|
||||||
path = ~/downloads/pirate-get/db
|
path = ~/downloads/pirate-get/db
|
||||||
|
|
||||||
[Misc]
|
[Misc]
|
||||||
; specify a custom command for opening the magnet
|
; specify a custom command for opening the magnet
|
||||||
; ex. myprogram --open %s
|
; ex. myprogram --open %s
|
||||||
; %s represent the magnet uri
|
; %s represent the magnet uri
|
||||||
openCommand =
|
openCommand =
|
||||||
|
|
||||||
; open magnets with transmission-remote client
|
; open magnets with transmission-remote client
|
||||||
transmission = false
|
transmission = false
|
||||||
|
; set to username:password if needed
|
||||||
|
transmission-auth =
|
||||||
|
; set to the port number if needed
|
||||||
|
transmission-port =
|
||||||
|
|
||||||
; use colored output
|
; use colored output
|
||||||
colors = true
|
colors = true
|
||||||
@ -60,15 +64,15 @@ colors = true
|
|||||||
mirror = http://thepiratebay.org
|
mirror = http://thepiratebay.org
|
||||||
```
|
```
|
||||||
|
|
||||||
Note:
|
Note:
|
||||||
Any command line option will override its respective setting in the config file.
|
Any command line option will override its respective setting in the config file.
|
||||||
|
|
||||||
|
|
||||||
## Local Database
|
## Local Database
|
||||||
If you want to use a local copy of the Pirate Bay database download a copy here (or wherever the latest version is currently):
|
If you want to use a local copy of the Pirate Bay database download a copy here (or wherever the latest version is currently):
|
||||||
|
|
||||||
http://thepiratebay.se/torrent/8156416
|
https://thepiratebay.org/static/dump/csv/
|
||||||
|
|
||||||
## License
|
## License
|
||||||
pirate-get is licensed under the GNU Affero General Public License version 3 or later.
|
pirate-get is licensed under the GNU Affero General Public License version 3 or later.
|
||||||
See the accompanying file LICENSE or http://www.gnu.org/licenses/agpl.html.
|
See the accompanying file LICENSE or http://www.gnu.org/licenses/agpl.html.
|
||||||
|
@ -5,7 +5,7 @@ import pkgutil
|
|||||||
def get_resource(filename):
|
def get_resource(filename):
|
||||||
return pkgutil.get_data(__package__, 'data/' + filename)
|
return pkgutil.get_data(__package__, 'data/' + filename)
|
||||||
|
|
||||||
version = '0.2.13'
|
version = '0.3.0'
|
||||||
|
|
||||||
categories = json.loads(get_resource('categories.json').decode())
|
categories = json.loads(get_resource('categories.json').decode())
|
||||||
sorts = json.loads(get_resource('sorts.json').decode())
|
sorts = json.loads(get_resource('sorts.json').decode())
|
||||||
@ -15,4 +15,4 @@ default_headers = {'User-Agent': 'pirate get'}
|
|||||||
default_timeout = 10
|
default_timeout = 10
|
||||||
|
|
||||||
default_mirror = 'https://thepiratebay.org/'
|
default_mirror = 'https://thepiratebay.org/'
|
||||||
mirror_list = 'https://proxybay.co/list.txt'
|
mirror_list = 'https://proxybay.bz/list.txt'
|
||||||
|
@ -1,40 +1,45 @@
|
|||||||
import urllib.parse as parse
|
import urllib.parse as parse
|
||||||
import html.parser as parser
|
import base64
|
||||||
|
import csv
|
||||||
|
|
||||||
|
# this is used to remove null bytes from the input stream because
|
||||||
|
# apparently they exist
|
||||||
|
def replace_iter(iterable):
|
||||||
|
for value in iterable:
|
||||||
|
yield value.replace("\0", "")
|
||||||
|
|
||||||
class BayParser(parser.HTMLParser):
|
# https://stackoverflow.com/questions/1094841/reusable-library-to-get-human-readable-version-of-file-size#1094933
|
||||||
title = ''
|
def sizeof_fmt(num, suffix='B'):
|
||||||
q = ''
|
for unit in ['','Ki','Mi','Gi','Ti','Pi','Ei','Zi']:
|
||||||
state = 'looking'
|
if abs(num) < 1024.0:
|
||||||
results = []
|
return "%3.1f %s%s" % (num, unit, suffix)
|
||||||
|
num /= 1024.0
|
||||||
def handle_starttag(self, tag, attrs):
|
return "%.1f %s%s" % (num, 'Yi', suffix)
|
||||||
if tag == 'title':
|
|
||||||
self.state = 'title'
|
|
||||||
if tag == 'magnet' and self.state == 'matched':
|
|
||||||
self.state = 'magnet'
|
|
||||||
|
|
||||||
def handle_data(self, data):
|
|
||||||
if self.state == 'title':
|
|
||||||
if data.lower().find(self.q) != -1:
|
|
||||||
self.title = data
|
|
||||||
self.state = 'matched'
|
|
||||||
else:
|
|
||||||
self.state = 'looking'
|
|
||||||
if self.state == 'magnet':
|
|
||||||
self.results.append([
|
|
||||||
'magnet:?xt=urn:btih:' +
|
|
||||||
parse.quote(data) +
|
|
||||||
'&dn=' +
|
|
||||||
parse.quote(self.title), '?', '?'])
|
|
||||||
self.state = 'looking'
|
|
||||||
|
|
||||||
|
|
||||||
def search(db, terms):
|
def search(db, terms):
|
||||||
f = open(db)
|
with open(db, 'r') as f:
|
||||||
xml = f.readlines()
|
results = []
|
||||||
f.close()
|
reader = csv.reader(replace_iter(f), delimiter=';')
|
||||||
parser = BayParser()
|
for row in reader:
|
||||||
parser.q = (' '.join(terms)).lower()
|
# skip comments
|
||||||
parser.feed(''.join(xml))
|
if row[0][0] == '#':
|
||||||
return parser.results
|
continue
|
||||||
|
# 0 is date in rfc 3339 format
|
||||||
|
# 1 magnet link hash
|
||||||
|
# 2 is title
|
||||||
|
# 3 is size in bytes
|
||||||
|
if ' '.join(terms).lower() in row[2].lower():
|
||||||
|
result = {
|
||||||
|
'date': row[0],
|
||||||
|
'size': sizeof_fmt(int(row[3])),
|
||||||
|
'magnet':
|
||||||
|
'magnet:?xt=urn:btih:' +
|
||||||
|
base64.b16encode(base64.b64decode(row[1])).decode('utf-8') +
|
||||||
|
'&dn=' +
|
||||||
|
parse.quote(row[2]),
|
||||||
|
}
|
||||||
|
results.append(result)
|
||||||
|
# limit page size to not print walls of results
|
||||||
|
# TODO: consider pagination
|
||||||
|
results = results[:30]
|
||||||
|
return results
|
||||||
|
@ -38,6 +38,8 @@ def parse_config_file(text):
|
|||||||
# for interpolating in the command
|
# for interpolating in the command
|
||||||
config.set('Misc', 'openCommand', '')
|
config.set('Misc', 'openCommand', '')
|
||||||
config.set('Misc', 'transmission', 'false')
|
config.set('Misc', 'transmission', 'false')
|
||||||
|
config.set('Misc', 'transmission-auth', '')
|
||||||
|
config.set('Misc', 'transmission-port', '')
|
||||||
config.set('Misc', 'colors', 'true')
|
config.set('Misc', 'colors', 'true')
|
||||||
config.set('Misc', 'mirror', pirate.data.default_mirror)
|
config.set('Misc', 'mirror', pirate.data.default_mirror)
|
||||||
|
|
||||||
@ -139,7 +141,8 @@ def parse_args(args_in):
|
|||||||
action='store_true',
|
action='store_true',
|
||||||
help='list Sortable Types')
|
help='list Sortable Types')
|
||||||
parser.add_argument('-L', '--local', dest='database',
|
parser.add_argument('-L', '--local', dest='database',
|
||||||
help='an xml file containing the Pirate Bay database')
|
help='a csv file containing the Pirate Bay database '
|
||||||
|
'downloaded from https://thepiratebay.org/static/dump/csv/')
|
||||||
parser.add_argument('-p', dest='pages', default=1, type=int,
|
parser.add_argument('-p', dest='pages', default=1, type=int,
|
||||||
help='the number of pages to fetch '
|
help='the number of pages to fetch '
|
||||||
"(doesn't work with --local)")
|
"(doesn't work with --local)")
|
||||||
@ -219,9 +222,14 @@ def combine_configs(config, args):
|
|||||||
args.transmission_command = ['transmission-remote']
|
args.transmission_command = ['transmission-remote']
|
||||||
if args.port:
|
if args.port:
|
||||||
args.transmission_command.append(args.port)
|
args.transmission_command.append(args.port)
|
||||||
|
elif config.get('Misc', 'transmission-port'):
|
||||||
|
args.transmission_command.append(config.get('Misc', 'transmission-port'))
|
||||||
if args.auth:
|
if args.auth:
|
||||||
args.transmission_command.append('--auth')
|
args.transmission_command.append('--auth')
|
||||||
args.transmission_command.append(args.auth)
|
args.transmission_command.append(args.auth)
|
||||||
|
elif config.get('Misc', 'transmission-auth'):
|
||||||
|
args.transmission_command.append('--auth')
|
||||||
|
args.transmission_command.append(config.get('Misc', 'transmission-auth'))
|
||||||
|
|
||||||
args.output = 'browser_open'
|
args.output = 'browser_open'
|
||||||
if args.transmission or config.getboolean('Misc', 'transmission'):
|
if args.transmission or config.getboolean('Misc', 'transmission'):
|
||||||
|
@ -43,10 +43,13 @@ class Printer:
|
|||||||
even = True
|
even = True
|
||||||
|
|
||||||
if local:
|
if local:
|
||||||
table = veryprettytable.VeryPrettyTable(['LINK', 'NAME'])
|
table = veryprettytable.VeryPrettyTable(['LINK', 'DATE', 'SIZE', 'NAME'])
|
||||||
|
|
||||||
|
table.align['SIZE'] = 'r'
|
||||||
|
table.align['NAME'] = 'l'
|
||||||
else:
|
else:
|
||||||
table = veryprettytable.VeryPrettyTable(['LINK', 'SEED', 'LEECH',
|
table = veryprettytable.VeryPrettyTable(['LINK', 'SEED', 'LEECH',
|
||||||
'RATIO', 'SIZE', '',
|
'RATIO', 'SIZE',
|
||||||
'UPLOAD', 'NAME'])
|
'UPLOAD', 'NAME'])
|
||||||
table.align['NAME'] = 'l'
|
table.align['NAME'] = 'l'
|
||||||
table.align['SEED'] = 'r'
|
table.align['SEED'] = 'r'
|
||||||
@ -65,7 +68,7 @@ class Printer:
|
|||||||
torrent_name = parse.unquote_plus(name.group(1))
|
torrent_name = parse.unquote_plus(name.group(1))
|
||||||
|
|
||||||
if local:
|
if local:
|
||||||
content = [n, torrent_name[:columns - 7]]
|
content = [n, result['date'], result['size'], torrent_name[:columns - 42]]
|
||||||
else:
|
else:
|
||||||
no_seeders = int(result['seeds'])
|
no_seeders = int(result['seeds'])
|
||||||
no_leechers = int(result['leechers'])
|
no_leechers = int(result['leechers'])
|
||||||
@ -85,8 +88,8 @@ class Printer:
|
|||||||
|
|
||||||
content = [n, no_seeders, no_leechers,
|
content = [n, no_seeders, no_leechers,
|
||||||
'{:.1f}'.format(ratio),
|
'{:.1f}'.format(ratio),
|
||||||
'{:.1f}'.format(size),
|
'{:.1f} '.format(size) + unit,
|
||||||
unit, date, torrent_name[:columns - 53]]
|
date, torrent_name[:columns - 50]]
|
||||||
|
|
||||||
if even or not self.enable_color:
|
if even or not self.enable_color:
|
||||||
table.add_row(content)
|
table.add_row(content)
|
||||||
|
BIN
tests/data/db.csv
Normal file
BIN
tests/data/db.csv
Normal file
Binary file not shown.
Can't render this file because it contains an unexpected character in line 5 and column 77.
|
@ -2,17 +2,36 @@
|
|||||||
import unittest
|
import unittest
|
||||||
import pirate.local
|
import pirate.local
|
||||||
import os
|
import os
|
||||||
|
import base64
|
||||||
|
|
||||||
from tests import util
|
from tests import util
|
||||||
|
|
||||||
|
|
||||||
class TestLocal(unittest.TestCase):
|
class TestLocal(unittest.TestCase):
|
||||||
|
|
||||||
def test_rich_xml(self):
|
def test_local_csv_db(self):
|
||||||
path = util.data_path('rich.xml')
|
path = util.data_path('db.csv')
|
||||||
expected = [['magnet:?xt=urn:btih:b03c8641415d3a0fc7077f5bf567634442989a74&dn=High.Chaparall.S02E02.PDTV.XViD.SWEDiSH-HuBBaTiX', '?', '?']]
|
expected = [
|
||||||
actual = pirate.local.search(path, ('High',))
|
{
|
||||||
self.assertEqual(actual, expected)
|
'date':'2018-May-14 11:05:31',
|
||||||
|
'magnet': 'magnet:?xt=urn:btih:34930674EF3BB9317FB5F263CCA830F52685235B&dn=ubuntu-14.04.5-desktop-amd64.iso',
|
||||||
|
'size': '1.0 GiB',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'date': '2018-Apr-15 00:04:09',
|
||||||
|
'magnet': 'magnet:?xt=urn:btih:F07E0B0584745B7BCB35E98097488D34E68623D0&dn=Ubuntu%2017.10.1%20Desktop%20%28amd64%29',
|
||||||
|
'size': '1.4 GiB',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'date': '2017-Aug-01 15:08:07',
|
||||||
|
'magnet': 'magnet:?xt=urn:btih:4096EC129404689CEB8056D907E384FF872C2CE9&dn=LINUX%20UBUNTU%2016.10%2032X64',
|
||||||
|
'size': '1.5 GiB',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
actual = pirate.local.search(path, ('ubuntu',))
|
||||||
|
self.assertEqual(len(actual), len(expected))
|
||||||
|
for i in range(len(expected)):
|
||||||
|
self.assertDictEqual(actual[i], expected[i])
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
@ -23,8 +23,8 @@ class TestPrint(unittest.TestCase):
|
|||||||
'uploaded': 'never'
|
'uploaded': 'never'
|
||||||
}]
|
}]
|
||||||
printer.search_results(results)
|
printer.search_results(results)
|
||||||
prettytable.assert_called_once_with(['LINK', 'SEED', 'LEECH', 'RATIO', 'SIZE', '', 'UPLOAD', 'NAME'])
|
prettytable.assert_called_once_with(['LINK', 'SEED', 'LEECH', 'RATIO', 'SIZE', 'UPLOAD', 'NAME'])
|
||||||
mock.add_row.assert_has_calls([call([0, 1, 2, '0.5', '3.0', 'MiB', 'never', 'name'])])
|
mock.add_row.assert_has_calls([call([0, 1, 2, '0.5', '3.0 MiB', 'never', 'name'])])
|
||||||
|
|
||||||
def test_print_results_local(self):
|
def test_print_results_local(self):
|
||||||
class MockTable:
|
class MockTable:
|
||||||
@ -36,14 +36,16 @@ class TestPrint(unittest.TestCase):
|
|||||||
with patch('veryprettytable.VeryPrettyTable', return_value=mock) as prettytable:
|
with patch('veryprettytable.VeryPrettyTable', return_value=mock) as prettytable:
|
||||||
results = [{
|
results = [{
|
||||||
'magnet': 'dn=name',
|
'magnet': 'dn=name',
|
||||||
'Name': 'name',
|
'date': '1',
|
||||||
|
'size': '1',
|
||||||
},{
|
},{
|
||||||
'magnet': 'dn=name2',
|
'magnet': 'dn=name2',
|
||||||
'Name': 'name2',
|
'date': '2',
|
||||||
|
'size': '2',
|
||||||
}]
|
}]
|
||||||
printer.search_results(results, local=True)
|
printer.search_results(results, local=True)
|
||||||
prettytable.assert_called_once_with(['LINK', 'NAME'])
|
prettytable.assert_called_once_with(['LINK', 'DATE', 'SIZE', 'NAME'])
|
||||||
mock.add_row.assert_has_calls([call([0, 'name']), call([1, 'name2'])])
|
mock.add_row.assert_has_calls([call([0, '1', '1', 'name']), call([1, '2', '2', 'name2'])])
|
||||||
|
|
||||||
def test_print_color(self):
|
def test_print_color(self):
|
||||||
printer = Printer(False)
|
printer = Printer(False)
|
||||||
@ -55,7 +57,7 @@ class TestPrint(unittest.TestCase):
|
|||||||
printer.print('abc', color='zebra_1')
|
printer.print('abc', color='zebra_1')
|
||||||
mock_print.assert_called_once_with('\x1b[34mabc', '\x1b[0m')
|
mock_print.assert_called_once_with('\x1b[34mabc', '\x1b[0m')
|
||||||
|
|
||||||
def test_print_results_local(self):
|
def test_print_results_local2(self):
|
||||||
class MockTable:
|
class MockTable:
|
||||||
add_row = MagicMock()
|
add_row = MagicMock()
|
||||||
align = {}
|
align = {}
|
||||||
@ -65,14 +67,16 @@ class TestPrint(unittest.TestCase):
|
|||||||
with patch('veryprettytable.VeryPrettyTable', return_value=mock) as prettytable:
|
with patch('veryprettytable.VeryPrettyTable', return_value=mock) as prettytable:
|
||||||
results = [{
|
results = [{
|
||||||
'magnet': 'dn=name',
|
'magnet': 'dn=name',
|
||||||
'Name': 'name',
|
'date': '1',
|
||||||
|
'size': '1',
|
||||||
},{
|
},{
|
||||||
'magnet': 'dn=name2',
|
'magnet': 'dn=name2',
|
||||||
'Name': 'name2',
|
'date': '2',
|
||||||
|
'size': '2',
|
||||||
}]
|
}]
|
||||||
printer.search_results(results, local=True)
|
printer.search_results(results, local=True)
|
||||||
prettytable.assert_called_once_with(['LINK', 'NAME'])
|
prettytable.assert_called_once_with(['LINK', 'DATE', 'SIZE', 'NAME'])
|
||||||
mock.add_row.assert_has_calls([call([0, 'name']), call([1, 'name2'], fore_color='blue')])
|
mock.add_row.assert_has_calls([call([0, '1', '1', 'name']), call([1, '2', '2', 'name2'], fore_color='blue')])
|
||||||
|
|
||||||
def test_print_descriptions(self):
|
def test_print_descriptions(self):
|
||||||
printer = Printer(False)
|
printer = Printer(False)
|
||||||
|
Loading…
Reference in New Issue
Block a user