summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore119
-rw-r--r--api.js73
-rw-r--r--bot.js66
-rw-r--r--commands/vainsocial/match.js41
-rw-r--r--commands/vainsocial/matches.js34
-rw-r--r--commands/vainsocial/user.js33
-rw-r--r--main.py184
-rw-r--r--package.json27
-rw-r--r--requirements.txt14
-rw-r--r--responses.js156
-rw-r--r--settings.sqlite3bin0 -> 8192 bytes
11 files changed, 473 insertions, 274 deletions
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
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
-# C extensions
-*.so
+# Runtime data
+pids
+*.pid
+*.seed
+*.pid.lock
-# Distribution / packaging
-.Python
-env/
-build/
-develop-eggs/
-dist/
-downloads/
-eggs/
-.eggs/
-lib/
-lib64/
-parts/
-sdist/
-var/
-wheels/
-*.egg-info/
-.installed.cfg
-*.egg
+# Directory for instrumented libs generated by jscoverage/JSCover
+lib-cov
-# 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
+# Coverage directory used by tools like istanbul
+coverage
-# Installer logs
-pip-log.txt
-pip-delete-this-directory.txt
+# nyc test coverage
+.nyc_output
-# Unit test / coverage reports
-htmlcov/
-.tox/
-.coverage
-.coverage.*
-.cache
-nosetests.xml
-coverage.xml
-*,cover
-.hypothesis/
+# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
-# Translations
-*.mo
-*.pot
+# Bower dependency directory (https://bower.io/)
+bower_components
-# Django stuff:
-*.log
-!/logs
-!run*.log
-local_settings.py
+# node-waf configuration
+.lock-wscript
-# Flask stuff:
-instance/
-.webassets-cache
+# Compiled binary addons (http://nodejs.org/api/addons.html)
+build/Release
-# Scrapy stuff:
-.scrapy
+# Dependency directories
+node_modules/
+jspm_packages/
-# Sphinx documentation
-docs/_build/
+# Typescript v1 declaration files
+typings/
-# PyBuilder
-target/
+# Optional npm cache directory
+.npm
-# Jupyter Notebook
-.ipynb_checkpoints
+# Optional eslint cache
+.eslintcache
-# pyenv
-.python-version
+# Optional REPL history
+.node_repl_history
-# celery beat schedule file
-celerybeat-schedule
+# Output of 'npm pack'
+*.tgz
-# dotenv
-.env
+# Yarn Integrity file
+.yarn-integrity
-# virtualenv
-.venv/
-venv/
-ENV/
-
-# Spyder project settings
-.spyderproject
+# 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
--- /dev/null
+++ b/settings.sqlite3
Binary files differ