From 840a7e6eedbf8791f455d7085a8180f586d42266 Mon Sep 17 00:00:00 2001 From: schneefux Date: Tue, 14 Mar 2017 17:46:23 +0100 Subject: attempt search without region, use new joblib --- main.py | 38 +++++++++++++++----------------------- 1 file changed, 15 insertions(+), 23 deletions(-) (limited to 'main.py') diff --git a/main.py b/main.py index f46fa73..2f5d733 100644 --- a/main.py +++ b/main.py @@ -57,31 +57,9 @@ async def on_ready(): name="alpha.vainsocial.com")) -@bot.event -async def on_command_error(error, ctx): - logging.error(error) - 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(name: str, region: str): +async def vainsocial(name: str, region: str = None): """Retrieves a player's stats.""" - region = region.lower() - if region not in ["na", "eu", "sg", "ea", "sa"]: - await bot.say("That region is not supported.") - return - query = """ SELECT player.name, @@ -153,6 +131,20 @@ async def vainsocial(name: str, region: str): lmcd = data["last_match_created_date"] msgid = await bot.say(embed=emb(data)) + if not in_cache: + if region is None: + await bot.edit_message(msgid, + "Can't find you. What is your region?") + return + region = region.lower() + if region not in ["na", "eu", "sg", "ea", "sa"]: + await bot.edit_message(msgid, + "That region is not supported.") + return + else: + if region is None: + region = data["shard_id"] + logging.info("'%s' cached: %s", name, in_cache) payload = { -- cgit v1.3.1 From a134ef3c7367269a1baafca86b1b69acdc06587b Mon Sep 17 00:00:00 2001 From: schneefux Date: Tue, 14 Mar 2017 17:51:11 +0100 Subject: alias to !v or !vain --- main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'main.py') diff --git a/main.py b/main.py index 2f5d733..63d44d8 100644 --- a/main.py +++ b/main.py @@ -57,7 +57,7 @@ async def on_ready(): name="alpha.vainsocial.com")) -@bot.command() +@bot.command(aliases=["v", "vain"]) async def vainsocial(name: str, region: str = None): """Retrieves a player's stats.""" query = """ -- cgit v1.3.1 From d10b8e4df90e1324be7cd31aa7654f3572d54cb9 Mon Sep 17 00:00:00 2001 From: schneefux Date: Wed, 15 Mar 2017 16:06:15 +0100 Subject: add about command --- main.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) (limited to 'main.py') diff --git a/main.py b/main.py index 63d44d8..d071350 100644 --- a/main.py +++ b/main.py @@ -57,6 +57,24 @@ async def on_ready(): name="alpha.vainsocial.com")) +@bot.command() +async def about(): + """Print invite links.""" + emb = discord.Embed( + title="Vainsocial Discord bot", + description="Built by the Vainsocial development team using the MadGlory API." + ) + emb.add_field(name="Website", + value="https://alpha.vainsocial.com") + emb.add_field(name="Bot invite link", + value="https://discordapp.com/oauth2/authorize?&client_id=287297889024213003&scope=bot") + emb.add_field(name="Developer Discord", + value="https://discord.gg/txTchJY") + emb.add_field(name="Twitter", + value="https://twitter.com/vainsocial") + await bot.say(embed=emb) + + @bot.command(aliases=["v", "vain"]) async def vainsocial(name: str, region: str = None): """Retrieves a player's stats.""" -- cgit v1.3.1 From f60fb8e7f90f21598a62f2300218ab2b9b8cae7b Mon Sep 17 00:00:00 2001 From: schneefux Date: Wed, 15 Mar 2017 16:19:30 +0100 Subject: add server counter --- main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'main.py') diff --git a/main.py b/main.py index d071350..bae83fa 100644 --- a/main.py +++ b/main.py @@ -62,7 +62,7 @@ async def about(): """Print invite links.""" emb = discord.Embed( title="Vainsocial Discord bot", - description="Built by the Vainsocial development team using the MadGlory API." + description="Built by the Vainsocial development team using the MadGlory API. Currently running on %i servers." % (len(bot.servers),) ) emb.add_field(name="Website", value="https://alpha.vainsocial.com") -- cgit v1.3.1 From 362e508c62d82376a932539199efb08469b1d8b4 Mon Sep 17 00:00:00 2001 From: schneefux Date: Wed, 15 Mar 2017 16:50:29 +0100 Subject: add tracking args --- main.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'main.py') diff --git a/main.py b/main.py index bae83fa..e429cdb 100644 --- a/main.py +++ b/main.py @@ -65,7 +65,7 @@ async def about(): description="Built by the Vainsocial development team using the MadGlory API. Currently running on %i servers." % (len(bot.servers),) ) emb.add_field(name="Website", - value="https://alpha.vainsocial.com") + value="https://alpha.vainsocial.com/?src=discordbot") emb.add_field(name="Bot invite link", value="https://discordapp.com/oauth2/authorize?&client_id=287297889024213003&scope=bot") emb.add_field(name="Developer Discord", @@ -120,16 +120,16 @@ async def vainsocial(name: str, region: str = None): emb = discord.Embed( title="%(name)s" % data, - url="https://alpha.vainsocial.com/players/%(shard_id)s/%(name)s" % data + url="https://alpha.vainsocial.com/players/%(shard_id)s/%(name)s/?src=discordbot" % data ) emb.set_author(name="Vainsocial", url="https://alpha.vainsocial.com") emb.add_field(name="Profile", value=("%(wins)i wins / %(played)i games\n" + - "https://alpha.vainsocial.com/players/%(shard_id)s/%(name)s") % data) + "https://alpha.vainsocial.com/players/%(shard_id)s/%(name)s/?src=discordbot") % data) emb.add_field(name="Last match", value=("%(result)s %(mode)s as %(hero)s %(kills)i/%(deaths)i/%(assists)i\n" + - "https://alpha.vainsocial.com/matches/%(match_api_id)s") % data) + "https://alpha.vainsocial.com/matches/%(match_api_id)s/?src=discordbot") % data) emb.set_footer(text="Vainsocial - Vainglory social stats service") emb.set_thumbnail(url="https://alpha.vainsocial.com/images/game/skill_tiers/%(skill_tier)s.png" % data) -- cgit v1.3.1 From e66f60fb9f2c0cdeb023feba23ac4f99a9405e01 Mon Sep 17 00:00:00 2001 From: schneefux Date: Wed, 15 Mar 2017 16:53:26 +0100 Subject: map sg -> sea --- main.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'main.py') diff --git a/main.py b/main.py index e429cdb..adada9d 100644 --- a/main.py +++ b/main.py @@ -155,6 +155,8 @@ async def vainsocial(name: str, region: str = None): "Can't find you. What is your region?") return region = region.lower() + if region == "sea": + region = "sg" if region not in ["na", "eu", "sg", "ea", "sa"]: await bot.edit_message(msgid, "That region is not supported.") -- cgit v1.3.1 From 40d8e60a5dc4017289e3eeee453a6da9e737e87f Mon Sep 17 00:00:00 2001 From: schneefux Date: Thu, 16 Mar 2017 20:15:18 +0100 Subject: update tracking params --- main.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'main.py') diff --git a/main.py b/main.py index adada9d..c8c5576 100644 --- a/main.py +++ b/main.py @@ -65,7 +65,7 @@ async def about(): description="Built by the Vainsocial development team using the MadGlory API. Currently running on %i servers." % (len(bot.servers),) ) emb.add_field(name="Website", - value="https://alpha.vainsocial.com/?src=discordbot") + value="https://alpha.vainsocial.com/?utm_source=discord&utm_medium=vainsocial") emb.add_field(name="Bot invite link", value="https://discordapp.com/oauth2/authorize?&client_id=287297889024213003&scope=bot") emb.add_field(name="Developer Discord", @@ -120,16 +120,16 @@ async def vainsocial(name: str, region: str = None): emb = discord.Embed( title="%(name)s" % data, - url="https://alpha.vainsocial.com/players/%(shard_id)s/%(name)s/?src=discordbot" % data + url="https://alpha.vainsocial.com/players/%(shard_id)s/%(name)s/?utm_source=discord&utm_medium=vainsocial" % data ) emb.set_author(name="Vainsocial", url="https://alpha.vainsocial.com") emb.add_field(name="Profile", value=("%(wins)i wins / %(played)i games\n" + - "https://alpha.vainsocial.com/players/%(shard_id)s/%(name)s/?src=discordbot") % data) + "https://alpha.vainsocial.com/players/%(shard_id)s/%(name)s/?utm_source=discord&utm_medium=vainsocial") % data) emb.add_field(name="Last match", value=("%(result)s %(mode)s as %(hero)s %(kills)i/%(deaths)i/%(assists)i\n" + - "https://alpha.vainsocial.com/matches/%(match_api_id)s/?src=discordbot") % data) + "https://alpha.vainsocial.com/matches/%(match_api_id)s/?utm_source=discord&utm_medium=vainsocial") % data) emb.set_footer(text="Vainsocial - Vainglory social stats service") emb.set_thumbnail(url="https://alpha.vainsocial.com/images/game/skill_tiers/%(skill_tier)s.png" % data) -- cgit v1.3.1 From 5b2c6e3f290379f1124758d77c55db8b79c2df8b Mon Sep 17 00:00:00 2001 From: schneefux Date: Sun, 19 Mar 2017 15:52:22 +0100 Subject: show last match updated date --- main.py | 1 + 1 file changed, 1 insertion(+) (limited to 'main.py') diff --git a/main.py b/main.py index c8c5576..304fcc2 100644 --- a/main.py +++ b/main.py @@ -120,6 +120,7 @@ async def vainsocial(name: str, region: str = None): emb = discord.Embed( title="%(name)s" % data, + description="Last match registered (GMT): %(last_match_created_date)s" % data, url="https://alpha.vainsocial.com/players/%(shard_id)s/%(name)s/?utm_source=discord&utm_medium=vainsocial" % data ) emb.set_author(name="Vainsocial", -- cgit v1.3.1 From ca785cb1fad8ac9b53bce829e7876a5d9c086385 Mon Sep 17 00:00:00 2001 From: schneefux Date: Mon, 20 Mar 2017 17:59:52 +0100 Subject: use named links --- main.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'main.py') diff --git a/main.py b/main.py index 304fcc2..693fdd7 100644 --- a/main.py +++ b/main.py @@ -65,13 +65,13 @@ async def about(): description="Built by the Vainsocial development team using the MadGlory API. Currently running on %i servers." % (len(bot.servers),) ) emb.add_field(name="Website", - value="https://alpha.vainsocial.com/?utm_source=discord&utm_medium=vainsocial") + value="[vainsocial.com](https://alpha.vainsocial.com/?utm_source=discord&utm_medium=vainsocial)") emb.add_field(name="Bot invite link", - value="https://discordapp.com/oauth2/authorize?&client_id=287297889024213003&scope=bot") + value="[discordapp.com](https://discordapp.com/oauth2/authorize?&client_id=287297889024213003&scope=bot)") emb.add_field(name="Developer Discord", - value="https://discord.gg/txTchJY") + value="[txTchJY](https://discord.gg/txTchJY)") emb.add_field(name="Twitter", - value="https://twitter.com/vainsocial") + value="[twitter/vainsocial](https://twitter.com/vainsocial)") await bot.say(embed=emb) @@ -127,10 +127,10 @@ async def vainsocial(name: str, region: str = None): url="https://alpha.vainsocial.com") emb.add_field(name="Profile", value=("%(wins)i wins / %(played)i games\n" + - "https://alpha.vainsocial.com/players/%(shard_id)s/%(name)s/?utm_source=discord&utm_medium=vainsocial") % data) + "[View on vainsocial.com](https://alpha.vainsocial.com/players/%(shard_id)s/%(name)s/?utm_source=discord&utm_medium=vainsocial)") % data) emb.add_field(name="Last match", value=("%(result)s %(mode)s as %(hero)s %(kills)i/%(deaths)i/%(assists)i\n" + - "https://alpha.vainsocial.com/matches/%(match_api_id)s/?utm_source=discord&utm_medium=vainsocial") % data) + "[View on vainsocial.com](https://alpha.vainsocial.com/matches/%(match_api_id)s/?utm_source=discord&utm_medium=vainsocial)") % data) emb.set_footer(text="Vainsocial - Vainglory social stats service") emb.set_thumbnail(url="https://alpha.vainsocial.com/images/game/skill_tiers/%(skill_tier)s.png" % data) -- cgit v1.3.1 From 6dccd6c2973b1cde3ac66f24e290b9ed107e94a5 Mon Sep 17 00:00:00 2001 From: schneefux Date: Mon, 20 Mar 2017 18:04:27 +0100 Subject: print region --- main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'main.py') diff --git a/main.py b/main.py index 693fdd7..c74ea4d 100644 --- a/main.py +++ b/main.py @@ -119,7 +119,7 @@ async def vainsocial(name: str, region: str = None): data["result"] = "won" if data["winner"] else "lost" emb = discord.Embed( - title="%(name)s" % data, + title="%(name)s (%(shard_id)s)" % data, description="Last match registered (GMT): %(last_match_created_date)s" % data, url="https://alpha.vainsocial.com/players/%(shard_id)s/%(name)s/?utm_source=discord&utm_medium=vainsocial" % data ) -- cgit v1.3.1 From 6905c585e8bff61dad66594e28ef17d574c665da Mon Sep 17 00:00:00 2001 From: Vainsocial root Date: Tue, 21 Mar 2017 07:23:41 +0000 Subject: request only ranked, casual --- main.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'main.py') diff --git a/main.py b/main.py index 304fcc2..78b196f 100644 --- a/main.py +++ b/main.py @@ -172,7 +172,8 @@ async def vainsocial(name: str, region: str = None): "region": region, "params": { "filter[createdAt-start]": lmcd, - "filter[playerNames]": name + "filter[playerNames]": name, + "filter[gameMode]": "casual,ranked" } } jobid = (await queue.request(jobtype="grab", -- cgit v1.3.1 From d9a4daf9070beb801b76799b615c4f7b0493abab Mon Sep 17 00:00:00 2001 From: Vainsocial root Date: Fri, 24 Mar 2017 18:17:35 +0000 Subject: use release url --- main.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) (limited to 'main.py') diff --git a/main.py b/main.py index 95f83d1..5f0e076 100644 --- a/main.py +++ b/main.py @@ -54,7 +54,7 @@ async def on_ready(): await connect(source_db, dest_db) await bot.change_presence( game=discord.Game( - name="alpha.vainsocial.com")) + name="vainsocial.com")) @bot.command() @@ -65,11 +65,11 @@ async def about(): description="Built by the Vainsocial development team using the MadGlory API. Currently running on %i servers." % (len(bot.servers),) ) emb.add_field(name="Website", - value="[vainsocial.com](https://alpha.vainsocial.com/?utm_source=discord&utm_medium=vainsocial)") + value="[vainsocial.com](https://vainsocial.com/?utm_source=discord&utm_medium=vainsocial)") emb.add_field(name="Bot invite link", value="[discordapp.com](https://discordapp.com/oauth2/authorize?&client_id=287297889024213003&scope=bot)") emb.add_field(name="Developer Discord", - value="[txTchJY](https://discord.gg/txTchJY)") + value="[discord.me/vainsocial](https://discord.me/vainsocial)") emb.add_field(name="Twitter", value="[twitter/vainsocial](https://twitter.com/vainsocial)") await bot.say(embed=emb) @@ -121,19 +121,19 @@ async def vainsocial(name: str, region: str = None): emb = discord.Embed( title="%(name)s (%(shard_id)s)" % data, description="Last match registered (GMT): %(last_match_created_date)s" % data, - url="https://alpha.vainsocial.com/players/%(shard_id)s/%(name)s/?utm_source=discord&utm_medium=vainsocial" % data + url="https://vainsocial.com/players/%(shard_id)s/%(name)s/?utm_source=discord&utm_medium=vainsocial" % data ) emb.set_author(name="Vainsocial", - url="https://alpha.vainsocial.com") + url="https://vainsocial.com") emb.add_field(name="Profile", value=("%(wins)i wins / %(played)i games\n" + - "[View on vainsocial.com](https://alpha.vainsocial.com/players/%(shard_id)s/%(name)s/?utm_source=discord&utm_medium=vainsocial)") % data) + "[View on vainsocial.com](https://vainsocial.com/players/%(shard_id)s/%(name)s/?utm_source=discord&utm_medium=vainsocial)") % data) emb.add_field(name="Last match", value=("%(result)s %(mode)s as %(hero)s %(kills)i/%(deaths)i/%(assists)i\n" + - "[View on vainsocial.com](https://alpha.vainsocial.com/matches/%(match_api_id)s/?utm_source=discord&utm_medium=vainsocial)") % data) + "[View on vainsocial.com](https://vainsocial.com/matches/%(match_api_id)s/?utm_source=discord&utm_medium=vainsocial)") % data) emb.set_footer(text="Vainsocial - Vainglory social stats service") - emb.set_thumbnail(url="https://alpha.vainsocial.com/images/game/skill_tiers/%(skill_tier)s.png" % data) + emb.set_thumbnail(url="https://vainsocial.com/images/game/skill_tiers/%(skill_tier)s.png" % data) return emb async with pool.acquire() as con: -- cgit v1.3.1 From 7eb8795a2d80b0c83b2b38711cebfd27a2ae7d3b Mon Sep 17 00:00:00 2001 From: schneefux Date: Sat, 25 Mar 2017 16:21:58 +0100 Subject: rewrite using updater --- main.py | 155 ++++++++++++++++++++++++++++------------------------------------ 1 file changed, 67 insertions(+), 88 deletions(-) (limited to 'main.py') diff --git a/main.py b/main.py index 5f0e076..81dcaa3 100644 --- a/main.py +++ b/main.py @@ -2,21 +2,15 @@ import logging import os +import http +import json 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" -} +from socketIO_client import SocketIO, LoggingNamespace -dest_db = { +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", @@ -26,24 +20,11 @@ dest_db = { bot = commands.Bot( - command_prefix="!", + 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( - min_size=1, **db) - @bot.event async def on_ready(): @@ -51,7 +32,10 @@ async def on_ready(): "https://discordapp.com/oauth2/authorize?&" + "client_id=%s&scope=bot)", bot.user.name, bot.user.id) - await connect(source_db, dest_db) + logging.warning("connecting to database") + global pool + pool = await asyncpg.create_pool( + min_size=2, **db) await bot.change_presence( game=discord.Game( name="vainsocial.com")) @@ -76,7 +60,7 @@ async def about(): @bot.command(aliases=["v", "vain"]) -async def vainsocial(name: str, region: str = None): +async def vainsocial(name: str): """Retrieves a player's stats.""" query = """ SELECT @@ -88,11 +72,11 @@ async def vainsocial(name: str, region: str = None): participant.kills, participant.deaths, participant.assists, participant.farm, player.skill_tier, player.played, player.wins, player.last_match_created_date::text - FROM match, roster, participant, player where + 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::text<>$2 + player.name=$1 ORDER BY match.created_at DESC LIMIT 1 """ @@ -139,68 +123,63 @@ async def vainsocial(name: str, region: str = None): 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(embed=emb(data)) - - if not in_cache: - if region is None: - await bot.edit_message(msgid, - "Can't find you. What is your region?") - return - region = region.lower() - if region == "sea": - region = "sg" - if region not in ["na", "eu", "sg", "ea", "sa"]: - await bot.edit_message(msgid, - "That region is not supported.") - return - else: - if region is None: - region = data["shard_id"] - - logging.info("'%s' cached: %s", name, in_cache) - - payload = { - "region": region, - "params": { - "filter[createdAt-start]": lmcd, - "filter[playerNames]": name, - "filter[gameMode]": "casual,ranked" - } - } - jobid = (await queue.request(jobtype="grab", - payload=[payload], - priority=[1]))[0] - - while True: - # wait for grab job to finish - status = await queue.status(jobid) - if status == 'finished': - break - if status == 'failed': - logging.warning("'%s': not found", name) - if not in_cache: - await bot.edit_message(msgid, - "Could not find you.") - return - asyncio.sleep(0.5) - - while True: - # wait for processor to update the player - data = await con.fetchrow(query, name, lmcd) - if data is not None: + bot_response = await bot.say("Loading…") + # TODO this is shitty. Shouldn't need globals to keep track of updates + # also, this is buggy. + # TODO: Use a Python-friendly alternative to socketio + global do_update, wait_for_update + wait_for_update = True # continue polling + do_update = True # poll immediately + + async def request_update(): + global wait_for_update + # request an update via Vainsocial API + api_con = http.client.HTTPConnection("localhost", 8080) + api_con.request("HEAD", "/api/player/name/" + name) + api_resp = api_con.getresponse() + logging.info("%s: API responded with status %i", + name, api_resp.status) + if api_resp.status == 404: + wait_for_update = False + await bot.edit_message(bot_response, + "Could not find you.") + api_con.close() + + def update_available(*args): + global do_update, wait_for_update + # TODO here is the problem ^ don't work in closures :( + if args[0] in ["process_finished", "compile_finished"]: + do_update = True + if args[0] in ["grab_failed", "process_finished", + "compile_finished"]: + wait_for_update = False + + io = SocketIO("localhost", 8080, LoggingNamespace) + io.on(name, update_available) + + asyncio.ensure_future(request_update()) + + has_embed = False + for _ in range(10): # try for 10s + if do_update: + logging.info("%s: updating bot response", + name) + do_update = False + data = await con.fetchrow(query, name) + if data is not None: + has_embed = True + await bot.edit_message(bot_response, + embed=emb(data)) + else: + logging.info("%s: no data in db, waiting for API", + name) + if not wait_for_update: break - asyncio.sleep(0.5) + io.wait(seconds=1) - await bot.edit_message(msgid, embed=emb(data)) + if has_embed: + await bot.edit_message(bot_response, "Up to date.") + # if not, 404 logging.basicConfig(level=logging.INFO) -- cgit v1.3.1 From 7a69ec99048c4f08b6623527c0271d7fb01c0072 Mon Sep 17 00:00:00 2001 From: schneefux Date: Sat, 25 Mar 2017 18:20:16 +0100 Subject: wait for 'done' event --- main.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'main.py') diff --git a/main.py b/main.py index 81dcaa3..f1b94ab 100644 --- a/main.py +++ b/main.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 +# TODO one request blocks the whole event loop. import logging import os @@ -150,8 +151,7 @@ async def vainsocial(name: str): # TODO here is the problem ^ don't work in closures :( if args[0] in ["process_finished", "compile_finished"]: do_update = True - if args[0] in ["grab_failed", "process_finished", - "compile_finished"]: + if args[0] == "done": wait_for_update = False io = SocketIO("localhost", 8080, LoggingNamespace) @@ -160,7 +160,7 @@ async def vainsocial(name: str): asyncio.ensure_future(request_update()) has_embed = False - for _ in range(10): # try for 10s + for _ in range(30): # try for s if do_update: logging.info("%s: updating bot response", name) @@ -175,6 +175,7 @@ async def vainsocial(name: str): name) if not wait_for_update: break + io.wait(seconds=1) if has_embed: -- cgit v1.3.1 From daecd8249d8e4c6b50dfe2541b9787c954b1d1e7 Mon Sep 17 00:00:00 2001 From: schneefux Date: Sat, 25 Mar 2017 19:19:33 +0100 Subject: do not block main loop --- main.py | 48 +++++++++++++++++++++--------------------------- 1 file changed, 21 insertions(+), 27 deletions(-) (limited to 'main.py') diff --git a/main.py b/main.py index f1b94ab..db36495 100644 --- a/main.py +++ b/main.py @@ -1,10 +1,9 @@ #!/usr/bin/env python3 -# TODO one request blocks the whole event loop. import logging import os +import concurrent.futures import http -import json import asyncio import asyncpg import discord @@ -19,12 +18,15 @@ db = { "database": os.environ.get("POSTGRESQL_DEST_DB") or "vainsocial-web" } +TIMEOUT = 120 # s until giving up on API bot = commands.Bot( command_prefix="?", description="Vainsocial Vainglory stats bot") pool = None +threadpool = concurrent.futures.ThreadPoolExecutor( + max_workers=10) @bot.event @@ -125,15 +127,10 @@ async def vainsocial(name: str): await bot.type() bot_response = await bot.say("Loading…") - # TODO this is shitty. Shouldn't need globals to keep track of updates - # also, this is buggy. - # TODO: Use a Python-friendly alternative to socketio - global do_update, wait_for_update - wait_for_update = True # continue polling - do_update = True # poll immediately + vainsocial.wait_for_update = True # continue polling + vainsocial.do_update = True # poll immediately async def request_update(): - global wait_for_update # request an update via Vainsocial API api_con = http.client.HTTPConnection("localhost", 8080) api_con.request("HEAD", "/api/player/name/" + name) @@ -141,46 +138,43 @@ async def vainsocial(name: str): logging.info("%s: API responded with status %i", name, api_resp.status) if api_resp.status == 404: - wait_for_update = False + vainsocial.wait_for_update = False await bot.edit_message(bot_response, "Could not find you.") api_con.close() def update_available(*args): - global do_update, wait_for_update - # TODO here is the problem ^ don't work in closures :( if args[0] in ["process_finished", "compile_finished"]: - do_update = True + # do partial update + vainsocial.do_update = True if args[0] == "done": - wait_for_update = False + vainsocial.wait_for_update = False + io = SocketIO("localhost", 8080, LoggingNamespace) io.on(name, update_available) asyncio.ensure_future(request_update()) + asyncio.ensure_future( + bot.loop.run_in_executor(threadpool, io.wait, TIMEOUT)) has_embed = False - for _ in range(30): # try for s - if do_update: - logging.info("%s: updating bot response", - name) - do_update = False + for _ in range(TIMEOUT): + if vainsocial.do_update: + logging.info("%s: updating bot response", name) + vainsocial.do_update = False data = await con.fetchrow(query, name) - if data is not None: + if data: has_embed = True await bot.edit_message(bot_response, embed=emb(data)) - else: - logging.info("%s: no data in db, waiting for API", - name) - if not wait_for_update: + if not vainsocial.wait_for_update: break - - io.wait(seconds=1) + await asyncio.sleep(0.1) if has_embed: await bot.edit_message(bot_response, "Up to date.") - # if not, 404 + # if not, it was a 404 logging.basicConfig(level=logging.INFO) -- cgit v1.3.1 From bf068ebf9e86f527474e30bdaf4a74503f11d608 Mon Sep 17 00:00:00 2001 From: schneefux Date: Sat, 25 Mar 2017 20:55:32 +0100 Subject: accidentally changed prefix --- main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'main.py') diff --git a/main.py b/main.py index db36495..0b81fa8 100644 --- a/main.py +++ b/main.py @@ -21,7 +21,7 @@ db = { TIMEOUT = 120 # s until giving up on API bot = commands.Bot( - command_prefix="?", + command_prefix="!", description="Vainsocial Vainglory stats bot") pool = None -- cgit v1.3.1 From 291cf5046f1497c49b0266d0d818882a2b96d5d4 Mon Sep 17 00:00:00 2001 From: schneefux Date: Sun, 26 Mar 2017 23:13:32 +0200 Subject: fix timeout occuring too early, kill websocket when it's not needed anymore --- main.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) (limited to 'main.py') diff --git a/main.py b/main.py index 0b81fa8..a80dc92 100644 --- a/main.py +++ b/main.py @@ -154,12 +154,13 @@ async def vainsocial(name: str): io = SocketIO("localhost", 8080, LoggingNamespace) io.on(name, update_available) - asyncio.ensure_future(request_update()) - asyncio.ensure_future( + socket_waiter = asyncio.ensure_future( bot.loop.run_in_executor(threadpool, io.wait, TIMEOUT)) + asyncio.ensure_future(request_update()) has_embed = False - for _ in range(TIMEOUT): + poll_frequency = 5 # Hz + for _ in range(TIMEOUT*poll_frequency): if vainsocial.do_update: logging.info("%s: updating bot response", name) vainsocial.do_update = False @@ -170,7 +171,9 @@ async def vainsocial(name: str): embed=emb(data)) if not vainsocial.wait_for_update: break - await asyncio.sleep(0.1) + await asyncio.sleep(1.0/poll_frequency) + + socket_waiter.cancel() # stop listening on websocket if has_embed: await bot.edit_message(bot_response, "Up to date.") -- cgit v1.3.1 From 0564241327ecc6eb08cd716f510ebb0afbb3eeb3 Mon Sep 17 00:00:00 2001 From: schneefux Date: Tue, 11 Apr 2017 21:21:22 +0200 Subject: draft: rewrite in node --- .gitignore | 129 +++++++++++------------------ api.js | 73 ++++++++++++++++ bot.js | 66 +++++++++++++++ commands/vainsocial/match.js | 41 +++++++++ commands/vainsocial/matches.js | 34 ++++++++ commands/vainsocial/user.js | 33 ++++++++ main.py | 184 ----------------------------------------- package.json | 27 ++++++ requirements.txt | 14 ---- responses.js | 156 ++++++++++++++++++++++++++++++++++ settings.sqlite3 | Bin 0 -> 8192 bytes 11 files changed, 478 insertions(+), 279 deletions(-) create mode 100644 api.js create mode 100644 bot.js create mode 100644 commands/vainsocial/match.js create mode 100644 commands/vainsocial/matches.js create mode 100644 commands/vainsocial/user.js delete mode 100644 main.py create mode 100644 package.json delete mode 100644 requirements.txt create mode 100644 responses.js create mode 100644 settings.sqlite3 (limited to 'main.py') diff --git a/.gitignore b/.gitignore index a3401aa..2788ca5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,93 +1,60 @@ -# 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: +# Logs +logs *.log -!/logs -!run*.log -local_settings.py +npm-debug.log* +yarn-debug.log* +yarn-error.log* -# Flask stuff: -instance/ -.webassets-cache +# Runtime data +pids +*.pid +*.seed +*.pid.lock -# Scrapy stuff: -.scrapy +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov -# Sphinx documentation -docs/_build/ +# Coverage directory used by tools like istanbul +coverage -# PyBuilder -target/ +# nyc test coverage +.nyc_output -# Jupyter Notebook -.ipynb_checkpoints +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt -# pyenv -.python-version +# Bower dependency directory (https://bower.io/) +bower_components -# celery beat schedule file -celerybeat-schedule +# node-waf configuration +.lock-wscript -# dotenv -.env +# Compiled binary addons (http://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Typescript v1 declaration files +typings/ + +# Optional npm cache directory +.npm -# virtualenv -.venv/ -venv/ -ENV/ +# Optional eslint cache +.eslintcache -# Spyder project settings -.spyderproject +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env -# Rope project settings -.ropeproject +.sqlite3 diff --git a/api.js b/api.js new file mode 100644 index 0000000..fef1ffe --- /dev/null +++ b/api.js @@ -0,0 +1,73 @@ +#!/usr/bin/node +/* jshint esnext:true */ +"use strict"; + +const request = require("request-promise-native"), + WebSocket = require("ws"), + webstomp = require("webstomp-client"), + Channel = require("async-csp").Channel; + +const API_FE_URL = process.env.API_FE_URL || "http://vainsocial.dev/bots/api", + API_WS_URL = process.env.API_WS_URL || "ws://vainsocial.dev/ws", + API_BE_URL = process.env.API_BE_URL || "http://vainsocial.dev/bridge"; + +const ws = new WebSocket(API_WS_URL, { perMessageDeflate: false }), + notif = webstomp.over(ws); + +function connect() { + notif.connect("web", "web", + () => console.log("connected to queue"), + (err) => console.error("error connecting to queue", err)); + keepalive(); +} + +function keepalive() { + ws.ping("", false, true); + setTimeout(this, 60000); +} + +ws.on("ready", connect); + +// TODO use keepalive / connection pool +async function getFE(url) { + return await request({ + uri: API_FE_URL + url, + json: true + }); +} + +async function postBE(url) { + return await request.post({ + uri: API_BE_URL + url, + json: true + }); +} + +function subscribe(topic, channel) { + notif.subscribe("/topic/" + topic, async (msg) => { + await channel.put(msg.body); + msg.ack(); + }, {"ack": "client"}); +} + +module.exports.searchPlayer = async function (name, timeout=30) { + let channel = new Channel(); + + subscribe("player." + name, channel); + await postBE("/player/" + name + "/search"); + setTimeout(() => channel.close(), timeout*1000); + channel.put(""); // initial fetch + + let generator = async () => { + let msg = await channel.take(); + if (msg == "search_fail") { + throw "not found"; + } + if (msg == Channel.DONE) { + throw "exhausted"; + } + return await getFE("/player/" + name); + } + + return { next: generator }; +} diff --git a/bot.js b/bot.js new file mode 100644 index 0000000..c3826c1 --- /dev/null +++ b/bot.js @@ -0,0 +1,66 @@ +#!/usr/bin/node +/* jshint esnext:true */ +"use strict"; + +const sqlite = require("sqlite"), + path = require("path"), + Commando = require("discord.js-commando"), + oneLine = require("common-tags").oneLine, + client = new Commando.Client({ + owner: "227440521704898561", // shutterfly + commandPrefix: "?" + }); + +const DISCORDTOKEN = process.env.DISCORDTOKEN || "Mjg5ODM2OTAwOTg5MDA5OTIw.C6SLKA.j8UETpPHztDV45xicf11hwpwNK8"; + +client + .on("error", console.error) + .on("warn", console.warn) + .on("debug", console.log) + .on("ready", () => { + console.log(`Client ready; logged in as ${client.user.username}#${client.user.discriminator} (${client.user.id})`); + }) + .on('disconnect', () => { console.warn('Disconnected!'); }) + .on('reconnecting', () => { console.warn('Reconnecting...'); }) + .on('commandError', (cmd, err) => { + if(err instanceof Commando.FriendlyError) return; + console.error(`Error in command ${cmd.groupID}:${cmd.memberName}`, err); + }) + .on('commandBlocked', (msg, reason) => { + console.log(oneLine` + Command ${msg.command ? `${msg.command.groupID}:${msg.command.memberName}` : ''} + blocked; ${reason} + `); + }) + .on('commandPrefixChange', (guild, prefix) => { + console.log(oneLine` + Prefix ${prefix === '' ? 'removed' : `changed to ${prefix || 'the default'}`} + ${guild ? `in guild ${guild.name} (${guild.id})` : 'globally'}. + `); + }) + .on('commandStatusChange', (guild, command, enabled) => { + console.log(oneLine` + Command ${command.groupID}:${command.memberName} + ${enabled ? 'enabled' : 'disabled'} + ${guild ? `in guild ${guild.name} (${guild.id})` : 'globally'}. + `); + }) + .on('groupStatusChange', (guild, group, enabled) => { + console.log(oneLine` + Group ${group.id} + ${enabled ? 'enabled' : 'disabled'} + ${guild ? `in guild ${guild.name} (${guild.id})` : 'globally'}. + `); + }); + +client.setProvider( + sqlite.open(path.join(__dirname, "settings.sqlite3")).then( + db => new Commando.SQLiteProvider(db))); + +client.registry + .registerGroup('vainsocial', 'VainSocial') + .registerDefaults() + .registerCommandsIn(path.join(__dirname, 'commands')); + + +client.login(DISCORDTOKEN); diff --git a/commands/vainsocial/match.js b/commands/vainsocial/match.js new file mode 100644 index 0000000..14b06a2 --- /dev/null +++ b/commands/vainsocial/match.js @@ -0,0 +1,41 @@ +#!/usr/bin/node +/* jshint esnext:true */ +"use strict"; + +const Commando = require("discord.js-commando"), + oneLine = require("common-tags").oneLine, + responses = require("../../responses"); + +module.exports = class ShowMatchCommand extends Commando.Command { + constructor(client) { + super(client, { + name: "vainsocial-match", + aliases: ["vm"], + group: "vainsocial", + memberName: "vainsocial-match", + description: "Show a user's match in detail.", + details: oneLine` + todo + `, + examples: ["vm shutterfly 1"], + argsType: "multiple", + argsCount: 2, + + args: [ { + key: "name", + label: "name", + prompt: "Please specify your in game name (Case Sensitive).", + type: "string" + }, { + key: "number", + label: "number", + prompt: "Please specify how far you want to go back in history. Use 1 or leave out for the latest match.", + type: "integer", + default: 1 + } ] + }); + } + async run(msg, args) { + await responses.showMatch(msg, args); + } +}; diff --git a/commands/vainsocial/matches.js b/commands/vainsocial/matches.js new file mode 100644 index 0000000..2660d73 --- /dev/null +++ b/commands/vainsocial/matches.js @@ -0,0 +1,34 @@ +#!/usr/bin/node +/* jshint esnext:true */ +"use strict"; + +const Commando = require("discord.js-commando"), + oneLine = require("common-tags").oneLine, + responses = require("../../responses"); + +module.exports = class ShowMatchesCommand extends Commando.Command { + constructor(client) { + super(client, { + name: "vainsocial-history", + aliases: ["vh"], + group: "vainsocial", + memberName: "vainsocial-matches", + description: "Show a user's match history.", + details: oneLine` + todo + `, + examples: ["vh shutterfly"], + argsType: "single", + + args: [ { + key: "name", + label: "name", + prompt: "Please specify your in game name (Case Sensitive).", + type: "string" + } ] + }); + } + async run(msg, args) { + await responses.showMatches(msg, args); + } +}; diff --git a/commands/vainsocial/user.js b/commands/vainsocial/user.js new file mode 100644 index 0000000..f544a5e --- /dev/null +++ b/commands/vainsocial/user.js @@ -0,0 +1,33 @@ +#!/usr/bin/node +/* jshint esnext:true */ +"use strict"; + +const Commando = require("discord.js-commando"), + oneLine = require("common-tags").oneLine, + responses = require("../../responses"); + +module.exports = class ShowUserCommand extends Commando.Command { + constructor(client) { + super(client, { + name: "vainsocial-user", + aliases: ["v", "vu"], + group: "vainsocial", + memberName: "vainsocial-user", + description: "Show a player\'s profile.", + details: oneLine` + Display VainSocial lifetime statistics from Vainglory + `, + examples: ["vu shutterfly"], + + args: [ { + key: "name", + label: "name", + prompt: "Please specify your in game name (Case Sensitive).", + type: "string" + } ] + }); + } + async run(msg, args) { + await responses.showUser(msg, args); + } +}; diff --git a/main.py b/main.py deleted file mode 100644 index a80dc92..0000000 --- a/main.py +++ /dev/null @@ -1,184 +0,0 @@ -#!/usr/bin/env python3 - -import logging -import os -import concurrent.futures -import http -import asyncio -import asyncpg -import discord -from discord.ext import commands -from socketIO_client import SocketIO, LoggingNamespace - -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" -} - -TIMEOUT = 120 # s until giving up on API - -bot = commands.Bot( - command_prefix="!", - description="Vainsocial Vainglory stats bot") - -pool = None -threadpool = concurrent.futures.ThreadPoolExecutor( - max_workers=10) - - -@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) - logging.warning("connecting to database") - global pool - pool = await asyncpg.create_pool( - min_size=2, **db) - await bot.change_presence( - game=discord.Game( - name="vainsocial.com")) - - -@bot.command() -async def about(): - """Print invite links.""" - emb = discord.Embed( - title="Vainsocial Discord bot", - description="Built by the Vainsocial development team using the MadGlory API. Currently running on %i servers." % (len(bot.servers),) - ) - emb.add_field(name="Website", - value="[vainsocial.com](https://vainsocial.com/?utm_source=discord&utm_medium=vainsocial)") - emb.add_field(name="Bot invite link", - value="[discordapp.com](https://discordapp.com/oauth2/authorize?&client_id=287297889024213003&scope=bot)") - emb.add_field(name="Developer Discord", - value="[discord.me/vainsocial](https://discord.me/vainsocial)") - emb.add_field(name="Twitter", - value="[twitter/vainsocial](https://twitter.com/vainsocial)") - await bot.say(embed=emb) - - -@bot.command(aliases=["v", "vain"]) -async def vainsocial(name: str): - """Retrieves a player's stats.""" - query = """ - SELECT - player.name, - player.shard_id, - match.game_mode, - roster.match_api_id, - participant.hero, participant.winner, - participant.kills, participant.deaths, participant.assists, participant.farm, - player.skill_tier, player.played, player.wins, - player.last_match_created_date::text - 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 - ORDER BY match.created_at DESC - LIMIT 1 - """ - def emb(dct): - data = dict(dct) - - 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" - } - data["mode"] = modes.get(data["game_mode"]) or data["game_mode"] - heroes = { - "Sayoc": "Taka", - "Hero009": "Krul", - "Hero010": "Skaarf", - "Hero016": "Rona" - } - data["hero"] = data["hero"].replace("*", "") - data["hero"] = heroes.get(data["hero"]) or data["hero"] - data["result"] = "won" if data["winner"] else "lost" - - emb = discord.Embed( - title="%(name)s (%(shard_id)s)" % data, - description="Last match registered (GMT): %(last_match_created_date)s" % data, - url="https://vainsocial.com/players/%(shard_id)s/%(name)s/?utm_source=discord&utm_medium=vainsocial" % data - ) - emb.set_author(name="Vainsocial", - url="https://vainsocial.com") - emb.add_field(name="Profile", - value=("%(wins)i wins / %(played)i games\n" + - "[View on vainsocial.com](https://vainsocial.com/players/%(shard_id)s/%(name)s/?utm_source=discord&utm_medium=vainsocial)") % data) - emb.add_field(name="Last match", - value=("%(result)s %(mode)s as %(hero)s %(kills)i/%(deaths)i/%(assists)i\n" + - "[View on vainsocial.com](https://vainsocial.com/matches/%(match_api_id)s/?utm_source=discord&utm_medium=vainsocial)") % data) - - emb.set_footer(text="Vainsocial - Vainglory social stats service") - emb.set_thumbnail(url="https://vainsocial.com/images/game/skill_tiers/%(skill_tier)s.png" % data) - return emb - - async with pool.acquire() as con: - await bot.type() - - bot_response = await bot.say("Loading…") - vainsocial.wait_for_update = True # continue polling - vainsocial.do_update = True # poll immediately - - async def request_update(): - # request an update via Vainsocial API - api_con = http.client.HTTPConnection("localhost", 8080) - api_con.request("HEAD", "/api/player/name/" + name) - api_resp = api_con.getresponse() - logging.info("%s: API responded with status %i", - name, api_resp.status) - if api_resp.status == 404: - vainsocial.wait_for_update = False - await bot.edit_message(bot_response, - "Could not find you.") - api_con.close() - - def update_available(*args): - if args[0] in ["process_finished", "compile_finished"]: - # do partial update - vainsocial.do_update = True - if args[0] == "done": - vainsocial.wait_for_update = False - - - io = SocketIO("localhost", 8080, LoggingNamespace) - io.on(name, update_available) - - socket_waiter = asyncio.ensure_future( - bot.loop.run_in_executor(threadpool, io.wait, TIMEOUT)) - asyncio.ensure_future(request_update()) - - has_embed = False - poll_frequency = 5 # Hz - for _ in range(TIMEOUT*poll_frequency): - if vainsocial.do_update: - logging.info("%s: updating bot response", name) - vainsocial.do_update = False - data = await con.fetchrow(query, name) - if data: - has_embed = True - await bot.edit_message(bot_response, - embed=emb(data)) - if not vainsocial.wait_for_update: - break - await asyncio.sleep(1.0/poll_frequency) - - socket_waiter.cancel() # stop listening on websocket - - if has_embed: - await bot.edit_message(bot_response, "Up to date.") - # if not, it was a 404 - - -logging.basicConfig(level=logging.INFO) -bot.run(os.environ["VAINSOCIAL_DISCORDTOKEN"]) diff --git a/package.json b/package.json new file mode 100644 index 0000000..ddf79e4 --- /dev/null +++ b/package.json @@ -0,0 +1,27 @@ +{ + "name": "discordbot", + "version": "2.0.0", + "description": "", + "main": "bot.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "schneefux", + "license": "UNLICENSED", + "dependencies": { + "async-csp": "^0.5.0", + "bufferutil": "^3.0.0", + "discord-emoji": "^1.1.1", + "discord.js": "^11.0.0", + "discord.js-commando": "^0.9.0", + "erlpack": "github:hammerandchisel/erlpack", + "request": "^2.81.0", + "request-promise": "^4.2.0", + "request-promise-native": "^1.0.3", + "sqlite": "^2.5.0", + "uws": "^0.14.1", + "webstomp": "0.0.10", + "webstomp-client": "^1.0.6", + "ws": "^2.2.3" + } +} diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 427dc0d..0000000 --- a/requirements.txt +++ /dev/null @@ -1,14 +0,0 @@ -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 -requests==2.13.0 -six==1.10.0 -socketIO-client==0.7.2 -websocket-client==0.40.0 -websockets==3.2 diff --git a/responses.js b/responses.js new file mode 100644 index 0000000..f54ea89 --- /dev/null +++ b/responses.js @@ -0,0 +1,156 @@ +#!/usr/bin/node +/* jshint esnext:true */ +"use strict"; + +const Commando = require("discord.js-commando"), + Discord = require("discord.js"), + emoji = require("discord-emoji"), + oneLine = require("common-tags").oneLine, + api = require("./api"); + +function vainsocialEmbed(title, link) { + return new Discord.RichEmbed() + .setTitle(title) + .setURL("https://vainsocial.com/" + link) + .setColor("#55ADD3") + .setAuthor("VainSocial", null, "https://vainsocial.com") + .setFooter("VainSocial - Vainglory social stats service") + ; +} + +// returns the shortest version of the usage help +// just '?vm' +function usg(msg, cmd) { + return msg.anyUsage(cmd, undefined, null); +} + +// TODO, obviously + +// show player profile and last match +module.exports.showUser = async (msg, args) => { + let ign = args.name, + iterator = await api.searchPlayer(ign), + response, data; + while (true) { + try { + data = await iterator.next(); + } catch (err) { + if (err.statusCode == 404) { + await msg.reply("Could not find `" + ign + "`"); + break; + } + if (err == "exhausted") + break; + } + let winstr = "won"; + if (data.last_result[0].winner == false) winstr = "lost"; + + let embed = vainsocialEmbed(ign, "player/" + ign) + .setThumbnail("https://vainsocial.com/images/game/skill_tiers/" + + data.skill_tier + ".png") + .setDescription("") + .addField("Profile", ` + (Player stats will be here) + `) + .addField("Last match", oneLine` + Played ${data.last_result[0].game_mode_id} with ${data.last_result[0].actor}, ${winstr} + *${emoji.symbols.information_source} or ${usg(msg, "vm " + ign)} for more* + `) + .setTimestamp(new Date(data.last_match_created_date)) + ; + + if (response == undefined) { + response = await msg.replyEmbed(embed); + await response.react(emoji.symbols.information_source); + msg.client.on("messageReactionAdd", async (react) => { + if (react.message != response) return; + if (react.emoji != emoji.symbols.information_source) return; + await showMatch(msg, { + name: ign, + id: data.last_result[0].match_api_id + }); + }); + } else { + response = await msg.editResponse(response, { + type: "plain", + content: "", + options: { embed: embed } + }); + } + } +} + +// show match in detail +let showMatch = module.exports.showMatch = async (msg, args) => { + let response, + ign = args.name, + id = args.id, + index = args.number, + match; + if (id != null) match = {}; + else match = {}; // fetch match from API + let embed = vainsocialEmbed("A Match", "match/" + 12345) + .setDescription("A lot of stuff happened here. This was ranked or casual?") + .addField("Left team", ` + Killed some heroes from the right team and someone died + `) + .addField("Right team", ` + Did good work too. Poor minions. + `) + //.setTimestamp(new Date(data.last_match_created_date)) + ; + response = await msg.editResponse(response, { + type: "plain", + content: "", + options: { embed: embed } + }); +} + +// show match history +module.exports.showMatches = async (msg, args) => { + let ign = args.name, + response; + let count = [ + emoji.symbols.one, + emoji.symbols.two, + emoji.symbols.three, + emoji.symbols.four, + emoji.symbols.five, + emoji.symbols.six, + emoji.symbols.seven, + emoji.symbols.eight, + emoji.symbols.nine, + emoji.symbols.ten + ]; + let embed = vainsocialEmbed(ign, "player/" + ign) + .setDescription(` + Last 1337 casual and ranked matches. + *${emoji.symbols["1234"]} or ${usg(msg, "vm " + ign)} number for details* + `) + .addField("Match 1", ` + Blablabla. Blabla. + `) + .addField("Match 2", ` + Ooooh did a Minion just walk into our base? + `) + ; + response = await msg.editResponse(response, { + type: "plain", + content: "", + options: { embed: embed } + }); + + msg.client.on("messageReactionAdd", async (react) => { + if (react.message != response) return; + if (react.count == 1) return; // was me + let idx = count.indexOf(react.emoji.name); + if (idx == -1) return; + await showMatch(msg, { + name: ign, + number: idx + }); + }); + + for (let num of count) + await response.react(num) +} diff --git a/settings.sqlite3 b/settings.sqlite3 new file mode 100644 index 0000000..15ce44e Binary files /dev/null and b/settings.sqlite3 differ -- cgit v1.3.1