15 __title__ =
"giantbomb_api - Simple-to-use Python interface to The GiantBomb's API (www.giantbomb.com/api)";
16 __author__=
"R.D. Vaughan"
18 This python script is intended to perform a variety of utility functions to search and access text
19 metadata and image URLs from GiantBomb. These routines are based on the GiantBomb api. Specifications
20 for this api are published at https://www.giantbomb.com/api/documentation/
27 import os, struct, sys, datetime, time, re
29 from copy
import deepcopy
31 IS_PY2 = sys.version_info[0] == 2
36 from .giantbomb_exceptions
import (GiantBombBaseError, GiantBombHttpError, GiantBombXmlError, GiantBombGameNotFound,)
40 """Wraps a stream with an encoder"""
49 """Wraps the output stream, encoding Unicode strings with the specified encoding"""
50 if isinstance(obj, unicode):
55 self.
out.buffer.write(obj)
58 """Delegate everything but write to the stream"""
59 return getattr(self.
out, attr)
66 from StringIO
import StringIO
68 from io
import StringIO
69 from lxml
import etree
70 except Exception
as e:
71 sys.stderr.write(
u'\n! Error - Importing the "lxml" and "StringIO" python libraries failed on error(%s)\n' % e)
79 for digit
in etree.LIBXML_VERSION:
80 version+=str(digit)+
'.'
81 version = version[:-1]
84 ! Error - The installed version of the "lxml" python library "libxml" version is too old.
85 At least "libxml" version 2.7.2 must be installed. Your version is (%s).
91 '''Methods that query api.giantbomb.com for metadata and outputs the results to stdout any errors are output
98 """apikey (str/unicode):
99 Specify the api.giantbomb.com API key. Applications need their own key.
100 See https://www.giantbomb.com/api/ to get your own API key
103 shows verbose debugging information
107 self.
config[
'apikey'] = apikey
108 self.
config[
'debug'] = debug
109 self.
config[
'searchURL'] =
u'https://www.giantbomb.com/api/search'
110 self.
config[
'dataURL'] =
u'https://www.giantbomb.com/api/game/%s'
112 self.
config[
'headers'] = {
"User-Agent":
'MythTV giantbomb grabber 0.2'}
114 self.
error_messages = {
'GiantBombHttpError':
u"! Error: A connection error to api.giantbomb.com was raised (%s)\n",
'GiantBombXmlError':
u"! Error: Invalid XML was received from api.giantbomb.com (%s)\n",
'GiantBombBaseError':
u"! Error: An error was raised (%s)\n", }
119 self.
xmlParser = etree.XMLParser(remove_blank_text=
True)
121 self.
supportedJobList = [
"actor",
"author",
"producer",
"executive producer",
"director",
"cinematographer",
"composer",
"editor",
"casting",
"voice actor",
"music",
"writer",
"technical director",
"design director", ]
125 'producer':
'Producer',
126 'executive producer':
'Executive Producer',
127 'director':
'Director',
128 'cinematographer':
'Cinematographer',
129 'composer':
'Composer',
131 'casting':
'Casting',
132 'voice actor':
'Actor',
135 'technical director':
'Director',
136 'design director':
'Director',
142 '''Removes HTML markup from a text string.
143 @param text The HTML source.
144 @return The plain text. If the HTML source contains non-ASCII
145 entities or character references, this is a Unicode string.
153 if text[:3] ==
"&#x":
154 return unichr(int(text[3:-1], 16))
156 return unichr(int(text[2:-1]))
159 elif text[:1] ==
"&":
161 from htmlentitydefs
import entitydefs
163 from html.entities
import entitydefs
164 entity = entitydefs.get(text[1:-1])
166 if entity[:2] ==
"&#":
168 return unichr(int(entity[2:-1]))
172 return unicode(entity,
"iso-8859-1")
175 text23 = self.
ampReplace(re.sub(
u"(?s)<[^>]*>|&#?\w+;", fixup, self.
textUtf8(text))).replace(
u'\n',
u' ')
177 text23 = self.
ampReplace(re.sub(
r"(?s)<[^>]*>|&#?\w+;", fixup, self.
textUtf8(text))).replace(
'\n',
' ')
187 except UnicodeDecodeError:
189 except (UnicodeEncodeError, TypeError):
195 '''Replace all "&" characters with "&"
198 return text.replace(
u'&',
u'~~~~~').replace(
u'&',
u'&').replace(
u'~~~~~',
u'&')
203 ''' Remove HTML tags and LFs from a string
204 return the string without HTML tags or LFs
208 return self.
massageText(html).strip().replace(
u'\n',
u' ').replace(
u'',
u"'").replace(
u'',
u"'")
212 ''' Take a HTML string and convert it to an HTML element. Then apply a filter and return
215 return an empty array if the filter failed to find any values.
219 xpathFilter = args[0]
225 htmlElement = etree.HTML(htmldata)
228 filteredData = htmlElement.xpath(xpathFilter)
229 if len(filteredData):
230 if xpathFilter.find(
'@') != -1:
231 return filteredData[0]
233 return filteredData[0].text
238 '''Convert a date/time string in a specified format into a pubDate. The default is the
240 return the formatted pubDate string
241 return on error return the original date string
244 for arg
in inputArgs:
248 index = args[0].
find(
'+')
250 index = args[0].
find(
'-')
251 if index != -1
and index > 5:
252 args[0] = args[0][:index].strip()
253 args[0] = args[0].replace(
',',
u'').replace(
'.',
u'')
256 pubdate = time.strptime(args[0], args[2])
258 args[1] = args[1].replace(
',',
u'').replace(
'.',
u'')
259 if args[1].
find(
'GMT') != -1:
260 args[1] = args[1][:args[1].
find(
'GMT')].strip()
261 args[0] = args[0][:args[0].rfind(
' ')].strip()
263 pubdate = time.strptime(args[0], args[1])
265 if args[1] ==
'%a %d %b %Y %H:%M:%S':
266 pubdate = time.strptime(args[0],
'%a %d %B %Y %H:%M:%S')
267 elif args[1] ==
'%a %d %B %Y %H:%M:%S':
268 pubdate = time.strptime(args[0],
'%a %d %b %Y %H:%M:%S')
270 return time.strftime(args[2], pubdate)
275 except Exception
as err:
276 sys.stderr.write(
u'! Error: pubDate variables(%s) error(%s)\n' % (args, err))
281 '''Convert the "expected" release date into the default MNV item format.
282 return the formatted pubDate string
283 return If there is not enough information to make a date then return an empty string
286 if gameElement.find(
'expected_release_year').text
is not None:
287 year = gameElement.find(
'expected_release_year').text
290 if gameElement.find(
'expected_release_quarter').text
is not None:
291 quarter = gameElement.find(
'expected_release_quarter').text
294 if gameElement.find(
'expected_release_month').text
is not None:
295 month = gameElement.find(
'expected_release_month').text
302 if month
and not quarter:
303 pubdate = time.strptime((
u'%s-%s-01' % (year, month)),
'%Y-%m-%d')
304 elif not month
and quarter:
305 month = str((int(quarter)*3))
306 pubdate = time.strptime((
u'%s-%s-01' % (year, month)),
'%Y-%m-%d')
308 pubdate = time.strptime((
u'%s-12-01' % (year, )),
'%Y-%m-%d')
310 return time.strftime(
'%Y-%m-%d', pubdate)
314 '''Parse the "image" and "description" elements for images and put in a persistant array
315 return True when there are images available
316 return False if there are no images
318 def makeImageElement(typeImage, url, thumb):
319 ''' Create a single Image element
320 return the image element
322 imageElement = etree.XML(
u"<image></image>")
323 imageElement.attrib[
'type'] = typeImage
324 imageElement.attrib[
'url'] = url
325 imageElement.attrib[
'thumb'] = thumb
329 superImageFilter = etree.XPath(
'.//super_url/text()')
331 for imageElement
in args[0]:
332 imageList = superImageFilter(imageElement)
334 for image
in imageList:
335 self.
imageElements.append(makeImageElement(
'coverart', image, image.replace(
u'super',
u'thumb')))
336 htmlElement = self.
getHtmlData(
'dummy', etree.tostring(args[1][0], method=
"text", encoding=unicode).strip())
338 for image
in htmlElement[0].xpath(
'.//a/img/@src'):
339 if image.find(
'screen') == -1:
341 if image.find(
'thumb') == -1:
343 self.
imageElements.append(makeImageElement(
'screenshot', image.replace(
u'thumb',
u'super'), image))
346 for imageElement
in args[2]:
347 imageList = superImageFilter(imageElement)
349 for image
in imageList:
350 self.
imageElements.append(makeImageElement(
'screenshot', image, image.replace(
u'super',
u'thumb')))
358 '''Return an array of image elements that was created be a previous "findImages" function call
359 return the array of image elements
365 '''Validate that the job category is supported by the
366 Universal Metadata Format item format
367 return True is supported
368 return False if not supported
371 tmpCopy = inputArgs[0]
373 tmpCopy = [inputArgs[0]]
381 '''Translate a tag name into the Universal Metadata Format item equivalent
382 return the translated tag equivalent
383 return the input name as the name does not need translating and is already been validated
393 """ Build a dictionary of the XPath extention function for the XSLT stylesheets
410 """Display a Game query in XML format:
411 https://www.mythtv.org/wiki/MythTV_Universal_Metadata_Format
414 with requests.Session()
as ReqSession:
415 url = self.
config[
'searchURL']
418 params[
"api_key"] = self.
config[
'apikey']
419 params[
"format"] =
'xml'
421 params[
"query"] = gameTitle
422 params[
"resources"] =
"game"
424 headers = self.
config[
'headers']
426 res = ReqSession.get(url, params=params, headers=headers)
429 queryResult = etree.fromstring(res.content)
430 except Exception
as errmsg:
431 sys.stderr.write(
u"! Error: Invalid XML was received from www.giantbomb.com (%s)\n" % errmsg)
434 queryXslt = etree.XSLT(etree.parse(
u'%s/XSLT/giantbombQuery.xsl' % self.
baseProcessingDir))
435 gamebombXpath = etree.FunctionNamespace(
'https://www.mythtv.org/wiki/MythTV_Universal_Metadata_Format')
436 gamebombXpath.prefix =
'gamebombXpath'
438 for key
in list(self.
FuncDict.keys()):
439 gamebombXpath[key] = self.
FuncDict[key]
441 items = queryXslt(queryResult)
443 if items.getroot()
is not None:
444 if len(items.xpath(
'//item')):
445 sys.stdout.write(etree.tostring(items, encoding=
'UTF-8', method=
"xml", xml_declaration=
True, pretty_print=
True, ))
450 """Display a Game details in XML format:
451 https://www.mythtv.org/wiki/MythTV_Universal_Metadata_Format
454 with requests.Session()
as ReqSession:
455 url = self.
config[
'dataURL'] % gameId
458 params[
"api_key"] = self.
config[
'apikey']
459 params[
"format"] =
'xml'
461 headers = self.
config[
'headers']
463 res = ReqSession.get(url, params=params, headers=headers)
466 videoResult = etree.fromstring(res.content)
467 except Exception
as errmsg:
468 sys.stderr.write(
u"! Error: Invalid XML was received from www.giantbomb.com (%s)\n" % errmsg)
471 gameXslt = etree.XSLT(etree.parse(
u'%s/XSLT/giantbombGame.xsl' % self.
baseProcessingDir))
472 gamebombXpath = etree.FunctionNamespace(
'https://www.mythtv.org/wiki/MythTV_Universal_Metadata_Format')
473 gamebombXpath.prefix =
'gamebombXpath'
475 for key
in list(self.
FuncDict.keys()):
476 gamebombXpath[key] = self.
FuncDict[key]
477 items = gameXslt(videoResult)
479 if items.getroot()
is not None:
480 if len(items.xpath(
'//item')):
481 sys.stdout.write(etree.tostring(items, encoding=
'UTF-8', method=
"xml", xml_declaration=
True, pretty_print=
True, ))
488 """Simple example of using giantbomb_api - it just
489 searches for any Game with the word "Grand" in its title and returns a list of matches
490 in Universal XML format. Also gets game details using a GameBomb#.
493 api_key =
"b5883a902a8ed88b15ce21d07787c94fd6ad9f33"
496 gamebomb.gameSearch(
u'Grand')
499 gamebomb.gameData(
u'19995')
502 if __name__ ==
'__main__':