Switching to pygazelle

This commit is contained in:
Aaron Cohen
2012-09-22 21:08:14 -07:00
parent 84aec7778d
commit 3d066d7e5b
14 changed files with 945 additions and 1495 deletions

View File

283
lib/pygazelle/api.py Normal file
View File

@@ -0,0 +1,283 @@
#!/usr/bin/env python
#
# PyGazelle - https://github.com/cohena/pygazelle
# A Python implementation of the What.cd Gazelle JSON API
#
# Loosely based on the API implementation from 'whatbetter', by Zachary Denton
# See https://github.com/zacharydenton/whatbetter
import json
import time
import requests
from user import User
from artist import Artist
from tag import Tag
from request import Request
from torrent_group import TorrentGroup
from torrent import Torrent
from category import Category
class LoginException(Exception):
pass
class RequestException(Exception):
pass
class GazelleAPI(object):
last_request = time.time() # share amongst all api objects
default_headers = {
'Connection': 'keep-alive',
'Cache-Control': 'max-age=0',
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_3)'\
'AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.79'\
'Safari/535.11',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9'\
',*/*;q=0.8',
'Accept-Encoding': 'gzip,deflate,sdch',
'Accept-Language': 'en-US,en;q=0.8',
'Accept-Charset': 'ISO-8859-1,utf-8;q=0.7,*;q=0.3'}
def __init__(self, username=None, password=None):
self.session = requests.session(headers=self.default_headers)
self.username = username
self.password = password
self.authkey = None
self.passkey = None
self.userid = None
self.logged_in_user = None
self.cached_users = {}
self.cached_artists = {}
self.cached_tags = {}
self.cached_torrent_groups = {}
self.cached_torrents = {}
self.cached_requests = {}
self.cached_categories = {}
self.site = "https://what.cd/"
self.rate_limit = 2.0 # seconds between requests
self._login()
def _login(self):
"""
Private method.
Logs in user and gets authkey from server.
"""
loginpage = 'https://what.cd/login.php'
data = {'username': self.username,
'password': self.password}
r = self.session.post(loginpage, data=data)
if r.status_code != 200:
raise LoginException
accountinfo = self.request('index')
self.userid = accountinfo['id']
self.authkey = accountinfo['authkey']
self.passkey = accountinfo['passkey']
self.logged_in_user = User(self.userid, self)
self.logged_in_user.set_index_data(accountinfo)
def request(self, action, **kwargs):
"""
Makes an AJAX request at a given action.
Pass an action and relevant arguments for that action.
"""
ajaxpage = 'ajax.php'
content = self.unparsed_request(ajaxpage, action, **kwargs)
try:
parsed = json.loads(content)
if parsed['status'] != 'success':
raise RequestException
return parsed['response']
except ValueError:
raise RequestException
def unparsed_request(self, page, action, **kwargs):
"""
Makes a generic HTTP request at a given page with a given action.
Also pass relevant arguments for that action.
"""
while time.time() - self.last_request < self.rate_limit:
time.sleep(0.1)
url = "%s/%s" % (self.site, page)
params = {'action': action}
if self.authkey:
params['auth'] = self.authkey
params.update(kwargs)
r = self.session.get(url, params=params, allow_redirects=False)
self.last_request = time.time()
return r.content
def get_user(self, id):
"""
Returns a User for the passed ID, associated with this API object. If the ID references the currently logged in
user, the user returned will be pre-populated with the information from an 'index' API call. Otherwise, you'll
need to call User.update_user_data(). This is done on demand to reduce unnecessary API calls.
"""
id = int(id)
if id == self.userid:
return self.logged_in_user
elif id in self.cached_users.keys():
return self.cached_users[id]
else:
return User(id, self)
def search_users(self, search_query):
"""
Returns a list of users returned for the search query. You can search by name, part of name, and ID number. If
one of the returned users is the currently logged-in user, that user object will be pre-populated with the
information from an 'index' API call. Otherwise only the limited info returned by the search will be pre-pop'd.
You can query more information with User.update_user_data(). This is done on demand to reduce unnecessary API calls.
"""
response = self.request(action='usersearch', search=search_query)
results = response['results']
found_users = []
for result in results:
user = self.get_user(result['userId'])
user.set_search_result_data(result)
found_users.append(user)
return found_users
def get_artist(self, id, name=None):
"""
Returns an Artist for the passed ID, associated with this API object. You'll need to call Artist.update_data()
if the artist hasn't already been cached. This is done on demand to reduce unnecessary API calls.
"""
id = int(id)
if id in self.cached_artists.keys():
artist = self.cached_artists[id]
else:
artist = Artist(id, self)
if name:
artist.name = name
return artist
def get_tag(self, name):
"""
Returns a Tag for the passed name, associated with this API object. If you know the count value for this tag,
pass it to update the object. There is no way to query the count directly from the API, but it can be retrieved
from other calls such as 'artist', however.
"""
if name in self.cached_tags.keys():
return self.cached_tags[name]
else:
return Tag(name, self)
def get_request(self, id):
"""
Returns a Request for the passed ID, associated with this API object. You'll need to call Request.update_data()
if the request hasn't already been cached. This is done on demand to reduce unnecessary API calls.
"""
id = int(id)
if id in self.cached_requests.keys():
return self.cached_requests[id]
else:
return Request(id, self)
def get_torrent_group(self, id):
"""
Returns a TorrentGroup for the passed ID, associated with this API object.
"""
id = int(id)
if id in self.cached_torrent_groups.keys():
return self.cached_torrent_groups[id]
else:
return TorrentGroup(id, self)
def get_torrent(self, id):
"""
Returns a TorrentGroup for the passed ID, associated with this API object.
"""
id = int(id)
if id in self.cached_torrents.keys():
return self.cached_torrents[id]
else:
return Torrent(id, self)
def get_category(self, id, name=None):
"""
Returns a Category for the passed ID, associated with this API object.
"""
id = int(id)
if id in self.cached_categories.keys():
cat = self.cached_categories[id]
else:
cat = Category(id, self)
if name:
cat.name = name
return cat
def search_torrents(self, **kwargs):
"""
Searches based on the args you pass and returns torrent groups filled with torrents.
Pass strings unless otherwise specified.
Valid search args:
searchstr (any arbitrary string to search for)
page (page to display -- default: 1)
artistname (self explanatory)
groupname (torrent group name, equivalent to album)
recordlabel (self explanatory)
cataloguenumber (self explanatory)
year (self explanatory)
remastertitle (self explanatory)
remasteryear (self explanatory)
remasterrecordlabel (self explanatory)
remastercataloguenumber (self explanatory)
filelist (can search for filenames found in torrent...unsure of formatting for multiple files)
encoding (use constants in pygazelle.Encoding module)
format (use constants in pygazelle.Format module)
media (use constants in pygazelle.Media module)
releasetype (use constants in pygazelle.ReleaseType module)
haslog (int 1 or 0 to represent boolean, 100 for 100% only, -1 for < 100% / unscored)
hascue (int 1 or 0 to represent boolean)
scene (int 1 or 0 to represent boolean)
vanityhouse (int 1 or 0 to represent boolean)
freetorrent (int 1 or 0 to represent boolean)
taglist (comma separated tag names)
tags_type (0 for 'any' matching, 1 for 'all' matching)
order_by (use constants in pygazelle.order module that start with by_ in their name)
order_way (use way_ascending or way_descending constants in pygazelle.order)
filter_cat (for each category you want to search, the param name must be filter_cat[catnum] and the value 1)
ex. filter_cat[1]=1 turns on Music.
filter_cat[1]=1, filter_cat[2]=1 turns on music and applications. (two separate params and vals!)
Category object ids return the correct int value for these. (verify?)
Returns a dict containing keys 'curr_page', 'pages', and 'results'. Results contains a matching list of Torrents
(they have a reference to their parent TorrentGroup).
"""
response = self.request(action='browse', **kwargs)
results = response['results']
if len(results):
curr_page = response['currentPage']
pages = response['pages']
else:
curr_page = 1
pages = 1
matching_torrents = []
for torrent_group_dict in results:
torrent_group = self.get_torrent_group(torrent_group_dict['groupId'])
torrent_group.set_torrent_search_data(torrent_group_dict)
for torrent_dict in torrent_group_dict['torrents']:
torrent_dict['groupId'] = torrent_group.id
torrent = self.get_torrent(torrent_dict['torrentId'])
torrent.set_torrent_search_data(torrent_dict)
matching_torrents.append(torrent)
return {'curr_page': curr_page, 'pages': pages, 'results': matching_torrents}
def generate_torrent_link(self, id):
url = "%storrents.php?action=download&id=%s&authkey=%s&torrent_pass=%s" %\
(self.site, id, self.logged_in_user.authkey, self.logged_in_user.passkey)
return url
def save_torrent_file(self, id, dest):
file_data = self.unparsed_request("torrents.php", 'download',
id=id, authkey=self.logged_in_user.authkey, torrent_pass=self.logged_in_user.passkey)
with open(dest, 'w+') as dest_file:
dest_file.write(file_data)

72
lib/pygazelle/artist.py Normal file
View File

@@ -0,0 +1,72 @@
class InvalidArtistException(Exception):
pass
class Artist(object):
"""
This class represents an Artist. It is created knowing only its ID. To reduce API accesses, load information using
Artist.update_data() only as needed.
"""
def __init__(self, id, parent_api):
self.id = id
self.parent_api = parent_api
self.name = None
self.notifications_enabled = None
self.has_bookmarked = None
self.image = None
self.body = None
self.vanity_house = None
self.tags = []
self.similar_artists_and_score = {}
self.statistics = None
self.torrent_groups = []
self.requests = []
self.parent_api.cached_artists[self.id] = self # add self to cache of known Artist objects
def update_data(self):
response = self.parent_api.request(action='artist', id=self.id)
self.set_data(response)
def set_data(self, artist_json_response):
if self.id != artist_json_response['id']:
raise InvalidArtistException("Tried to update an artists's information from an 'artist' API call with a different id." +
" Should be %s, got %s" % (self.id, artist_json_response['id']) )
self.name = artist_json_response['name']
self.notifications_enabled = artist_json_response['notificationsEnabled']
self.has_bookmarked = artist_json_response['hasBookmarked']
self.image = artist_json_response['image']
self.body = artist_json_response['body']
self.vanity_house = artist_json_response['vanityHouse']
self.tags = []
for tag_dict in artist_json_response['tags']:
tag = self.parent_api.get_tag(tag_dict['name'])
tag.set_artist_count(self, tag_dict['count'])
self.tags.append(tag)
self.similar_artists_and_score = {}
for similar_artist_dict in artist_json_response['similarArtists']:
similar_artist = self.parent_api.get_artist(similar_artist_dict['artistId'])
similar_artist.name = similar_artist_dict['name']
self.similar_artists_and_score[similar_artist] = similar_artist_dict['score']
self.statistics = artist_json_response['statistics']
self.torrent_groups = []
for torrent_group_item in artist_json_response['torrentgroup']:
torrent_group = self.parent_api.get_torrent_group(torrent_group_item['groupId'])
torrent_group.set_artist_group_data(torrent_group_item)
self.torrent_groups.append(torrent_group)
self.requests = []
for request_json_item in artist_json_response['requests']:
request = self.parent_api.get_request(request_json_item['requestId'])
request.set_data(request_json_item)
self.requests.append(request)
def __repr__(self):
return "Artist: %s - ID: %s" % (self.name, self.id)

13
lib/pygazelle/category.py Normal file
View File

@@ -0,0 +1,13 @@
class InvalidCategoryException(Exception):
pass
class Category(object):
def __init__(self, id, parent_api):
self.id = id
self.parent_api = parent_api
self.name = None
self.parent_api.cached_categories[self.id] = self # add self to cache of known Category objects
def __repr__(self):
return "Category: %s - id: %s" % (self.name, self.id)

13
lib/pygazelle/encoding.py Normal file
View File

@@ -0,0 +1,13 @@
C192 = "192"
APS = "APS (VBR)"
V2 = "V2 (VBR)"
V1 = "V1 (VBR)"
C256 = "256"
APX = "APX (VBR)"
V0 = "V0 (VBR)"
C320 = "320"
LOSSLESS = "Lossless"
LOSSLESS_24 = "24bit Lossless"
V8 = "V8 (VBR)"
ALL_ENCODINGS = [C192, APS, V2, V1, C256, APX, V0, C320, LOSSLESS, LOSSLESS_24, V8]

8
lib/pygazelle/format.py Normal file
View File

@@ -0,0 +1,8 @@
MP3 = "MP3"
FLAC = "FLAC"
AAC = "AAC"
AC3 = "AC3"
DTS = "DTS"
OGG_VORBIS = "Ogg Vorbis"
ALL_FORMATS = [MP3, FLAC, AAC, AC3, DTS, OGG_VORBIS]

11
lib/pygazelle/media.py Normal file
View File

@@ -0,0 +1,11 @@
CD = "CD"
DVD = "DVD"
VINYL = "Vinyl"
SOUNDBOARD = "Soundboard"
SACD = "SACD"
DAT = "DAT"
CASETTE = "Casette"
WEB = "WEB"
BLU_RAY = "Blu-ray"
ALL_MEDIAS = [CD, DVD, VINYL, SOUNDBOARD, SACD, DAT, CASETTE, WEB, BLU_RAY]

View File

@@ -0,0 +1,19 @@
ALBUM = "Album"
SOUNDTRACK = "Soundtrack"
EP = "EP"
ANTHOLOGY = "Anthology"
COMPILATION = "Compilation"
DJ_MIX = "DJ Mix"
SINGLE = "Single"
LIVE_ALBUM = "Live album"
REMIX = "Remix"
BOOTLEG = "Bootleg"
INTERVIEW = "Interview"
MIXTAPE = "Mixtape"
UNKNOWN = "Unknown"
ALL_RELEASE_TYPES = [ALBUM, SOUNDTRACK, EP, ANTHOLOGY, COMPILATION, DJ_MIX, SINGLE, LIVE_ALBUM, REMIX, BOOTLEG,
INTERVIEW, MIXTAPE, UNKNOWN]
def get_int_val(release_type):
return ALL_RELEASE_TYPES.index(release_type) + 1

29
lib/pygazelle/request.py Normal file
View File

@@ -0,0 +1,29 @@
class InvalidRequestException(Exception):
pass
class Request(object):
def __init__(self, id, parent_api):
self.id = id
self.parent_api = parent_api
self.category = None
self.title = None
self.year = None
self.time_added = None
self.votes = None
self.bounty = None
self.parent_api.cached_requests[self.id] = self # add self to cache of known Request objects
def set_data(self, request_item_json_data):
if self.id != request_item_json_data['requestId']:
raise InvalidRequestException("Tried to update a Request's information from a request JSON item with a different id." +
" Should be %s, got %s" % (self.id, request_item_json_data['requestId']) )
self.category = self.parent_api.get_category(request_item_json_data['categoryId'])
self.title = request_item_json_data['title']
self.year = request_item_json_data['year']
self.time_added = request_item_json_data['timeAdded']
self.votes = request_item_json_data['votes']
self.bounty = request_item_json_data['bounty']
def __repr__(self):
return "Request: %s - ID: %s" % (self.title, self.id)

17
lib/pygazelle/tag.py Normal file
View File

@@ -0,0 +1,17 @@
class Tag(object):
def __init__(self, name, parent_api):
self.name = name
self.artist_counts = {}
self.parent_api = parent_api
self.parent_api.cached_tags[self.name] = self # add self to cache of known Tag objects
def set_artist_count(self, artist, count):
"""
Adds an artist to the known list of artists tagged with this tag (if necessary), and sets the count of times
that that artist has been known to be tagged with this tag.
"""
self.artist_counts[artist] = count
def __repr__(self):
return "Tag: %s" % self.name

128
lib/pygazelle/torrent.py Normal file
View File

@@ -0,0 +1,128 @@
import re
class InvalidTorrentException(Exception):
pass
class Torrent(object):
def __init__(self, id, parent_api):
self.id = id
self.parent_api = parent_api
self.group = None
self.media = None
self.format = None
self.encoding = None
self.remaster_year = None
self.remastered = None
self.remaster_title = None
self.remaster_record_label = None
self.remaster_catalogue_number = None
self.scene = None
self.has_log = None
self.has_cue = None
self.log_score = None
self.file_count = None
self.free_torrent = None
self.size = None
self.leechers = None
self.seeders = None
self.snatched = None
self.time = None
self.has_file = None
self.description = None
self.file_list = []
self.file_path = None
self.user = None
self.parent_api.cached_torrents[self.id] = self
def set_torrent_artist_data(self, artist_torrent_json_response):
if self.id != artist_torrent_json_response['id']:
raise InvalidTorrentException("Tried to update a Torrent's information from an 'artist' API call with a different id." +
" Should be %s, got %s" % (self.id, artist_torrent_json_response['id']) )
self.group = self.parent_api.get_torrent_group(artist_torrent_json_response['groupId'])
self.media = artist_torrent_json_response['media']
self.format = artist_torrent_json_response['format']
self.encoding = artist_torrent_json_response['encoding']
self.remaster_year = artist_torrent_json_response['remasterYear']
self.remastered = artist_torrent_json_response['remastered']
self.remaster_title = artist_torrent_json_response['remasterTitle']
self.remaster_record_label = artist_torrent_json_response['remasterRecordLabel']
self.scene = artist_torrent_json_response['scene']
self.has_log = artist_torrent_json_response['hasLog']
self.has_cue = artist_torrent_json_response['hasCue']
self.log_score = artist_torrent_json_response['logScore']
self.file_count = artist_torrent_json_response['fileCount']
self.free_torrent = artist_torrent_json_response['freeTorrent']
self.size = artist_torrent_json_response['size']
self.leechers = artist_torrent_json_response['leechers']
self.seeders = artist_torrent_json_response['seeders']
self.snatched = artist_torrent_json_response['snatched']
self.time = artist_torrent_json_response['time']
self.has_file = artist_torrent_json_response['hasFile']
def set_torrent_group_data(self, group_torrent_json_response):
if self.id != group_torrent_json_response['id']:
raise InvalidTorrentException("Tried to update a Torrent's information from a 'torrentgroup' API call with a different id." +
" Should be %s, got %s" % (self.id, group_torrent_json_response['id']) )
self.group = self.parent_api.get_torrent_group(group_torrent_json_response['groupId'])
self.media = group_torrent_json_response['media']
self.format = group_torrent_json_response['format']
self.encoding = group_torrent_json_response['encoding']
self.remastered = group_torrent_json_response['remastered']
self.remaster_year = group_torrent_json_response['remasterYear']
self.remaster_title = group_torrent_json_response['remasterTitle']
self.remaster_record_label = group_torrent_json_response['remasterRecordLabel']
self.remaster_catalogue_number = group_torrent_json_response['remasterCatalogueNumber']
self.scene = group_torrent_json_response['scene']
self.has_log = group_torrent_json_response['hasLog']
self.has_cue = group_torrent_json_response['hasCue']
self.log_score = group_torrent_json_response['logScore']
self.file_count = group_torrent_json_response['fileCount']
self.size = group_torrent_json_response['size']
self.seeders = group_torrent_json_response['seeders']
self.leechers = group_torrent_json_response['leechers']
self.snatched = group_torrent_json_response['snatched']
self.free_torrent = group_torrent_json_response['freeTorrent']
self.time = group_torrent_json_response['time']
self.description = group_torrent_json_response['description']
self.file_list = [ re.match("(.+){{{(\d+)}}}", item).groups()
for item in group_torrent_json_response['fileList'].split("|||") ] # tuple ( filename, filesize )
self.file_path = group_torrent_json_response['filePath']
self.user = self.parent_api.get_user(group_torrent_json_response['userId'])
def set_torrent_search_data(self, search_torrent_json_response):
if self.id != search_torrent_json_response['torrentId']:
raise InvalidTorrentException("Tried to update a Torrent's information from a 'browse'/search API call with a different id." +
" Should be %s, got %s" % (self.id, search_torrent_json_response['torrentId']) )
# TODO: Add conditionals to handle torrents that aren't music
self.group = self.parent_api.get_torrent_group(search_torrent_json_response['groupId'])
self.remastered = search_torrent_json_response['remastered']
self.remaster_year = search_torrent_json_response['remasterYear']
self.remaster_title = search_torrent_json_response['remasterTitle']
self.remaster_catalogue_number = search_torrent_json_response['remasterCatalogueNumber']
self.media = search_torrent_json_response['media']
self.format = search_torrent_json_response['format']
self.encoding = search_torrent_json_response['encoding']
self.has_log = search_torrent_json_response['hasLog']
self.has_cue = search_torrent_json_response['hasCue']
self.log_score = search_torrent_json_response['logScore']
self.scene = search_torrent_json_response['scene']
self.file_count = search_torrent_json_response['fileCount']
self.size = search_torrent_json_response['size']
self.seeders = search_torrent_json_response['seeders']
self.leechers = search_torrent_json_response['leechers']
self.snatched = search_torrent_json_response['snatches']
self.free_torrent = search_torrent_json_response['isFreeleech'] or search_torrent_json_response['isPersonalFreeleech']
self.time = search_torrent_json_response['time']
def __repr__(self):
if self.group:
groupname = self.group.name
else:
groupname = "Unknown Group"
return "Torrent: %s - %s - ID: %s" % (groupname, self.encoding, self.id)

View File

@@ -0,0 +1,135 @@
from torrent import Torrent
class InvalidTorrentGroupException(Exception):
pass
class TorrentGroup(object):
"""
Represents a Torrent Group (usually an album). Note that TorrentGroup.torrents may not be comprehensive if you
haven't called TorrentGroup.update_group_data()...it may have only been populated with filtered search results.
Check TorrentGroup.has_complete_torrent_list (boolean) to be sure.
"""
def __init__(self, id, parent_api):
self.id = id
self.parent_api = parent_api
self.name = None
self.wiki_body = None
self.wiki_image = None
self.year = None
self.record_label = None
self.catalogue_number = None
self.tags = []
self.release_type = None
self.vanity_house = None
self.has_bookmarked = None
self.category = None
self.time = None
self.music_info = None
self.torrents = []
self.has_complete_torrent_list = False
self.parent_api.cached_torrent_groups[self.id] = self
def update_group_data(self):
response = self.parent_api.request(action='torrentgroup', id=self.id)
self.set_group_data(response)
def set_group_data(self, torrent_group_json_response):
"""
Takes parsed JSON response from 'torrentgroup' action on api, and updates relevant information.
To avoid problems, only pass in data from an API call that used this torrentgroup's ID as an argument.
"""
if self.id != torrent_group_json_response['group']['id']:
raise InvalidTorrentGroupException("Tried to update a TorrentGroup's information from an 'artist' API call with a different id." +
" Should be %s, got %s" % (self.id, torrent_group_json_response['group']['groupId']) )
self.name = torrent_group_json_response['group']['name']
self.year = torrent_group_json_response['group']['year']
self.wiki_body = torrent_group_json_response['group']['wikiBody']
self.wiki_image = torrent_group_json_response['group']['wikiImage']
self.record_label = torrent_group_json_response['group']['recordLabel']
self.catalogue_number = torrent_group_json_response['group']['catalogueNumber']
self.release_type = torrent_group_json_response['group']['releaseType']
self.category = self.parent_api.get_category(torrent_group_json_response['group']['categoryId'],
torrent_group_json_response['group']['categoryName'])
self.time = torrent_group_json_response['group']['time']
self.vanity_house = torrent_group_json_response['group']['vanityHouse']
self.music_info = torrent_group_json_response['group']['musicInfo']
self.music_info['artists'] = [ self.parent_api.get_artist(artist['id'], artist['name'])
for artist in self.music_info['artists'] ]
self.music_info['with'] = [ self.parent_api.get_artist(artist['id'], artist['name'])
for artist in self.music_info['with'] ]
self.torrents = []
for torrent_dict in torrent_group_json_response['torrents']:
torrent_dict['groupId'] = self.id
torrent = self.parent_api.get_torrent(torrent_dict['id'])
torrent.set_torrent_group_data(torrent_dict)
self.torrents.append(torrent)
self.has_complete_torrent_list = True
def set_artist_group_data(self, artist_group_json_response):
"""
Takes torrentgroup section from parsed JSON response from 'artist' action on api, and updates relevant information.
"""
if self.id != artist_group_json_response['groupId']:
raise InvalidTorrentGroupException("Tried to update a TorrentGroup's information from an 'artist' API call with a different id." +
" Should be %s, got %s" % (self.id, artist_group_json_response['groupId']) )
self.name = artist_group_json_response['groupName']
self.year = artist_group_json_response['groupYear']
self.record_label = artist_group_json_response['groupRecordLabel']
self.catalogue_number = artist_group_json_response['groupCatalogueNumber']
self.tags = []
for tag_name in artist_group_json_response['tags']:
tag = self.parent_api.get_tag(tag_name)
self.tags.append(tag)
self.release_type = artist_group_json_response['releaseType']
self.has_bookmarked = artist_group_json_response['hasBookmarked']
self.torrents = []
for torrent_dict in artist_group_json_response['torrent']:
torrent = self.parent_api.get_torrent(torrent_dict['id'])
torrent.set_torrent_artist_data(torrent_dict)
self.torrents.append(torrent)
self.has_complete_torrent_list = True
def set_torrent_search_data(self, search_json_response):
if self.id != search_json_response['groupId']:
raise InvalidTorrentGroupException("Tried to update a TorrentGroup's information from an 'browse'/search API call with a different id." +
" Should be %s, got %s" % (self.id, search_json_response['groupId']) )
self.name = search_json_response['groupName']
# purposefully ignoring search_json_response['artist']...the other data updates don't include it, would just get confusing
self.tags = []
for tag_name in search_json_response['tags']:
tag = self.parent_api.get_tag(tag_name)
self.tags.append(tag)
# some of the below keys aren't in things like comics...should probably watch out for this elsewhere
if 'bookmarked' in search_json_response.keys():
self.has_bookmarked = search_json_response['bookmarked']
if 'vanityHouse' in search_json_response.keys():
self.vanity_house = search_json_response['vanityHouse']
if 'groupYear' in search_json_response.keys():
self.year = search_json_response['groupYear']
if 'releaseType' in search_json_response.keys():
self.release_type = search_json_response['releaseType']
self.time = search_json_response['groupTime']
if 'torrentId' in search_json_response.keys():
search_json_response['torrents'] = [{'torrentId': search_json_response['torrentId']}]
new_torrents = []
for torrent_dict in search_json_response['torrents']:
torrent_dict['groupId'] = self.id
torrent = self.parent_api.get_torrent(torrent_dict['torrentId'])
new_torrents.append(torrent)
# torrent information gets populated in API search call, no need to duplicate that here
self.torrents = self.torrents + new_torrents
def __repr__(self):
return "TorrentGroup: %s - ID: %s" % (self.name, self.id)

217
lib/pygazelle/user.py Normal file
View File

@@ -0,0 +1,217 @@
class InvalidUserException(Exception):
pass
class User(object):
"""
This class represents a User, whether your own or someone else's. It is created knowing only its ID. To reduce
API accesses, load information using User.update_index_data() or User.update_user_data only as needed.
"""
def __init__(self, id, parent_api):
self.id = id
self.parent_api = parent_api
self.username = None
self.authkey = None
self.passkey = None
self.avatar = None
self.is_friend = None
self.profile_text = None
self.notifications = None
self.stats = None
self.ranks = None
self.personal = None
self.community = None
self.parent_api.cached_users[self.id] = self # add self to cache of known User objects
def update_index_data(self):
"""
Calls 'index' API action, then updates this User objects information with it.
NOTE: Only call if this user is the logged-in user...throws InvalidUserException otherwise.
"""
response = self.parent_api.request(action='index')
self.set_index_data(response)
def set_index_data(self, index_json_response):
"""
Takes parsed JSON response from 'index' action on api, and updates the available subset of user information.
ONLY callable if this User object represents the currently logged in user. Throws InvalidUserException otherwise.
"""
if self.id != index_json_response['id']:
raise InvalidUserException("Tried to update non-logged-in User's information from 'index' API call." +
" Should be %s, got %s" % (self.id, index_json_response['id']) )
self.username = index_json_response['username']
self.authkey = index_json_response['authkey']
self.passkey = index_json_response['passkey']
self.notifications = index_json_response['notifications']
if self.stats:
self.stats = dict(self.stats.items() + index_json_response['userstats'].items()) # merge in new info
else:
self.stats = index_json_response['userstats']
# cross pollinate some data that is located in multiple locations in API
if self.personal:
self.personal['class'] = self.stats['class']
self.personal['passkey'] = self.passkey
def update_user_data(self):
response = self.parent_api.request(action='user', id=self.id)
self.set_user_data(response)
def set_user_data(self, user_json_response):
"""
Takes parsed JSON response from 'user' action on api, and updates relevant user information.
To avoid problems, only pass in user data from an API call that used this user's ID as an argument.
"""
if self.id != user_json_response['id']:
raise InvalidUserException("Tried to update a user's information from a 'user' API call with a different id." +
" Should be %s, got %s" % (self.id, user_json_response['id']) )
self.username = user_json_response['username']
self.avatar = user_json_response['avatar']
self.is_friend = user_json_response['isFriend']
self.profile_text = user_json_response['profileText']
if self.stats:
self.stats = dict(self.stats.items() + user_json_response['stats'].items()) # merge in new info
else:
self.stats = user_json_response['stats']
self.ranks = user_json_response['ranks']
self.personal = user_json_response['personal']
self.community = user_json_response['community']
# cross pollinate some data that is located in multiple locations in API
self.stats['class'] = self.personal['class']
self.passkey = self.personal['passkey']
def set_search_result_data(self, search_result_item):
"""
Takes a single user result item from a 'usersearch' API call and updates user info.
"""
if self.id != search_result_item['userId']:
raise InvalidUserException("Tried to update existing user with another user's search result data (IDs don't match).")
self.username = search_result_item['username']
if not self.personal:
self.personal = {}
self.personal['donor'] = search_result_item['donor']
self.personal['warned'] = search_result_item['warned']
self.personal['enabled'] = search_result_item['enabled']
self.personal['class'] = search_result_item['class']
def __repr__(self):
return "User: %s - ID: %s" % (self.username, self.id)
#URL:
#ajax.php?action=usersearch
#Argument:
#search - The search term.
#{
# "status": "success",
# "response": {
# "currentPage": 1,
# "pages": 1,
# "results": [
# {
# "userId": 469,
# "username": "dr4g0n",
# "donor": true,
# "warned": false,
# "enabled": true,
# "class": "VIP"
# },
# // ...
# ]
# }
#}
#URL:
#ajax.php?action=user
#
#Arguments:
#id - id of the user to display
#
#Response format:
#{
# "status": "success",
# "response": {
# "username": "xxxx",
# "avatar": "http://asdf.com/asdf.png",
# "isFriend": false,
# "profileText": "",
# "stats": {
# "joinedDate": "2007-10-28 14:26:12",
# "lastAccess": "2012-08-09 00:17:52",
# "uploaded": 585564424629,
# "downloaded": 177461229738,
# "ratio": 3.3,
# "requiredRatio": 0.6
# },
# "ranks": {
# "uploaded": 98,
# "downloaded": 95,
# "uploads": 85,
# "requests": 0,
# "bounty": 79,
# "posts": 98,
# "artists": 0,
# "overall": 85
# },
# "personal": {
# "class": "VIP",
# "paranoia": 0,
# "paranoiaText": "Off",
# "donor": true,
# "warned": false,
# "enabled": true,
# "passkey": "31d59d20ac9233bf2038e35e72c4d61e"
# },
# "community": {
# "posts": 863,
# "torrentComments": 13,
# "collagesStarted": 0,
# "collagesContrib": 0,
# "requestsFilled": 0,
# "requestsVoted": 13,
# "perfectFlacs": 2,
# "uploaded": 29,
# "groups": 14,
# "seeding": 309,
# "leeching": 0,
# "snatched": 678,
# "invited": 7
# }
# }
#}
#URL:
#ajax.php?action=index
#
#Arguments: None
#{
# "status": "success",
# "response": {
# "username": "xxxx",
# "id": 0000,
# "authkey": "redacted",
# "passkey": "redacted",
# "notifications": {
# "messages": 0,
# "notifications": 9000,
# "newAnnouncement": false,
# "newBlog": false
# },
# "userstats": {
# "uploaded": 585564424629,
# "downloaded": 177461229738,
# "ratio": 3.29,
# "requiredratio": 0.6,
# "class": "VIP"
# }
# }
#}

File diff suppressed because it is too large Load Diff