def get_guild_user_list(guild_id, bgg=None):
    """Fetch the member list for a BGG Guild"""
    if bgg is None:
        bgg = BoardGameGeek()

    guild = bgg.guild(guild_id)
    return guild.members
示例#2
0
def fill_game_table(game_ids):
    bgg = BoardGameGeek()

    con = mdb.connect(user= '******', passwd='', db='userlist',unix_socket="/tmp/mysql.sock") #user, password, #database, #unix socket

    cur = con.cursor()
    tmpfile = open('gameIDs_done.txt','w')
    
    for idx,id in enumerate(game_ids):

        
        
        if idx%10==0:
            print "On file: ",idx

        
        g = bgg.game(game_id=id)
        
        try:
            cur.execute('''INSERT into games_new (id,name,expansion,year,playtime,bggrank,image,thumbnail,min_players,max_players,min_age,rating_average,rating_average_weight,rating_bayes_average,rating_median,rating_num_weights,rating_stddev,users_commented,users_owned,users_rated,users_trading,users_wanting,users_wishing,mechanics,categories,description)
                       values (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)''',
                        (g.id,g.name.encode('latin-1','ignore'),g.expansion,g.year,g.playing_time,g.boardgame_rank,g.image,g.thumbnail,g.min_players,g.max_players,g.min_age,g.rating_average,g.rating_average_weight,g.rating_bayes_average,g.rating_median,g.rating_num_weights,g.rating_stddev,g.users_commented,g.users_owned,g.users_rated,g.users_trading,g.users_wanting,g.users_wishing,', '.join(g.mechanics),', '.join(g.categories),g.description.encode('latin-1','ignore')))

        except mdb.IntegrityError:
            print "Integrity Error, duplicate game id found: ",g.id
    

        con.commit()
        tmpfile.write(str(id)+'\n')
        

    tmpfile.close()
    con.close()
示例#3
0
def test_sqlite_caching():
    # test that we can use the SQLite cache
    # generate a temporary file
    fd, name = tempfile.mkstemp(suffix=".cache")

    # close the file and unlink it, we only need the temporary name
    os.close(fd)
    os.unlink(name)

    assert not os.path.isfile(name)

    with pytest.raises(BoardGameGeekError):
        # invalid value for the ttl parameter
        BoardGameGeek(cache="sqlite://{}?ttl=blabla&fast_save=0".format(name))

    with pytest.raises(BoardGameGeekError):
        BoardGameGeek(cache="invalid://cache")

    bgg = BoardGameGeek(cache="sqlite://{}?ttl=1000".format(name))

    user = bgg.user(TEST_VALID_USER)
    assert user is not None
    assert user.name == TEST_VALID_USER

    assert os.path.isfile(name)

    # clean up..
    os.unlink(name)
示例#4
0
    def get(self, request):
        bgg = BoardGameGeek(retries=10, retry_delay=2)
        game = bgg.game("Dominion")
        game_rank = game.id

        ctx = {'game': game_rank}
        return render(request, 'board/bgg.html', ctx)
示例#5
0
def test_sqlite_caching():
    # test that we can use the SQLite cache
    # generate a temporary file
    fd, name = tempfile.mkstemp(suffix=".cache")

    # close the file and unlink it, we only need the temporary name
    os.close(fd)
    os.unlink(name)

    assert not os.path.isfile(name)

    with pytest.raises(BoardGameGeekError):
        # invalid value for the ttl parameter
        BoardGameGeek(cache="sqlite://{}?ttl=blabla&fast_save=0".format(name))

    with pytest.raises(BoardGameGeekError):
        BoardGameGeek(cache="invalid://cache")

    bgg = BoardGameGeek(cache="sqlite://{}?ttl=1000".format(name))

    user = bgg.user(TEST_VALID_USER)
    assert user is not None
    assert user.name == TEST_VALID_USER

    assert os.path.isfile(name)

    # clean up..
    os.unlink(name)
示例#6
0
def test_no_caching():
    # test that we can disable caching
    bgg = BoardGameGeek(cache=None)

    user = bgg.user(TEST_VALID_USER)

    assert user is not None
    assert user.name == TEST_VALID_USER
示例#7
0
def test_no_caching():
    # test that we can disable caching
    bgg = BoardGameGeek(cache=None)

    user = bgg.user(TEST_VALID_USER)

    assert user is not None
    assert user.name == TEST_VALID_USER
示例#8
0
def bgg_game(request, game_id):

    bgg = BoardGameGeek()

    # we return error if game already in DB
    try:
        game = Game.objects.get(id_bgg=game_id)
    except Game.DoesNotExist:
        # the game is not in the DB. All fine
        # fetch game data from BGG
        try:
            bgg_game = bgg.game(game_id=game_id)
        except BoardGameGeekError:
            pass

        # We try to guess the game type - for suggestion to the user
        if "Children's Game" in bgg_game.categories:
            type_genre = 'Enfants'
        elif 'Abstract Strategy' in bgg_game.categories:
            type_genre = 'Stratégie'
        elif 'Area Control / Area Influence' in bgg_game.mechanics or 'Tile Placement' in bgg_game.mechanics:
            type_genre = 'Placement'
        elif 'Worker Placement' in bgg_game.mechanics:
            type_genre = 'Gestion'
        elif 'Racing' in bgg_game.mechanics:
            type_genre = 'Parcours'
        elif 'Auction/Bidding' in bgg_game.mechanics:
            type_genre = 'Enchères'
        elif 'Co-operative Play' in bgg_game.mechanics:
            type_genre = 'Coopératif'
        elif 'Party Game' in bgg_game.categories:
            type_genre = 'Ambiance'
        else:
            type_genre = None

        data = {
            'id_bgg': bgg_game.id,
            'name': bgg_game.name,
            'type_genre': type_genre,
            'min_player': bgg_game.min_players,
            'max_player': bgg_game.max_players,
            'min_age': bgg_game.min_age,
            'duration': bgg_game.playing_time,
            'description': bgg_game.description,
            'thumbnail': bgg_game.thumbnail,
            'image': bgg_game.image
        }

        return JsonResponse(data)
    else:
        # Game is already in DB - return 400
        return JsonResponse(
            {"__errors__": [
                {
                    'field': 'ID BGG',
                    'errors': ['Ce jeu existe deja. Nom du jeu: %s (ID BGG: %s)' % (game.name, game_id)]
                }
            ]}, status=400)
示例#9
0
 def save(self, *args, **kwargs):
     bgg = BoardGameGeek()
     game = bgg.game(game_id=self.bgg_id)
     self.name = "{0} ({1})".format(game.name, game.year)
     if game.image:
         img_response  = requests.get(game.image)
         filename = game.image.split('/')[-1]
         self.image.save(filename, ContentFile(img_response.content), save=False)
     self.data['url'] = "https://boardgamegeek.com/boardgame/{0}".format(game.id)
     super().save(*args, **kwargs)
def get_game_info(game_id, bgg=None):
    """Fetch the BGG info for game having game_id"""
    if bgg is None:
        bgg = BoardGameGeek()

    try:
        game = bgg.game(game_id=game_id)
    except BoardGameGeekAPIRetryError:
        game = None

    return game
示例#11
0
 def render_to_response(self, context, **response_kwargs):
     bgg = BoardGameGeek()
     results = bgg.search(
         self.request.GET.get('query'),
         search_type = ['boardgame']
     )
     result_dict = [{'id': 'bgg_{0}'.format(r.id), 'name': "{0} ({1})".format(r.name, r.year)} for r in results]
     filtered_results = list(dict((v['id'], v) for v in result_dict).values())
     return JsonResponse(
         {"results": filtered_results}
     )
示例#12
0
def bgg_game(request, game_id):

	bgg = BoardGameGeek()

	# we return error if game already in DB
	try:
		game = Game.objects.get(id_bgg=game_id)
	except Game.DoesNotExist:
		# the game is not in the DB. All fine
		# fetch game data from BGG
		try:
			bgg_game = bgg.game(game_id=game_id)
		except BoardGameGeekError:
			pass

		# We try to guess the game type - for suggestion to the user
		if "Children's Game" in bgg_game.categories:
			type_genre = 'Enfants'
		elif 'Abstract Strategy' in bgg_game.categories:
			type_genre = 'Stratégie'
		elif 'Area Control / Area Influence' in bgg_game.mechanics or 'Tile Placement' in bgg_game.mechanics:
			type_genre = 'Placement'
		elif 'Worker Placement' in bgg_game.mechanics:
			type_genre = 'Gestion'
		elif 'Racing' in bgg_game.mechanics:
			type_genre = 'Parcours'
		elif 'Auction/Bidding' in bgg_game.mechanics:
			type_genre = 'Enchères'
		elif 'Co-operative Play' in bgg_game.mechanics:
			type_genre = 'Coopératif'
		elif 'Party Game' in bgg_game.categories:
			type_genre = 'Ambiance'
		else:
			type_genre = None

		data = {
			'id_bgg': bgg_game.id,
			'name': bgg_game.name,
			'type_genre': type_genre,
			'min_player': bgg_game.min_players,
			'max_player': bgg_game.max_players,
			'min_age': bgg_game.min_age,
			'duration': bgg_game.playing_time,
			'description': bgg_game.description,
			'thumbnail': bgg_game.thumbnail,
			'image': bgg_game.image
		}

		return JsonResponse(data)
		
	else:
		# Game is already in DB - return 400
		return JsonResponse({"__errors__": [{'field': 'ID BGG', 'errors': ['Ce jeu existe deja. Nom du jeu: %s (ID BGG: %s)' % (game.name, game_id)]}]}, status=400)
def get_user_ratings(username, bgg=None):
    """Returns a dict: gameid -> rating"""
    if bgg is None:
        bgg = BoardGameGeek()

    collection = bgg.collection(username)

    user_ratings = dict()
    for item in collection:
        if item.rating:
            user_ratings[item.id] = item.rating

    return user_ratings
示例#14
0
def test_rate_limiting_for_requests():
    # create two threads, give each a list of games to fetch, disable cache and time the amount needed to
    # fetch the data. requests should be serialized, even if made from two different threads

    test_set_1 = [5,    # acquire
                  31260, # agricola
                  72125] # "eclipse"

    test_set_2 = [18602, #caylus
                28720, #  brass
                53953] # thunderstone]

    def _worker_thread(games):
        bgg = BoardGameGeek(cache=None, requests_per_minute=20)
        for g in games:
            bgg.game(game_id=g)

    t1 = threading.Thread(target=_worker_thread, args=(test_set_1, ))
    t2 = threading.Thread(target=_worker_thread, args=(test_set_2, ))

    start_time = time.time()
    t1.start()
    t2.start()

    t1.join(timeout=10000)
    t2.join(timeout=10000)
    end_time = time.time()

    # 20 requests per minute => a request every 3 seconds x 6 games => should take around 18 seconds
    assert 15 < end_time - start_time < 21      # +/- a few seconds...


    # second test, use caching and confirm it's working when combined with the rate limiting algorithm
    # do cached requests for the test set, then do them again. should take only half of the time

    bgg = BoardGameGeek(requests_per_minute=20)

    start_time = time.time()
    for g in test_set_1:
        bgg.game(game_id=g)
    end_time = time.time()

    assert 7 < end_time - start_time < 11       # 3 games should take ~9 seconds

    # repeat requests, should be served from cache
    for g in test_set_1:
        bgg.game(game_id=g)

    assert 0 < time.time() - end_time < 2
示例#15
0
 def __init__(self, db):
     self.db = db
     self.bgg = BoardGameGeek()
     self.publishers = {
             "999 Games": "&include%5Bpublisherid%5D=267",
             "Asmodee": "&include%5Bpublisherid%5D=157",
             "Avalon Hill": "&include%5Bpublisherid%5D=5",
             "Bergsala Enigma": "&include%5Bpublisherid%5D=6784",
             "Cool mini or not": "&include%5Bpublisherid%5D=34793",
             "Czech Games Edition": "&include%5Bpublisherid%5D=7345",
             "Days of Wonder": "&include%5Bpublisherid%5D=1027",
             "Don & Co": "&include%5Bpublisherid%5D=137",
             "Fantasy Flight Games": "&include%5Bpublisherid%5D=17",
             "Fun Forge": "&include%5Bpublisherid%5D=8832",
             "Games Workshop": "&include%5Bpublisherid%5D=26",
             "Guillotine Games": "&include%5Bpublisherid%5D=21020",
             "Hurrican": "&include%5Bpublisherid%5D=6015",
             "Iello": "&include%5Bpublisherid%5D=8923",
             "Intrafin Games": "&include%5Bpublisherid%5D=5380",
             "Libellud": "&include%5Bpublisherid%5D=9051",
             "Ludonaute": "&include%5Bpublisherid%5D=11688",
             "Monolith Games": "&include%5Bpublisherid%5D=27147",
             "Osprey Games": "&include%5Bpublisherid%5D=29313",
             "Plaid Hat Games": "&include%5Bpublisherid%5D=10754",
             "Queen Games": "&include%5Bpublisherid%5D=47",
             "Repos Production": "&include%5Bpublisherid%5D=4384",
             "Space Cowboys": "&include%5Bpublisherid%5D=25842",
             "Steve Jackson Games": "&include%5Bpublisherid%5D=19",
             "Story Factory": "&include%5Bpublisherid%5D=17940",
             "Studio McVey": "&include%5Bpublisherid%5D=21608",
             "The Game Master": "&include%5Bpublisherid%5D=2862",
             "White Goblin Games": "&include%5Bpublisherid%5D=4932",
             "Z-Man Games": "&include%5Bpublisherid%5D=538"
         }
示例#16
0
def getNotFoundGames(games):
    '''take a list of games and return those that are not found.'''
    bgg = BGG()
    not_found = []
    for game in games:
        try:
            log.info(u'Looking for {} on BGG.'.format(game))
            found = bgg.game(game)
        except boardgamegeek.exceptions.BoardGameGeekError as e:
            log.critical(u'Error talking to BGG about {}'.format(game))
            continue

        if not found:
            log.warning(u'Unable to find {} on BGG'.format(game))
            not_found.append(game)

    return not_found
示例#17
0
def getNotFoundGames(games):
    '''take a list of games and return those that are not found.'''
    bgg = BGG()
    not_found = []
    for game in games:
        try:
            log.info(u'Looking for {} on BGG.'.format(game))
            found = bgg.game(game)
        except boardgamegeek.exceptions.BoardGameGeekError as e:
            log.critical(u'Error talking to BGG about {}'.format(game))
            continue

        if not found:
            log.warning(u'Unable to find {} on BGG'.format(game))
            not_found.append(game)

    return not_found
示例#18
0
    def __init__(self, UID, botdb):
        self._botdb = botdb
        self._botname = UID
        self._header = (
            u'^*[{}](/r/r2d8)* ^*issues* ^*a* ^*series* ^*of* ^*sophisticated* '
            u'^*bleeps* ^*and* ^*whistles...*\n\n'.format(self._botname))

        dbpath = pjoin(getcwd(), u'{}-bgg.db'.format(self._botname))
        self._bgg = BGG(cache=u'sqlite://{}?ttl=86400'.format(dbpath))
示例#19
0
    def get(self, request, game_id):
        form = Game.objects.get(pk=game_id)
        title_en = form.title_en

        try:
            bgg = BoardGameGeek(retries=10, retry_delay=2)
            game = bgg.game(title_en)
            game_rank = game.ranks
            game_rank_dict = game_rank[0]
            rank = game_rank_dict['value']

            ctx = {'form': form, 'game_rank': rank}
            return render(request, 'board/game.html', ctx)

        except AttributeError:
            rank = "n/a"
            ctx = {'form': form, 'game_rank': rank}
            return render(request, 'board/game.html', ctx)
示例#20
0
def bgg_games(ids=None, user=None, filename=None, number=None, progress=False,
              **kwargs):
    """Return a list of BoardGameGeek games; sourced by ID, or user, or file

    Args:
        ids: list
            games IDs (integers) used by BGG
        user: string
            BGG user name; if supplied, then :
            * IDs will be ignored
        filename: string
            name of file containing game data saved in JSON format
        number: integer
            max no. of valid games to retrieve; default is 10
        progress: boolean
            show which games are being retrieved
    """
    if not number:
        number = 10
    else:
        number = int(number)
    tmpdir = tempfile.mkdtemp()
    predictable_filename = 'bgggames.cache'
    dbname = os.path.join(tmpdir, predictable_filename)
    bgg = BoardGameGeek(cache="sqlite://{}?ttl=1000".format(dbname),
                        disable_ssl=True)
    games = []
    if user:
        ids = []
        #bgg_user = bgg.user(user)
        try:
            collection = bgg.collection(user)
            if collection:
                for game in collection:
                    ids.append(game.id)
            else:
                print 'Unable to retrieve collection for %s - do they exist?' \
                    % user
        except BoardGameGeekAPIRetryError, err:
            print err
示例#21
0
 def __init__(self, game_id, short=500):
     """
     Args:
         short: int
             number of characters to use for short description
     """
     self._game = None
     self.short = int(short) or 500
     self.bgg = BoardGameGeek(disable_ssl=True)
     if isinstance(game_id, int):
         self._game = self.bgg.game(game_id=game_id)
     elif isinstance(game_id, ""):
         self._game = self.bgg.game(name=game_id)
     else:
         print
     self.set_properties()
示例#22
0
def test_rate_limiting_for_requests():
    # create two threads, give each a list of games to fetch, disable cache and time the amount needed to
    # fetch the data. requests should be serialized, even if made from two different threads

    test_set_1 = [
        5,  # acquire
        31260,  # agricola
        72125
    ]  # "eclipse"

    test_set_2 = [
        18602,  #caylus
        28720,  #  brass
        53953
    ]  # thunderstone]

    def _worker_thread(games):
        bgg = BoardGameGeek(cache=None, requests_per_minute=20)
        for g in games:
            bgg.game(game_id=g)

    t1 = threading.Thread(target=_worker_thread, args=(test_set_1, ))
    t2 = threading.Thread(target=_worker_thread, args=(test_set_2, ))

    start_time = time.time()
    t1.start()
    t2.start()

    t1.join(timeout=10000)
    t2.join(timeout=10000)
    end_time = time.time()

    # 20 requests per minute => a request every 3 seconds x 6 games => should take around 18 seconds
    assert 15 < end_time - start_time < 21  # +/- a few seconds...

    # second test, use caching and confirm it's working when combined with the rate limiting algorithm
    # do cached requests for the test set, then do them again. should take only half of the time

    bgg = BoardGameGeek(requests_per_minute=20)

    start_time = time.time()
    for g in test_set_1:
        bgg.game(game_id=g)
    end_time = time.time()

    assert 7 < end_time - start_time < 11  # 3 games should take ~9 seconds

    # repeat requests, should be served from cache
    for g in test_set_1:
        bgg.game(game_id=g)

    assert 0 < time.time() - end_time < 2
示例#23
0
def bgg():
    return BoardGameGeek(cache=None, retries=0,
                         retry_delay=0)  # disable retrying for testing
示例#24
0
文件: bgg_games.py 项目: ed-kung/bgg
import csv
import time
from boardgamegeek import BoardGameGeek

bgg = BoardGameGeek()

# first import the game id list
f = open("bgg_game_ids.txt")
idlist = []
for line in f:
	idlist.append(int(line.split()[0]))
f.close()

# data file
datafile = "bgg_games.csv"

# max and min game id 
# (if you don't want to scrape the whole dataset in one go)
min_game_id = 1
max_game_id = 100

# header line (variable names)
header = (	'snapshot_date', \
			'id', \
			'name', \
			'year', \
			'artists', \
			'categories', \
			'designers', \
			'expansion', \
			'expands', \
示例#25
0
username = "******"    # your name
oldest_game = datetime(2015,12,31,0,0,0) # year, month, day of the oldest game you want to import (only valid for first time running the script)

# connect to spreadsheet
json_key = json.load(open(json_file))
scope = ['https://spreadsheets.google.com/feeds']
credentials = SignedJwtAssertionCredentials( json_key['client_email'], json_key['private_key'], scope)

gc = gspread.authorize(credentials)

sh = gc.open(worksheet)
sheet1 = sh.worksheet("Sheet1")


# connect to BGG
bgg = BoardGameGeek()
userplays = bgg.plays(bgg_username).plays

# most recent game in spreadsheet
gamedates = sheet1.col_values(1)
oldest_game.strftime('%m/%d/%Y')

if gamedates[len(gamedates)-1] == 'date':
    latestgame = oldest_game
else:
    latestgame = gamedates[len(gamedates)-1]
    latestgame = datetime.strptime(latestgame,'%m/%d/%Y')

lastrow = len(gamedates)

headers = sheet1.row_values(1)
示例#26
0
"""
This script creates a list of all games belonging to my collection
on boardgamegeek.com, and saves that list to a JSON file. It uses the 
BoardGameGeek library to get the collection and game information.   
"""
from boardgamegeek import BoardGameGeek
import json
import urllib
import re

bgg = BoardGameGeek()
collection = bgg.collection("escrimeuse")
# First we get the IDs of all the games in the collection
ids = []
for item in collection:
	ids.append(item.id)
# Now we get the information for each game
games = []
for i in ids:
	games.append(bgg.game(None,i))
# We're only interested in a subset of properties, so we create new objects
wanted_keys = ["name", "year", "mechanics", "thumbnail", "categories", "max_players", "min_players", "playing_time"]
new_games = [ {
	"id" : game.id,
	"name" : game.name,
	"year" : game.year,
	"mechanic" : game.mechanics,
	"thumbnail" : game.thumbnail,
	"category" : game.categories,
	"max_players" : game.max_players,
	"min_players" : game.min_players,
示例#27
0
import csv
import time
from boardgamegeek import BoardGameGeek
import json
import diskcache as dc
import progressbar

cache = dc.Cache('tmp')

name = {
    'Ludimus': 'Ludimus',
}

bgg = BoardGameGeek(retries=10)


def create_line(pair):
    id, owners = pair

    if cache.get(id, default=False):
        return json.loads(cache[id])

    try:
        game = bgg.game(game_id=id)
    except:
        time.sleep(1)
        try:
            game = bgg.game(game_id=id)
        except:
            return None
    if game.expansion:
示例#28
0
import json
import requests
import datetime
from boardgamegeek import BoardGameGeek
from collections import defaultdict

# DATA PATH
DIRPATH = "/data/bodogeimu"

# Hipchat Option
URL="api.hipchat.com"
AUTH_TOKEN=""
ROOM=""

# BoardGameGeek Object
bgg = BoardGameGeek()

# ========================================
# Hipchat Notify
# ========================================
def notify_hipchat(rank, rank_diff) :

    # Message Option
    CARD_URL="https://www.boardgamegeek.com/boardgame/191894/imagine"
    ID="ranking"
    TITLE="BoardGameGeek Imagine Ranking"
    ICON_URL="http://cf.geekdo-images.com/images/pic3061260.jpg"
    COLOR="yellow"
    DESCRIPTION_VALUE="2016/08/24 <b>"+str(rank)+"位</b> (前日比"+rank_diff+") です"

    # Create CONTENT
示例#29
0
 def _worker_thread(games):
     bgg = BoardGameGeek(cache=None, requests_per_minute=20)
     for g in games:
         bgg.game(game_id=g)
示例#30
0
from boardgamegeek import BoardGameGeek
from boardgamegeek import BoardGameGeekAPIError
import pandas as pd
bgg = BoardGameGeek()
gameframe = pd.DataFrame({
    "Name": [],
    "Year": [],
    "ID": [],
    "BGGRank": [],
    "Mechanics": [],
    "Playtime": [],
    "Min_Age": [],
    "Median_Rating": [],
    "Bays_Rating": []
})
for i in range(1, 12):
    try:
        g = bgg.game(game_id=i)
        gamedat = pd.DataFrame({
            "Name": [g.name],
            "ID": [g.id],
            "Year": [g.year],
            "BGGRank": [g.boardgame_rank],
            "Mechanics": [g.mechanics],
            "Playtime": [g.playing_time],
            "Min_Age": [g.min_age],
            "Median_Rating": [g.rating_median],
            "Bays_Rating": [g.rating_bayes_average]
        })

        gameframe = pd.concat([gameframe, gamedat])
示例#31
0
 def _worker_thread(games):
     bgg = BoardGameGeek(cache=None, requests_per_minute=20)
     for g in games:
         bgg.game(game_id=g)
示例#32
0
class CommentHandler(object):
    def __init__(self, UID, botdb):
        self._botdb = botdb
        self._botname = UID
        self._header = (
            '^*[{}](/r/r2d8)* ^*issues* ^*a* ^*series* ^*of* ^*sophisticated* '
            '^*bleeps* ^*and* ^*whistles...*\n\n'.format(self._botname))
        self._footer = ''

        dbpath = pjoin(getcwd(), '{}-bgg.db'.format(self._botname))
        self._bgg = BGG(cache='sqlite://{}?ttl=86400'.format(dbpath))

    def _bggQueryGame(self, name):
        '''Try "name", then if not found try a few other small things in an effort to find it.'''
        name = name.lower().strip(
        )  # GTL extra space at ends shouldn't be matching anyway, fix this.
        if not name:
            return None

        if len(name) > 128:
            log.warn('Got too long game name: {}'.format(name))
            return None

        # Search IDs when name format is '#1234'
        if re.search('^#(\d+)$', name):
            game = self._bgg.game(name=None, game_id=name[1:])
            if game:
                log.debug('found game {} via searching by ID'.format(name))
                return game

        game = self._bgg.game(name)
        if game:
            return game

        # embedded url? If so, extract.
        log.debug('Looking for embedded URL')
        m = re.search('\[([^]]*)\]', name)
        if m:
            name = m.group(1)
            game = self._bgg.game(name)
            if game:
                return game

        # note: unembedded from here down
        # remove 'the's
        log.debug('removing "the"s')
        tmpname = re.sub('^the ', '', name)
        tmpname = re.sub('\sthe\s', ' ', tmpname)
        if tmpname != name:
            game = self._bgg.game(tmpname)
            if game:
                return game

        # add a "the" at start.
        log.debug('adding "the" at start')
        game = self._bgg.game('The ' + name)
        if game:
            return game

        # various substistutions.
        subs = [('[?!.:,]*', '', 'removing punctuation'),
                ('\sand\s', ' & ', 'and --> &'),
                ('\s&\s', ' and ', '& --> and')]
        for search, sub, logmess in subs:
            log.debug(logmess)
            tmpname = re.sub(search, sub, name)
            if tmpname != name:
                game = self._bgg.game(tmpname)
                if game:
                    return game

        # well OK - let's pull out the heavy guns and use the search API.
        # this will give us a bunch of things to sort through, but hopefully
        # find something.
        return self._bggSearchGame(name)

    def _bggSearchGame(self, name):
        '''Use the much wider search API to find the game.'''
        items = self._bgg.search(
            name,
            search_type=BoardGameGeekNetworkAPI.SEARCH_BOARD_GAME,
            exact=True)
        if items and len(items) == 1:
            log.debug('Found exact match using search().')
            return self._bgg.game(items[0].name)

        # exact match not found, trying sloppy match
        items = self._bgg.search(
            name, search_type=BoardGameGeekNetworkAPI.SEARCH_BOARD_GAME)
        if items and not len(items):
            log.debug('Found no matches at all using search().')
            return None

        if items and len(items) == 1:
            log.debug('Found one match usinh search().')
            return self._bgg.game(items[0].name)

        if not items:
            return None

        # assume most owned is what people want. Is this good? Dunno.
        most_owned = None
        for i in items:
            game = self._bgg.game(None, game_id=i.id)
            # GTL the lib throws an uncaught exception if BGG assessed too quickly.
            # GTL - this needs to be fixed in the library.
            sleep(1)
            if getattr(game, 'expansion', False):
                log.debug('ignoring expansion')
                continue
            else:
                if not most_owned:
                    most_owned = game
                else:
                    most_owned = game if getattr(
                        game, 'owned', 0) > most_owned.owned else most_owned

        if most_owned:
            return most_owned

        return None

    NO_SORT_KEYWORD = 'nosort'
    DEFAULT_SORT = 'sort_name'
    SORT_FUNCTIONS = {
        'sort_name': lambda g: g.name,
        'sort_year': lambda g: g.year,
        'sort_rank': lambda g: g.rank
        # TODO
    }

    def _findGames(self, items, sort='name'):
        # convert aliases to real names. It may be better to do this after we don't find the
        # game. Oh, well.
        #   I think this might be better behavior, since it makes it easy to
        #   replace findable-but-unlikely results with the more popular result
        #   that was probably intended. -TDHS
        for i in range(len(items)):
            real_name = self._botdb.get_name_from_alias(items[i])
            if real_name:
                items[i] = real_name

        # filter out dups.
        items = list(set(items))
        items = [unquote(b) for b in items]

        games = []
        not_found = []

        seen = set()
        for game_name in items:
            log.info('asking BGG for info on {}'.format(game_name))
            try:
                # game = self._bgg.game(game_name)
                game = self._bggQueryGame(game_name)
                if game:
                    if game.id not in seen:
                        games.append(game)
                    # don't add dups. This can happen when the same game is calledby two valid
                    # names in a post.
                    seen.add(game.id)
                else:
                    not_found.append(game_name)

            except boardgamegeek.exceptions.BoardGameGeekError as e:
                log.error('Error getting info from BGG on {}: {}'.format(
                    game_name, e))
                continue

        # sort by game name because why not?
        if sort and sort in self.SORT_FUNCTIONS:
            fn = self.SORT_FUNCTIONS.get(sort)
            games = sorted(games, key=fn)

        return [games, not_found]

    def _getBoldedEntries(self, comment):
        body = comment.body
        # bolded = re.findall(u'\*\*([^\*]+)\*\*', body)
        # Now I've got two problems.
        bolded = re.findall(
            '\*\*(#?[\w][\w\.\s:\-?$,!\'–&()\[\]]*[\w\.:\-?$,!\'–&()\[\]])\*\*',
            body,
            flags=re.UNICODE)
        if not bolded:
            log.warn(
                'Got getinfo command, but nothing is bolded. Ignoring comment.'
            )
            log.debug('comment was: {}'.format(body))
            return
        # we now have all the games.

        if comment.subreddit.display_name.lower() == 'boardgamescirclejerk':
            cjgames = ['Gloomhaven', 'Patchwork', 'Scythe']
            bolded = [choice(cjgames), 'Keyforge', 'Keyforge', 'Keyforge']
        return bolded

    def _getInfoResponseBody(self,
                             comment,
                             gameNames,
                             mode,
                             columns=None,
                             sort=None):
        assert mode
        assert gameNames

        [games, not_found] = self._findGames(gameNames, sort)

        # disallow long mode for
        if mode == 'long' and len(games) > 6:
            mode = 'short'

        if comment.subreddit.display_name.lower() == 'boardgamescirclejerk':
            not_found = None

        if not_found:
            log.debug('not found: {}'.format(', '.join(not_found)))

        if games:
            log.debug('Found games {}'.format(','.join(
                ['{} ({})'.format(g.name, g.year) for g in games])))
        else:
            log.warn('Found no games in comment {}'.format(comment.id))

        log.warning('Using mode {} and columns {}'.format(mode, columns))

        if mode == 'short':
            infos = self._getShortInfos(games)
        elif mode == 'long':
            infos = self._getLongInfos(games)
        elif mode == 'tabular':
            assert columns
            infos = self._getInfoTable(games, columns)
        else:
            infos = self._getStdInfos(games)

        # append not found string if we didn't find a bolded string.
        if not_found:
            not_found = [
                '[{}](http://boardgamegeek.com/geeksearch.php?action=search'
                '&objecttype=boardgame&q={}&B1=Go)'.format(n, quote(n))
                for n in not_found
            ]
            infos.append(
                '\n\nBolded items not found at BGG (click to search): {}\n\n'.
                format(', '.join(not_found)))

        response = None
        if len(infos):
            response = self._header + '\n'.join([i for i in infos
                                                 ]) + self._footer
            # TODO: why the copied list?

        return response

    def _getPlayers(self, game):
        if not game.min_players:
            return None

        if game.min_players == game.max_players:
            players = '{} p'.format(game.min_players)
        else:
            players = '{}-{} p'.format(game.min_players, game.max_players)

        return players

    DISPLAY_MODES = ['short', 'standard', 'long', 'tabular']
    DEFAULT_DISPLAY_MODE = 'standard'

    def getInfo(self,
                comment: praw.models.Comment,
                subcommands: list,
                config: dict,
                replyTo=None):
        '''Reply to comment with game information. If replyTo is given reply to original else
        reply to given comment.'''
        if self._botdb.ignore_user(comment.author.name):
            log.info("Ignoring comment by {}".format(comment.author.name))
            return

        mode = None
        if len(subcommands) > 0 and subcommands[0].lower(
        ) in self.DISPLAY_MODES:
            mode = subcommands[0].lower()
        else:
            mode = self.DEFAULT_DISPLAY_MODE
        columns = subcommands[1:] if mode == 'tabular' else None

        sort = self.DEFAULT_SORT
        if self.NO_SORT_KEYWORD in subcommands:
            sort = None
        else:
            for sort_type in self.SORT_FUNCTIONS.keys():
                if sort_type in subcommands:
                    sort = sort_type
                    break

        footer = '\n' + config['footer'] if 'footer' in config else ''

        bolded = self._getBoldedEntries(comment)
        response = None
        if bolded:
            response = self._getInfoResponseBody(comment, bolded, mode,
                                                 columns, sort)
        if response:
            if replyTo:
                replyTo.reply(response + footer)
            else:
                comment.reply(response + footer)
            log.info('Replied to info request for comment {}'.format(
                comment.id))
        else:
            log.warn('Did not find anything to reply to in comment {}'.format(
                comment.id))

    def _getShortInfos(self, games):
        infos = list()
        for game in games:
            players = self._getPlayers(game)
            info = (' * [**{}**](http://boardgamegeek.com/boardgame/{}) '
                    ' ({}) by {}. '.format(
                        game.name, game.id, game.year,
                        ', '.join(getattr(game, 'designers', 'Unknown'))))
            if players:
                info += '{}; '.format(players)
            if game.playing_time and int(game.playing_time) != 0:
                info += '{} mins '.format(game.playing_time)

            infos.append(info)

        return infos

    def _getStdInfos(self, games):
        infos = list()
        for game in games:
            players = self._getPlayers(game)
            info = ('[**{}**](http://boardgamegeek.com/boardgame/{}) '
                    ' ({}) by {}. {}; '.format(
                        game.name, game.id, game.year,
                        ', '.join(getattr(game, 'designers',
                                          'Unknown')), players))

            if game.playing_time and int(game.playing_time) != 0:
                info += '{} minutes; '.format(game.playing_time)

            if game.image:
                info += '[BGG Image]({}) '.format(game.image)

            info += '\n\n'

            data = ', '.join(getattr(game, 'mechanics', ''))
            if data:
                info += ' * Mechanics: {}\n'.format(data)
            people = 'people' if game.users_rated > 1 else 'person'
            info += ' * Average rating is {}; rated by {} {}. Weight: {}\n'.format(
                game.rating_average, game.users_rated, people,
                game.rating_average_weight)
            data = ', '.join([
                '{}: {}'.format(r['friendlyname'], r['value'])
                for r in game.ranks
            ])
            info += ' * {}\n\n'.format(data)

            log.debug('adding info: {}'.format(info))
            infos.append(info)

        return infos

    ALLOWED_COLUMNS = {
        'year': 'Year Published',
        # 'designers': 'Designers',
        # 'artists': 'Artists',
        'rank': 'BGG Rank',
        'rating': 'Average Rating',
        'score': 'Geek Score (Weighted Rating)',
        'rating_median': 'Median Rating',
        'rating_stddev': 'Rating Standard Deviation',
        'raters': 'Rating Count',
        'owners': 'BGG Owner Count',
        # 'playercount': 'Player Count (min-max)'
        'id': 'BGG ID'
    }

    def _getGameColumn(self, game: boardgamegeek.games.BoardGame, column):
        if column == 'year':
            return str(game.year)
        elif column == 'rank':
            return str(game.boardgame_rank)
        elif column == 'rating':
            return str(game.rating_average)
        elif column == 'score':
            return str(game.rating_bayes_average)
        elif column == 'rating_median':
            return str(game.rating_median)
        elif column == 'rating_stddev':
            return str(game.rating_stddev)
        elif column == 'raters':
            return str(game.users_rated)
        elif column == 'owners':
            return str(game.users_owned)
        elif column == 'id':
            return str(game.id)
        return 'unknown `{}`'.format(column)

    def _getInfoTable(self, games, columns):
        rows = list()
        # build header
        header = 'Game Name'
        alignment = ':--'
        unknownColumns = list()

        for column in columns:
            if column not in self.ALLOWED_COLUMNS:
                log.info('Unknown tabular column {}, skipping'.format(column))
                unknownColumns.append(column)
                continue
            header = header + '|' + self.ALLOWED_COLUMNS[column]
            alignment = alignment + '|:--'

        rows.append(header)
        rows.append(alignment)
        columns = [c for c in columns if c not in unknownColumns]

        # build rows
        for game in games:
            row = '[**{}**](http://boardgamegeek.com/boardgame/{})'.format(
                game.name, game.id)
            for column in columns:
                row = row + '|' + self._getGameColumn(game, column)
            log.info('adding info: {}'.format(row))
            rows.append(row)

        return rows

    def _getLongInfos(self, games):
        infos = list()
        for game in games:
            players = self._getPlayers(game)
            info = (
                'Details for [**{}**](http://boardgamegeek.com/boardgame/{}) '
                ' ({}) by {}. '.format(
                    game.name, game.id, game.year,
                    ', '.join(getattr(game, 'designers', 'Unknown'))))
            if players:
                info += '{}; '.format(players)
            if game.playing_time and int(game.playing_time) != 0:
                info += '{} minutes; '.format(game.playing_time)
            if game.image:
                info += '[BGG Image]({}) '.format(game.image)
            info += '\n\n'

            data = ', '.join(getattr(game, 'mechanics', ''))
            if data:
                info += ' * Mechanics: {}\n'.format(data)
            people = 'people' if game.users_rated > 1 else 'person'
            info += ' * Average rating is {}; rated by {} {}\n'.format(
                game.rating_average, game.users_rated, people)
            info += ' * Average Weight: {}; Number of Weights {}\n'.format(
                game.rating_average_weight, game.rating_num_weights)
            data = ', '.join([
                '{}: {}'.format(r['friendlyname'], r['value'])
                for r in game.ranks
            ])
            info += ' * {}\n\n'.format(data)

            info += 'Description:\n\n{}\n\n'.format(game.description)

            if len(games) > 1:
                info += '------'

            log.debug('adding info: {}'.format(info))
            infos.append(info)

        return infos

    def repairComment(self, comment: praw.models.Comment, subcommands: list,
                      config: dict):
        '''Look for maps from missed game names to actual game names. If
        found repair orginal comment.'''
        if self._botdb.ignore_user(comment.author.name):
            log.info("Ignoring comment by {}".format(comment.author.name))
            return
        #
        # The repair is done by replacing the new games names with the old (wrong)
        # games names in the original /u/r2d8 response, then recreating the entire
        # post by regenerating it with the new (fixed) bolded game names. The just replacing
        # the orginal response with the new one.
        #
        log.debug('Got repair response, id {}'.format(comment.id))

        if comment.is_root:
            # error here - this comment should be in response to a u/r2d8 comment.
            log.info('Got a repair comment as root, ignoring.')
            return

        parent = comment.parent()
        if parent.author.name != self._botname:
            log.info(
                'Parent of repair comment is not authored by the bot, ignoring.'
            )
            return

        # Look for patterns of **something**=**somethingelse**. This line creates a dict
        # of something: somethingelse for each one pattern found.
        repairs = {
            match[0]: match[1]
            for match in re.findall('\*\*([^\*]+)\*\*=\*\*([^\*]+)\*\*',
                                    comment.body)
        }

        pbody = parent.body
        for wrongName, repairedName in repairs.items():
            # check to see if it's actually a game.
            log.info('Repairing {} --> {}'.format(wrongName, repairedName))
            alias = self._botdb.get_name_from_alias(repairedName)
            tmp_name = alias if alias else repairedName
            tmp_game = self._bggQueryGame(
                tmp_name)  # with caching it's ok to check twice
            if tmp_game:
                # In the parent body we want to replace [NAME](http://... with **NAME**(http://
                pbody = pbody.replace('[' + wrongName + ']',
                                      '**' + tmp_name + '**')
            else:
                log.info(
                    '{} seems to not be a game name according to BGG, ignoring.'
                    .format(tmp_name))

        # Now re-bold the not found strings so they are re-searched or re-added to the not found list.
        for nf in re.findall(
                '\[([\w|\s]+)]\(http://boardgamegeek.com/geeksearch.php',
                pbody):
            pbody += ' **{}**'.format(nf)

        # now re-insert the original command to retain the mode.
        grandparent = parent.parent()
        modes = list()
        if not grandparent:
            log.error('Cannot find original GP post. Assuming normal mode.')
        else:
            modes = re.findall('[getparent|get]info\s(\w+)', grandparent.body)

        targetmode = modes[0] if modes else self.DEFAULT_DISPLAY_MODE

        parent = parent.edit(pbody)
        bolded = self._getBoldedEntries(comment)
        new_reply = self._getInfoResponseBody(parent, bolded, targetmode)

        # should check for Editiable class somehow here. GTL
        log.debug('Replacing bot comment {} with: {}'.format(
            parent.id, new_reply))
        parent.edit(new_reply)

    def xyzzy(self, comment: praw.models.Comment, subcommands: list,
              config: dict):
        comment.reply('Nothing happens.')

    def getParentInfo(self, comment: praw.models.Comment, subcommands: list,
                      config: dict):
        '''Allows others to call the bot to getInfo for parent posts.'''
        if self._botdb.ignore_user(comment.author.name):
            log.info("Ignoring comment by {}".format(comment.author.name))
            return

        log.debug('Got getParentInfo comment in id {}'.format(comment.id))

        if comment.is_root:
            # error here - this comment should be in response to a u/r2d8 comment.
            log.info('Got a repair comment as root, ignoring.')
            return

        parent = comment.parent()
        self.getInfo(parent,
                     subcommands=subcommands,
                     config=config,
                     replyTo=comment)

    def alias(self, comment: praw.models.Comment, subcommands: list,
              config: dict):
        '''add an alias to the database.'''
        if not self._botdb.is_admin(comment.author.name):
            log.info('got alias command from non admin {}, ignoring.'.format(
                comment.author.name))
            return

        response = 'executing alias command.\n\n'
        # TODO: use bold fn
        for match in re.findall('\*\*([^\*]+)\*\*=\*\*([^\*]+)\*\*',
                                comment.body):
            mess = 'Adding alias to database: "{}" = "{}"'.format(
                match[0], match[1])
            log.info(mess)
            response += mess + '\n\n'
            self._botdb.add_alias(match[0], match[1])

        comment.reply(response)

    def getaliases(self, comment: praw.models.Comment, subcommands: list,
                   config: dict):
        if self._botdb.ignore_user(comment.author.name):
            log.info("Ignoring comment by {}".format(comment.author.name))
            return

        aliases = self._botdb.aliases()
        response = 'Current aliases:\n\n'
        for name, alias in sorted(aliases, key=lambda g: g[1]):
            response += ' * {} = {}\n'.format(alias, name)

        log.info('Responding to getalaises request with {} aliases'.format(
            len(aliases)))
        comment.reply(response)

    def expandURLs(self, comment: praw.models.Comment, subcommands: list,
                   config: dict):
        if self._botdb.ignore_user(comment.author.name):
            log.info("Ignoring comment by {}".format(comment.author.name))
            return

        replyTo = None
        mode = None
        if len(subcommands) > 0 and subcommands[0].lower(
        ) in self.DISPLAY_MODES:
            mode = subcommands[0].lower()
        else:
            mode = self.DEFAULT_DISPLAY_MODE

        footer = '\n' + config['footer'] if 'footer' in config else ''

        body = comment.body
        urls = [
            ('#' + id)
            for id in re.findall('boardgamegeek.com/(?:boardgame|thing)/(\d+)',
                                 body,
                                 flags=re.UNICODE)
        ]

        response = self._getInfoResponseBody(comment, urls, mode)
        log.error('footer {} ({})'.format(footer, type(footer)))
        if response:
            if replyTo:
                replyTo.reply(response + footer)
            else:
                comment.reply(response + footer)
            log.info('Replied to info request for comment {}'.format(
                comment.id))
        else:
            log.warn('Did not find anything to reply to in comment {}'.format(
                comment.id))

    def removalRequest(self, comment: praw.models.Comment, subcommands: list,
                       config: dict):
        # if self._botdb.ignore_user(comment.author.name):
        #     log.info("Ignoring comment by {}".format(comment.author.name))
        #     return

        # for now removals are limited to admins
        if not self._botdb.is_admin(comment.author.name):
            log.info('got remove command from non admin {}, ignoring.'.format(
                comment.author.name))
            return

        if comment.is_root:
            log.error(
                'removal requested on top-level comment {}, ignoring'.format(
                    comment.id))
            return

        botmessage: praw.models.Comment = comment.parent()
        try:
            # delete the post
            botmessage.delete()
            # attempt to unmark the parent as read
            if not botmessage.is_root:
                self._botdb.remove_comment(botmessage.parent)
        except boardgamegeek.exceptions.BoardGameGeekError as e:
            log.error('Error deleting comment {} by {}'.format(
                original.id, original.author.name))
        return
示例#33
0
from slackbot.bot import respond_to,listen_to
from boardgamegeek import BoardGameGeek

import re, json

bgg = BoardGameGeek()

def jointwo(string1, string2, joinstr):
        return joinstr.join([x for x in (string2, string2) if x])

@respond_to('user (.*)')
@listen_to('\[\[user (.*)\]\]')
def user_info(message, username):
	print('Getting information on user ' + username)
	message.reply('Getting user information on *{}*'.format(username))

	user = bgg.user(username)

	if user == None:
		message.reply('No information found for user *{}*'.format(username))
		return

    	attachments = [{
        	'fallback' : 'User information on ' + username,
                'author_name': jointwo(user.firstname, user.lastname, ' ') + ' (' + user.name + ')',
                'author_link': 'https://boardgamegeek.com/user/' + user.name,
                'author_icon': 'https:' + user.avatarlink,
                'color': 'good',
                'thumb_url': 'https:' + user.avatarlink,
                'fields' : [
                {
示例#34
0
def getGotWPostText(game_name, next_game_name):
    '''Take the name of a game, and return the GotW text to post to Reddit'''
    bgg = BGG()
    try:
        game = bgg.game(game_name)
    except boardgamegeek.exceptions.BoardGameGeekError as e:
        log.critical(u'Error getting info from BGG on {}: {}'.format(
            game_name, e))
        return None

    if not game:
        log.critical(u'Unable to find {} on BGG'.format(game_name))
        return None

    title = u'Game of the Week: {}'.format(game.name)
    text = u'[//]: # (GOTWS)\n'
    text += (u'This week\'s game is [**{}**](http:{})\n\n'.format(
        game.name, game.image))
    text += u' * **BGG Link**: [{}](http://www.boardgamegeek.com/boardgame/{})\n'.format(
        game.name, game.id)

    designers = getattr(game, u'designers', [u'Unknown'])
    plural = u's' if len(designers) > 1 else u''
    text += u' * **Designer{}**: {}\n'.format(plural, ', '.join(designers))

    publishers = getattr(game, u'publishers', [u'Unknown'])
    plural = u's' if len(publishers) > 1 else u''
    text += u' * **Publisher{}**: {}\n'.format(plural, ', '.join(publishers))

    text += u' * **Year Released**: {}\n'.format(game.year)

    mechanics = getattr(game, u'mechanics', [u'Unknown'])
    plural = u's' if len(mechanics) > 1 else u''
    text += u' * **Mechanic{}**: {}\n'.format(plural, ', '.join(mechanics))

    if game.min_players == game.max_players:
        players = '{}'.format(game.min_players)
    else:
        players = '{} - {}'.format(game.min_players, game.max_players)
    text += u' * **Number of Players**: {}\n'.format(players)

    text += u' * **Playing Time**: {} minutes\n'.format(game.playing_time)

    expansions = getattr(game, 'expansions', None)
    if expansions:
        text += u' * **Expansions**: {}\n'.format(', '.join(
            [e.name for e in expansions]))

    text += u' * **Ratings**:\n'
    people = u'people' if game.users_rated > 1 else u'person'
    text += u'    * Average rating is {} (rated by {} {})\n'.format(
        game.rating_average, game.users_rated, people)
    ranks = u', '.join([
        u'{}: {}'.format(r[u'friendlyname'], r[u'value']) for r in game.ranks
    ])
    text += u'    * {}\n'.format(ranks)

    text += u'\n\n'

    text += u'**Description from Boardgamegeek**:\n\n{}\n\n'.format(
        game.description)

    text += u'[//]: # (GOTWE)\n\n'
    text += '---------------------\n\n'

    if not next_game_name:
        text += u'There is no Game of the Week scheduled for next week.'
    else:
        try:
            game = bgg.game(next_game_name)
        except boardgamegeek.exceptions.BoardGameGeekError as e:
            log.critical(u'Error getting info from BGG on {}: {}'.format(
                next_game_name, e))

        if not game:
            text += u'Next Week: {}'.format(next_game_name)
        else:
            text += (u'Next Week: [**{}**](http://www.boardgamegeek'
                     u'.com/boardgame/{})\n\n'.format(game.name, game.id))

        text += (u' * The GOTW archive and schedule can be found '
                 u'[here](http://www.reddit.com/r/boardgames/wiki/'
                 u'game_of_the_week).\n\n * Vote for future Games of the Week '
                 u'[here](/2l5xum).\n')

    return title, text
示例#35
0
class CommentHandler(object):
    def __init__(self, UID, botdb):
        self._botdb = botdb
        self._botname = UID
        self._header = (
            u'^*[{}](/r/r2d8)* ^*issues* ^*a* ^*series* ^*of* ^*sophisticated* '
            u'^*bleeps* ^*and* ^*whistles...*\n\n'.format(self._botname))

        dbpath = pjoin(getcwd(), u'{}-bgg.db'.format(self._botname))
        self._bgg = BGG(cache=u'sqlite://{}?ttl=86400'.format(dbpath))

    def _bggQueryGame(self, name):
        '''Try "name", then if not found try a few other small things in an effort to find it.'''
        name = name.lower().strip(
        )  # GTL extra space at ends shouldn't be matching anyway, fix this.
        if not name:
            return None

        if len(name) > 128:
            log.warn('Got too long game name: {}'.format(name))
            return None

        game = self._bgg.game(name)
        if game:
            return game

        # Well OK, how about game ID?
        if not re.search(u'([^\d]+)', name):  # all digits is probably an ID
            game = self._bgg.game(name=None, game_id=name)
            if game:
                log.debug('found game {} via searching by ID'.format(name))
                return game

        # embedded url? If so, extract.
        log.debug('Looking for embedded URL')
        m = re.search('\[([^]]*)\]', name)
        if m:
            name = m.group(1)
            game = self._bgg.game(name)
            if game:
                return game

        # note: unembedded from here down
        # remove 'the's
        log.debug('removing "the"s')
        tmpname = re.sub('^the ', '', name)
        tmpname = re.sub('\sthe\s', ' ', tmpname)
        if tmpname != name:
            game = self._bgg.game(tmpname)
            if game:
                return game

        # add a "the" at start.
        log.debug('adding "the" at start')
        game = self._bgg.game('The ' + name)
        if game:
            return game

        # various substistutions.
        subs = [('[?!.:,]*', '', 'removing punctuation'),
                ('\sand\s', ' & ', 'and --> &'),
                ('\s&\s', ' and ', '& --> and')]
        for search, sub, logmess in subs:
            log.debug(logmess)
            tmpname = re.sub(search, sub, name)
            if tmpname != name:
                game = self._bgg.game(tmpname)
                if game:
                    return game

        # well OK - let's pull out the heavy guns and use the search API.
        # this will give us a bunch of things to sort through, but hopefully
        # find something.
        return self._bggSearchGame(name)

    def _bggSearchGame(self, name):
        '''Use the much wider search API to find the game.'''
        items = self._bgg.search(
            name,
            search_type=BoardGameGeekNetworkAPI.SEARCH_BOARD_GAME,
            exact=True)
        if items and len(items) == 1:
            log.debug('Found exact match using search().')
            return self._bgg.game(items[0].name)

        # exact match not found, trying sloppy match
        items = self._bgg.search(
            name, search_type=BoardGameGeekNetworkAPI.SEARCH_BOARD_GAME)
        if items and not len(items):
            log.debug('Found no matches at all using search().')
            return None

        if items and len(items) == 1:
            log.debug('Found one match usinh search().')
            return self._bgg.game(items[0].name)

        if not items:
            return None

        # assume most owned is what people want. Is this good? Dunno.
        most_owned = None
        for i in items:
            game = self._bgg.game(None, game_id=i.id)
            # GTL the lib throws an uncaught exception if BGG assessed too quickly.
            # GTL - this needs to be fixed in the library.
            sleep(1)
            if getattr(game, 'expansion', False):
                log.debug('ignoring expansion')
                continue
            else:
                if not most_owned:
                    most_owned = game
                else:
                    most_owned = game if getattr(
                        game, 'owned', 0) > most_owned.owned else most_owned

        if most_owned:
            return most_owned

        return None

    def _getInfoResponseBody(self, comment, mode=None):
        body = comment.body
        # bolded = re.findall(u'\*\*([^\*]+)\*\*', body)
        # Now I've got two problems.
        bolded = re.findall(
            u'\*\*([\w][\w\.\s:\-?$,!\'–&()\[\]]*[\w\.:\-?$,!\'–&()\[\]])\*\*',
            body,
            flags=re.UNICODE)
        if not bolded:
            log.warn(
                u'Got getinfo command, but nothing is bolded. Ignoring comment.'
            )
            log.debug(u'comment was: {}'.format(body))
            return

        # convert aliases to real names. It may be better to do this after we don't find the
        # game. Oh, well.
        for i in xrange(len(bolded)):
            real_name = self._botdb.get_name_from_alias(bolded[i])
            if real_name:
                bolded[i] = real_name

        # filter out dups.
        bolded = list(set(bolded))
        bolded = [unquote(b) for b in bolded]

        games = []
        not_found = []

        if comment.subreddit.display_name.lower() == u'boardgamescirclejerk':
            cjgames = [[u'Dead of Winter: A Crossroads Game'], [u'Scythe']]
            bolded = choice(cjgames)
            bolded = ['Scythe', 'Scythe', 'Scythe']

        seen = set()
        for game_name in bolded:
            log.info(u'asking BGG for info on {}'.format(game_name))
            try:
                # game = self._bgg.game(game_name)
                game = self._bggQueryGame(game_name)
                if game:
                    if game.name not in seen:
                        games.append(game)
                    # don't add dups. This can happen when the same game is calledby two valid
                    # names in a post.
                    seen.add(game.name)
                else:
                    not_found.append(game_name)

            except boardgamegeek.exceptions.BoardGameGeekError as e:
                log.error(u'Error getting info from BGG on {}: {}'.format(
                    game_name, e))
                continue

        # sort by game name because why not?
        games = sorted(games, key=lambda g: g.name)

        # we now have all the games.
        mode = u'short' if len(games) > 6 else mode
        # not_found = list(set(bolded) - set([game.name for game in games]))

        if comment.subreddit.display_name.lower() == u'boardgamescirclejerk':
            not_found = None

        if not_found:
            log.debug(u'not found: {}'.format(u', '.join(not_found)))

        if games:
            log.debug(u'Found games {}'.format(u','.join(
                [u'{} ({})'.format(g.name, g.year) for g in games])))
        else:
            log.warn(u'Found no games in comment {}'.format(comment.id))

        # get the information for each game in a nice tidy list of strings.
        # get the mode if given. Can be short or long or normal. Default is normal.
        if not mode:
            m = re.search(u'getinfo\s(\w+)', body, flags=re.IGNORECASE)
            if m:
                mode = m.group(1).lower() if m.group(1).lower() in [
                    u'short', u'long'
                ] else mode

        if mode == u'short':
            infos = self._getShortInfos(games)
        elif mode == u'long':
            infos = self._getLongInfos(games)
        else:
            infos = self._getStdInfos(games)

        # append not found string if we didn't find a bolded string.
        if not_found:
            not_found = [
                u'[{}](http://boardgamegeek.com/geeksearch.php?action=search'
                '&objecttype=boardgame&q={}&B1=Go)'.format(n, quote(n))
                for n in not_found
            ]
            infos.append(
                u'\n\nBolded items not found at BGG (click to search): {}\n\n'.
                format(u', '.join(not_found)))

        response = None
        if len(infos):
            response = self._header + u'\n'.join([i for i in infos])

        return response

    def _getPlayers(self, game):
        if not game.min_players:
            return None

        if game.min_players == game.max_players:
            players = '{} p'.format(game.min_players)
        else:
            players = '{}-{} p'.format(game.min_players, game.max_players)

        return players

    def getInfo(self, comment, replyTo=None, mode=None):
        '''Reply to comment with game information. If replyTo isot given reply to original else
        reply to given comment.'''
        if self._botdb.ignore_user(comment.author.name):
            log.info("Ignoring comment by {}".format(comment.author.name))
            return

        response = self._getInfoResponseBody(comment, mode)
        if response:
            if replyTo:
                replyTo.reply(response)
            else:
                comment.reply(response)
            log.info(u'Replied to info request for comment {}'.format(
                comment.id))
        else:
            log.warn(u'Did not find anything to reply to in comment'.format(
                comment.id))

    def _getShortInfos(self, games):
        infos = list()
        for game in games:
            players = self._getPlayers(game)
            info = (u' * [**{}**](http://boardgamegeek.com/boardgame/{}) '
                    u' ({}) by {}. '.format(
                        game.name, game.id, game.year,
                        u', '.join(getattr(game, u'designers', u'Unknown'))))
            if players:
                info += '{}; '.format(players)
            if game.playing_time and int(game.playing_time) != 0:
                info += '{} mins '.format(game.playing_time)

            infos.append(info)

        return infos

    def _getStdInfos(self, games):
        infos = list()
        for game in games:
            players = self._getPlayers(game)
            info = (u'[**{}**](http://boardgamegeek.com/boardgame/{}) '
                    u' ({}) by {}. {}; '.format(
                        game.name, game.id, game.year,
                        u', '.join(getattr(game, u'designers',
                                           u'Unknown')), players))

            if game.playing_time and int(game.playing_time) != 0:
                info += '{} minutes; '.format(game.playing_time)

            if game.image:
                info += '[img]({}) '.format(game.image)

            info += '\n\n'

            data = u', '.join(getattr(game, u'mechanics', u''))
            if data:
                info += u' * Mechanics: {}\n'.format(data)
            people = u'people' if game.users_rated > 1 else u'person'
            info += u' * Average rating is {}; rated by {} {}. Weight: {}\n'.format(
                game.rating_average, game.users_rated, people,
                game.rating_average_weight)
            data = u', '.join([
                u'{}: {}'.format(r[u'friendlyname'], r[u'value'])
                for r in game.ranks
            ])
            info += u' * {}\n\n'.format(data)

            log.debug(u'adding info: {}'.format(info))
            infos.append(info)

        return infos

    def _getLongInfos(self, games):
        infos = list()
        for game in games:
            players = self._getPlayers(game)
            info = (
                u'Details for [**{}**](http://boardgamegeek.com/boardgame/{}) '
                u' ({}) by {}. '.format(
                    game.name, game.id, game.year,
                    u', '.join(getattr(game, u'designers', u'Unknown'))))
            if players:
                info += '{}; '.format(players)
            if game.playing_time and int(game.playing_time) != 0:
                info += '{} minutes; '.format(game.playing_time)
            if game.image:
                info += '[img]({}) '.format(game.image)
            info += '\n\n'

            data = u', '.join(getattr(game, u'mechanics', u''))
            if data:
                info += u' * Mechanics: {}\n'.format(data)
            people = u'people' if game.users_rated > 1 else u'person'
            info += u' * Average rating is {}; rated by {} {}\n'.format(
                game.rating_average, game.users_rated, people)
            info += u' * Average Weight: {}; Number of Weights {}\n'.format(
                game.rating_average_weight, game.rating_num_weights)
            data = u', '.join([
                u'{}: {}'.format(r[u'friendlyname'], r[u'value'])
                for r in game.ranks
            ])
            info += u' * {}\n\n'.format(data)

            info += u'Description:\n\n{}\n\n'.format(game.description)

            if len(games) > 1:
                info += u'------'

            log.debug(u'adding info: {}'.format(info))
            infos.append(info)

        return infos

    def repairComment(self, comment):
        '''Look for maps from missed game names to actual game names. If
        found repair orginal comment.'''
        if self._botdb.ignore_user(comment.author.name):
            log.info("Ignoring comment by {}".format(comment.author.name))
            return
        #
        # The repair is done by replacing the new games names with the old (wrong)
        # games names in the original /u/r2d8 response, then recreating the entire
        # post by regenerating it with the new (fixed) bolded game names. The just replacing
        # the orginal response with the new one.
        #
        log.debug(u'Got repair response, id {}'.format(comment.id))

        if comment.is_root:
            # error here - this comment should be in response to a u/r2d8 comment.
            log.info(u'Got a repair comment as root, ignoring.')
            return

        parent = comment.reddit_session.get_info(thing_id=comment.parent_id)
        if parent.author.name != self._botname:
            log.info(
                u'Parent of repair comment is not authored by the bot, ignoring.'
            )
            return

        # Look for patterns of **something**=**somethingelse**. This line creates a dict
        # of something: somethingelse for each one pattern found.
        repairs = {
            match[0]: match[1]
            for match in re.findall(u'\*\*([^\*]+)\*\*=\*\*([^\*]+)\*\*',
                                    comment.body)
        }

        pbody = parent.body
        for wrongName, repairedName in repairs.iteritems():
            # check to see if it's actually a game.
            log.info(u'Repairing {} --> {}'.format(wrongName, repairedName))
            alias = self._botdb.get_name_from_alias(repairedName)
            tmp_name = alias if alias else repairedName
            tmp_game = self._bggQueryGame(
                tmp_name)  # with caching it's ok to check twice
            if tmp_game:
                # In the parent body we want to replace [NAME](http://... with **NAME**(http://
                pbody = pbody.replace(u'[' + wrongName + u']',
                                      u'**' + tmp_name + u'**')
            else:
                log.info(
                    u'{} seems to not be a game name according to BGG, ignoring.'
                    .format(tmp_name))

        # Now re-bold the not found strings so they are re-searched or re-added to the not found list.
        for nf in re.findall(
                u'\[([\w|\s]+)]\(http://boardgamegeek.com/geeksearch.php',
                pbody):
            pbody += u' **{}**'.format(nf)

        # now re-insert the original command to retain the mode.
        grandparent = parent.reddit_session.get_info(thing_id=parent.parent_id)
        modes = list()
        if not grandparent:
            log.error(u'Cannot find original GP post. Assuming normal mode.')
        else:
            modes = re.findall(u'[getparent|get]info\s(\w+)', grandparent.body)

        if modes:
            log.debug(u'Recreating {} mode from the GP.'.format(modes[0]))
            pbody += u' /u/{} getinfo {}'.format(self._botname, modes[0])
        else:
            pbody += u' /u/{} getinfo'.format(self._botname)

        parent = parent.edit(pbody)
        new_reply = self._getInfoResponseBody(parent)

        # should check for Editiable class somehow here. GTL
        log.debug(u'Replacing bot comment {} with: {}'.format(
            parent.id, new_reply))
        parent.edit(new_reply)

    def xyzzy(self, comment):
        comment.reply(u'Nothing happens.')

    def getParentInfo(self, comment):
        '''Allows others to call the bot to getInfo for parent posts.'''
        if self._botdb.ignore_user(comment.author.name):
            log.info("Ignoring comment by {}".format(comment.author.name))
            return

        log.debug(u'Got getParentInfo comment in id {}'.format(comment.id))

        if comment.is_root:
            # error here - this comment should be in response to a u/r2d8 comment.
            log.info(u'Got a repair comment as root, ignoring.')
            return

        m = re.search(u'getparentinfo\s(\w+)', comment.body, re.IGNORECASE)
        mode = None
        if m:
            mode = u'short' if m.group(1).lower() == u'short' else u'long'

        parent = comment.reddit_session.get_info(thing_id=comment.parent_id)
        self.getInfo(parent, comment, mode)

    def alias(self, comment):
        '''add an alias to the database.'''
        if not self._botdb.is_admin(comment.author.name):
            log.info(u'got alias command from non admin {}, ignoring.'.format(
                comment.author.name))
            return

        response = u'executing alias command.\n\n'
        for match in re.findall(u'\*\*([^\*]+)\*\*=\*\*([^\*]+)\*\*',
                                comment.body):
            mess = u'Adding alias to database: "{}" = "{}"'.format(
                match[0], match[1])
            log.info(mess)
            response += mess + u'\n\n'
            self._botdb.add_alias(match[0], match[1])

        comment.reply(response)

    def getaliases(self, comment):
        if self._botdb.ignore_user(comment.author.name):
            log.info("Ignoring comment by {}".format(comment.author.name))
            return

        aliases = self._botdb.aliases()
        response = u'Current aliases:\n\n'
        for name, alias in sorted(aliases, key=lambda g: g[1]):
            response += u' * {} = {}\n'.format(alias, name)

        log.info(u'Responding to getalaises request with {} aliases'.format(
            len(aliases)))
        comment.reply(response)
示例#36
0
 def boardgamegeek(self) -> BoardGameGeek:
     return BoardGameGeek()
示例#37
0
def getGotWPostText(game_name, next_game_name, vote_thread_url):
    '''Take the name of a game, and return the GotW text to post to Reddit'''
    bgg = BGG()
    try:
        game = bgg.game(game_name)
    except boardgamegeek.exceptions.BoardGameGeekError as e:
        log.critical(u'Error getting info from BGG on {}: {}'.format(
            game_name, e))
        return None

    if not game:
        log.critical(u'Unable to find {} on BGG'.format(game_name))
        return None

    title = u'Game of the Week: {}'.format(game.name)
    text = u'[//]: # (GOTWS)\n'
    text += (u'This week\'s game is [**{}**]({})\n\n'.format(game.name, game.image))
    text += u' * **BGG Link**: [{}](http://www.boardgamegeek.com/boardgame/{})\n'.format(
        game.name, game.id)

    designers = getattr(game, u'designers', [u'Unknown'])
    plural = u's' if len(designers) > 1 else u''
    text += u' * **Designer{}**: {}\n'.format(plural, ', '.join(designers))
    
    publishers = getattr(game, u'publishers', [u'Unknown'])
    plural = u's' if len(publishers) > 1 else u''
    text += u' * **Publisher{}**: {}\n'.format(plural, ', '.join(publishers))
    
    text += u' * **Year Released**: {}\n'.format(game.year)
    
    mechanics = getattr(game, u'mechanics', [u'Unknown'])
    plural = u's' if len(mechanics) > 1 else u''
    text += u' * **Mechanic{}**: {}\n'.format(plural, ', '.join(mechanics))
   
    categories = getattr(game, u'categories', [u'unknown'])
    plural = u'ies' if len(categories) > 1 else u'y'
    text += u' * **Categor{}**: {}\n'.format(plural, ', '.join(categories))

    if game.min_players == game.max_players:
        players = '{}'.format(game.min_players)
    else:
        players = '{} - {}'.format(game.min_players, game.max_players)
    text += u' * **Number of Players**: {}\n'.format(players)
    
    text += u' * **Playing Time**: {} minutes\n'.format(game.playing_time)
    
    expansions = getattr(game, 'expansions', None)
    if expansions:
        text += u' * **Expansions**: {}\n'.format(', '.join([e.name for e in expansions]))

    text += u' * **Ratings**:\n'
    people = u'people' if game.users_rated > 1 else u'person'
    text += u'    * Average rating is {} (rated by {} {})\n'.format(
        game.rating_average, game.users_rated, people)
    ranks = u', '.join([u'{}: {}'.format(
        r[u'friendlyname'], r[u'value']) for r in game.ranks])
    text += u'    * {}\n'.format(ranks)

    text += u'\n\n'

    text += u'**Description from Boardgamegeek**:\n\n{}\n\n'.format(game.description)

    text += u'[//]: # (GOTWE)\n\n'
    text += '---------------------\n\n'

    if not next_game_name:
        text += u'There is no Game of the Week scheduled for next week.'
    else:
        try:
            game = bgg.game(next_game_name)
        except boardgamegeek.exceptions.BoardGameGeekError as e:
            log.critical(u'Error getting info from BGG on {}: {}'.format(
                next_game_name, e))

        if not game:
            text += u'Next Week: {}'.format(next_game_name)
        else:
            text += (u'Next Week: [**{}**](http://www.boardgamegeek'
                     u'.com/boardgame/{})\n\n'.format(game.name, game.id))

        text += (u' * The GOTW archive and schedule can be found '
                 u'[here](http://www.reddit.com/r/boardgames/wiki/'
                 u'game_of_the_week).\n\n * Vote for future Games of the Week '
                 u'[here](/{}).\n'.format(vote_thread_url))

    return title, text
    Add logger for boardgamegeek.api
    Fix bug that causes certain game names to crash the program

"""

#change these to check other bgg user and send to different email
bggUser = "******"
userEmail = "*****@*****.**"

r = praw.Reddit('BGG Wishlist Mention Tracker by /u/shbones v 1.1')
sent_already = []

while True:
    #Repopulate boardgamegeek.com wishlist every run
    wishlist = []
    bgg = BoardGameGeek()
    collection = bgg.collection(bggUser)
    try:
        games = collection.items
        print games
        for each_game in games:
            if each_game.wishlist == True:
                wishlist.append(each_game.name.lower())

        #Check for mentions in Title or Text in Top 10 current posts on reddit.com/r/boardgames
        subreddit = r.get_subreddit('boardgames')
        for submission in subreddit.get_hot(limit=10):
            post_text = submission.selftext.lower()
            post_title = submission.title.lower()
            has_game = any(string in (post_text + post_title)
                           for string in wishlist)
示例#39
0
def game_to_db(game, cur, conn):
    insert_board(game, cur)
    insert_designer(game, cur)
    insert_artist(game, cur)
    insert_family(game, cur)
    insert_genre(game, cur)
    insert_mechanic(game, cur)
    insert_publisher(game, cur)


def print_game(game):
    print("Inserted " + game.name)


bgg = BoardGameGeek()
output = open('output.txt', 'w')
game_ids = [line.rstrip('\n').split(";")[0] for line in open('data.csv', 'r')]
games = []

for game_id in game_ids:
    try:
        game_id = int(game_id)
    except:
        continue
    if (game_id > 2290):
        try:
            game = bgg.game(game_id=game_id)
            print_game(game)
            # print_to_file(game, output)
            game_to_db(game, cur, conn)
示例#40
0
def trending_games():
    bgg = BoardGameGeek()
    hot_items_dict = dict()
    for item in bgg.hot_items("boardgame"):
        hot_items_dict[item.id] = item.name
    return render_template('page.html', hot_items_dict=hot_items_dict)
示例#41
0
文件: bgg.py 项目: metinkler/db
def game_to_db(game,cur,conn):
	insert_board(game, cur)
	insert_designer(game, cur)
	insert_artist(game, cur)
	insert_family(game, cur)
	insert_genre(game, cur)
	insert_mechanic(game, cur)
	insert_publisher(game, cur)
	


def print_game(game):
	print ("Inserted " + game.name )

bgg = BoardGameGeek()
output = open('output.txt', 'w')
game_ids = [line.rstrip('\n').split(";")[0] for line in open('data.csv', 'r')]
games = []

for game_id in game_ids:
	try:
		game_id = int(game_id)
	except: 
		continue
	if (game_id > 2290):
		try:
			game = bgg.game(game_id = game_id)
			print_game(game)
			# print_to_file(game, output)
			game_to_db(game,cur,conn)
from functs import add_scores, read_cell
from boardgamegeek import BoardGameGeek
from datetime import datetime
from connect import connect


bgg = BoardGameGeek()
al = bgg.plays('mad4hatter')
alplays = al.plays

entry, client, key = connect()

latestgame = read_cell(client,key)

print latestgame


for games in alplays:
    if games.date > latestgame:
            temp = games.players
            troytest = ['yes' for players in temp if players.name == 'Troy']
            
            
            if troytest == ['yes']:
                entry, client, key = connect()
                
                entry.set_value('date',games.date.strftime('%m/%d/%y'))
                entry.set_value('gamename',games.game_name)
                entry.set_value('duration', str(games.duration))
                
示例#43
0
class BGGGame(object):
    """Wrapper around the `game` object from the boardgamegeek API"""

    def __init__(self, game_id, short=500):
        """
        Args:
            short: int
                number of characters to use for short description
        """
        self._game = None
        self.short = int(short) or 500
        self.bgg = BoardGameGeek(disable_ssl=True)
        if isinstance(game_id, int):
            self._game = self.bgg.game(game_id=game_id)
        elif isinstance(game_id, ""):
            self._game = self.bgg.game(name=game_id)
        else:
            print
        self.set_properties()

    def get_description_custom(self):
        """Create a custom, abbreviated description for a game."""
        if self._game:
            desc = self._game.description[0:self.short]
            _cut = int(
                (len(desc) -
                 len(desc.replace(',', '').replace('.', '').replace(':', '')))
                / 2 + self.short)
            desc = self._game.description[0:_cut]
            return desc[0:-3] + '...'

    def HTML_description(self, text):
        """Changes the BGG [] notation to <> and adds line breaks"""
        #print "bgg.self._game.description\n", self._game.description
        return self._game.description.replace('[', '<').replace(']', '>').\
            replace('\n', '<br/>')

    def set_properties(self):
        """Create both raw (_ prefix) and string formatted versions of props"""
        if self._game:
            self._alternative_names = self._game.alternative_names
            self.alternative_names = ', '.join(self._game.alternative_names)
            self._artists = self._game.artists
            self.artists = ', '.join(self._game.artists)
            self._average = self._game.average
            self.average = '%.3f' % self._game.average
            self._averageweight = self._game.averageweight
            self.averageweight = '%.2f' % self._game.averageweight
            self.percentageweight = '%s' % math.ceil(self._game.averageweight * 20.0)
            self._bayesaverage = self._game.bayesaverage
            self.bayesaverage = '%.3f' % self._game.bayesaverage
            self._categories = self._game.categories
            self.categories = ', '.join(self._game.categories)
            self._description = self._game.description
            self.description = '%s' % self._game.description
            self.description_html = '%s' % \
                self.HTML_description(self._game.description)
            try:
                self.description_short = '%s' % self._game.description_short
                self.description_short_html = '%s' % \
                    self.HTML_description(self._game.description_short)
            except AttributeError:
                self.description_short = ''
                self.description_short_html = ''
            self._designers = self._game.designers
            self.designers = ', '.join(self._game.designers)
            self._expands = self._game.expands
            self.expands = ', '.join([exp.name for exp in self._game.expands])
            self._expansion = self._game.expansion
            if self._game.expansion is True:
                self.expansion = 'Yes'
            else:
                self.expansion = 'No'
            self._expansions = self._game.expansions
            self.expansions = ', '.join(
                [exp.name for exp in self._game.expansions])
            self._families = self._game.families
            self.families = ', '.join(self._game.families)
            self._id = self._game.id
            self.id = '%s' % self._game.id
            self._image = self._game.image
            self.image = '%s' % self._game.image
            self._implementations = self._game.implementations
            self.implementations = ', '.join(self._game.implementations)
            self._maxplayers = self._game.maxplayers
            self.maxplayers = '%s' % self._game.maxplayers
            self._mechanics = self._game.mechanics
            self.mechanics = ', '.join(self._game.mechanics)
            self._median = self._game.median
            self.median = '%.3f' % self._game.median
            self._minage = self._game.minage
            self.minage = '%s' % self._game.minage
            self._minplayers = self._game.minplayers
            self.minplayers = '%s' % self._game.minplayers
            self._name = self._game.name
            self.name = '%s' % self._game.name
            self._numcomments = self._game.numcomments
            self.numcomments = '%s' % self._game.numcomments
            self._numweights = self._game.numweights
            self.numweights = '%s' % self._game.numweights
            self._owned = self._game.owned
            self.owned = '%s' % self._game.owned
            self._playingtime = self._game.playingtime
            self.playingtime = '%s' % self._game.playingtime
            self._publishers = self._game.publishers
            self.publishers = ', '.join(self._game.publishers)
            self._ranks = self._game.ranks
            self.ranks = '%s' % self._game.ranks
            self._stddev = self._game.stddev
            self.stddev = '%.3f' % self._game.stddev
            self._thumbnail = self._game.thumbnail
            self.thumbnail = '%s' % self._game.thumbnail
            self._trading = self._game.trading
            self.trading = '%s' % self._game.trading
            self._usersrated = self._game.usersrated
            self.usersrated = '%s' % self._game.usersrated
            self._wanting = self._game.wanting
            self.wanting = '%s' % self._game.wanting
            self._wishing = self._game.wishing
            self.wishing = '%s' % self._game.wishing
            self._yearpublished = self._game.yearpublished
            self.yearpublished = '%s' % self._game.yearpublished
            # custom fields
            self.description_custom = self.get_description_custom()
            self._description_custom = self.description_custom
            if self._game.minplayers == self._game.maxplayers:
                    self.players = '%s' % self._game.maxplayers
            else:
                self.players = '%s-%s' % (self._game.minplayers,
                                          self._game.maxplayers)
            self._players = (self._game.minplayers, self._game.maxplayers)
            self.age = '%s+' % self._game.minage
            self._age = self._game.minage