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
|
||||
db/*.db
|
||||
start.sh
|
||||
config/integrations/*_development.yaml
|
||||
config/integrations/*_production.yaml
|
||||
|
||||
# Logs
|
||||
logs
|
||||
|
8
app.js
8
app.js
@ -1,10 +1,18 @@
|
||||
var log = require("./src/util/LogService");
|
||||
var Dimension = require("./src/Dimension");
|
||||
var DimensionStore = require("./src/storage/DimensionStore");
|
||||
var DemoBot = require("./src/matrix/DemoBot");
|
||||
var config = require("config");
|
||||
|
||||
log.info("app", "Bootstrapping Dimension...");
|
||||
var db = new DimensionStore();
|
||||
db.prepare().then(() => {
|
||||
var app = new Dimension(db);
|
||||
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));
|
@ -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)
|
||||
web:
|
||||
# The port to run the webserver on
|
||||
port: 8184
|
||||
|
||||
# The address to bind to (0.0.0.0 for all interfaces)
|
||||
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
|
||||
logging:
|
||||
file: logs/dimension.log
|
||||
@ -52,3 +12,14 @@ logging:
|
||||
rotate:
|
||||
size: 52428800 # bytes, default is 50mb
|
||||
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",
|
||||
"express": "^4.15.2",
|
||||
"js-yaml": "^3.8.2",
|
||||
"lodash": "^4.17.4",
|
||||
"matrix-js-sdk": "^0.7.8",
|
||||
"moment": "^2.18.1",
|
||||
"random-string": "^0.2.0",
|
||||
"request": "^2.81.0",
|
||||
|
@ -8,6 +8,10 @@ var MatrixLiteClient = require("./matrix/MatrixLiteClient");
|
||||
var randomString = require("random-string");
|
||||
var ScalarClient = require("./scalar/ScalarClient.js");
|
||||
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
|
||||
@ -48,8 +52,8 @@ class Dimension {
|
||||
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/dimension/bots", this._getBots.bind(this));
|
||||
this._app.post("/api/v1/dimension/kick", this._kickUser.bind(this));
|
||||
this._app.get("/api/v1/dimension/integrations", this._getIntegrations.bind(this));
|
||||
this._app.post("/api/v1/dimension/removeIntegration", this._removeIntegration.bind(this));
|
||||
}
|
||||
|
||||
start() {
|
||||
@ -57,8 +61,7 @@ class Dimension {
|
||||
log.info("Dimension", "API and UI listening on " + config.get("web.address") + ":" + config.get("web.port"));
|
||||
}
|
||||
|
||||
_kickUser(req, res) {
|
||||
// {roomId: roomId, userId: userId, scalarToken: scalarToken}
|
||||
_removeIntegration(req, res) {
|
||||
var roomId = req.body.roomId;
|
||||
var userId = req.body.userId;
|
||||
var scalarToken = req.body.scalarToken;
|
||||
@ -68,27 +71,32 @@ class Dimension {
|
||||
return;
|
||||
}
|
||||
|
||||
var integrationName = null;
|
||||
var integrationConfig = integrations.byUserId[userId];
|
||||
if (!integrationConfig) {
|
||||
res.status(400).send({error: "Unknown integration"});
|
||||
return;
|
||||
}
|
||||
|
||||
this._db.checkToken(scalarToken).then(() => {
|
||||
for (var bot of config.bots) {
|
||||
if (bot.mxid == userId) {
|
||||
integrationName = bot.upstreamType;
|
||||
break;
|
||||
}
|
||||
if (integrationConfig.upstream) {
|
||||
return this._db.getUpstreamToken(scalarToken).then(upstreamToken => new UpstreamIntegration(integrationConfig, upstreamToken));
|
||||
} else return new HostedIntegration(integrationConfig);
|
||||
}).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);
|
||||
}).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) {
|
||||
_getIntegrations(req, res) {
|
||||
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) {
|
||||
|
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) {
|
||||
var url = config.scalar.upstreamRestUrl + endpoint;
|
||||
// TODO: Generify URL
|
||||
var url = config.get("upstreams.vector") + endpoint;
|
||||
|
||||
log.verbose("ScalarClient", "Performing request: " + url);
|
||||
|
||||
|
@ -33,7 +33,10 @@ class VectorScalarClient {
|
||||
* @return {Promise<>} resolves when complete
|
||||
*/
|
||||
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) {
|
||||
log.error("VectorScalarClient", response.body);
|
||||
return Promise.reject(response.body);
|
||||
@ -44,7 +47,7 @@ class VectorScalarClient {
|
||||
}
|
||||
|
||||
_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);
|
||||
|
||||
|
@ -9,11 +9,11 @@ import { removeNgStyles, createNewHosts } from "@angularclass/hmr";
|
||||
import { NgbModule } from "@ng-bootstrap/ng-bootstrap";
|
||||
import { RiotComponent } from "./riot/riot.component";
|
||||
import { ApiService } from "./shared/api.service";
|
||||
import { BotComponent } from "./bot/bot.component";
|
||||
import { UiSwitchModule } from "angular2-ui-switch";
|
||||
import { ScalarService } from "./shared/scalar.service";
|
||||
import { ToasterModule } from "angular2-toaster";
|
||||
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
|
||||
import { IntegrationComponent } from "./integration/integration.component";
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
@ -30,7 +30,7 @@ import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
|
||||
AppComponent,
|
||||
HomeComponent,
|
||||
RiotComponent,
|
||||
BotComponent,
|
||||
IntegrationComponent,
|
||||
|
||||
// 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">
|
||||
<img [src]="bot.avatar" class="avatar">
|
||||
<img [src]="integration.avatar" class="avatar">
|
||||
<div class="title">
|
||||
<b>{{ bot.name }}</b>
|
||||
<b>{{ integration.name }}</b>
|
||||
<div style="display: flex;">
|
||||
<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 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>
|
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>
|
||||
<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">
|
||||
<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>
|
@ -1,9 +1,9 @@
|
||||
import { Component } from "@angular/core";
|
||||
import { ActivatedRoute } from "@angular/router";
|
||||
import { ApiService } from "../shared/api.service";
|
||||
import { Bot } from "../shared/models/bot";
|
||||
import { ScalarService } from "../shared/scalar.service";
|
||||
import { ToasterService } from "angular2-toaster";
|
||||
import { Integration } from "../shared/models/integration";
|
||||
|
||||
@Component({
|
||||
selector: 'my-riot',
|
||||
@ -13,7 +13,7 @@ import { ToasterService } from "angular2-toaster";
|
||||
export class RiotComponent {
|
||||
|
||||
public error: string;
|
||||
public bots: Bot[] = [];
|
||||
public integrations: Integration[] = [];
|
||||
public loading = true;
|
||||
public roomId: string;
|
||||
|
||||
@ -40,47 +40,50 @@ export class RiotComponent {
|
||||
}
|
||||
|
||||
private init() {
|
||||
this.api.getBots().then(bots => {
|
||||
this.bots = bots;
|
||||
let promises = bots.map(b => this.updateBotState(b));
|
||||
this.api.getIntegrations().then(integrations => {
|
||||
this.integrations = integrations;
|
||||
let promises = integrations.map(b => this.updateIntegrationState(b));
|
||||
return Promise.all(promises);
|
||||
}).then(() => this.loading = false);
|
||||
}
|
||||
|
||||
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;
|
||||
}).then(() => this.loading = false).catch(err => {
|
||||
this.error = "Unable to communicate with Dimension";
|
||||
console.error(err);
|
||||
});
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
if (!bot.isEnabled) {
|
||||
promise = this.api.kickUser(this.roomId, bot.mxid, this.scalarToken);
|
||||
} else promise = this.scalar.inviteUser(this.roomId, bot.mxid);
|
||||
if (!integration.isEnabled) {
|
||||
promise = this.api.removeIntegration(this.roomId, integration.userId, this.scalarToken);
|
||||
} else promise = this.scalar.inviteUser(this.roomId, integration.userId);
|
||||
|
||||
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 => {
|
||||
let errorMessage = "Could not update bot status";
|
||||
let errorMessage = "Could not update integration status";
|
||||
|
||||
if (err.json) {
|
||||
errorMessage = err.json().error;
|
||||
} else errorMessage = err.response.error.message;
|
||||
|
||||
bot.isEnabled = !bot.isEnabled;
|
||||
integration.isEnabled = !integration.isEnabled;
|
||||
this.toaster.pop("error", errorMessage);
|
||||
});
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Injectable } from "@angular/core";
|
||||
import { Http } from "@angular/http";
|
||||
import { Bot } from "./models/bot";
|
||||
import { Integration } from "./models/integration";
|
||||
|
||||
@Injectable()
|
||||
export class ApiService {
|
||||
@ -12,13 +12,13 @@ export class ApiService {
|
||||
.map(res => res.status === 200).toPromise();
|
||||
}
|
||||
|
||||
getBots(): Promise<Bot[]> {
|
||||
return this.http.get("/api/v1/dimension/bots")
|
||||
getIntegrations(): Promise<Integration[]> {
|
||||
return this.http.get("/api/v1/dimension/integrations")
|
||||
.map(res => res.json()).toPromise();
|
||||
}
|
||||
|
||||
kickUser(roomId: string, userId: string, scalarToken: string): Promise<any> {
|
||||
return this.http.post("/api/v1/dimension/kick", {roomId: roomId, userId: userId, scalarToken: scalarToken})
|
||||
removeIntegration(roomId: string, userId: string, scalarToken: string): Promise<any> {
|
||||
return this.http.post("/api/v1/dimension/removeIntegration", {roomId: roomId, userId: userId, scalarToken: scalarToken})
|
||||
.map(res => res.json()).toPromise();
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
export interface Bot {
|
||||
mxid: string;
|
||||
export interface Integration {
|
||||
type: string;
|
||||
userId: string;
|
||||
name: string;
|
||||
avatar: string;
|
||||
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