summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorschneefux <schneefux+commit@schneefux.xyz>2017-05-04 22:33:17 +0200
committerschneefux <schneefux+commit@schneefux.xyz>2017-05-04 22:33:17 +0200
commit5045641b065bfea9a0db39dd4814b2cf2269e723 (patch)
tree08f3a90d952fb5a5655a105437d0b9de6572a6a6
parent188e3997092a57be11574e5e121af5882dfb40ec (diff)
downloaddiscordbot-5045641b065bfea9a0db39dd4814b2cf2269e723.tar.gz
discordbot-5045641b065bfea9a0db39dd4814b2cf2269e723.zip
fame support (wip)
-rw-r--r--api.js25
-rw-r--r--bot.js6
-rw-r--r--commands/vainsocial/guild_add.js80
-rw-r--r--commands/vainsocial/guild_create.js66
-rw-r--r--commands/vainsocial/guild_view.js58
-rw-r--r--commands/vainsocial/me.js26
-rw-r--r--package.json1
-rw-r--r--responses.js214
-rw-r--r--util.js118
9 files changed, 419 insertions, 175 deletions
diff --git a/api.js b/api.js
index a5d42af..5e0a456 100644
--- a/api.js
+++ b/api.js
@@ -2,7 +2,7 @@
/* jshint esnext:true */
"use strict";
-const request = require("request-promise-native"),
+const request = require("request-promise"),
Promise = require("bluebird"),
WebSocket = require("ws"),
webstomp = require("webstomp-client"),
@@ -16,7 +16,7 @@ let cache = cacheManager.caching({
const UPDATE_TIMEOUT = parseInt(process.env.UPDATE_TIMEOUT) || 60; // s
-const API_FE_URL = process.env.API_FE_URL || "http://vainsocial.dev/bots/api",
+const API_FE_URL = process.env.API_FE_URL || "http://vainsocial.dev/bot/api",
API_MAP_URL = process.env.API_MAP_URL || "http://vainsocial.dev/masters/",
API_WS_URL = process.env.API_WS_URL || "ws://vainsocial.dev/ws",
API_BE_URL = process.env.API_BE_URL || "http://vainsocial.dev/bridge";
@@ -113,14 +113,9 @@ module.exports.subscribeUpdates = function(name, timeout=UPDATE_TIMEOUT) {
let msg;
return { next: async function () {
- if (this._first) {
- this._first = false;
- await postBE("/player/" + name + "/update");
- return true;
- }
do {
msg = await channel.take();
- } while([Channel.DONE, "initial", "search_fail",
+ } while([Channel.DONE, "search_fail", "search_success",
"stats_update", "matches_update"].indexOf(msg) == -1);
// bust caches
if (["stats_update"].indexOf(msg) != -1)
@@ -133,8 +128,18 @@ module.exports.subscribeUpdates = function(name, timeout=UPDATE_TIMEOUT) {
subscription.unsubscribe();
return undefined;
}
- return true;
- }, _first: true };
+ return msg;
+ } };
+}
+
+// search an unknown player
+module.exports.searchPlayer = async function(name) {
+ return await postBE("/player/" + name + "/search");
+}
+
+// update a known player
+module.exports.updatePlayer = async function(name) {
+ return await postBE("/player/" + name + "/update");
}
// return player
diff --git a/bot.js b/bot.js
index 13ca105..5968392 100644
--- a/bot.js
+++ b/bot.js
@@ -17,7 +17,8 @@ const sqlite = require("sqlite"),
invite: "https://discord.gg/txTchJY",
unknownCommandResponse: false
}),
- responses = require("./responses");
+ responses = require("./responses"),
+ util = require("./util");
const DISCORD_TOKEN = process.env.DISCORD_TOKEN,
LOGGLY_TOKEN = process.env.LOGGLY_TOKEN;
@@ -81,7 +82,7 @@ client
})
// response reaction interface
- .on("messageReactionAdd", responses.onNewReaction);
+ .on("messageReactionAdd", util.onNewReaction);
client.setProvider(
sqlite.open(path.join(__dirname, "settings.sqlite3")).then(
@@ -89,6 +90,7 @@ client.setProvider(
client.registry
.registerGroup('vainsocial', 'VainSocial')
+ .registerGroup('vainsocial-guild', 'VainSocial Guild management')
.registerDefaultTypes()
.registerDefaultGroups()
.registerDefaultCommands({ eval_: false, commandState: false })
diff --git a/commands/vainsocial/guild_add.js b/commands/vainsocial/guild_add.js
new file mode 100644
index 0000000..7f6aac7
--- /dev/null
+++ b/commands/vainsocial/guild_add.js
@@ -0,0 +1,80 @@
+#!/usr/bin/node
+/* jshint esnext:true */
+"use strict";
+
+const Commando = require("discord.js-commando"),
+ oneLine = require("common-tags").oneLine,
+ Promise = require("bluebird"),
+ request = require("request-promise"),
+ api = require("../../api"),
+ util = require("../../util");
+
+const API_FE_URL = process.env.API_FE_URL || "http://vainsocial.dev/bot/api";
+
+module.exports = class AddGuildMemberCommand extends Commando.Command {
+ constructor(client) {
+ super(client, {
+ name: "vainsocial-guildadd",
+ aliases: ["vguild-add", "vgadd", "vga"],
+ group: "vainsocial-guild",
+ memberName: "vainsocial-guildadd",
+ description: "Register a member to your Guild.",
+ details: oneLine`
+Register IGNs to your Guild.
+`,
+ examples: ["vgadd StormCallerSr", "vgadd StormCallerSr shutterfly"],
+ argsType: "multiple"
+ });
+ }
+ // register a VainSocial Guild to a Discord account
+ async run(msg, args) {
+ util.trackAction(msg, "vainsocial-guild-add");
+ let response, total_progress = "";
+
+ async function progress(part, final=false) {
+ response = await util.respond(msg,
+ total_progress + "\n" + part, response);
+ if (final) total_progress += "\n" + part;
+ }
+
+ // for each IGN
+ await Promise.each(args, async (user) => {
+ await progress(`Adding ${user}…\n`);
+ // make sure player exists in db
+ let player = await api.getPlayer(user);
+ if (player == undefined) {
+ await progress(`Loading ${user}'s data into VainSocial…`);
+ // if not, wait for backend to fetch
+ const waiter = api.subscribeUpdates(user);
+ await api.searchPlayer(user);
+ let success = false, notif;
+ // wait until search success
+ while (true) {
+ notif = await waiter.next();
+ if (notif == "search_success") break;
+ if (notif == undefined) {
+ // give up
+ await progress(`Ooops! Could not find ${user}.`, true);
+ return;
+ }
+ }
+ } else {
+ await api.updatePlayer(user);
+ }
+
+ // all good, register to self guild
+ await request.post(API_FE_URL + "/guild/members", {
+ forever: true,
+ form: {
+ user_token: msg.author.id,
+ member_name: user
+ }
+ });
+ await progress(`Added ${user}.`, true);
+ });
+ await progress(oneLine`
+Guild members have been added.
+You can now use ${util.usg(msg, "vgview")} for an overview.
+`, true);
+ }
+};
diff --git a/commands/vainsocial/guild_create.js b/commands/vainsocial/guild_create.js
new file mode 100644
index 0000000..9114703
--- /dev/null
+++ b/commands/vainsocial/guild_create.js
@@ -0,0 +1,66 @@
+#!/usr/bin/node
+/* jshint esnext:true */
+"use strict";
+
+const Commando = require("discord.js-commando"),
+ oneLine = require("common-tags").oneLine,
+ request = require("request-promise"),
+ util = require("../../util");
+
+const API_FE_URL = process.env.API_FE_URL || "http://vainsocial.dev/bot/api";
+
+module.exports = class RegisterGuildCommand extends Commando.Command {
+ constructor(client) {
+ super(client, {
+ name: "vainsocial-guildcreate",
+ aliases: ["vguild-create", "vgcreate"],
+ group: "vainsocial-guild",
+ memberName: "vainsocial-guildcreate",
+ description: "Register a new Guild.",
+ details: oneLine`
+Create a Guild with your VainSocial profile as leader.
+`,
+ examples: ["vguild-create MyAmazingGuild MAG eu"],
+
+ args: [ {
+ key: "name",
+ label: "name",
+ prompt: "Please specify your Guild's name.",
+ type: "string",
+ min: 3
+ }, {
+ key: "tag",
+ label: "tag",
+ prompt: "Please specify your Guild's tag.",
+ type: "string",
+ min: 3,
+ max: 4
+ }, {
+ // TODO use enum
+ key: "region",
+ label: "region",
+ prompt: "Please specify your Guild's region.",
+ type: "string",
+ min: 2,
+ max: 10
+ } ]
+ });
+ }
+ // register a VainSocial Guild to a Discord account
+ async run(msg, args) {
+ util.trackAction(msg, "vainsocial-guild-create");
+ console.log(API_FE_URL + "/guild");
+ await request.post(API_FE_URL + "/guild", {
+ forever: true,
+ form: {
+ shard_id: args.region,
+ name: args.name,
+ identifier: args.tag,
+ user_token: msg.author.id
+ }
+ });
+ await msg.reply(oneLine`
+You can now use ${util.usg(msg, "vgadd")} to add members to your Guild.
+`);
+ }
+};
diff --git a/commands/vainsocial/guild_view.js b/commands/vainsocial/guild_view.js
new file mode 100644
index 0000000..c459590
--- /dev/null
+++ b/commands/vainsocial/guild_view.js
@@ -0,0 +1,58 @@
+#!/usr/bin/node
+/* jshint esnext:true */
+"use strict";
+
+const Commando = require("discord.js-commando"),
+ oneLine = require("common-tags").oneLine,
+ request = require("request-promise"),
+ util = require("../../util");
+
+const API_FE_URL = process.env.API_FE_URL || "http://vainsocial.dev/bot/api";
+
+module.exports = class ViewGuildCommand extends Commando.Command {
+ constructor(client) {
+ super(client, {
+ name: "vainsocial-guildview",
+ aliases: ["vguild-view", "vgview", "vgv"],
+ group: "vainsocial-guild",
+ memberName: "vainsocial-guildview",
+ description: "View members of a Guild.",
+ details: oneLine`
+Show a summary of your Guild.
+`,
+ examples: ["vgview"],
+
+ args: [ {
+ key: "name",
+ label: "name",
+ prompt: "Please specify your Guild's name.",
+ type: "string",
+ min: 3,
+ default: "?"
+ } ]
+ });
+ }
+ // show Guild details
+ async run(msg, args) {
+ util.trackAction(msg, "vainsocial-guild-view");
+ // get this user's guild
+ const guild = await request(API_FE_URL + "/guild", {
+ forever: true,
+ json: true,
+ qs: {
+ user_token: msg.author.id
+ }
+ });
+ if (guild == undefined) {
+ await msg.reply("You are not registered in any guilds.");
+ return;
+ }
+ // build response
+ let response = "";
+ response += `${guild.name} (${guild.shard_id})\n`;
+ guild.members.forEach((member) => {
+ response += `${member.player.name}: ${member.fame} VainSocial Fame\n`;
+ });
+ await msg.say(response);
+ }
+};
diff --git a/commands/vainsocial/me.js b/commands/vainsocial/me.js
index 4de7c8f..02818be 100644
--- a/commands/vainsocial/me.js
+++ b/commands/vainsocial/me.js
@@ -4,18 +4,21 @@
const Commando = require("discord.js-commando"),
oneLine = require("common-tags").oneLine,
- responses = require("../../responses");
+ request = require("request-promise"),
+ util = require("../../util");
-module.exports = class RememberUserCommand extends Commando.Command {
+const API_FE_URL = process.env.API_FE_URL || "http://vainsocial.dev/bot/api";
+
+module.exports = class RegisterUserCommand extends Commando.Command {
constructor(client) {
super(client, {
name: "vainsocial-me",
aliases: ["vme"],
group: "vainsocial",
memberName: "vainsocial-me",
- description: "Remembers a users's in game name.",
+ description: "Register a users's in game name.",
details: oneLine`
-Store your in game name for quicker access to other commands.
+Store your in game name for quicker access to other commands and for Guild management.
`,
examples: ["vme shutterfly"],
@@ -29,7 +32,20 @@ Store your in game name for quicker access to other commands.
} ]
});
}
+ // register a Discord account at VainSocial
async run(msg, args) {
- await responses.rememberUser(msg, args);
+ const ign = args.name;
+ util.trackAction(msg, "vainsocial-me", ign);
+ await request.post(API_FE_URL + "/user", {
+ forever: true,
+ form: {
+ name: ign,
+ user_token: msg.author.id
+ }
+ });
+ await msg.reply(oneLine`
+You are now able to use ${util.usg(msg, "v")} to access your profile faster.
+You now have access to Guild management features.
+`);
}
};
diff --git a/package.json b/package.json
index 40fbca2..b5962a4 100644
--- a/package.json
+++ b/package.json
@@ -19,7 +19,6 @@
"erlpack": "github:hammerandchisel/erlpack",
"request": "^2.81.0",
"request-promise": "^4.2.0",
- "request-promise-native": "^1.0.3",
"sqlite": "^2.5.0",
"universal-analytics": "^0.4.13",
"webstomp": "0.0.10",
diff --git a/responses.js b/responses.js
index 692d1f4..49302a4 100644
--- a/responses.js
+++ b/responses.js
@@ -3,58 +3,17 @@
"use strict";
const Commando = require("discord.js-commando"),
- Discord = require("discord.js"),
Promise = require("bluebird"),
- ua = require("universal-analytics"),
emoji = require("discord-emoji"),
oneLine = require("common-tags").oneLine,
- Channel = require("async-csp").Channel,
+ util = require("./util"),
api = require("./api");
const PREVIEW = process.env.PREVIEW != "false",
MATCH_HISTORY_LEN = parseInt(process.env.MATCH_HISTORY_LEN) || 3,
IGN_ROTATE_TIMEOUT = parseInt(process.env.IGN_ROTATE_TIMEOUT) || 300,
- REACTION_TIMEOUT = parseInt(process.env.REACTION_TIMEOUT) || 60, // s
- GOOGLEANALYTICS_ID = process.env.GOOGLEANALYTICS_ID,
ROOTURL = (PREVIEW? "https://preview.vainsocial.com/":"https://vainsocial.com/");
-const reactionsPipe = new Channel();
-
-// embed template
-function vainsocialEmbed(title, link, command) {
- return new Discord.RichEmbed()
- .setTitle(title)
- .setColor("#55ADD3")
- .setURL(ROOTURL + link + track(command))
- .setAuthor("VainSocial" + (PREVIEW? " preview":""), ROOTURL + "images/brands/logo-blue.png",
- ROOTURL + track(command))
- .setFooter("VainSocial" + (PREVIEW? " preview":""), ROOTURL + "images/brands/logo-blue.png")
-}
-
-// analytics url
-function track(command) {
- return "?utm_source=discordbot&utm_medium=discord&utm_campaign=" + command;
-}
-
-// direct analytics
-function trackAction(msg, action, ign="") {
- if (GOOGLEANALYTICS_ID == undefined) return;
- const user = ua(GOOGLEANALYTICS_ID, msg.author.id,
- { strictCidFormat: false });
- user.pageview({
- documentPath: action,
- documentTitle: ign,
- campaignSource: msg.guild.id,
- campaignMedium: msg.guild.name
- }).send();
-}
-
-// return the shortest version of the usage help
-// just '?vm'
-function usg(msg, cmd) {
- return msg.anyUsage(cmd, undefined, null);
-}
-
// based on impact score float, return an Emoji
function emojifyScore(score) {
if (score > 0.7) return emoji.people.heart_eyes;
@@ -89,7 +48,7 @@ async function formatMatchDetail(match) {
for(let participant of roster.participants) {
const hero = await api.mapActor(participant.actor);
teamstr += `
-\`${hero}\`, [${participant.player.name}](${ROOTURL}player/${participant.player.name}${track("match-detail")}) \`T${Math.floor(participant.skill_tier/3+1)}\` | \`${participant.stats.kills}/${participant.stats.deaths}/${participant.stats.assists}\`, \`${Math.floor(participant.stats.farm)}\`, Score ${emojifyScore(participant.stats.impact_score)} \`${Math.floor(100 * participant.stats.impact_score)}%\``;
+\`${hero}\`, [${participant.player.name}](${ROOTURL}player/${participant.player.name}${util.track("match-detail")}) \`T${Math.floor(participant.skill_tier/3+1)}\` | \`${participant.stats.kills}/${participant.stats.deaths}/${participant.stats.assists}\`, \`${Math.floor(participant.stats.farm)}\`, Score ${emojifyScore(participant.stats.impact_score)} \`${Math.floor(100 * participant.stats.impact_score)}%\``;
}
strings.push([rosterstr, teamstr]);
}
@@ -139,78 +98,15 @@ module.exports.rotateGameStatus = (client) => {
})();
}
-// reaction -> pipe ->>> consumers
-// handle new reaction event
-module.exports.onNewReaction = (reaction) => {
- if (reaction.users.array().length <= 1) return; // was me TODO
- reactionsPipe.put(reaction);
-}
-
-// create an iterator that returns promises to await new reactions
-// the Promise result is the reaction name
-function awaitReactions(message, emoji, timeout=REACTION_TIMEOUT) {
- const pipeOut = new Channel();
- let reactions = [];
- // stop listening after timeout
- setTimeout(() => {
- pipeOut.close();
- Promise.map(reactions, async (r) => r.remove());
- }, timeout*1000);
-
- // async in background
- Promise.each(emoji, async (em) =>
- reactions.push(await message.react(em)));
-
- let reaction;
- reactionsPipe.pipe(pipeOut);
- return {
- next: async function() {
- do {
- reaction = await pipeOut.take();
- if (reaction == Channel.DONE)
- return undefined;
- } while (reaction.message.id != message.id ||
- emoji.indexOf(reaction.emoji.name) == -1);
- return reaction.emoji.name;
- }
- }
-}
-
-// respond or say text or embed
-async function respond(msg, data, response) {
- if (response == undefined) {
- if (typeof data === "string") {
- response = await msg.say(data);
- } else {
- response = await msg.embed(data);
- }
- } else {
- if (typeof data === "string") {
- if (data != response.content)
- await response.edit(data);
- } else {
- if (new Date(response.embeds[0].createdTimestamp).getTime()
- != data.timestamp.getTime())
- // TODO how2 edit embed properly?!
- await msg.editResponse(response, {
- type: "plain",
- content: "",
- options: { embed: data }
- });
- }
- }
- return response;
-}
-
// about
module.exports.showAbout = async (msg) => {
- trackAction(msg, "about");
- await msg.embed(vainsocialEmbed("About VainSocial", "", "about")
+ util.trackAction(msg, "about");
+ await msg.embed(util.vainsocialEmbed("About VainSocial", "", "about")
.setDescription(
`Built by the VainSocial development team using the MadGlory API.
Currently running on ${msg.client.guilds.size} servers.`)
.addField("Website",
- ROOTURL + track("about"), true)
+ ROOTURL + util.track("about"), true)
.addField("Bot invite link",
"https://discordapp.com/oauth2/authorize?&client_id=287297889024213003&scope=bot&permissions=52288", true)
.addField("Developer Discord invite",
@@ -222,20 +118,8 @@ Currently running on ${msg.client.guilds.size} servers.`)
// return the sqlite stored ign for this user
function nameByUser(msg) {
- return msg.guild.settings.get("remember+" + msg.author.id);
-}
-
-// tell the user that they need to store their name
-function formatSorryUnknown(msg) {
- return `You're unknown to our service. Try ${usg(msg, "help vme")}.`;
-}
-
-module.exports.rememberUser = async (msg, args) => {
- const ign = args.name;
- trackAction(msg, "vainsocial-me", ign);
- await msg.guild.settings.set("remember+" + msg.author.id, ign);
- await msg.reply(
-`You are now able to use ${usg(msg, "v")} to access your profile faster.`);
+ throw "nope that's not possible yet";
+ //return msg.guild.settings.get("remember+" + msg.author.id);
}
// show player profile and last match
@@ -244,84 +128,90 @@ module.exports.showUser = async (msg, args) => {
response,
ign = args.name,
reactionsAdded = false;
- trackAction(msg, "vainsocial-user", ign);
+ util.trackAction(msg, "vainsocial-user", ign);
// shorthand
// "?" is not accepted as user input, but the default for empty
if (ign == "?") {
ign = nameByUser(msg);
if (ign == undefined) {
- await msg.reply(formatSorryUnknown(msg));
+ await msg.reply(util.formatSorryUnknown(msg));
return;
}
}
+ // TODO refactor the update flow a bit
+ // trigger backend
+ if (await api.getPlayer(ign) == undefined)
+ await api.searchPlayer(ign);
+ else await api.updatePlayer(ign);
+
const waiter = api.subscribeUpdates(ign);
- while (await waiter.next() != undefined) {
+ do {
const [player, data] = await Promise.all([
api.getPlayer(ign),
api.getMatches(ign)
]);
if (player == undefined) {
- response = await respond(msg,
+ response = await util.respond(msg,
"Loading your data…", response);
continue;
}
if (data == undefined || data[0].data.length == 0) {
- response = await respond(msg,
+ response = await util.respond(msg,
"No match history for you yet", response);
continue;
}
const matches = data[0];
const moreHelp = oneLine`
-*${emoji.symbols.information_source} or ${usg(msg, "vm " + ign)} for detail,
-${emoji.symbols["1234"]} or ${usg(msg, "vh " + ign)} for more*`
+*${emoji.symbols.information_source} or ${util.usg(msg, "vm " + ign)} for detail,
+${emoji.symbols["1234"]} or ${util.usg(msg, "vh " + ign)} for more*`
- const embed = vainsocialEmbed(`${ign} - ${player.shard_id}`, "player/" + ign, "vainsocial-user")
+ const embed = util.vainsocialEmbed(`${ign} - ${player.shard_id}`, "player/" + ign, "vainsocial-user")
.setThumbnail(ROOTURL + "images/game/skill_tiers/" +
matches.data[0].skill_tier + ".png")
.setDescription("")
.addField("Profile", await formatPlayer(player), true)
.addField("Last match", await formatMatch(matches.data[0]) + moreHelp, true)
.setTimestamp(new Date(matches.data[0].created_at));
- response = await respond(msg, embed, response);
+ response = await util.respond(msg, embed, response);
if (!reactionsAdded) {
reactionsAdded = true;
- let reactionWaiter = awaitReactions(response,
+ let reactionWaiter = util.awaitReactions(response,
[emoji.symbols.information_source, emoji.symbols["1234"]]);
(async () => {
while (true) {
let rmoji = await reactionWaiter.next();
if (rmoji == undefined) break; // timeout
if (rmoji == emoji.symbols.information_source) {
- trackAction(msg, "reaction-match", ign);
+ util.trackAction(msg, "reaction-match", ign);
await respondMatch(msg, ign, matches.data[0].match_api_id);
}
if (rmoji == emoji.symbols["1234"]) {
- trackAction(msg, "reaction-matches", ign);
+ util.trackAction(msg, "reaction-matches", ign);
await respondMatches(msg, ign);
}
}
})(); // async in background
}
- }
+ } while (await waiter.next() != undefined);
if (!reactionsAdded)
- await respond(msg, `Could not find \`${ign}\`.`, response);
+ await util.respond(msg, `Could not find \`${ign}\`.`, response);
}
// show match in detail
async function respondMatch(msg, ign, id, response=undefined) {
const match = await api.getMatch(id);
- let embed = vainsocialEmbed(`${match.game_mode}, ${match.duration} minutes`,
+ let embed = util.vainsocialEmbed(`${match.game_mode}, ${match.duration} minutes`,
"player/" + ign + "/match/" + id, "vainsocial-match")
.setTimestamp(new Date(match.created_at));
(await formatMatchDetail(match)).forEach(([title, text]) => {
embed.addField(title, text, true);
});
- return await respond(msg, embed, response);
+ return await util.respond(msg, embed, response);
}
module.exports.showMatch = async (msg, args) => {
@@ -329,37 +219,42 @@ module.exports.showMatch = async (msg, args) => {
let responded = false,
ign = args.name,
response;
- trackAction(msg, "vainsocial-match", ign);
+ util.trackAction(msg, "vainsocial-match", ign);
// shorthand
if (ign == "?") {
ign = nameByUser(msg);
if (ign == undefined) {
- await msg.reply(formatSorryUnknown(msg));
+ await msg.reply(util.formatSorryUnknown(msg));
return;
}
}
+ // trigger backend
+ if (await api.getPlayer(ign) == undefined)
+ await api.searchPlayer(ign);
+ else await api.updatePlayer(ign);
+
const waiter = api.subscribeUpdates(ign);
- while (await waiter.next() != undefined) {
+ do {
const data = await api.getMatches(ign);
if (data == undefined || data[0].data.length == 0) {
- response = await respond(msg, "No matches for you yet",
+ response = await util.respond(msg, "No matches for you yet",
response);
continue;
}
const matches = data[0];
if (index - 1 > matches.data.length) {
- response = await respond(msg, "Not enough matches yet",
+ response = await util.respond(msg, "Not enough matches yet",
response);
continue;
}
let id = matches.data[index -1].match_api_id;
response = await respondMatch(msg, ign, id, response);
responded = true;
- }
+ } while (await waiter.next() != undefined);
if (!responded)
- await respond(msg, `Could not find \`${ign}\`.`, response);
+ await util.respond(msg, `Could not find \`${ign}\`.`, response);
}
// show match history
@@ -373,18 +268,18 @@ async function respondMatches(msg, ign, response=undefined) {
matches = data[0].data.slice(0, MATCH_HISTORY_LEN), // TODO
matches_num = matches.length;
- let embed = vainsocialEmbed(ign, "player/" + ign, "vainsocial-matches")
+ let embed = util.vainsocialEmbed(ign, "player/" + ign, "vainsocial-matches")
.setDescription(`
Last ${matches_num} casual and ranked matches.
-*${emoji.symbols["1234"]} or ${usg(msg, "vm " + ign + " number")} for details*
+*${emoji.symbols["1234"]} or ${util.usg(msg, "vm " + ign + " number")} for details*
`)
.setTimestamp(new Date(matches[0].created_at));
await Promise.each(matches, async (match, idx) =>
embed.addField(`Match ${idx + 1}`, await formatMatch(match))
);
- response = await respond(msg, embed, response);
+ response = await util.respond(msg, embed, response);
- const reactionWaiter = awaitReactions(response,
+ const reactionWaiter = util.awaitReactions(response,
count.slice(0, matches_num));
(async () => {
while (true) {
@@ -392,7 +287,7 @@ Last ${matches_num} casual and ranked matches.
if (rmoji == undefined) break; // timeout
let idx = count.indexOf(rmoji);
await respondMatch(msg, ign, matches[idx].match_api_id);
- trackAction(msg, "reaction-match", ign);
+ util.trackAction(msg, "reaction-match", ign);
}
})(); // async in background
return response;
@@ -402,30 +297,35 @@ module.exports.showMatches = async (msg, args) => {
let ign = args.name,
responded = false,
response;
- trackAction(msg, "vainsocial-matches", ign);
+ util.trackAction(msg, "vainsocial-matches", ign);
// shorthand
if (ign == "?") {
ign = nameByUser(msg);
if (ign == undefined) {
- await msg.reply(formatSorryUnknown(msg));
+ await msg.reply(util.formatSorryUnknown(msg));
return;
}
}
+ // trigger backend
+ if (await api.getPlayer(ign) == undefined)
+ await api.searchPlayer(ign);
+ else await api.updatePlayer(ign);
+
const waiter = api.subscribeUpdates(ign);
- while (await waiter.next() != undefined) {
+ do {
const data = await api.getMatches(ign);
if (data == undefined || data[0].data.length == 0) {
- response = await respond(msg, "No matches for you yet",
+ response = await util.respond(msg, "No matches for you yet",
response);
continue;
}
const matches = data[0];
response = await respondMatches(msg, ign, response);
responded = true;
- }
+ } while (await waiter.next() != undefined);
if (!responded)
- await respond(msg, `Could not find \`${ign}\`.`,
+ await util.respond(msg, `Could not find \`${ign}\`.`,
response);
}
diff --git a/util.js b/util.js
new file mode 100644
index 0000000..f2e894b
--- /dev/null
+++ b/util.js
@@ -0,0 +1,118 @@
+#!/usr/bin/node
+/* jshint esnext:true */
+"use strict";
+
+const Discord = require("discord.js"),
+ ua = require("universal-analytics"),
+ Promise = require("bluebird"),
+ Channel = require("async-csp").Channel;
+
+const GOOGLEANALYTICS_ID = process.env.GOOGLEANALYTICS_ID,
+ REACTION_TIMEOUT = parseInt(process.env.REACTION_TIMEOUT) || 60, // s
+ PREVIEW = process.env.PREVIEW != "false",
+ ROOTURL = (PREVIEW? "https://preview.vainsocial.com/":"https://vainsocial.com/");
+
+const reactionsPipe = new Channel();
+
+// embed template
+module.exports.vainsocialEmbed = (title, link, command) => {
+ return new Discord.RichEmbed()
+ .setTitle(title)
+ .setColor("#55ADD3")
+ .setURL(ROOTURL + link + module.exports.track(command))
+ .setAuthor("VainSocial" + (PREVIEW? " preview":""), ROOTURL + "images/brands/logo-blue.png",
+ ROOTURL + module.exports.track(command))
+ .setFooter("VainSocial" + (PREVIEW? " preview":""), ROOTURL + "images/brands/logo-blue.png")
+};
+
+// analytics url
+module.exports.track = (command) => {
+ return "?utm_source=discordbot&utm_medium=discord&utm_campaign=" + command;
+};
+
+// direct analytics
+module.exports.trackAction = (msg, action, ign="") => {
+ if (GOOGLEANALYTICS_ID == undefined) return;
+ const user = ua(GOOGLEANALYTICS_ID, msg.author.id,
+ { strictCidFormat: false });
+ user.pageview({
+ documentPath: action,
+ documentTitle: ign,
+ campaignSource: msg.guild.id,
+ campaignMedium: msg.guild.name
+ }).send();
+};
+
+// return the shortest version of the usage help
+// just '?vm'
+module.exports.usg = (msg, cmd) => {
+ return msg.anyUsage(cmd, undefined, null);
+};
+
+// reaction -> pipe ->>> consumers
+// handle new reaction event
+module.exports.onNewReaction = (reaction) => {
+ if (reaction.users.array().length <= 1) return; // was me TODO
+ reactionsPipe.put(reaction);
+};
+
+// create an iterator that returns promises to await new reactions
+// the Promise result is the reaction name
+module.exports.awaitReactions = (message, emoji, timeout=REACTION_TIMEOUT) => {
+ const pipeOut = new Channel();
+ let reactions = [];
+ // stop listening after timeout
+ setTimeout(() => {
+ pipeOut.close();
+ Promise.map(reactions, async (r) => r.remove());
+ }, timeout*1000);
+
+ // async in background
+ Promise.each(emoji, async (em) =>
+ reactions.push(await message.react(em)));
+
+ let reaction;
+ reactionsPipe.pipe(pipeOut);
+ return {
+ next: async function() {
+ do {
+ reaction = await pipeOut.take();
+ if (reaction == Channel.DONE)
+ return undefined;
+ } while (reaction.message.id != message.id ||
+ emoji.indexOf(reaction.emoji.name) == -1);
+ return reaction.emoji.name;
+ }
+ }
+};
+
+// respond or say text or embed
+module.exports.respond = async (msg, data, response) => {
+ if (response == undefined) {
+ if (typeof data === "string") {
+ response = await msg.say(data);
+ } else {
+ response = await msg.embed(data);
+ }
+ } else {
+ if (typeof data === "string") {
+ if (data != response.content)
+ await response.edit(data);
+ } else {
+ if (new Date(response.embeds[0].createdTimestamp).getTime()
+ != data.timestamp.getTime())
+ // TODO how2 edit embed properly?!
+ await msg.editResponse(response, {
+ type: "plain",
+ content: "",
+ options: { embed: data }
+ });
+ }
+ }
+ return response;
+};
+
+// tell the user that they need to store their name
+module.exports.formatSorryUnknown = (msg) => {
+ return `You're unknown to our service. Try ${util.usg(msg, "help vme")}.`;
+};