diff --git a/WatchedGuild.js b/WatchedGuild.js
new file mode 100644
index 0000000..1796711
--- /dev/null
+++ b/WatchedGuild.js
@@ -0,0 +1,80 @@
+const { EventEmitter } = require("events");
+
+class WatchedGuild extends EventEmitter {
+ constructor() {
+ super();
+
+ this.knownWebhooks = new Map();
+ this.eventStack = [];
+ this.upstreamGuildId = null;
+ }
+
+ pushEvent(e) {
+ this.eventStack.push(e);
+ this.emit("pushed", e);
+ }
+
+ consumeEvent() {
+ return this.eventStack.pop();
+ }
+
+ consumeAll() {
+ const events = [...this.eventStack];
+ this.eventStack = [];
+ return events;
+ }
+
+ hasEvents() {
+ return this.eventStack.length > 0;
+ }
+
+ _pushMessageEvent(message) {
+ this.pushEvent({
+ eventType: "messageCreate",
+ message: message.toJSON()
+ });
+ }
+
+ discordConnect(bot) {
+ this.bot = bot;
+ this.bot.on("messageCreate", (message) => {
+ if (message.guildId !== this.upstreamGuildId)
+ return;
+
+ this._pushMessageEvent(message);
+ });
+ }
+
+ async discordSendMessage(messageContent, channelId, username, avatarURL=undefined) {
+ if (!this.bot)
+ throw new Error("Bot not connected");
+
+ let webhook = this.knownWebhooks.get(channelId);
+ if (!webhook) {
+ webhook = (await this.bot.getChannelWebhooks(channelId))
+ .filter(w => w.name == "well_known__bridge")[0];
+
+ if (!webhook)
+ webhook = await this.bot.createChannelWebhook(channelId, {
+ name: "well_known__bridge"
+ }, "This webhook was created by the bridge API bot.");
+
+ this.knownWebhooks.set(channelId, webhook);
+ }
+
+ await this.bot.executeWebhook(webhook.id, webhook.token, {
+ allowedMentions: {
+ everyone: false,
+ roles: false,
+ users: true
+ },
+ content: messageContent,
+ tts: false,
+ wait: true,
+ avatarURL,
+ username
+ });
+ }
+}
+
+module.exports = WatchedGuild;
diff --git a/common.js b/common.js
new file mode 100644
index 0000000..cf0b699
--- /dev/null
+++ b/common.js
@@ -0,0 +1,28 @@
+const Eris = require("eris");
+const { discordToken, watchedGuildIds } = require("./config");
+const WatchedGuild = require("./WatchedGuild");
+
+const bot = new Eris(discordToken, {
+ intents: [
+ "guildMessages"
+ ]
+});
+
+const guildMap = new Map();
+
+bot.on("ready", () => {
+ console.log("discord bot: ready");
+ watchedGuildIds.forEach(id => {
+ const watchedGuild = new WatchedGuild();
+ watchedGuild.upstreamGuildId = id;
+ watchedGuild.discordConnect(bot);
+ guildMap.set(id, watchedGuild);
+ });
+});
+
+bot.connect();
+
+module.exports = {
+ bot,
+ guildMap
+};
diff --git a/config.js b/config.js
new file mode 100644
index 0000000..1fcc5fe
--- /dev/null
+++ b/config.js
@@ -0,0 +1,7 @@
+module.exports = {
+ mainHttpListenPort: 4050,
+ watchedGuildIds: ["822089558886842418"],
+ jwtSecret: process.env.JWT_SECRET,
+ discordToken: process.env.DISCORD_TOKEN,
+ dangerousAdminMode: true
+};
diff --git a/index.js b/index.js
index e69de29..9368aef 100644
--- a/index.js
+++ b/index.js
@@ -0,0 +1,16 @@
+const express = require("express");
+const { mainHttpListenPort } = require("./config");
+const app = express();
+const apiRoute = require("./routes/api");
+
+app.use(express.json());
+app.use("/", express.static("public/"));
+app.use("/api/v1", apiRoute);
+
+app.get("/", (req, res) => {
+ res.send("hello");
+});
+
+app.listen(mainHttpListenPort, () => {
+ console.log(`server main: listen on ${mainHttpListenPort}`);
+});
\ No newline at end of file
diff --git a/package.json b/package.json
index 26ec4a0..2ebc709 100644
--- a/package.json
+++ b/package.json
@@ -4,6 +4,8 @@
"main": "index.js",
"license": "MIT",
"dependencies": {
- "express": "^4.17.2"
+ "eris": "^0.16.1",
+ "express": "^4.17.2",
+ "jsonwebtoken": "^8.5.1"
}
}
diff --git a/public/index.html b/public/index.html
new file mode 100644
index 0000000..89b0668
--- /dev/null
+++ b/public/index.html
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+ Document
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/routes/api.js b/routes/api.js
new file mode 100644
index 0000000..91c7155
--- /dev/null
+++ b/routes/api.js
@@ -0,0 +1,56 @@
+const express = require("express");
+const { guildMap } = require("../common");
+const { dangerousAdminMode } = require("../config");
+const { checkAuth, createToken } = require("../tokens");
+const router = express();
+
+router.get("/", (req, res) => {
+ res.status(200).send({ error: false, message: "SUCCESS_API_OK" });
+});
+
+router.post("/tokens/create", async (req, res) => {
+ if (!dangerousAdminMode)
+ return res.status(403).send({ error: true, message: "ERROR_FEATURE_DISABLED" });
+
+ const { username, avatarURL, discordID, guildAccess } = req.body;
+ if (!username || !discordID || !guildAccess)
+ return res.status(400).send({ error: true, message: "ERROR_BAD_REQUEST" });
+
+ try {
+ const token = await createToken({ username, avatarURL, discordID, guildAccess });
+ res.status(200).send({ error: false, message: "SUCCESS_TOKEN_CREATED", token });
+ } catch(e) {
+ res.status(500).send({ error: true, message: "ERROR_TOKEN_CREATE_FAILURE" });
+ }
+});
+
+router.post("/guilds/:guildId/channels/:channelId/messages/create", checkAuth(async (req, res) => {
+ const messageContent = req.body.content;
+ if (!messageContent)
+ return res.status(400).send({ error: true, message: "ERROR_NO_MESSAGE_CONTENT" });
+
+ const guildId = req.params.guildId;
+ if (!guildId)
+ return res.status(400).send({ error: true, message: "ERROR_NO_GUILD_ID" });
+
+ const channelId = req.params.channelId;
+ if (!channelId)
+ return res.status(400).send({ error: true, message: "ERROR_NO_CHANNEL_ID" });
+
+
+ const { username, avatarURL, guildAccess } = req.user;
+
+ if (guildAccess.indexOf(guildId) === -1)
+ return res.status(403).send({ error: true, message: "ERROR_NO_GUILD_ACCESS" });
+
+ const guild = guildMap.get(guildId);
+ try {
+ await guild.discordSendMessage(messageContent, channelId, username, avatarURL);
+ res.status(201).send({ error: false, message: "SUCCESS_MESSAGE_CREATED" });
+ } catch(e) {
+ console.error("server main: api: message create: error: ", e);
+ res.status(500).send({ error: true, message: "ERROR_MESSAGE_SEND_FAILURE" });
+ }
+}));
+
+module.exports = router;
\ No newline at end of file
diff --git a/tokens.js b/tokens.js
new file mode 100644
index 0000000..55ebfd6
--- /dev/null
+++ b/tokens.js
@@ -0,0 +1,56 @@
+const jsonwebtoken = require("jsonwebtoken");
+const { jwtSecret } = require("./config");
+
+function createToken({ username, avatarURL, discordID, guildAccess }) {
+ return new Promise((resolve, reject) => {
+ jsonwebtoken.sign({ username, avatarURL, discordID, guildAccess }, jwtSecret, (err, token) => {
+ if (err)
+ return reject(err);
+
+ resolve(token);
+ });
+ });
+}
+
+function decodeToken(token) {
+ return new Promise((resolve, reject) => {
+ jsonwebtoken.verify(token, jwtSecret, (err, token) => {
+ if (err)
+ return reject(err);
+
+ resolve(token);
+ });
+ });
+}
+
+function checkAuth(callback) {
+ return async (req, res) => {
+ const token = req.get("authorization");
+ if (token) {
+ let user;
+ try {
+ user = await decodeToken(token);
+ } catch(e) {
+ res.status(403).send({ error: true, message: "ERROR_FORBIDDEN" });
+ return;
+ }
+ if (user) {
+ req.user = user;
+ req.authenticated = true;
+ return await callback(req, res);
+ } else {
+ res.status(401).send({ error: true, message: "ERROR_UNAUTHORIZED" });
+ return;
+ }
+ } else {
+ res.status(401).send({ error: true, message: "ERROR_UNAUTHORIZED" });
+ return;
+ }
+ };
+}
+
+module.exports = {
+ createToken,
+ decodeToken,
+ checkAuth
+};
diff --git a/yarn.lock b/yarn.lock
index 5795407..f3e1991 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -31,6 +31,11 @@ body-parser@1.19.1:
raw-body "2.4.2"
type-is "~1.6.18"
+buffer-equal-constant-time@1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819"
+ integrity sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=
+
bytes@3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.1.tgz#3f018291cb4cbad9accb6e6970bca9c8889e879a"
@@ -75,6 +80,13 @@ destroy@~1.0.4:
resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80"
integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=
+ecdsa-sig-formatter@1.0.11:
+ version "1.0.11"
+ resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf"
+ integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==
+ dependencies:
+ safe-buffer "^5.0.1"
+
ee-first@1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
@@ -85,6 +97,16 @@ encodeurl@~1.0.2:
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=
+eris@^0.16.1:
+ version "0.16.1"
+ resolved "https://registry.yarnpkg.com/eris/-/eris-0.16.1.tgz#44b0a9220944fc73dd74538cd614826bfbfcde61"
+ integrity sha512-fqjgaddSvUlUjA7s85OvZimLrgCwX58Z6FXOIxdNFJdT6XReJ/LOWZKdew2CaalM8BvN2JKzn98HmKYb3zMhKg==
+ dependencies:
+ ws "^8.2.3"
+ optionalDependencies:
+ opusscript "^0.0.8"
+ tweetnacl "^1.0.3"
+
escape-html@~1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
@@ -182,6 +204,74 @@ ipaddr.js@1.9.1:
resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3"
integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==
+jsonwebtoken@^8.5.1:
+ version "8.5.1"
+ resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz#00e71e0b8df54c2121a1f26137df2280673bcc0d"
+ integrity sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==
+ dependencies:
+ jws "^3.2.2"
+ lodash.includes "^4.3.0"
+ lodash.isboolean "^3.0.3"
+ lodash.isinteger "^4.0.4"
+ lodash.isnumber "^3.0.3"
+ lodash.isplainobject "^4.0.6"
+ lodash.isstring "^4.0.1"
+ lodash.once "^4.0.0"
+ ms "^2.1.1"
+ semver "^5.6.0"
+
+jwa@^1.4.1:
+ version "1.4.1"
+ resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a"
+ integrity sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==
+ dependencies:
+ buffer-equal-constant-time "1.0.1"
+ ecdsa-sig-formatter "1.0.11"
+ safe-buffer "^5.0.1"
+
+jws@^3.2.2:
+ version "3.2.2"
+ resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304"
+ integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==
+ dependencies:
+ jwa "^1.4.1"
+ safe-buffer "^5.0.1"
+
+lodash.includes@^4.3.0:
+ version "4.3.0"
+ resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f"
+ integrity sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=
+
+lodash.isboolean@^3.0.3:
+ version "3.0.3"
+ resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6"
+ integrity sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=
+
+lodash.isinteger@^4.0.4:
+ version "4.0.4"
+ resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343"
+ integrity sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=
+
+lodash.isnumber@^3.0.3:
+ version "3.0.3"
+ resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc"
+ integrity sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=
+
+lodash.isplainobject@^4.0.6:
+ version "4.0.6"
+ resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb"
+ integrity sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=
+
+lodash.isstring@^4.0.1:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451"
+ integrity sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=
+
+lodash.once@^4.0.0:
+ version "4.1.1"
+ resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac"
+ integrity sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=
+
media-typer@0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
@@ -219,7 +309,7 @@ ms@2.0.0:
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=
-ms@2.1.3:
+ms@2.1.3, ms@^2.1.1:
version "2.1.3"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
@@ -236,6 +326,11 @@ on-finished@~2.3.0:
dependencies:
ee-first "1.1.1"
+opusscript@^0.0.8:
+ version "0.0.8"
+ resolved "https://registry.yarnpkg.com/opusscript/-/opusscript-0.0.8.tgz#00b49e81281b4d99092d013b1812af8654bd0a87"
+ integrity sha512-VSTi1aWFuCkRCVq+tx/BQ5q9fMnQ9pVZ3JU4UHKqTkf0ED3fKEPdr+gKAAl3IA2hj9rrP6iyq3hlcJq3HELtNQ==
+
parseurl@~1.3.3:
version "1.3.3"
resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4"
@@ -274,7 +369,7 @@ raw-body@2.4.2:
iconv-lite "0.4.24"
unpipe "1.0.0"
-safe-buffer@5.2.1:
+safe-buffer@5.2.1, safe-buffer@^5.0.1:
version "5.2.1"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
@@ -284,6 +379,11 @@ safe-buffer@5.2.1:
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
+semver@^5.6.0:
+ version "5.7.1"
+ resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
+ integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
+
send@0.17.2:
version "0.17.2"
resolved "https://registry.yarnpkg.com/send/-/send-0.17.2.tgz#926622f76601c41808012c8bf1688fe3906f7820"
@@ -328,6 +428,11 @@ toidentifier@1.0.1:
resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35"
integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==
+tweetnacl@^1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-1.0.3.tgz#ac0af71680458d8a6378d0d0d050ab1407d35596"
+ integrity sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==
+
type-is@~1.6.18:
version "1.6.18"
resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131"
@@ -350,3 +455,8 @@ vary@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=
+
+ws@^8.2.3:
+ version "8.4.2"
+ resolved "https://registry.yarnpkg.com/ws/-/ws-8.4.2.tgz#18e749868d8439f2268368829042894b6907aa0b"
+ integrity sha512-Kbk4Nxyq7/ZWqr/tarI9yIt/+iNNFOjBXEWgTb4ydaNHBNGgvf2QHbS9fdfsndfjFlFwEd4Al+mw83YkaD10ZA==