summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorschneefux <schneefux+commit@schneefux.xyz>2017-04-15 17:07:20 +0200
committerschneefux <schneefux+commit@schneefux.xyz>2017-04-15 17:07:20 +0200
commit795126408fc717a11d832f412470597377d71449 (patch)
treee81700812c75453d1fcf80c296e0a07b84ae449c
parent0564241327ecc6eb08cd716f510ebb0afbb3eeb3 (diff)
downloaddiscordbot-795126408fc717a11d832f412470597377d71449.tar.gz
discordbot-795126408fc717a11d832f412470597377d71449.zip
implement basic functionality
-rw-r--r--api.js70
-rw-r--r--bot.js9
-rw-r--r--commands/vainsocial/match.js8
-rw-r--r--commands/vainsocial/matches.js4
-rw-r--r--commands/vainsocial/user.js4
-rw-r--r--package.json2
-rw-r--r--responses.js187
7 files changed, 198 insertions, 86 deletions
diff --git a/api.js b/api.js
index fef1ffe..37ed6ae 100644
--- a/api.js
+++ b/api.js
@@ -11,63 +11,77 @@ 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);
+const notif = webstomp.over(new WebSocket(API_WS_URL,
+ { perMessageDeflate: false }));
-function connect() {
+(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);
+ (err) => connect()
+ );
+})();
// TODO use keepalive / connection pool
-async function getFE(url) {
- return await request({
+function getFE(url) {
+ return request({
uri: API_FE_URL + url,
json: true
});
}
-async function postBE(url) {
- return await request.post({
+function postBE(url) {
+ return request.post({
uri: API_BE_URL + url,
json: true
});
}
function subscribe(topic, channel) {
- notif.subscribe("/topic/" + topic, async (msg) => {
- await channel.put(msg.body);
+ return notif.subscribe("/topic/" + topic, (msg) => {
+ 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
+// be an async generator
+// next() returns player data whenever an update is available
+module.exports.searchPlayer = async function (name, timeout=60) {
+ let channel = new Channel(),
+ subscription = subscribe("player." + name, channel);
+ await postBE("/player/" + name + "/update");
+ // stop updates after timeout
+ setTimeout(() => {
+ channel.close();
+ subscription.unsubscribe();
+ }, timeout*1000);
+ channel.put("initial"); // initial fetch
let generator = async () => {
- let msg = await channel.take();
+ let msg;
+ while (true) {
+ msg = await channel.take();
+ if (["initial", "search_fail", "stats_update",
+ "matches_update"].indexOf(msg) != -1)
+ break;
+ }
if (msg == "search_fail") {
throw "not found";
}
if (msg == Channel.DONE) {
throw "exhausted";
}
- return await getFE("/player/" + name);
+ return getFE("/player/" + name);
}
return { next: generator };
}
+
+// return matches
+module.exports.searchMatches = async function (name) {
+ return getFE("/player/" + name + "/matches/1.1.1.1");
+}
+
+// return single match
+module.exports.searchMatch = async function (id) {
+ return getFE("/match/" + id);
+}
diff --git a/bot.js b/bot.js
index c3826c1..bb2e9b2 100644
--- a/bot.js
+++ b/bot.js
@@ -19,6 +19,7 @@ client
.on("debug", console.log)
.on("ready", () => {
console.log(`Client ready; logged in as ${client.user.username}#${client.user.discriminator} (${client.user.id})`);
+ client.user.setGame("?v shutterflySEA | vainsocial.com");
})
.on('disconnect', () => { console.warn('Disconnected!'); })
.on('reconnecting', () => { console.warn('Reconnecting...'); })
@@ -59,8 +60,14 @@ client.setProvider(
client.registry
.registerGroup('vainsocial', 'VainSocial')
- .registerDefaults()
+ .registerDefaultTypes()
+ .registerDefaultGroups()
+ .registerDefaultCommands({ eval_: false, commandState: false })
.registerCommandsIn(path.join(__dirname, 'commands'));
client.login(DISCORDTOKEN);
+
+process.on("unhandledRejection", err => {
+ console.error("Uncaught Promise Error: \n" + err.stack);
+});
diff --git a/commands/vainsocial/match.js b/commands/vainsocial/match.js
index 14b06a2..c8167ea 100644
--- a/commands/vainsocial/match.js
+++ b/commands/vainsocial/match.js
@@ -25,13 +25,17 @@ module.exports = class ShowMatchCommand extends Commando.Command {
key: "name",
label: "name",
prompt: "Please specify your in game name (Case Sensitive).",
- type: "string"
+ type: "string",
+ min: 3,
+ max: 16
}, {
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
+ default: 1,
+ min: 1,
+ max: 10
} ]
});
}
diff --git a/commands/vainsocial/matches.js b/commands/vainsocial/matches.js
index 2660d73..3f2de2f 100644
--- a/commands/vainsocial/matches.js
+++ b/commands/vainsocial/matches.js
@@ -24,7 +24,9 @@ module.exports = class ShowMatchesCommand extends Commando.Command {
key: "name",
label: "name",
prompt: "Please specify your in game name (Case Sensitive).",
- type: "string"
+ type: "string",
+ min: 3,
+ max: 16
} ]
});
}
diff --git a/commands/vainsocial/user.js b/commands/vainsocial/user.js
index f544a5e..1f41c12 100644
--- a/commands/vainsocial/user.js
+++ b/commands/vainsocial/user.js
@@ -23,7 +23,9 @@ module.exports = class ShowUserCommand extends Commando.Command {
key: "name",
label: "name",
prompt: "Please specify your in game name (Case Sensitive).",
- type: "string"
+ type: "string",
+ min: 3,
+ max: 16
} ]
});
}
diff --git a/package.json b/package.json
index ddf79e4..6630525 100644
--- a/package.json
+++ b/package.json
@@ -12,7 +12,7 @@
"async-csp": "^0.5.0",
"bufferutil": "^3.0.0",
"discord-emoji": "^1.1.1",
- "discord.js": "^11.0.0",
+ "discord.js": "git+https://github.com/hydrabolt/discord.js.git",
"discord.js-commando": "^0.9.0",
"erlpack": "github:hammerandchisel/erlpack",
"request": "^2.81.0",
diff --git a/responses.js b/responses.js
index f54ea89..9945861 100644
--- a/responses.js
+++ b/responses.js
@@ -14,7 +14,7 @@ function vainsocialEmbed(title, link) {
.setURL("https://vainsocial.com/" + link)
.setColor("#55ADD3")
.setAuthor("VainSocial", null, "https://vainsocial.com")
- .setFooter("VainSocial - Vainglory social stats service")
+ .setFooter("VainSocial")
;
}
@@ -24,52 +24,119 @@ function usg(msg, cmd) {
return msg.anyUsage(cmd, undefined, null);
}
-// TODO, obviously
+// based on impact score float, return an Emoji
+function emojifyScore(score) {
+ if (score > 0.7) return emoji.people.heart_eyes;
+ if (score > 0.6) return emoji.people.blush;
+ if (score > 0.5) return emoji.people.yum;
+ if (score > 0.3) return emoji.people.relieved;
+ return emoji.people.upside_down;
+}
+
+// return a match overview string
+function formatMatch(participant) {
+ let winstr = "Won";
+ if (!participant.winner) winstr = "Lost";
+ return `
+${winstr} ${participant.game_mode_id} with \`${participant.actor.replace(/\*/g, "")}\`
+KDA, CS | \`${participant.stats.kills}/${participant.stats.deaths}/${participant.stats.assists}\`, \`${Math.round(participant.stats.farm)}\`
+Kill Participation | \`${Math.floor(100 * participant.stats.kill_participation)}%\`
+Score | ${emojifyScore(participant.stats.impact_score)} \`${Math.floor(100 * participant.stats.impact_score)}%\`
+`;
+}
+
+// return [[title, text], …] for rosters
+function formatMatchDetail(match) {
+ let strings = [];
+ for(let roster of match.rosters) {
+ let rosterstr = `${roster.side} - \`${roster.hero_kills}\` Kills`;
+ let teamstr = "";
+ for(let participant of roster.participants) {
+ teamstr += `
+\`${participant.actor.replace(/\*/g, "")}\`, [${participant.player.name}](https://vainsocial.com/player/${participant.player.name}) \`T${Math.floor(participant.skill_tier/3)}\` | \`${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]);
+ }
+ return strings;
+}
+
+// return a profile string
+function formatPlayer(player) {
+ let stats = oneLine`
+ Win Rate | \`${Math.round(100 *
+ player.currentSeries.reduce((t, s) => t + s.wins, 0) /
+ player.currentSeries.reduce((t, s) => t + s.played, 0)
+ )}%\`
+ `,
+ total_kda = oneLine`
+ Total KDA | \`${player.stats.kills}\` / \`${player.stats.deaths}\` / \`${player.stats.assists}\`
+ `,
+ best_hero = "",
+ picks = "";
+ if (player.best_hero.length > 0)
+ best_hero = oneLine`
+ Best | \`${player.best_hero[0].name}\`
+ `;
+ if (player.picks.length > 0)
+ picks = oneLine`
+ Favorite | \`${player.picks[0].name}\`, \`${player.picks[0].hero_pick} picks\`
+ `;
+ return `
+${stats}
+${total_kda}
+${best_hero}
+${picks}
+ `;
+}
// show player profile and last match
module.exports.showUser = async (msg, args) => {
let ign = args.name,
iterator = await api.searchPlayer(ign),
- response, data;
+ response, player;
while (true) {
try {
- data = await iterator.next();
+ player = await iterator.next();
} catch (err) {
- if (err.statusCode == 404) {
- await msg.reply("Could not find `" + ign + "`");
+ if (err == "not found") {
+ await msg.say("Could not find player `" + ign + "`.");
break;
}
if (err == "exhausted")
break;
+ if (err.statusCode == 404)
+ continue;
}
- let winstr = "won";
- if (data.last_result[0].winner == false) winstr = "lost";
+ let matches = (await api.searchMatches(ign)).data;
+ let matchstr = "not available";
+ if (matches.length > 0) matchstr = formatMatch(matches[0]);
- let embed = vainsocialEmbed(ign, "player/" + ign)
+ let embed = vainsocialEmbed(`${ign} - ${player.shard_id}`, "player/" + ign)
.setThumbnail("https://vainsocial.com/images/game/skill_tiers/" +
- data.skill_tier + ".png")
+ player.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))
+ .addField("Profile", formatPlayer(player), true)
+ .addField("Last match", matchstr + `
+*${emoji.symbols.information_source} or ${usg(msg, "vm " + ign)} for detail, ${emoji.symbols["1234"]} or ${usg(msg, "vh " + ign)} for more*
+ `, true)
+ .setTimestamp(new Date(player.last_match_created_date))
;
if (response == undefined) {
- response = await msg.replyEmbed(embed);
- await response.react(emoji.symbols.information_source);
+ response = await msg.embed(embed);
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
- });
+ if (react.users.array().length <= 1) return; // was me
+ if (react.emoji.name == emoji.symbols.information_source)
+ await showMatch(msg, {
+ name: ign,
+ id: matches[0].match_api_id
+ });
+ if (react.emoji.name == emoji.symbols["1234"])
+ await showMatches(msg, { name: ign });
});
+ await response.react(emoji.symbols.information_source);
+ await response.react(emoji.symbols["1234"]);
} else {
response = await msg.editResponse(response, {
type: "plain",
@@ -85,19 +152,26 @@ 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))
+ index = args.number;
+
+ let match;
+ try {
+ if (id == undefined)
+ id = (await api.searchMatches(ign)).data[index - 1].match_api_id;
+ match = await api.searchMatch(id);
+ } catch (err) {
+ await msg.say(oneLine`
+ Ooops! I don't have any data for you yet.
+ Please take a look at your profile first!
+ ${usg(msg, "v " + ign)}
+ `); // TODO!
+ return;
+ }
+ let embed = vainsocialEmbed(`${match.game_mode}, \`${match.duration}\` minutes`, "match/" + id)
+ .setTimestamp(new Date(match.created_at))
+ formatMatchDetail(match).forEach(([title, text]) => {
+ embed.addField(title, text, true);
+ });
;
response = await msg.editResponse(response, {
type: "plain",
@@ -107,7 +181,7 @@ let showMatch = module.exports.showMatch = async (msg, args) => {
}
// show match history
-module.exports.showMatches = async (msg, args) => {
+let showMatches = module.exports.showMatches = async (msg, args) => {
let ign = args.name,
response;
let count = [
@@ -122,18 +196,27 @@ module.exports.showMatches = async (msg, args) => {
emoji.symbols.nine,
emoji.symbols.ten
];
+ let data, matches, matches_num;
+ try {
+ data = await api.searchMatches(ign);
+ } catch (err) {
+ await msg.say(oneLine`
+ Ooops! I don't have any data for you yet.
+ Please take a look at your profile first!
+ ${usg(msg, "v " + ign)}
+ `); // TODO!
+ return;
+ }
+ matches = data.data.slice(0, 3);
+ matches_num = matches.length;
+
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?
- `)
- ;
+ Last ${matches_num} casual and ranked matches.
+ *${emoji.symbols["1234"]} or ${usg(msg, "vm " + ign + " number")} for details*
+ `);
+ matches.forEach((match, idx) =>
+ embed.addField(`Match ${idx + 1}`, formatMatch(match)));
response = await msg.editResponse(response, {
type: "plain",
content: "",
@@ -142,15 +225,15 @@ module.exports.showMatches = async (msg, args) => {
msg.client.on("messageReactionAdd", async (react) => {
if (react.message != response) return;
- if (react.count == 1) return; // was me
+ if (react.users.array().length <= 1) return; // was me
let idx = count.indexOf(react.emoji.name);
if (idx == -1) return;
await showMatch(msg, {
name: ign,
- number: idx
+ id: matches[idx].match_api_id
});
});
- for (let num of count)
+ for (let num of count.slice(0, matches_num))
await response.react(num)
}