summaryrefslogtreecommitdiff
path: root/api.js
diff options
context:
space:
mode:
Diffstat (limited to 'api.js')
-rw-r--r--api.js294
1 files changed, 294 insertions, 0 deletions
diff --git a/api.js b/api.js
new file mode 100644
index 0000000..12f58c3
--- /dev/null
+++ b/api.js
@@ -0,0 +1,294 @@
+#!/usr/bin/node
+/* jshint esnext:true */
+"use strict";
+
+const request = require("request-promise"),
+ Promise = require("bluebird"),
+ WebSocket = require("ws"),
+ webstomp = require("webstomp-client"),
+ cacheManager = require("cache-manager"),
+ sleep = require("sleep-promise"),
+ Channel = require("async-csp").Channel;
+const api = module.exports;
+
+let cache = cacheManager.caching({
+ store: "memory",
+ ttl: 10 // s
+});
+
+const UPDATE_TIMEOUT = parseInt(process.env.UPDATE_TIMEOUT) || 30; // s
+
+const API_FE_URL = process.env.API_FE_URL || "http://vainsocial.bot/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";
+
+const notif = webstomp.over(new WebSocket(API_WS_URL,
+ { perMessageDeflate: false }));
+
+(function connect() {
+ notif.connect("web", "web",
+ () => console.log("connected to queue"),
+ (err) => connect()
+ );
+})();
+
+module.exports.getMap = async (url) => {
+ return await request({
+ uri: API_MAP_URL + url,
+ json: true,
+ forever: true
+ });
+}
+
+module.exports.getFE = module.exports.get = async (url, params={}, ttl=60, cachekey=undefined) => {
+ if (cachekey == undefined) cachekey = url + JSON.stringify(params);
+ return await cache.wrap(cachekey, async () => await request({
+ uri: API_FE_URL + url,
+ qs: params,
+ json: true,
+ forever: true
+ }), { ttl: ttl });
+}
+
+// send a POST and optionally bust cache
+module.exports.postFE = module.exports.post = async (url, params={}, cachekey=undefined) => {
+ if (cachekey) cache.del(cachekey);
+ return await request.post(API_FE_URL + url, {
+ form: params,
+ json: true,
+ forever: true
+ });
+}
+
+// send a DELETE and optionally bust cache
+module.exports.deleteFE = module.exports.delete = async (url, params={}, cachekey=undefined) => {
+ if (cachekey) cache.del(cachekey);
+ return await request.delete(API_FE_URL + url, {
+ form: params,
+ json: true,
+ forever: true
+ });
+}
+
+// send a PUT and optionally bust cache
+module.exports.putFE = module.exports.put = async (url, params={}, cachekey=undefined) => {
+ if (cachekey) cache.del(cachekey);
+ return await request.put(API_FE_URL + url, {
+ form: params,
+ json: true,
+ forever: true
+ });
+}
+
+module.exports.postBE = module.exports.backend = async (url) => {
+ return await request.post({
+ uri: API_BE_URL + url,
+ json: true,
+ forever: true
+ });
+}
+
+function subscribe(topic, channel) {
+ return notif.subscribe("/topic/" + topic, (msg) => {
+ channel.put(msg.body);
+ msg.ack();
+ }, { ack: "client" });
+}
+
+// return id<->name mappings
+module.exports.getMappings = async () => {
+ return await cache.wrap("mappings", async () => {
+ let mapping = new Map();
+ await Promise.all([
+ Promise.map(
+ ["gamemode"], async (table) => {
+ mapping[table] = new Map();
+ (await api.getMap(table)).map(
+ (map) => mapping[table][map["id"]] = map["name"])
+ }
+ ),
+ // name <-> API name
+ Promise.map(
+ ["hero"], async (table) => {
+ mapping[table] = new Map();
+ (await api.getMap(table)).map(
+ (map) => mapping[table][map["api_name"]] = map["name"])
+ }
+ )
+ ]);
+ return mapping;
+ }, { ttl: 60 * 30 });
+}
+
+module.exports.mapGameMode = async (id) =>
+ (await api.getMappings())["gamemode"][id];
+
+module.exports.mapActor = async (api_name) =>
+ (await api.getMappings())["hero"][api_name];
+
+// return a set of IGN of supporters
+module.exports.getGamers = async () =>
+ (await api.getFE("/gamer", {}, 60 * 30)).map((gamer) => gamer.name);
+
+// be an async iterator
+// next() returns promises that are awaited until there is an update
+module.exports.subscribeUpdates = (name, timeout=UPDATE_TIMEOUT) => {
+ const channel = new Channel(),
+ subscription = subscribe("player." + name, channel);
+ let subscribed = true;
+
+ function stop() {
+ if (!subscribed) return;
+ subscribed = false;
+ channel.close();
+ subscription.unsubscribe();
+ clearTimeout(timer);
+ }
+
+ // stop updates after timeout
+ const timer = setTimeout(() => stop(), timeout*1000);
+
+ return { next: async () => {
+ let msg;
+ do msg = await channel.take();
+ while([Channel.DONE, "search_fail", "search_success",
+ "stats_update", "matches_update", "matches_none"]
+ .indexOf(msg) == -1);
+ // bust caches
+ if (msg == "stats_update")
+ cache.del("player+" + name);
+ if (msg == "matches_update") {
+ cache.del("matches+" + name);
+ cache.del("player+" + name);
+ }
+ if (msg == "matches_none") stop(); // no new data
+ if (msg == "search_fail") {
+ stop();
+ throw { error: {
+ err: "No player found for the provided IGN."
+ } };
+ }
+ if (msg == Channel.DONE) {
+ subscribed = false;
+ subscription.unsubscribe();
+ return undefined;
+ }
+ return msg;
+ }, stop: stop };
+}
+
+// search an unknown player
+module.exports.searchPlayer = (name) =>
+ api.postBE("/player/" + name + "/search");
+
+// update a known player
+module.exports.updatePlayer = (name) =>
+ api.postBE("/player/" + name + "/update");
+
+// return player
+module.exports.getPlayer = async (name) => {
+ return await api.getFE("/player/" + name, {}, 60, "player+" + name);
+}
+
+// search or update a player
+module.exports.upsearchPlayer = async (name) => {
+ try {
+ await api.getPlayer(name);
+ await api.updatePlayer(name);
+ } catch (err) {
+ await api.searchPlayer(name);
+ }
+}
+
+// block until update is completely done
+module.exports.upsearchPlayerSync = async (name) => {
+ const waiter = await api.subscribeUpdates(name);
+ await api.upsearchPlayer(name);
+ while ([undefined, "matches_update", "matches_none"]
+ .indexOf(await waiter.next()) == -1);
+ waiter.stop();
+}
+
+// return matches
+module.exports.getMatches = async (name) => {
+ const data = await api.getFE("/player/" + name + "/matches/1.1.1.1", {},
+ 60 * 60, "matches+" + name);
+ return data.data;
+}
+
+// return single match
+module.exports.getMatch = async (id) => {
+ return await api.getFE("/match/" + id, {}, 60 * 60);
+}
+
+// return a guild
+module.exports.getGuild = async (token) => {
+ return await api.getFE("/guild", { user_token: token }, 60, "guild+" + token);
+}
+
+// return a guild
+module.exports.getGuildMembersByGuildName = async (name) => {
+ // TODO caching
+ return await api.getFE("/guild/" + name + "/members", { }, 0);
+}
+// TODO! cache guilds by guild id, not by user token
+
+// add user to guild
+module.exports.addToGuild = async (token, member) => {
+ const membership = await api.postFE("/guild/members", {
+ user_token: token,
+ member_name: member
+ }, "guild+" + token);
+ return membership;
+}
+
+// kick from guild
+module.exports.removeFromGuild = async (token, member) => {
+ const membership = await api.deleteFE("/guild/members/" + member, {
+ user_token: token
+ }, "guild+" + token);
+ return membership;
+}
+
+// change a role
+module.exports.changeRole = async (token, member, role) => {
+ const membership = await api.putFE("/guild/members/updateRole", {
+ user_token: token,
+ member_name: member,
+ new_role: role
+ }, "guild+" + token);
+ return membership;
+}
+
+// recalc fame, block until timeout or points update
+module.exports.calculateGuild = async (id, token) => {
+ const channel = new Channel(),
+ subscription = subscribe("global", channel);
+ await api.backend("/team/" + id + "/crunch");
+
+ // stop updates after timeout
+ setTimeout(() => channel.close(), UPDATE_TIMEOUT*1000);
+
+ let msg;
+ do msg = await channel.take();
+ while([Channel.DONE, "points_update"].indexOf(msg) == -1);
+
+ if (msg == "points_update") cache.del("guild+" + token);
+ subscription.unsubscribe();
+}
+
+// store Discord ID <-> IGN
+module.exports.setUser = async (token, name) => {
+ cache.del("user+" + token);
+ await api.post("/user", {
+ name: name,
+ user_token: token
+ });
+}
+
+// retrieve Discord ID -> IGN
+module.exports.getUser = async (token) => {
+ const user = await api.getFE("/user", { user_token: token }, 60, "user+" + token);
+ return user.name;
+}