def add_show(self, item): """Adds a new show in the server""" self.check_credentials() self.msg.info(self.name, "Adding show %s..." % item['title']) xml = self._build_xml(item) # Send the XML as POST data to the MyAnimeList API values = {'data': xml} data = self._urlencode(values) try: response = self.opener.open(self.url + self.mediatype + "list/add/" + str(item['id']) + ".xml", data) return True except urllib2.HTTPError, e: raise utils.APIError('Error adding: ' + str(e.code))
def update_show(self, item): """Sends a show update to the server""" self.check_credentials() self.msg.info(self.name, "Updating show %s..." % item['title']) xml = self._build_xml(item) # Send the XML as POST data to the MyAnimeList API values = {'data': xml} data = self._urlencode(values) try: self.opener.open( self.url + self.mediatype + "list/update/" + str(item['id']) + ".xml", data) except urllib2.HTTPError, e: raise utils.APIError('Error updating: ' + str(e.code))
def check_credentials(self): self.msg.info(self.name, 'Logging in...') try: response = self.opener.open( "http://melative.com/api/account/verify_credentials.json") self.logged_in = True # Parse user information data = json.load(response) self.username = data['name'] self.userid = data['id'] return True except urllib2.HTTPError, e: raise utils.APIError("Incorrect credentials.")
def add_show(self, item): """Adds a new show in the server""" self.check_credentials() self.msg.info(self.name, "Adding show %s..." % item['title']) data = self._build_data(item) try: data = self._request('POST', self.prefix + "/library-entries", body=data, auth=True) data_json = json.loads(data) return int(data_json['data']['id']) except urllib.request.HTTPError as e: raise utils.APIError('Error adding: ' + str(e.code))
def search(self, criteria): """Searches MyAnimeList database for the queried show""" self.msg.info(self.name, "Searching for %s..." % criteria) # Send the urlencoded query to the search API query = self._urlencode({'q': criteria}) data = self._request(self.url + self.mediatype + "/search.xml?" + query) # Load the results into XML try: root = ET.ElementTree().parse(data, parser=self._make_parser()) except ET.ParseError, e: if e.code == 3: # Empty document; no results return [] else: raise utils.APIError("Parser error: %s" % repr(e.message))
def request_info(self, itemlist): resultdict = dict() for item in itemlist: # Search for it only if it hasn't been found earlier if item['id'] not in resultdict: infos = self.search(item['title']) for info in infos: showid = info['id'] resultdict[showid] = info itemids = [ show['id'] for show in itemlist ] try: reslist = [ resultdict[itemid] for itemid in itemids ] except KeyError: raise utils.APIError('There was a problem getting the show details.') return reslist
def update_show(self, item): """Sends a show update to the server""" self.check_credentials() self.msg.info(self.name, "Updating show %s..." % item['title']) # Send the POST data to the Hummingbird API values = {'auth_token': self.auth} # Update necessary keys if 'my_progress' in item.keys(): values['episodes_watched'] = item['my_progress'] if 'my_status' in item.keys(): values['status'] = item['my_status'] if 'my_score' in item.keys(): values['sane_rating_update'] = item['my_score'] try: self._request("/libraries/%s" % item['id'], post=values) except urllib2.HTTPError, e: raise utils.APIError('Error updating: ' + str(e.code))
def check_credentials(self): """Checks if credentials are correct; returns True or False.""" if self.logged_in: return True # Already logged in self.msg.info(self.name, 'Logging in...') try: response = self._request(self.url + "account/verify_credentials.xml") root = ET.ElementTree().parse(response, parser=self._make_parser()) (userid, username) = self._parse_credentials(root) self.username = username self._set_userconfig('userid', userid) self._set_userconfig('username', username) self._emit_signal('userconfig_changed') self.logged_in = True return True except urllib2.HTTPError, e: raise utils.APIError("Incorrect credentials.")
def search(self, criteria): self.check_credentials() self.msg.info(self.name, "Searching for {}...".format(criteria)) param = {'access_token': self._get_userconfig('access_token')} data = self._request("GET", "{0}/search/{1}".format(self.mediatype, criteria), get=param) if type(data) == dict: # In case of error API returns a small JSON payload # which translates into a dict with the key 'error' # instead of a list. if data['error']['messages'][0] == 'No Results.': data = [] else: raise utils.APIError("Error while searching for \ {0}: {1}".format(criteria, str(data))) showlist = [] for item in data: show = utils.show() showid = item['id'] showdata = { 'id': showid, 'title': item['title_romaji'], 'aliases': [item['title_english']], 'type': item['type'], 'status': item[self.airing_str], 'my_status': self.media_info()['status_start'], 'total': item[self.total_str], 'image': item['image_url_lge'], 'image_thumb': item['image_url_med'], 'url': str("https://anilist.co/%s/%d" % (self.mediatype, showid)), } show.update({k:v for k,v in showdata.items() if v}) showlist.append( show ) return showlist
def _sendcmd(self, cmd, options=None): """Send a VNDB compatible command and return the response data""" msg = cmd if options: msg += " " + json.dumps(options, separators=(',', ':')) msg = msg.encode('utf-8') msg += b"\x04" # EOT # Send message self.s.sendall(msg) # Construct response lines = [] while True: line = self.s.recv(65536) if line.endswith(b"\x04"): line = line.strip(b"\x04") lines.append(line) response = b"".join(lines).decode('utf-8') break else: lines.append(line) # Separate into response name and JSON data _resp = response.split(' ', 1) name = _resp[0] try: data = json.loads(_resp[1]) except IndexError: data = None # Treat error as an exception if name == 'error': raise utils.APIError(data['msg']) return (name, data)
def fetch_list(self): """Queries the full list from the remote server. Returns the list if successful, False otherwise.""" self.check_credentials() self.msg.info(self.name, 'Downloading list...') try: # Get an XML list from MyAnimeList API data = self._request("http://myanimelist.net/malappinfo.php?u="+self.username+"&status=all&type="+self.mediatype) # Parse the XML data and load it into a dictionary # using the proper function (anime or manga) root = ET.ElementTree().parse(data, parser=self._make_parser()) if self.mediatype == 'anime': self.msg.info(self.name, 'Parsing anime list...') return self._parse_anime(root) elif self.mediatype == 'manga': self.msg.info(self.name, 'Parsing manga list...') return self._parse_manga(root) else: raise utils.APIFatal('Attempted to parse unsupported media type.') except urllib2.HTTPError, e: raise utils.APIError("Error getting list.")
class libanilist(lib): """ API class to communicate with Anilist Website: http://anilist.co messenger: Messenger object to send useful messages to """ name = 'libanilist' msg = None logged_in = False api_info = {'name': 'Anilist', 'version': '1', 'merge': False} mediatypes = dict() mediatypes['anime'] = { 'has_progress': True, 'can_add': True, 'can_delete': True, 'can_score': True, 'can_status': True, 'can_update': True, 'can_play': True, 'status_start': 'watching', 'status_finish': 'completed', 'statuses': ['watching', 'completed', 'on-hold', 'dropped', 'plan to watch'], 'statuses_dict': { 'watching': 'Watching', 'completed': 'Completed', 'on-hold': 'On Hold', 'dropped': 'Dropped', 'plan to watch': 'Plan to Watch' }, 'score_max': 100, 'score_step': 1, } mediatypes['manga'] = { 'has_progress': True, 'can_add': True, 'can_delete': True, 'can_score': True, 'can_status': True, 'can_update': True, 'can_play': False, 'status_start': 'reading', 'status_finish': 'completed', 'statuses': ['reading', 'completed', 'on-hold', 'dropped', 'plan to read'], 'statuses_dict': { 'reading': 'Reading', 'completed': 'Completed', 'on-hold': 'On Hold', 'dropped': 'Dropped', 'plan to read': 'Plan to Read' }, 'score_max': 100, 'score_step': 1, } default_mediatype = 'anime' # Supported signals for the data handler signals = { 'show_info_changed': None, } url = "http://anilist.co/api/" client_id = "z411-gdjc3" _client_secret = "MyzwuYoMqPPglXwCTcexG1i" def __init__(self, messenger, account, userconfig): """Initializes the API""" super(libanilist, self).__init__(messenger, account, userconfig) self.pin = account['password'].strip() self.userid = userconfig['userid'] if len(self.pin) != 40: raise utils.APIFatal("Invalid PIN.") if self.mediatype == 'manga': self.total_str = "total_chapters" self.watched_str = "chapters_read" self.airing_str = "publishing_status" self.status_translate = { 'publishing': 1, 'finished': 2, 'not yet published': 3, } else: self.total_str = "total_episodes" self.watched_str = "episodes_watched" self.airing_str = "airing_status" self.status_translate = { 'currently airing': 1, 'finished airing': 2, 'not yet aired': 3, } #handler=urllib2.HTTPHandler(debuglevel=1) #self.opener = urllib2.build_opener(handler) self.opener = urllib2.build_opener() self.opener.addheaders = [('User-agent', 'Trackma/0.1')] def _request(self, method, url, get=None, post=None, auth=False): if get: url = "{}?{}".format(url, urllib.urlencode(get)) if post: post = urllib.urlencode(post) request = urllib2.Request(self.url + url, post) request.get_method = lambda: method if auth: request.add_header('Content-Type', 'application/x-www-form-urlencoded') request.add_header( 'Authorization', '{0} {1}'.format( self._get_userconfig('token_type').capitalize(), self._get_userconfig('access_token'), )) try: response = self.opener.open(request, timeout=10) return json.load(response) except urllib2.HTTPError, e: if e.code == 400: raise utils.APIError( "Invalid PIN. It is either probably expired or meant for another application." ) else: raise utils.APIError("Connection error: %s" % e) except socket.timeout: raise utils.APIError("Connection timed out.")
def fetch_list(self): """Queries the full list from the remote server. Returns the list if successful, False otherwise.""" self.check_credentials() self.msg.info(self.name, 'Downloading list...') try: showlist = dict() infolist = list() # Get first page and continue from there params = { "filter[user_id]": self._get_userconfig('userid'), "filter[kind]": self.mediatype, # "include": self.mediatype, # TODO : This returns a 500 for some reason. "include": "media", # TODO : List for manga should be different f"fields[{self.mediatype}]": ','.join([ 'id', 'slug', 'canonicalTitle', 'titles', 'episodeCount' if self.mediatype in ['anime', 'drama'] else 'chapterCount', 'description', 'status', 'tba', 'subtype', 'posterImage', 'startDate', 'endDate', 'abbreviatedTitles', 'averageRating', 'popularityRank', 'ratingRank', 'ageRating', 'ageRatingGuide', 'userCount', 'favoritesCount' ]), "page[limit]": "250", } if self.mediatype == 'anime': params['fields[anime]'] += ',nsfw' if self.mediatype == 'manga': params['fields[manga]'] += ',serialization' url = "{}/library-entries?{}".format( self.prefix, urllib.parse.urlencode(params)) i = 1 while url: self.msg.info(self.name, 'Getting page {}...'.format(i)) data = self._request('GET', url) data_json = json.loads(data) #print(json.dumps(data_json, sort_keys=True, indent=2)) # return [] entries = data_json['data'] links = data_json['links'] for entry in entries: # TODO : Including the mediatype returns a 500 for some reason. #showid = int(entry['relationships'][self.mediatype]['data']['id']) showid = int(entry['relationships']['media']['data']['id']) status = entry['attributes']['status'] rating = entry['attributes']['ratingTwenty'] showlist[showid] = utils.show() showlist[showid].update({ 'id': showid, 'my_id': entry['id'], 'my_progress': entry['attributes']['progress'], 'my_score': float(rating) / 4.00 if rating is not None else 0.0, 'my_status': entry['attributes']['status'], 'my_start_date': self._iso2date(entry['attributes']['startedAt']), 'my_finish_date': self._iso2date(entry['attributes']['finishedAt']), }) if 'included' in data_json: medias = data_json['included'] for media in medias: info = self._parse_info(media) infolist.append(info) self._emit_signal('show_info_changed', infolist) url = links.get('next') i += 1 return showlist except urllib.request.HTTPError as e: raise utils.APIError("Error getting list (HTTPError): %s" % e.read()) except urllib.error.URLError as e: raise utils.APIError("Error getting list (URLError): %s" % e.reason)
# Parse the XML data and load it into a dictionary # using the proper function (anime or manga) root = ET.ElementTree().parse(data, parser=self._make_parser()) if self.mediatype == 'anime': self.msg.info(self.name, 'Parsing anime list...') return self._parse_anime(root) elif self.mediatype == 'manga': self.msg.info(self.name, 'Parsing manga list...') return self._parse_manga(root) else: raise utils.APIFatal('Attempted to parse unsupported media type.') except urllib2.HTTPError, e: raise utils.APIError("Error getting list.") except IOError, e: raise utils.APIError("Error reading list: %s" % e.message) def add_show(self, item): """Adds a new show in the server""" self.check_credentials() self.msg.info(self.name, "Adding show %s..." % item['title']) xml = self._build_xml(item) # Send the XML as POST data to the MyAnimeList API values = {'data': xml} data = self._urlencode(values) try: response = self.opener.open(self.url + self.mediatype + "list/add/" + str(item['id']) + ".xml", data) return True except urllib2.HTTPError, e:
class libmal(lib): """ API class to communicate with MyAnimeList Should inherit a base library interface. Website: http://www.myanimelist.net API documentation: http://myanimelist.net/modules.php?go=api Designed by: Garrett Gyssler (http://myanimelist.net/profile/Xinil) """ name = 'libmal' username = '' # TODO Must be filled by check_credentials logged_in = False opener = None api_info = { 'name': 'MyAnimeList', 'shortname': 'mal', 'version': 'v0.3', 'merge': False } default_mediatype = 'anime' mediatypes = dict() mediatypes['anime'] = { 'has_progress': True, 'can_add': True, 'can_delete': True, 'can_score': True, 'can_status': True, 'can_update': True, 'can_play': True, 'can_date': True, 'status_start': 1, 'status_finish': 2, 'statuses': [1, 2, 3, 4, 6], 'statuses_dict': { 1: 'Watching', 2: 'Completed', 3: 'On Hold', 4: 'Dropped', 6: 'Plan to Watch' }, 'score_max': 10, 'score_step': 1, } mediatypes['manga'] = { 'has_progress': True, 'can_add': True, 'can_delete': True, 'can_score': True, 'can_status': True, 'can_update': True, 'can_play': False, 'can_date': True, 'status_start': 1, 'status_finish': 2, 'statuses': [1, 2, 3, 4, 6], 'statuses_dict': { 1: 'Reading', 2: 'Completed', 3: 'On Hold', 4: 'Dropped', 6: 'Plan to Read' }, 'score_max': 10, 'score_step': 1, } # Authorized User-Agent for Trackma url = 'http://myanimelist.net/api/' useragent = 'api-team-f894427cc1c571f79da49605ef8b112f' def __init__(self, messenger, account, userconfig): """Initializes the useragent through credentials.""" # Since MyAnimeList uses a cookie we just create a HTTP Auth handler # together with the urllib2 opener. super(libmal, self).__init__(messenger, account, userconfig) auth_string = 'Basic ' + base64.encodestring( '%s:%s' % (account['username'], account['password'])).replace('\n', '') self.username = self._get_userconfig('username') self.opener = urllib2.build_opener() self.opener.addheaders = [ ('User-Agent', self.useragent), ('Authorization', auth_string), ] def _request(self, url): """ Requests the page as gzip and uncompresses it Returns a stream object """ try: request = urllib2.Request(url) request.add_header('Accept-Encoding', 'gzip') response = self.opener.open(request, timeout=10) except urllib2.HTTPError, e: if e.code == 401: raise utils.APIError( "Unauthorized. Please check if your username and password are correct." "\n\nPlease note that you might also be getting this error if you have " "non-alphanumeric characters in your password due to an upstream " "MAL bug (#138).") else: raise utils.APIError("HTTP error %d: %s" % (e.code, e.reason)) except urllib2.URLError, e: raise utils.APIError("Connection error: %s" % e)
def search(self, criteria): """Searches MyAnimeList database for the queried show""" self.msg.info(self.name, "Searching for %s..." % criteria) # Send the urlencoded query to the search API query = urllib.parse.urlencode({'q': criteria}) data = self._request(self.url + self.mediatype + "/search.xml?" + query) # Load the results into XML try: root = self._parse_xml(data) except ET.ParseError as e: if e.code == 3: # Empty document; no results return [] else: raise utils.APIError("Parser error: %r" % e) except IOError: raise utils.APIError("IO error: %r" % e) # Use the correct tag name for episodes if self.mediatype == 'manga': episodes_str = 'chapters' else: episodes_str = 'episodes' # Since the MAL API returns the status as a string, and # we handle statuses as integers, we need to convert them if self.mediatype == 'anime': status_translate = { 'Currently Airing': utils.STATUS_AIRING, 'Finished Airing': utils.STATUS_FINISHED, 'Not yet aired': utils.STATUS_NOTYET } elif self.mediatype == 'manga': status_translate = { 'Publishing': utils.STATUS_AIRING, 'Finished': utils.STATUS_AIRING } entries = list() for child in root.iter('entry'): show = utils.show() showid = int(child.find('id').text) show.update({ 'id': showid, 'title': child.find('title').text, 'type': child.find('type').text, 'status': status_translate[child.find( 'status').text], # TODO : This should return an int! 'total': int(child.find(episodes_str).text), 'image': child.find('image').text, 'url': "https://myanimelist.net/anime/%d" % showid, 'start_date': self._str2date(child.find('start_date').text), 'end_date': self._str2date(child.find('end_date').text), 'extra': [ ('English', child.find('english').text), ('Synonyms', child.find('synonyms').text), ('Synopsis', self._translate_synopsis(child.find('synopsis').text)), (episodes_str.title(), child.find(episodes_str).text), ('Type', child.find('type').text), ('Score', child.find('score').text), ('Status', child.find('status').text), ('Start date', child.find('start_date').text), ('End date', child.find('end_date').text), ] }) entries.append(show) self._emit_signal('show_info_changed', entries) return entries
def fetch_list(self): """Queries the full list from the remote server. Returns the list if successful, False otherwise.""" self.check_credentials() self.msg.info(self.name, 'Downloading list...') try: showlist = dict() infolist = list() # Get first page and continue from there params = { "filter[user_id]": self._get_userconfig('userid'), "filter[kind]": self.mediatype, #"include": self.mediatype, # TODO : This returns a 500 for some reason. "include": "media", # TODO : List for manga should be different "fields[anime]": "id,slug,canonicalTitle,titles,episodeCount,synopsis,subtype,posterImage,startDate,endDate", "page[limit]": "250", } url = "{}/library-entries?{}".format(self.prefix, urllib.parse.urlencode(params)) i = 1 while url: self.msg.info(self.name, 'Getting page {}...'.format(i)) data = self._request('GET', url) data_json = json.loads(data) #print(json.dumps(data_json, sort_keys=True, indent=2)) #return [] entries = data_json['data'] links = data_json['links'] for entry in entries: # TODO : Including the mediatype returns a 500 for some reason. #showid = int(entry['relationships'][self.mediatype]['data']['id']) showid = int(entry['relationships']['media']['data']['id']) status = entry['attributes']['status'] rating = entry['attributes']['rating'] showlist[showid] = utils.show() showlist[showid].update({ 'id': showid, 'my_id': entry['id'], 'my_progress': entry['attributes']['progress'], 'my_score': float(rating) if rating is not None else 0.0, 'my_status': entry['attributes']['status'], 'my_start_date': self._iso2date(entry['attributes']['startedAt']), 'my_finish_date': self._iso2date(entry['attributes']['finishedAt']), }) if 'included' in data_json: medias = data_json['included'] for media in medias: info = self._parse_info(media) infolist.append(info) self._emit_signal('show_info_changed', infolist) url = links.get('next') i += 1 return showlist except urllib.request.HTTPError as e: raise utils.APIError("Error getting list.")
class libshikimori(lib): """ API class to communicate with Shikimori Website: http://shikimori.org messenger: Messenger object to send useful messages to """ name = 'libshikimori' msg = None logged_in = False api_info = { 'name': 'Shikimori', 'shortname': 'shikimori', 'version': '1', 'merge': False } mediatypes = dict() mediatypes['anime'] = { 'has_progress': True, 'can_add': True, 'can_delete': True, 'can_score': True, 'can_status': True, 'can_update': True, 'can_play': True, 'status_start': 1, 'status_finish': 2, 'statuses': [1, 2, 3, 9, 4, 0], 'statuses_dict': { 1: 'Watching', 2: 'Completed', 3: 'On-Hold', 9: 'Rewatching', 4: 'Dropped', 0: 'Plan to Watch' }, 'score_max': 10, 'score_step': 1, } mediatypes['manga'] = { 'has_progress': True, 'can_add': True, 'can_delete': True, 'can_score': True, 'can_status': True, 'can_update': True, 'can_play': False, 'status_start': 1, 'status_finish': 2, 'statuses': [1, 2, 3, 9, 4, 0], 'statuses_dict': { 1: 'Reading', 2: 'Completed', 3: 'On-Hold', 9: 'Rereading', 4: 'Dropped', 0: 'Plan to Read' }, 'score_max': 10, 'score_step': 1, } default_mediatype = 'anime' # Supported signals for the data handler signals = { 'show_info_changed': None, } url = "http://shikimori.org" def __init__(self, messenger, account, userconfig): """Initializes the API""" super(libshikimori, self).__init__(messenger, account, userconfig) self.username = account['username'] self.password = account['password'] self.userid = userconfig['userid'] if not self.password: raise utils.APIFatal("No password.") if self.mediatype == 'manga': self.total_str = "chapters" self.watched_str = "chapters" self.airing_str = "publishing_status" self.status_translate = { 'publishing': utils.STATUS_AIRING, 'finished': utils.STATUS_FINISHED, 'not yet published': utils.STATUS_NOTYET, 'cancelled': utils.STATUS_CANCELLED, } else: self.total_str = "episodes" self.watched_str = "episodes" self.airing_str = "airing_status" self.status_translate = { 'currently airing': utils.STATUS_AIRING, 'finished airing': utils.STATUS_FINISHED, 'not yet aired': utils.STATUS_NOTYET, 'cancelled': utils.STATUS_CANCELLED, } #handler=urllib2.HTTPHandler(debuglevel=1) #self.opener = urllib2.build_opener(handler) self.opener = urllib2.build_opener() self.opener.addheaders = [('User-agent', 'Trackma/0.4')] def _request(self, method, url, get=None, post=None, jsondata=None, auth=False): if get: url = "{}?{}".format(url, urllib.urlencode(get)) if post: post = urllib.urlencode(post) if jsondata: post = json.dumps(jsondata, separators=(',', ':')) request = urllib2.Request(self.url + url, post) request.get_method = lambda: method if auth: request.add_header('Content-Type', 'application/json') request.add_header('X-User-Nickname', self.username) request.add_header('X-User-Api-Access-Token', self._get_userconfig('access_token')) try: response = self.opener.open(request, timeout=10) return json.load(response) except urllib2.HTTPError, e: if e.code == 400: raise utils.APIError("400") else: raise utils.APIError("Connection error: %s" % e) except socket.timeout: raise utils.APIError("Connection timed out.")