diff options
| -rw-r--r-- | .gitignore | 93 | ||||
| -rw-r--r-- | .gitmodules | 3 | ||||
| m--------- | joblib | 0 | ||||
| -rw-r--r-- | main.py | 177 | ||||
| -rw-r--r-- | requirements.txt | 11 |
5 files changed, 284 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a3401aa --- /dev/null +++ b/.gitignore @@ -0,0 +1,93 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*,cover +.hypothesis/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +!/logs +!run*.log +local_settings.py + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# dotenv +.env + +# virtualenv +.venv/ +venv/ +ENV/ + +# Spyder project settings +.spyderproject + +# Rope project settings +.ropeproject diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..52948d0 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "joblib"] + path = joblib + url = https://github.com/vainglorygame/joblib diff --git a/joblib b/joblib new file mode 160000 +Subproject 75c4caa934676f4addf633008d20137b45c15c8 @@ -0,0 +1,177 @@ +#!/usr/bin/env python3 + +import logging +import os +import asyncio +import asyncpg +import discord +from discord.ext import commands +import joblib.joblib + +source_db = { + "host": os.environ.get("POSTGRESQL_SOURCE_HOST") or "localhost", + "port": os.environ.get("POSTGRESQL_SOURCE_PORT") or 5433, + "user": os.environ.get("POSTGRESQL_SOURCE_USER") or "vainraw", + "password": os.environ.get("POSTGRESQL_SOURCE_PASSWORD") or "vainraw", + "database": os.environ.get("POSTGRESQL_SOURCE_DB") or "vainsocial-raw" +} + +dest_db = { + "host": os.environ.get("POSTGRESQL_DEST_HOST") or "localhost", + "port": os.environ.get("POSTGRESQL_DEST_PORT") or 5432, + "user": os.environ.get("POSTGRESQL_DEST_USER") or "vainweb", + "password": os.environ.get("POSTGRESQL_DEST_PASSWORD") or "vainweb", + "database": os.environ.get("POSTGRESQL_DEST_DB") or "vainsocial-web" +} + + +bot = commands.Bot( + command_prefix="!", + description="Vainsocial Vainglory stats bot") + +queue = None +pool = None + +async def connect(queuedb, db): + logging.warning("connecting to database") + + global queue + queue = joblib.joblib.JobQueue() + await queue.connect(**queuedb) + await queue.setup() + + global pool + pool = await asyncpg.create_pool(**db) + + +@bot.event +async def on_ready(): + logging.warning("Logged in as %s (" + + "https://discordapp.com/oauth2/authorize?&" + + "client_id=%s&scope=bot)", + bot.user.name, bot.user.id) + await connect(source_db, dest_db) + + +@bot.event +async def on_command_error(error, ctx): + if ctx.invoked_subcommand: + pages = bot.formatter.format_help_for( + ctx, ctx.invoked_subcommand) + for page in pages: + await bot.send_message( + ctx.message.channel, page) + else: + pages = bot.formatter.format_help_for( + ctx, ctx.command) + for page in pages: + await bot.send_message( + ctx.message.channel, page) + + +@bot.command() +async def vainsocial(region: str, name: str): + """Retrieves a player's stats.""" + if region not in ["na", "eu", "sg", "ea", "sa"]: + await bot.say("That region is not supported.") + return + + query = """ + SELECT + player.name, + match.game_mode, + participant.hero, participant.winner, + participant.kills, participant.deaths, participant.assists, participant.farm, + participant.skill_tier, player.played, player.wins, + player.last_match_created_date + FROM match, roster, participant, player where + match.api_id=roster.match_api_id AND + roster.api_id=participant.roster_api_id AND + participant.player_api_id=player.api_id AND + player.name=$1 AND player.last_match_created_date<>$2 + ORDER BY match.created_at DESC + LIMIT 1 + """ + def msg(dct): + tiers = ["Just Beginning", "Getting There", "Rock Solid", "Got Swagger", "Credible Threat", "The Hotness", "Simply Amazing", "Pinnacle Of Awesome", "Vainglorious"] + if data["skill_tier"] == -1: + tier = "Unranked" + else: + subtiers = ["Bronze", "Silver", "Gold"] + tier = tiers[data["skill_tier"]//3-1] + " " + subtiers[data["skill_tier"]%3] + modes = { + "blitz_pvp_ranked": "Blitz", + "casual_aral": "Battle Royale", + "private": "private casual", + "private_party_draft_match": "private draft", + "private_party_blitz_match": "private Blitz", + "private_party_aral_match": "private Battle Royale" + } + mode = modes.get(data["game_mode"]) or data["game_mode"] + heroes = { + "Sayoc": "Taka", + "Hero009": "Krul", + "Hero010": "Skaarf", + "Hero016": "Rona" + } + hero = heroes.get(data["hero"]) or data["hero"].replace("*", "") + + return (""" +%s: %s, %s wins / %s games +Last match: %s %s as %s %s/%s/%s +""") % ( + data["name"], tier, + data["wins"], data["played"], + ("won" if data["winner"] else "lost"), + mode, hero, + data["kills"], data["deaths"], data["assists"] + ) + + async with pool.acquire() as con: + await bot.type() + + data = await con.fetchrow(query, name, 'NULL') + if data is None: + in_cache = False # new user + lmcd = "2017-02-01T00:00:00Z" + msgid = await bot.say( + "%s: please wait…" % name) + else: + in_cache = True # returning user + lmcd = data["last_match_created_date"] + msgid = await bot.say(msg(data)) + + payload = { + "region": region, + "params": { + "filter[createdAt-start]": lmcd, + "filter[playerNames]": name + } + } + jobid = await queue.request(jobtype="grab", + payload=payload) + + while True: + # wait for grab job to finish + status = await queue.status(jobid) + if status == 'finished': + break + if status == 'failed': + if not in_cache: + await bot.edit_message( + "Could not find you.") + return + asyncio.sleep(0.1) + + while True: + # wait for processor to update the player + data = await con.fetchrow(query, name, lmcd) + if data is not None: + break + asyncio.sleep(0.1) + + await bot.edit_message(msgid, msg(data)) + + +logging.basicConfig(level=logging.INFO) +bot.run(os.environ["VAINSOCIAL_DISCORDTOKEN"]) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..7c3bc62 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,11 @@ +aiohttp==1.0.5 +appdirs==1.4.2 +async-timeout==1.1.0 +asyncpg==0.9.0 +chardet==2.3.0 +discord.py==0.16.7 +multidict==2.1.4 +packaging==16.8 +pyparsing==2.1.10 +six==1.10.0 +websockets==3.2 |
