The following code can query the BGG API (XML) for game information - either by BGG identifier or by name (possible with a year)
#!/usr/bin/env python
# --------------------------------------------------------------------
def request(query,base='https://boardgamegeek.com/xmlapi/'):
'''Do a request against BGG API
Parameters
----------
query : str
Query string - need to be URL encoded
Returns
-------
response : str
Response text (XML)
'''
from requests import get
url = base + query
response = get(url)
return response.text
# --------------------------------------------------------------------
def parse(text):
'''Parse XML reponse text and return as dictonary
Parameters
----------
text : str
XML response text
Returns
-------
db : dict
Dictionary of reponse
'''
from xml.dom.minidom import parseString
doc = parseString(text)
top = doc.documentElement
return {top.tagName:parseNode(top)}['boardgames']
# --------------------------------------------------------------------
def parseNode(node):
'''Parse a single node and recurse
Node attributes are stored in returned dictionary.
Child nodes are stored as dictionaries. In case there's more than
one child node of a particular type, then those nodes are stored
in a list.
Parameters
----------
node : xml.dom.minidom.Node
Node to parse
Return
------
db : dict
Dictionary of attributes and children
'''
from xml.dom.minidom import Text, Document
ret = {}
attrs = node.attributes
for ind in range(len(attrs)):
attribute = attrs.item(ind)
ret[attribute.name] = attribute.value
for child in node.childNodes:
if isinstance(child,Text):
data = child.data.strip('\t').strip('\n')
if len(data) > 0:
ret['text'] = data
continue
dct = parseNode(child)
tag = child.tagName
if tag in ret:
if isinstance(ret[tag],dict):
old = ret[tag]
ret[tag] = [old]
ret[tag].append(dct)
else:
ret[tag] = dct
return ret
# --------------------------------------------------------------------
def get_info(name,iden,year=None,exact=True):
'''Get information about a game, by name or identifier
Names are looked up. If year is given, then filter by year. If
exact is true, then match names exactly.
Parameters
----------
iden : int
Game identifier
name : str
Name of game
year : int
Optional year to narrow searces with
exact : boolean
If true, match exact name
Return
------
db : dict
Dictionary of game
opt : list
List of options found
'''
idens = [iden]
if iden is None:
from urllib.parse import quote_plus
qname = quote_plus(name)
info = parse(request(f'search?search={qname}'
f'&exact={1 if exact else 0}'))
cand = info.get('boardgame',None)
if cand is None:
raise RuntimeError(f'"{name}" not found')
if isinstance(cand,list):
idens = [c.get('objectid',None) for c in cand
if year is None or
year == int(c.get('yearpublished',{}).get('text',0))]
ret = [parse(request(f'boardgame/{iden}'))
for iden in idens if iden is not None]
if len(ret) == 1:
return ret[0]
return ret
# ====================================================================
def show(info):
'''Print final result of the query'''
from pprint import pprint
if not isinstance(info,list):
pprint(info)
return
print(f'Got back {len(info)} replies, please narrrow')
for inf in info:
# print(inf)
dct = inf.get('boardgame',{})
nam = dct.get('name',None)
if isinstance(nam,list):
nams = nam
for n in nams:
nam = n
if 'primary' in n and n['primary'] == 'true':
break
nam = nam.get('text','<<?>>')
iden = int(dct.get('objectid',0))
year = int(dct.get('yearpublished',{}).get('text',0))
print(f'{iden:8d}: {nam} ({year})')
# ====================================================================
if __name__ == '__main__':
from argparse import ArgumentParser
from pprint import pprint
ap = ArgumentParser(description='Get info from BGG')
ap.add_argument('-i','--id',type=int,
help='Get by board game ID')
ap.add_argument('-n','--name',type=str,
help='Get by name')
ap.add_argument('-y','--year',type=int,
help='Narrow name search by year')
gr = ap.add_mutually_exclusive_group()
gr.add_argument('--any', action='store_false',dest='exact',default=True,
help='Loose matching of name')
gr.add_argument('--exact', action='store_true',
help='Exact matching of name')
args = ap.parse_args()
# print(vars(args))
if args.name is None and args.id is None:
raise RuntimeError('Please specify a name or id')
show(get_info(args.name,args.id,args.year,args.exact))
# --------------------------------------------------------------------
Use it if you like - perhaps that can help fill some stuff on the game pages too.
According to a previous thread variants of modules by the same maintainer should show up as different packages within the same project (and projects are split by maintainers - but I gues you haven’t done that yet). For Afrika Korps, that means that
AfrikaKorps-2.1-ch.vmod, AfrikaKorps-2.0-ch.vmod, and AfrikaKorps-1.1-ch.vmod should be one package, and
AfrikaKorps-2.1-ch-oldschool.vmod, AfrikaKorps-2.0-ch-oldschool.vmod, and AfrikaKorps-1.1-ch-oldschool.vmod should be a different package.
with obvious version numbers. Similar for D-Daych project.Thanks.
Good point. I was actually comparing the two, anyway (I noticed both versions of Talisman were missing from the new library), but I will explicitly commit to all the "T"s up to “Terrible Swift Sword” as listed in the old library, instead.
The new “Project” section shows owners but does not mention any contributors - even when they are listed in the old “Module Information” section - is that correct?
New games table that is the parent of projects. This subsumes “game” data from the projects table, and includes the bgg_id field to refer back to BoardGameGeek. The front-end can also use that to look-up additional information if so desired. The games rows has two fields summary and text which should contain a short and longer description, respectively, of the game. This could be lifted from BGG - either in the back-end or front-end as needed.
projects table is a lot smaller as most information is stored in the games table. Also note that each projects row has summary and text fields - similar to the games rows. The summary field could be shown by the front end so that users may quickly and easily get an overview of the various projects of a single game.
files do not point to projects but only to packages as each file will belong to a package - not a project.
I think it would be a good idea to get something like this in place before you start to consolidate the database, let alone deploy it. It will be a huge hassle to remake the database to accommodate the “same module” categorisation of “projects”.
In the current database scheme, the game_length field is configured as TEXT, so it seems that there’s no reason why you couldn’t store short, medium, long there. Perhaps you need another field game_minutes which records the average number of minutes a game takes, if given.
I need to look at that a lot more when I have time but I believe we are on the same page.
Ultimately we could eat their db and even create the module pages where we have no actual module in existence and place a method of making the page “currently inactive” until such time somebody makes the module and uploads and gets an option" hi btw is this the game you are referring to ?.."
Take out all the legwork from the developer as far as page creation goes and for us…