mirror of
https://github.com/turt2live/matrix-dimension.git
synced 2024-10-01 01:05:53 -04:00
Change up bot structure to support hosted bots. Adds #12
This commit is contained in:
parent
01ed07479e
commit
35559c9373
2
.gitignore
vendored
2
.gitignore
vendored
@ -6,6 +6,8 @@ config/development.yaml
|
|||||||
config/production.yaml
|
config/production.yaml
|
||||||
db/*.db
|
db/*.db
|
||||||
start.sh
|
start.sh
|
||||||
|
config/integrations/*_development.yaml
|
||||||
|
config/integrations/*_production.yaml
|
||||||
|
|
||||||
# Logs
|
# Logs
|
||||||
logs
|
logs
|
||||||
|
8
app.js
8
app.js
@ -1,10 +1,18 @@
|
|||||||
var log = require("./src/util/LogService");
|
var log = require("./src/util/LogService");
|
||||||
var Dimension = require("./src/Dimension");
|
var Dimension = require("./src/Dimension");
|
||||||
var DimensionStore = require("./src/storage/DimensionStore");
|
var DimensionStore = require("./src/storage/DimensionStore");
|
||||||
|
var DemoBot = require("./src/matrix/DemoBot");
|
||||||
|
var config = require("config");
|
||||||
|
|
||||||
log.info("app", "Bootstrapping Dimension...");
|
log.info("app", "Bootstrapping Dimension...");
|
||||||
var db = new DimensionStore();
|
var db = new DimensionStore();
|
||||||
db.prepare().then(() => {
|
db.prepare().then(() => {
|
||||||
var app = new Dimension(db);
|
var app = new Dimension(db);
|
||||||
app.start();
|
app.start();
|
||||||
|
|
||||||
|
if (config.get("demobot.enabled")) {
|
||||||
|
log.info("app", "Demo bot enabled - starting up");
|
||||||
|
var bot = new DemoBot(config.get("demobot.homeserverUrl"), config.get("demobot.userId"), config.get("demobot.accessToken"));
|
||||||
|
bot.start();
|
||||||
|
}
|
||||||
}, err => log.error("app", err)).catch(err => log.error("app", err));
|
}, err => log.error("app", err)).catch(err => log.error("app", err));
|
@ -1,48 +1,8 @@
|
|||||||
# Configuration for the bots this Dimension supports
|
|
||||||
bots:
|
|
||||||
# Giphy (from matrix.org)
|
|
||||||
- mxid: "@neb_giphy:matrix.org"
|
|
||||||
name: "Giphy"
|
|
||||||
avatar: "/img/avatars/giphy.png"
|
|
||||||
about: "Use `!giphy query` to find an animated GIF on demand"
|
|
||||||
upstreamType: "giphy"
|
|
||||||
# Guggy (from matrix.org)
|
|
||||||
- mxid: "@_neb_guggy:matrix.org"
|
|
||||||
name: "Guggy"
|
|
||||||
avatar: "/img/avatars/guggy.png"
|
|
||||||
about: "Use `!guggy sentence` to create an animated GIF from a sentence"
|
|
||||||
upstreamType: "guggy"
|
|
||||||
# Imgur (from matrix.org)
|
|
||||||
- mxid: "@_neb_imgur:matrix.org"
|
|
||||||
name: "Imgur"
|
|
||||||
avatar: "/img/avatars/imgur.png"
|
|
||||||
about: "Use `!imgur query` to find an image from Imgur"
|
|
||||||
upstreamType: "imgur"
|
|
||||||
# Wikipedia (from matrix.org)
|
|
||||||
- mxid: "@_neb_wikipedia:matrix.org"
|
|
||||||
name: "Wikipedia"
|
|
||||||
avatar: "/img/avatars/wikipedia.png"
|
|
||||||
about: "Use `!wikipedia query` to find something from Wikipedia"
|
|
||||||
upstreamType: "wikipedia"
|
|
||||||
# Google (from matrix.org)
|
|
||||||
- mxid: "@_neb_google:matrix.org"
|
|
||||||
name: "Google"
|
|
||||||
avatar: "/img/avatars/google.png"
|
|
||||||
about: "Use `!google image query` to find an image from Google"
|
|
||||||
upstreamType: "google"
|
|
||||||
|
|
||||||
# The web settings for the service (API and UI)
|
# The web settings for the service (API and UI)
|
||||||
web:
|
web:
|
||||||
# The port to run the webserver on
|
|
||||||
port: 8184
|
port: 8184
|
||||||
|
|
||||||
# The address to bind to (0.0.0.0 for all interfaces)
|
|
||||||
address: '0.0.0.0'
|
address: '0.0.0.0'
|
||||||
|
|
||||||
# Upstream scalar configuration. This should almost never change.
|
|
||||||
scalar:
|
|
||||||
upstreamRestUrl: "https://scalar.vector.im/api"
|
|
||||||
|
|
||||||
# Settings for controlling how logging works
|
# Settings for controlling how logging works
|
||||||
logging:
|
logging:
|
||||||
file: logs/dimension.log
|
file: logs/dimension.log
|
||||||
@ -52,3 +12,14 @@ logging:
|
|||||||
rotate:
|
rotate:
|
||||||
size: 52428800 # bytes, default is 50mb
|
size: 52428800 # bytes, default is 50mb
|
||||||
count: 5
|
count: 5
|
||||||
|
|
||||||
|
# Demo bot configuration. Used purely to show how to configure a self-hosted bot in Dimension
|
||||||
|
demobot:
|
||||||
|
enabled: false
|
||||||
|
userId: "@dimension:t2bot.io"
|
||||||
|
homeserverUrl: "https://t2bot.io"
|
||||||
|
accessToken: "something"
|
||||||
|
|
||||||
|
# Upstream configuration. This should almost never change.
|
||||||
|
upstreams:
|
||||||
|
vector: "https://scalar.vector.im/api"
|
9
config/integrations/dimension_demo_bot.yaml
Normal file
9
config/integrations/dimension_demo_bot.yaml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
type: "bot"
|
||||||
|
enabled: true
|
||||||
|
userId: "@dimension:t2bot.io"
|
||||||
|
name: "Demo Bot"
|
||||||
|
about: "A bot that has no functionality. This is just a demonstration on the config."
|
||||||
|
avatar: "/img/avatars/demobot.png"
|
||||||
|
hosted:
|
||||||
|
homeserverUrl: "https://t2bot.io"
|
||||||
|
accessToken: "your_matrix_access_token_here"
|
9
config/integrations/giphy.yaml
Normal file
9
config/integrations/giphy.yaml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
type: "bot"
|
||||||
|
enabled: true
|
||||||
|
userId: "@neb_giphy:matrix.org"
|
||||||
|
name: "Giphy"
|
||||||
|
about: "Use `!giphy query` to find an animated GIF on demand"
|
||||||
|
avatar: "/img/avatars/giphy.png"
|
||||||
|
upstream:
|
||||||
|
type: "vector"
|
||||||
|
id: "giphy"
|
9
config/integrations/google.yaml
Normal file
9
config/integrations/google.yaml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
type: "bot"
|
||||||
|
enabled: true
|
||||||
|
userId: "@_neb_google:matrix.org"
|
||||||
|
name: "Google"
|
||||||
|
about: "Use `!google image query` to find an image from Google"
|
||||||
|
avatar: "/img/avatars/google.png"
|
||||||
|
upstream:
|
||||||
|
type: "vector"
|
||||||
|
id: "google"
|
9
config/integrations/guggy.yaml
Normal file
9
config/integrations/guggy.yaml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
type: "bot"
|
||||||
|
enabled: true
|
||||||
|
userId: "@_neb_guggy:matrix.org"
|
||||||
|
name: "Guggy"
|
||||||
|
about: "Use `!guggy sentence` to create an animated GIF from a sentence"
|
||||||
|
avatar: "/img/avatars/guggy.png"
|
||||||
|
upstream:
|
||||||
|
type: "vector"
|
||||||
|
id: "guggy"
|
9
config/integrations/imgur.yaml
Normal file
9
config/integrations/imgur.yaml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
type: "bot"
|
||||||
|
enabled: true
|
||||||
|
userId: "@_neb_imgur:matrix.org"
|
||||||
|
name: "Imgur"
|
||||||
|
about: "Use `!imgur query` to find an image from Imgur"
|
||||||
|
avatar: "/img/avatars/imgur.png"
|
||||||
|
upstream:
|
||||||
|
type: "vector"
|
||||||
|
id: "imgur"
|
9
config/integrations/wikipedia.yaml
Normal file
9
config/integrations/wikipedia.yaml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
type: "bot"
|
||||||
|
enabled: true
|
||||||
|
userId: "@_neb_wikipedia:matrix.org"
|
||||||
|
name: "Wikipedia"
|
||||||
|
about: "Use `!wikipedia query` to find something from Wikipedia"
|
||||||
|
avatar: "/img/avatars/wikipedia.png"
|
||||||
|
upstream:
|
||||||
|
type: "vector"
|
||||||
|
id: "wikipedia"
|
@ -21,6 +21,8 @@
|
|||||||
"db-migrate-sqlite3": "^0.2.1",
|
"db-migrate-sqlite3": "^0.2.1",
|
||||||
"express": "^4.15.2",
|
"express": "^4.15.2",
|
||||||
"js-yaml": "^3.8.2",
|
"js-yaml": "^3.8.2",
|
||||||
|
"lodash": "^4.17.4",
|
||||||
|
"matrix-js-sdk": "^0.7.8",
|
||||||
"moment": "^2.18.1",
|
"moment": "^2.18.1",
|
||||||
"random-string": "^0.2.0",
|
"random-string": "^0.2.0",
|
||||||
"request": "^2.81.0",
|
"request": "^2.81.0",
|
||||||
|
@ -8,6 +8,10 @@ var MatrixLiteClient = require("./matrix/MatrixLiteClient");
|
|||||||
var randomString = require("random-string");
|
var randomString = require("random-string");
|
||||||
var ScalarClient = require("./scalar/ScalarClient.js");
|
var ScalarClient = require("./scalar/ScalarClient.js");
|
||||||
var VectorScalarClient = require("./scalar/VectorScalarClient");
|
var VectorScalarClient = require("./scalar/VectorScalarClient");
|
||||||
|
var integrations = require("./integration");
|
||||||
|
var _ = require("lodash");
|
||||||
|
var UpstreamIntegration = require("./integration/UpstreamIntegration");
|
||||||
|
var HostedIntegration = require("./integration/HostedIntegration");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Primary entry point for Dimension
|
* Primary entry point for Dimension
|
||||||
@ -48,8 +52,8 @@ class Dimension {
|
|||||||
this._app.post("/api/v1/scalar/register", this._scalarRegister.bind(this));
|
this._app.post("/api/v1/scalar/register", this._scalarRegister.bind(this));
|
||||||
this._app.get("/api/v1/scalar/checkToken", this._checkScalarToken.bind(this));
|
this._app.get("/api/v1/scalar/checkToken", this._checkScalarToken.bind(this));
|
||||||
|
|
||||||
this._app.get("/api/v1/dimension/bots", this._getBots.bind(this));
|
this._app.get("/api/v1/dimension/integrations", this._getIntegrations.bind(this));
|
||||||
this._app.post("/api/v1/dimension/kick", this._kickUser.bind(this));
|
this._app.post("/api/v1/dimension/removeIntegration", this._removeIntegration.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
@ -57,8 +61,7 @@ class Dimension {
|
|||||||
log.info("Dimension", "API and UI listening on " + config.get("web.address") + ":" + config.get("web.port"));
|
log.info("Dimension", "API and UI listening on " + config.get("web.address") + ":" + config.get("web.port"));
|
||||||
}
|
}
|
||||||
|
|
||||||
_kickUser(req, res) {
|
_removeIntegration(req, res) {
|
||||||
// {roomId: roomId, userId: userId, scalarToken: scalarToken}
|
|
||||||
var roomId = req.body.roomId;
|
var roomId = req.body.roomId;
|
||||||
var userId = req.body.userId;
|
var userId = req.body.userId;
|
||||||
var scalarToken = req.body.scalarToken;
|
var scalarToken = req.body.scalarToken;
|
||||||
@ -68,27 +71,32 @@ class Dimension {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var integrationName = null;
|
var integrationConfig = integrations.byUserId[userId];
|
||||||
|
if (!integrationConfig) {
|
||||||
|
res.status(400).send({error: "Unknown integration"});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this._db.checkToken(scalarToken).then(() => {
|
this._db.checkToken(scalarToken).then(() => {
|
||||||
for (var bot of config.bots) {
|
if (integrationConfig.upstream) {
|
||||||
if (bot.mxid == userId) {
|
return this._db.getUpstreamToken(scalarToken).then(upstreamToken => new UpstreamIntegration(integrationConfig, upstreamToken));
|
||||||
integrationName = bot.upstreamType;
|
} else return new HostedIntegration(integrationConfig);
|
||||||
break;
|
}).then(integration => integration.leaveRoom(roomId)).then(() => {
|
||||||
}
|
res.status(200).send({success: true});
|
||||||
|
}).catch(err => res.status(500).send({error: err.message}));
|
||||||
}
|
}
|
||||||
|
|
||||||
return this._db.getUpstreamToken(scalarToken);
|
_getIntegrations(req, res) {
|
||||||
}).then(upstreamToken => {
|
|
||||||
if (!upstreamToken || !integrationName) {
|
|
||||||
res.status(400).send({error: "Missing token or integration name"});
|
|
||||||
return Promise.resolve();
|
|
||||||
} else return VectorScalarClient.removeIntegration(integrationName, roomId, upstreamToken);
|
|
||||||
}).then(() => res.status(200).send({success: true})).catch(err => res.status(500).send({error: err.message}));
|
|
||||||
}
|
|
||||||
|
|
||||||
_getBots(req, res) {
|
|
||||||
res.setHeader("Content-Type", "application/json");
|
res.setHeader("Content-Type", "application/json");
|
||||||
res.send(JSON.stringify(config.bots));
|
|
||||||
|
var results = _.map(integrations.all, i => {
|
||||||
|
var integration = JSON.parse(JSON.stringify(i));
|
||||||
|
integration.upstream = undefined;
|
||||||
|
integration.hosted = undefined;
|
||||||
|
return integration;
|
||||||
|
});
|
||||||
|
|
||||||
|
res.send(results);
|
||||||
}
|
}
|
||||||
|
|
||||||
_checkScalarToken(req, res) {
|
_checkScalarToken(req, res) {
|
||||||
|
35
src/integration/HostedIntegration.js
Normal file
35
src/integration/HostedIntegration.js
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
var sdk = require("matrix-js-sdk");
|
||||||
|
var log = require("../util/LogService");
|
||||||
|
var StubbedIntegration = require("./StubbedIntegration");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents an integration hosted on a known homeserver
|
||||||
|
*/
|
||||||
|
class HostedIntegration extends StubbedIntegration {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new hosted integration
|
||||||
|
* @param integrationSettings the integration settings
|
||||||
|
*/
|
||||||
|
constructor(integrationSettings) {
|
||||||
|
super();
|
||||||
|
this._settings = integrationSettings;
|
||||||
|
this._client = sdk.createClient({
|
||||||
|
baseUrl: this._settings.hosted.homeserverUrl,
|
||||||
|
accessToken: this._settings.hosted.accessToken,
|
||||||
|
userId: this._settings.userId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Leaves a given Matrix room
|
||||||
|
* @param {string} roomId the room to leave
|
||||||
|
* @returns {Promise<>} resolves when completed
|
||||||
|
*/
|
||||||
|
leaveRoom(roomId) {
|
||||||
|
log.info("HostedIntegration", "Removing " + this._settings.userId + " from " + roomId);
|
||||||
|
return this._client.leave(roomId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = HostedIntegration;
|
7
src/integration/StubbedIntegration.js
Normal file
7
src/integration/StubbedIntegration.js
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
class StubbedIntegration {
|
||||||
|
leaveRoom(roomId) {
|
||||||
|
throw new Error("Not implemented");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = StubbedIntegration;
|
33
src/integration/UpstreamIntegration.js
Normal file
33
src/integration/UpstreamIntegration.js
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
var VectorScalarClient = require("../scalar/VectorScalarClient");
|
||||||
|
var log = require("../util/LogService");
|
||||||
|
var StubbedIntegration = require("./StubbedIntegration");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An integration that is handled by an upstream Scalar instance
|
||||||
|
*/
|
||||||
|
class UpstreamIntegration extends StubbedIntegration {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new hosted integration
|
||||||
|
* @param integrationSettings the integration settings
|
||||||
|
* @param {string} upstreamToken the upstream scalar token
|
||||||
|
*/
|
||||||
|
constructor(integrationSettings, upstreamToken) {
|
||||||
|
super();
|
||||||
|
this._settings = integrationSettings;
|
||||||
|
this._upstreamToken = upstreamToken;
|
||||||
|
if (this._settings.upstream.type !== "vector") throw new Error("Unknown upstream type: " + this._settings.upstream.type);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Leaves a given Matrix room
|
||||||
|
* @param {string} roomId the room to leave
|
||||||
|
* @returns {Promise<>} resolves when completed
|
||||||
|
*/
|
||||||
|
leaveRoom(roomId) {
|
||||||
|
log.info("UpstreamIntegration", "Removing " + this._settings.userId + " from " + roomId);
|
||||||
|
return VectorScalarClient.removeIntegration(this._settings.upstream.id, roomId, this._upstreamToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = UpstreamIntegration;
|
55
src/integration/index.js
Normal file
55
src/integration/index.js
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
var config = require("config");
|
||||||
|
var log = require("../util/LogService");
|
||||||
|
var fs = require("fs");
|
||||||
|
var path = require("path");
|
||||||
|
var _ = require("lodash");
|
||||||
|
|
||||||
|
log.info("Integrations", "Discovering integrations");
|
||||||
|
|
||||||
|
var searchPath = path.join(process.cwd(), "config", "integrations");
|
||||||
|
var files = _.filter(fs.readdirSync(searchPath), f => !fs.statSync(path.join(searchPath, f)).isDirectory() && f.endsWith(".yaml"));
|
||||||
|
var currentEnv = config.util.initParam("NODE_ENV", "development");
|
||||||
|
|
||||||
|
if (currentEnv !== "development" && currentEnv !== "production")
|
||||||
|
throw new Error("Unknown node environment: " + currentEnv);
|
||||||
|
|
||||||
|
var configs = {};
|
||||||
|
|
||||||
|
for (var file of files) {
|
||||||
|
if (file.endsWith("_development.yaml") || file.endsWith("_production.yaml")) {
|
||||||
|
if (!file.endsWith("_" + currentEnv + ".yaml")) continue;
|
||||||
|
var fileName = file.replace("_development.yaml", "").replace("_production.yaml", "") + ".yaml";
|
||||||
|
|
||||||
|
if (!configs[fileName]) configs[fileName] = {};
|
||||||
|
configs[fileName]["alt"] = config.util.parseFile(path.join(searchPath, file));
|
||||||
|
} else {
|
||||||
|
if (!configs[file]) configs[file] = {};
|
||||||
|
configs[file]["defaults"] = config.util.parseFile(path.join(searchPath, file));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var keys = _.keys(configs);
|
||||||
|
log.info("Integrations", "Discovered " + keys.length + " integrations. Parsing definitions...");
|
||||||
|
|
||||||
|
var linear = [];
|
||||||
|
var byUserId = {};
|
||||||
|
|
||||||
|
for (var key of keys) {
|
||||||
|
log.info("Integrations", "Preparing " + key);
|
||||||
|
var merged = config.util.extendDeep(configs[key].defaults, configs[key].alt);
|
||||||
|
if (!merged['enabled']) {
|
||||||
|
log.warn("Integrations", "Integration " + key + " is not enabled - skipping");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
linear.push(merged);
|
||||||
|
if (merged['userId'])
|
||||||
|
byUserId[merged['userId']] = merged;
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("Integrations", "Loaded " + linear.length + " integrations");
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
all: linear,
|
||||||
|
byUserId: byUserId
|
||||||
|
};
|
66
src/matrix/DemoBot.js
Normal file
66
src/matrix/DemoBot.js
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
var sdk = require("matrix-js-sdk");
|
||||||
|
var log = require("../util/LogService");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dimension demo bot. Doesn't do anything except show how to add a self-hosted bot to Dimension
|
||||||
|
*/
|
||||||
|
class DemoBot {
|
||||||
|
|
||||||
|
constructor(homeserverUrl, userId, accessToken) {
|
||||||
|
this._rooms = [];
|
||||||
|
|
||||||
|
log.info("DemoBot", "Constructing bot as " + userId);
|
||||||
|
this._client = sdk.createClient({
|
||||||
|
baseUrl: homeserverUrl,
|
||||||
|
accessToken: accessToken,
|
||||||
|
userId: userId,
|
||||||
|
});
|
||||||
|
|
||||||
|
this._client.on('event', event => {
|
||||||
|
if (event.getType() !== "m.room.member") return;
|
||||||
|
if (event.getStateKey() != this._client.credentials.userId) return;
|
||||||
|
if (event.getContent().membership === 'invite') {
|
||||||
|
if (this._rooms.indexOf(event.getRoomId()) !== -1) return;
|
||||||
|
log.info("DemoBot", "Joining " + event.getRoomId());
|
||||||
|
this._client.joinRoom(event.getRoomId()).then(() => {
|
||||||
|
this._recalculateRooms();
|
||||||
|
this._client.sendMessage(event.getRoomId(), {
|
||||||
|
msgtype: "m.notice",
|
||||||
|
body: "Hello! I'm a small bot that acts as an example for how to set up your own bot on Dimension."
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else this._recalculateRooms();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
start() {
|
||||||
|
log.info("DemoBot", "Starting bot");
|
||||||
|
this._client.startClient();
|
||||||
|
|
||||||
|
this._client.on('sync', state => {
|
||||||
|
if (state == 'PREPARED') this._recalculateRooms();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_recalculateRooms() {
|
||||||
|
var rooms = this._client.getRooms();
|
||||||
|
this._rooms = [];
|
||||||
|
for (var room of rooms) {
|
||||||
|
var me = room.getMember(this._client.credentials.userId);
|
||||||
|
if (!me) continue;
|
||||||
|
|
||||||
|
if (me.membership == "invite") {
|
||||||
|
this._client.joinRoom(room.roomId);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (me.membership != "join") continue;
|
||||||
|
this._rooms.push(room.roomId);
|
||||||
|
}
|
||||||
|
|
||||||
|
log.verbose("DemoBot", "Currently in " + this._rooms.length + " rooms");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = DemoBot;
|
@ -29,8 +29,10 @@ class ScalarClient {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Merge this, VectorScalarClient, and MatrixLiteClient into a base class
|
||||||
_do(method, endpoint, qs = null, body = null) {
|
_do(method, endpoint, qs = null, body = null) {
|
||||||
var url = config.scalar.upstreamRestUrl + endpoint;
|
// TODO: Generify URL
|
||||||
|
var url = config.get("upstreams.vector") + endpoint;
|
||||||
|
|
||||||
log.verbose("ScalarClient", "Performing request: " + url);
|
log.verbose("ScalarClient", "Performing request: " + url);
|
||||||
|
|
||||||
|
@ -33,7 +33,10 @@ class VectorScalarClient {
|
|||||||
* @return {Promise<>} resolves when complete
|
* @return {Promise<>} resolves when complete
|
||||||
*/
|
*/
|
||||||
removeIntegration(type, roomId, scalarToken) {
|
removeIntegration(type, roomId, scalarToken) {
|
||||||
return this._do("POST", "/removeIntegration", {scalar_token: scalarToken}, {type: type, room_id: roomId}).then((response, body) => {
|
return this._do("POST", "/removeIntegration", {scalar_token: scalarToken}, {
|
||||||
|
type: type,
|
||||||
|
room_id: roomId
|
||||||
|
}).then((response, body) => {
|
||||||
if (response.statusCode !== 200) {
|
if (response.statusCode !== 200) {
|
||||||
log.error("VectorScalarClient", response.body);
|
log.error("VectorScalarClient", response.body);
|
||||||
return Promise.reject(response.body);
|
return Promise.reject(response.body);
|
||||||
@ -44,7 +47,7 @@ class VectorScalarClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_do(method, endpoint, qs = null, body = null) {
|
_do(method, endpoint, qs = null, body = null) {
|
||||||
var url = config.scalar.upstreamRestUrl + endpoint;
|
var url = config.get("upstreams.vector") + endpoint;
|
||||||
|
|
||||||
log.verbose("VectorScalarClient", "Performing request: " + url);
|
log.verbose("VectorScalarClient", "Performing request: " + url);
|
||||||
|
|
||||||
|
@ -9,11 +9,11 @@ import { removeNgStyles, createNewHosts } from "@angularclass/hmr";
|
|||||||
import { NgbModule } from "@ng-bootstrap/ng-bootstrap";
|
import { NgbModule } from "@ng-bootstrap/ng-bootstrap";
|
||||||
import { RiotComponent } from "./riot/riot.component";
|
import { RiotComponent } from "./riot/riot.component";
|
||||||
import { ApiService } from "./shared/api.service";
|
import { ApiService } from "./shared/api.service";
|
||||||
import { BotComponent } from "./bot/bot.component";
|
|
||||||
import { UiSwitchModule } from "angular2-ui-switch";
|
import { UiSwitchModule } from "angular2-ui-switch";
|
||||||
import { ScalarService } from "./shared/scalar.service";
|
import { ScalarService } from "./shared/scalar.service";
|
||||||
import { ToasterModule } from "angular2-toaster";
|
import { ToasterModule } from "angular2-toaster";
|
||||||
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
|
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
|
||||||
|
import { IntegrationComponent } from "./integration/integration.component";
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
@ -30,7 +30,7 @@ import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
|
|||||||
AppComponent,
|
AppComponent,
|
||||||
HomeComponent,
|
HomeComponent,
|
||||||
RiotComponent,
|
RiotComponent,
|
||||||
BotComponent,
|
IntegrationComponent,
|
||||||
|
|
||||||
// Vendor
|
// Vendor
|
||||||
],
|
],
|
||||||
|
@ -1,21 +0,0 @@
|
|||||||
import { Component, Input, Output, EventEmitter } from "@angular/core";
|
|
||||||
import { Bot } from "../shared/models/bot";
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'my-bot',
|
|
||||||
templateUrl: './bot.component.html',
|
|
||||||
styleUrls: ['./bot.component.scss'],
|
|
||||||
})
|
|
||||||
export class BotComponent {
|
|
||||||
|
|
||||||
@Input() bot: Bot;
|
|
||||||
@Output() updated: EventEmitter<any> = new EventEmitter();
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public update(): void {
|
|
||||||
this.bot.isEnabled = !this.bot.isEnabled;
|
|
||||||
this.updated.emit();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,13 +1,13 @@
|
|||||||
<div class="bot">
|
<div class="bot">
|
||||||
<img [src]="bot.avatar" class="avatar">
|
<img [src]="integration.avatar" class="avatar">
|
||||||
<div class="title">
|
<div class="title">
|
||||||
<b>{{ bot.name }}</b>
|
<b>{{ integration.name }}</b>
|
||||||
<div style="display: flex;">
|
<div style="display: flex;">
|
||||||
<div class="switch">
|
<div class="switch">
|
||||||
<ui-switch [checked]="bot.isEnabled" size="small" [disabled]="bot.isBroken" (change)="update()"></ui-switch>
|
<ui-switch [checked]="integration.isEnabled" size="small" [disabled]="integration.isBroken" (change)="update()"></ui-switch>
|
||||||
</div>
|
</div>
|
||||||
<div class="toolbar">
|
<div class="toolbar">
|
||||||
<i class="fa fa-question-circle text-info" ngbTooltip="{{bot.about}}" *ngIf="bot.about"></i>
|
<i class="fa fa-question-circle text-info" ngbTooltip="{{integration.about}}" *ngIf="integration.about"></i>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
21
web/app/integration/integration.component.ts
Normal file
21
web/app/integration/integration.component.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { Component, Input, Output, EventEmitter } from "@angular/core";
|
||||||
|
import { Integration } from "../shared/models/integration";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'my-integration',
|
||||||
|
templateUrl: './integration.component.html',
|
||||||
|
styleUrls: ['./integration.component.scss'],
|
||||||
|
})
|
||||||
|
export class IntegrationComponent {
|
||||||
|
|
||||||
|
@Input() integration: Integration;
|
||||||
|
@Output() updated: EventEmitter<any> = new EventEmitter();
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public update(): void {
|
||||||
|
this.integration.isEnabled = !this.integration.isEnabled;
|
||||||
|
this.updated.emit();
|
||||||
|
}
|
||||||
|
}
|
@ -9,7 +9,7 @@
|
|||||||
<h3>Manage Integrations</h3>
|
<h3>Manage Integrations</h3>
|
||||||
<p>Turn on anything you like. If an integration has some special configuration, it will have a configuration icon next to it.</p>
|
<p>Turn on anything you like. If an integration has some special configuration, it will have a configuration icon next to it.</p>
|
||||||
<div class="integration-container">
|
<div class="integration-container">
|
||||||
<my-bot *ngFor="let bot of bots" [bot]="bot" (updated)="updateBot(bot)"></my-bot>
|
<my-integration *ngFor="let integration of integrations" [integration]="integration" (updated)="updateIntegration(integration)"></my-integration>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
@ -1,9 +1,9 @@
|
|||||||
import { Component } from "@angular/core";
|
import { Component } from "@angular/core";
|
||||||
import { ActivatedRoute } from "@angular/router";
|
import { ActivatedRoute } from "@angular/router";
|
||||||
import { ApiService } from "../shared/api.service";
|
import { ApiService } from "../shared/api.service";
|
||||||
import { Bot } from "../shared/models/bot";
|
|
||||||
import { ScalarService } from "../shared/scalar.service";
|
import { ScalarService } from "../shared/scalar.service";
|
||||||
import { ToasterService } from "angular2-toaster";
|
import { ToasterService } from "angular2-toaster";
|
||||||
|
import { Integration } from "../shared/models/integration";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'my-riot',
|
selector: 'my-riot',
|
||||||
@ -13,7 +13,7 @@ import { ToasterService } from "angular2-toaster";
|
|||||||
export class RiotComponent {
|
export class RiotComponent {
|
||||||
|
|
||||||
public error: string;
|
public error: string;
|
||||||
public bots: Bot[] = [];
|
public integrations: Integration[] = [];
|
||||||
public loading = true;
|
public loading = true;
|
||||||
public roomId: string;
|
public roomId: string;
|
||||||
|
|
||||||
@ -40,47 +40,50 @@ export class RiotComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private init() {
|
private init() {
|
||||||
this.api.getBots().then(bots => {
|
this.api.getIntegrations().then(integrations => {
|
||||||
this.bots = bots;
|
this.integrations = integrations;
|
||||||
let promises = bots.map(b => this.updateBotState(b));
|
let promises = integrations.map(b => this.updateIntegrationState(b));
|
||||||
return Promise.all(promises);
|
return Promise.all(promises);
|
||||||
}).then(() => this.loading = false);
|
}).then(() => this.loading = false).catch(err => {
|
||||||
}
|
this.error = "Unable to communicate with Dimension";
|
||||||
|
console.error(err);
|
||||||
private updateBotState(bot: Bot) {
|
|
||||||
return this.scalar.getMembershipState(this.roomId, bot.mxid).then(payload => {
|
|
||||||
bot.isBroken = false;
|
|
||||||
|
|
||||||
if (!payload.response) {
|
|
||||||
bot.isEnabled = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
bot.isEnabled = (payload.response.membership === 'join' || payload.response.membership === 'invite');
|
|
||||||
}, (error) => {
|
|
||||||
console.error(error);
|
|
||||||
bot.isEnabled = false;
|
|
||||||
bot.isBroken = true;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public updateBot(bot: Bot) {
|
private updateIntegrationState(integration: Integration) {
|
||||||
|
return this.scalar.getMembershipState(this.roomId, integration.userId).then(payload => {
|
||||||
|
integration.isBroken = false;
|
||||||
|
|
||||||
|
if (!payload.response) {
|
||||||
|
integration.isEnabled = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
integration.isEnabled = (payload.response.membership === 'join' || payload.response.membership === 'invite');
|
||||||
|
}, (error) => {
|
||||||
|
console.error(error);
|
||||||
|
integration.isEnabled = false;
|
||||||
|
integration.isBroken = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public updateIntegration(integration: Integration) {
|
||||||
let promise = null;
|
let promise = null;
|
||||||
|
|
||||||
if (!bot.isEnabled) {
|
if (!integration.isEnabled) {
|
||||||
promise = this.api.kickUser(this.roomId, bot.mxid, this.scalarToken);
|
promise = this.api.removeIntegration(this.roomId, integration.userId, this.scalarToken);
|
||||||
} else promise = this.scalar.inviteUser(this.roomId, bot.mxid);
|
} else promise = this.scalar.inviteUser(this.roomId, integration.userId);
|
||||||
|
|
||||||
promise
|
promise
|
||||||
.then(() => this.toaster.pop("success", bot.name + " invited to the room"))
|
.then(() => this.toaster.pop("success", integration.name + " invited to the room"))
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
let errorMessage = "Could not update bot status";
|
let errorMessage = "Could not update integration status";
|
||||||
|
|
||||||
if (err.json) {
|
if (err.json) {
|
||||||
errorMessage = err.json().error;
|
errorMessage = err.json().error;
|
||||||
} else errorMessage = err.response.error.message;
|
} else errorMessage = err.response.error.message;
|
||||||
|
|
||||||
bot.isEnabled = !bot.isEnabled;
|
integration.isEnabled = !integration.isEnabled;
|
||||||
this.toaster.pop("error", errorMessage);
|
this.toaster.pop("error", errorMessage);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { Injectable } from "@angular/core";
|
import { Injectable } from "@angular/core";
|
||||||
import { Http } from "@angular/http";
|
import { Http } from "@angular/http";
|
||||||
import { Bot } from "./models/bot";
|
import { Integration } from "./models/integration";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ApiService {
|
export class ApiService {
|
||||||
@ -12,13 +12,13 @@ export class ApiService {
|
|||||||
.map(res => res.status === 200).toPromise();
|
.map(res => res.status === 200).toPromise();
|
||||||
}
|
}
|
||||||
|
|
||||||
getBots(): Promise<Bot[]> {
|
getIntegrations(): Promise<Integration[]> {
|
||||||
return this.http.get("/api/v1/dimension/bots")
|
return this.http.get("/api/v1/dimension/integrations")
|
||||||
.map(res => res.json()).toPromise();
|
.map(res => res.json()).toPromise();
|
||||||
}
|
}
|
||||||
|
|
||||||
kickUser(roomId: string, userId: string, scalarToken: string): Promise<any> {
|
removeIntegration(roomId: string, userId: string, scalarToken: string): Promise<any> {
|
||||||
return this.http.post("/api/v1/dimension/kick", {roomId: roomId, userId: userId, scalarToken: scalarToken})
|
return this.http.post("/api/v1/dimension/removeIntegration", {roomId: roomId, userId: userId, scalarToken: scalarToken})
|
||||||
.map(res => res.json()).toPromise();
|
.map(res => res.json()).toPromise();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
export interface Bot {
|
export interface Integration {
|
||||||
mxid: string;
|
type: string;
|
||||||
|
userId: string;
|
||||||
name: string;
|
name: string;
|
||||||
avatar: string;
|
avatar: string;
|
||||||
about: string; // nullable
|
about: string; // nullable
|
BIN
web/public/img/avatars/demobot.png
Normal file
BIN
web/public/img/avatars/demobot.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.0 KiB |
Loading…
Reference in New Issue
Block a user