Fetches credits from Comic Vine in a friendly format.
#!/usr/bin/env python
# coding=utf-8
"""
Fetches credits from Comic Vine in a friendly format.
Version 0.0.7 (Python2)
Before running, change the API variable below (line 13)
"""
import datetime, json, re, sys
import requests # Use pip
#import pprint # Just for debugging
API ="" # Paste your API between the quotes
def fetchVine(urlStart,query):
"Grabs info from ComicVine."
# Download the JSON data.
url = urlStart + query
response = requests.get(url)
response.raise_for_status()
# Load JSON data into a Python variable.
jsonData = json.loads(response.text)
return jsonData
def flatName(the_name):
"Regularize the_name for matching purposes"
the_name = the_name.lower()
# strip articles
if the_name.startswith('the '):
the_name = the_name[4:]
elif the_name.startswith('a '):
the_name = the_name[2:]
# drop ambiguous characters
return_name = ''
for theChar in the_name:
if theChar not in u'-–—:,!?\'\"':
return_name += theChar
l = return_name.split(' ') # in case we've left two spaces behind
return_name = ' '.join(l)
return return_name
def issueLoop(vol_ID,the_pub):
"Continually prompts for issue numbers and returns info"
global API
while True:
print('** Enter an issue number (or Return to start over): ') # input next line
issue_no = raw_input() # makes the division b/t multiple issues easier to spot
if issue_no:
url_start ='http://www.comicvine.com/api/issues/'
query = '?api_key=%s' % API
query += '&format=json&field_list=api_detail_url'
query += '&filter=volume:%s,issue_number:%s' % (vol_ID,issue_no)
the_data = fetchVine(url_start,query)
if the_data['error'] != 'OK':
print('CV Returned Error: ' + the_data['error'])
return
if not the_data['results']:
print('No results for issue %s!' % issue_no)
continue
iss_result = the_data['results']
url_start = iss_result[0]['api_detail_url']
query = '?api_key=%s' % API
query += '&format=json&field_list=character_credits,team_credits'
query += ',person_credits,description,store_date,name'
query += '&filter=id:%s,issue_number:%s' % (vol_ID,issue_no)
the_data = fetchVine(url_start,query)
if the_data['error'] != 'OK':
print('CV Returned Error: ' + the_data['error'])
return
if the_data['number_of_total_results'] != 1: # Haven't actually seen this
print('Error: Too many results from CV!')
return
print('>> Issue %s PUBLISHER:\n%s' % (issue_no,the_pub)) # print pub.
the_results = the_data['results']
characters = the_results['character_credits'] # print characters
characterOutput(issue_no,characters)
teams = the_results['team_credits'] # print teams
teamOutput(issue_no,teams)
people = the_results['person_credits'] # print creator credits
peopleOutput(issue_no,people)
description = the_results['description'] # print description
descriptionOutput(issue_no,description,people)
date = the_results['store_date'] # Get date
dateOutput(issue_no,date)
else:
return
def characterOutput(issue_no,characters):
"Gets and outputs list of characters"
character_list = []
for character in characters:
character_name = character['name']
character_list.append(character_name)
print('>> Issue %s CHARACTERS:' % issue_no)
if not character_list:
print('None found!')
else:
character_print = ", ".join(character_list)
print(character_print)
return
def teamOutput(issue_no,teams):
"Gets and outputs list of teams"
team_list = []
for team in teams:
team_name = team['name']
team_list.append(team_name)
print('>> Issue %s TEAMS:' % issue_no)
if not team_list:
print('None found!')
else:
team_print = ", ".join(team_list)
print(team_print)
return
def peopleOutput(issue_no,people):
"Gets and outputs artists and writers"
writer_list, artist_list = [], []
for person in people:
person_role, person_name = person['role'], person['name']
if ('writer' in person_role):
writer_list.append(person_name)
if ('artist' in person_role):
artist_list.append(person_name)
elif ('penciler' in person_role):
artist_list.append(person_name)
elif ('inker' in person_role):
artist_list.append(person_name)
elif ('colorist' in person_role):
artist_list.append(person_name)
elif ('cover' in person_role): # TODO make this optional?
artist_list.append(person_name)
print('>> Issue %s WRITERS:' % issue_no)
if not writer_list:
print('None found!')
else:
writer_print = ", ".join(writer_list)
print(writer_print)
print('>> Issue %s ARTISTS:' % issue_no)
if not artist_list:
print('None found!')
else:
artist_print = ", ".join(artist_list)
print(artist_print)
return
def peopleOutput(issue_no,people):
"Gets and outputs artists and writers"
writer_list, artist_list = [], []
for person in people:
person_role, person_name = person['role'], person['name']
if ('writer' in person_role):
writer_list.append(person_name)
# could be writer/artist
if ('artist' in person_role):
artist_list.append(person_name)
elif ('penciler' in person_role):
artist_list.append(person_name)
elif ('inker' in person_role):
artist_list.append(person_name)
elif ('colorist' in person_role):
artist_list.append(person_name)
elif ('cover' in person_role): # TODO make this optional?
artist_list.append(person_name)
print('>> Issue %s WRITERS:' % issue_no)
if not writer_list:
print('None found!')
else:
writer_print = ", ".join(writer_list)
print(writer_print)
print('>> Issue %s ARTISTS:' % issue_no)
if not artist_list:
print('None found!')
else:
artist_print = ", ".join(artist_list)
print(artist_print)
return
def descriptionOutput(issue_no,description,people):
"Gets and outputs description"
regular_CA = ''
if description:
description_list = description.split('<h4>List of covers and their creators:</h4>')
description = description_list[0]
# try to ID regular cover artist
if len(description_list) > 1:
dl_1 = description_list[1]
dl_1_s = dl_1.split('<tbody>') # should be in the first line of table body
table_body = dl_1_s[1]
table_body = table_body.lstrip('<trd>')
table_body = table_body.rstrip('</tdrbodyale>')
table_list = table_body.split('</td></tr><tr><td>')
reg_line_list = table_list[0].split('</td><td>')
regular_CA = reg_line_list[2]
# get description - Solicit description usually in italics
italics = re.compile('<em>(.*)</em>')
mo = italics.search(description)
if (mo and mo.group(1)):
description = mo.group(1)
description = re.sub('<[^<]+?>', '', description) # Hack to remove HTML
description = '[quote]%s[/quote]' % description # quotes for solicit
else: # use what we got
description = re.sub('<[^<]+?>', '', description) # Hack to remove HTML
else:
description = 'None found!'
print('>> Issue %s DESCRIPTION:' % issue_no)
print(description)
if people:
creator_list = []
for person in people:
person_role, person_name = person['role'], person['name']
if person_name in regular_CA:
person_role = re.sub('cover','regular cover',person_role)
if ('writer' in person_role):
creator_list.append('%s: %s' % (person_name,person_role))
elif ('artist' in person_role):
creator_list.append('%s: %s' % (person_name,person_role))
elif ('penciler' in person_role):
creator_list.append('%s: %s' % (person_name,person_role))
elif ('inker' in person_role):
creator_list.append('%s: %s' % (person_name,person_role))
elif ('colorist' in person_role):
creator_list.append('%s: %s' % (person_name,person_role))
elif ('cover' in person_role): # TODO make optional?
creator_list.append('%s: %s' % (person_name,person_role))
if creator_list:
creator_list.sort()
creators = '\n[*]'.join(creator_list)
print('Creators: \n[*]%s' % creators)
def dateOutput(issue_no,date):
"Gets, formats, and outputs date"
if not date:
date = 'None found!'
else:
do = datetime.datetime.strptime(date, '%Y-%m-%d').date()
date = do.strftime('%d %b %Y')
print('>> Issue %s store date:' % issue_no)
print(date)
return
def getVolumes(which_volume):
url_start ='http://www.comicvine.com/api/volumes/'
query = '?api_key=' + API
query += '&format=json&field_list=name,start_year,id,count_of_issues,publisher'
query += '&filter=name:%s' % which_volume
"""
#Experimenting with dictionary query
#Format: 'http://www.comicvine.com/api/volumes/?field_list=name&field_list=start_year&field_list=id&field_list=count_of_issues&field_list=publisher&format=json&filter=name&filter=grayson&api_key=00000'
f_list = ['name','start_year','id','count_of_issues','publisher'] # fields requested
query = {'api_key': API,'format': 'json',{'field_list':f_list}, 'filter':('name:%s'% which_volume)}
pprint.pprint(query)
"""
print('Searching ComicVine...')
the_data = fetchVine(url_start,query)
if the_data['error'] != 'OK':
print('CV Returned Error: %s' % the_data['error'])
return
num_results = the_data['number_of_total_results']
if not num_results:
print('No results returned!')
return
print('Found %s results.' % num_results)
the_results = the_data['results']
# get any additional results
add_results = int(num_results) - 100
if add_results > 0:
offset = 100
while add_results > 0:
print('Fetching additional results (>%s)' % offset)
query += "&offset=%s" % offset
the_data = fetchVine(url_start,query)
the_results.extend(the_data['results'])
add_results -= 100
offset += 100
# make sure fields are populated
for i in the_results:
check_year = i.get('start_year')
check_pub = i.get('publisher')
if not check_pub:
i['publisher'] = {}
i['publisher']['name'] = 'Unknown'
if not check_year:
i['start_year'] = '????'
the_results.sort(key=lambda k: k['start_year'])
# check for close matches
flat_volume = flatName(which_volume)
match_results = []
for i in the_results:
if (flatName(i['name']) == flat_volume):
match_results.append(i)
# show matched (or all) results
if not match_results:
print('No matches for %s found.' % which_volume)
print('Showing all %d results:' % len(the_results))
for i in the_results:
match_results.append(i)
the_choice = showVolumeResults(match_results, None)
if the_choice == 'go back':
return
the_choice = int(the_choice) - 1
the_match = match_results[the_choice]
vol_ID = str(the_match['id'])
the_pub = the_match['publisher']['name']
#print('Volume: %s PUBLISHER:\n%s' % (vol_ID,the_pub))
else:
print('Found %s matches:' % len(match_results))
if (len(match_results) == len(the_results)):
the_choice = showVolumeResults(match_results, None)
else:
the_choice = showVolumeResults(match_results,'Show all results')
if the_choice == 'go back':
return
the_choice = int(the_choice) - 1
if the_choice >= 0:
the_match = match_results[the_choice]
vol_ID = str(the_match['id'])
the_pub = the_match['publisher']['name']
else:
print('Showing all %s results:' % len(the_results))
the_choice = showVolumeResults(the_results,None)
if the_choice == 'go back':
return
the_choice = int(the_choice) - 1
the_match = the_results[the_choice]
vol_ID = str(the_match['id'])
the_pub = the_match['publisher']['name']
return vol_ID, the_pub
def showVolumeResults(result_list, zero_option):
"Shows a list (and optional option), returns choice as string"
upper_range = len(result_list) + 1
for i, m in enumerate(result_list):
name, startYear, issueCount = m['name'], m['start_year'], m['count_of_issues']
publisher = m['publisher']['name']
print('%d : %s (%s) %s (~%s issues)' % (i+1, name, startYear, publisher, issueCount))
if zero_option:
print('%d : %s' % (0, zero_option))
lower_range = 0
else:
lower_range = 1
the_range = range(lower_range, upper_range)
# Get volume selection
the_input = ''
while not the_input:
the_input = raw_input('Enter the number before the correct Volume (or Return to start over): ')
if the_input == '':
return 'go back' # to distinguish from 0!
if the_input.isdigit():
if int(the_input) in the_range:
break
the_input = ''
return the_input
def main():
while True:
which_volume = raw_input('Enter a Volume Title (or Return to quit): ')
if not which_volume:
sys.exit('Quitting...')
else:
vol_ID, the_pub = getVolumes(which_volume)
if vol_ID:
issueLoop(vol_ID, the_pub)
if __name__ == "__main__":
if not API:
# TODO ask for and save API
print('No API! Open this script in an editor and follow the instructions on lines 5 & 6.')
else:
main()