mirror of
https://github.com/turt2live/matrix-dimension.git
synced 2024-10-01 05:05:53 +00:00
Move app from src-ts to src
This commit is contained in:
parent
70d1d23352
commit
c5b803343e
4
.gitignore
vendored
4
.gitignore
vendored
@ -10,8 +10,8 @@ config/integrations/*_development.yaml
|
||||
config/integrations/*_production.yaml
|
||||
build/
|
||||
dimension.db
|
||||
src-ts/**/*.js
|
||||
src-ts/**/*.js.map
|
||||
src/**/*.js
|
||||
src/**/*.js.map
|
||||
web/**/*.js
|
||||
web/**/*.js.map
|
||||
|
||||
|
@ -1,67 +0,0 @@
|
||||
var express = require("express");
|
||||
var config = require("config");
|
||||
var log = require("./util/LogService");
|
||||
var DimensionStore = require("./storage/DimensionStore");
|
||||
var bodyParser = require('body-parser');
|
||||
var path = require("path");
|
||||
var DimensionApi = require("./DimensionApi");
|
||||
var ScalarApi = require("./ScalarApi");
|
||||
var IRCApi = require("./integration/impl/irc/IRCApi");
|
||||
var URL = require("url");
|
||||
|
||||
// TODO: Convert backend to typescript? Would avoid stubbing classes all over the place
|
||||
|
||||
/**
|
||||
* Primary entry point for Dimension
|
||||
*/
|
||||
class Dimension {
|
||||
|
||||
/**
|
||||
* Creates a new Dimension
|
||||
*/
|
||||
constructor() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the Dimension service
|
||||
* @param {DimensionStore} db the store to use
|
||||
*/
|
||||
start(db) {
|
||||
this._db = db;
|
||||
this._app = express();
|
||||
this._app.use(express.static('web-dist'));
|
||||
this._app.use(bodyParser.json());
|
||||
|
||||
// Register routes for angular app
|
||||
this._app.get(['/riot', '/riot/*', '/widgets', '/widgets/*'], (req, res) => {
|
||||
res.sendFile(path.join(__dirname, "..", "web-dist", "index.html"));
|
||||
});
|
||||
|
||||
// Allow CORS
|
||||
this._app.use((req, res, next) => {
|
||||
res.setHeader("Access-Control-Allow-Origin", "*");
|
||||
res.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
|
||||
next();
|
||||
});
|
||||
|
||||
// Logging incoming requests
|
||||
this._app.use((req, res, next) => {
|
||||
var parsedUrl = URL.parse(req.url, true);
|
||||
if (parsedUrl.query && parsedUrl.query["scalar_token"]) {
|
||||
parsedUrl.query["scalar_token"] = "redacted";
|
||||
parsedUrl.search = undefined; // to trigger the URL.format to use `query`
|
||||
}
|
||||
log.verbose("Dimension", "Incoming: " + req.method + " " + URL.format(parsedUrl));
|
||||
next();
|
||||
});
|
||||
|
||||
DimensionApi.bootstrap(this._app, this._db);
|
||||
ScalarApi.bootstrap(this._app, this._db);
|
||||
IRCApi.bootstrap(this._app, this._db);
|
||||
|
||||
this._app.listen(config.get('web.port'), config.get('web.address'));
|
||||
log.info("Dimension", "API and UI listening on " + config.get("web.address") + ":" + config.get("web.port"));
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new Dimension();
|
@ -1,307 +0,0 @@
|
||||
const IntegrationImpl = require("./integration/impl/index");
|
||||
const Integrations = require("./integration/index");
|
||||
const _ = require("lodash");
|
||||
const log = require("./util/LogService");
|
||||
const request = require("request");
|
||||
const dns = require("dns-then");
|
||||
const urlParse = require("url");
|
||||
const Netmask = require("netmask").Netmask;
|
||||
const config = require("config");
|
||||
|
||||
/**
|
||||
* API handler for the Dimension API
|
||||
*/
|
||||
class DimensionApi {
|
||||
|
||||
/**
|
||||
* Creates a new Dimension API
|
||||
*/
|
||||
constructor() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstraps the Dimension API
|
||||
* @param {*} app the Express application
|
||||
* @param {DimensionStore} db the store to use
|
||||
*/
|
||||
bootstrap(app, db) {
|
||||
this._db = db;
|
||||
|
||||
app.get("/api/v1/dimension/integrations/:roomId", this._getIntegrations.bind(this));
|
||||
app.delete("/api/v1/dimension/integrations/:roomId/:type/:integrationType", this._removeIntegration.bind(this));
|
||||
app.put("/api/v1/dimension/integrations/:roomId/:type/:integrationType/state", this._updateIntegrationState.bind(this));
|
||||
app.get("/api/v1/dimension/integrations/:roomId/:type/:integrationType/state", this._getIntegrationState.bind(this));
|
||||
app.get("/api/v1/dimension/widgets/embeddable", this._checkEmbeddable.bind(this));
|
||||
app.get("/api/v1/dimension/integration/:type/:integrationType", this._getIntegration.bind(this));
|
||||
app.get("/api/v1/dimension/whoami", this._getTokenOwner.bind(this));
|
||||
}
|
||||
|
||||
_getTokenOwner(req, res) {
|
||||
res.setHeader("Content-Type", "application/json");
|
||||
|
||||
var scalarToken = req.query.scalar_token;
|
||||
if (!scalarToken) {
|
||||
res.status(400).send({error: 'Missing scalar token'});
|
||||
return;
|
||||
}
|
||||
|
||||
this._db.getTokenOwner(scalarToken).then(userId => {
|
||||
res.status(200).send({userId: userId});
|
||||
}).catch(err => {
|
||||
log.error("DimensionApi", err);
|
||||
console.error(err);
|
||||
res.status(401).send({error: 'Invalid token or other error'});
|
||||
});
|
||||
}
|
||||
|
||||
_checkEmbeddable(req, res) {
|
||||
// Unauthed endpoint.
|
||||
|
||||
var url = req.query.url;
|
||||
var parts = urlParse.parse(url);
|
||||
var processed = false;
|
||||
|
||||
// Only allow http and https
|
||||
if (parts.protocol !== "http:" && parts.protocol !== "https:") {
|
||||
res.status(400).send({error: "Invalid request scheme " + parts.protocol, canEmbed: false});
|
||||
processed = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Verify the address is permitted for widgets
|
||||
var hostname = parts.hostname.split(":")[0];
|
||||
dns.resolve4(hostname).then(addresses => {
|
||||
log.verbose("DimensionApi", "Hostname " + hostname + " resolves to " + addresses);
|
||||
if (addresses.length == 0) {
|
||||
res.status(400).send({error: "Unrecongized address", canEmbed: false});
|
||||
processed = true;
|
||||
return;
|
||||
}
|
||||
for (var ipOrCidr of config.get("widgetBlacklist")) {
|
||||
var block = new Netmask(ipOrCidr);
|
||||
for (var address of addresses) {
|
||||
if (block.contains(address)) {
|
||||
res.status(400).send({error: "Address not allowed", canEmbed: false});
|
||||
processed = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}, err => {
|
||||
log.verbose("DimensionApi", "Error resolving host " + hostname);
|
||||
log.verbose("DimensionApi", err);
|
||||
|
||||
res.status(400).send({error: "DNS error", canEmbed: false});
|
||||
processed = true;
|
||||
}).then(() => {
|
||||
if (processed) return;
|
||||
|
||||
// Verify that the content can actually be embedded (CORS)
|
||||
request(url, (err, response) => {
|
||||
if (err) {
|
||||
log.verbose("DimensionApi", "Error contacting host " + hostname);
|
||||
log.verbose("DimensionApi", err);
|
||||
|
||||
res.status(400).send({error: "Host error", canEmbed: false});
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.statusCode >= 200 && response.statusCode < 300) {
|
||||
// 200 OK
|
||||
var headers = response.headers;
|
||||
var xFrameOptions = (headers['x-frame-options'] || '').toLowerCase();
|
||||
|
||||
if (xFrameOptions === 'sameorigin' || xFrameOptions === 'deny') {
|
||||
res.status(400).send({error: "X-Frame-Options forbids embedding", canEmbed: false});
|
||||
} else res.status(200).send({canEmbed: true});
|
||||
} else {
|
||||
res.status(400).send({error: "Unsuccessful status code: " + response.statusCode, canEmbed: false});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
_findIntegration(integrationConfig, roomId, scalarToken) {
|
||||
var factory = IntegrationImpl.getFactory(integrationConfig);
|
||||
if (!factory) throw new Error("Missing config factory for " + integrationConfig.name);
|
||||
|
||||
try {
|
||||
return factory(this._db, integrationConfig, roomId, scalarToken);
|
||||
} catch (err) {
|
||||
throw new Error("Error using factory for " + integrationConfig.name + ". Please either fix the integration settings or disable the integration.", err);
|
||||
}
|
||||
}
|
||||
|
||||
_getIntegration(req, res) {
|
||||
res.setHeader("Content-Type", "application/json");
|
||||
// Unauthed endpoint.
|
||||
|
||||
var type = req.params.type;
|
||||
var integrationType = req.params.integrationType;
|
||||
|
||||
if (!type || !integrationType) {
|
||||
res.status(400).send({error: "Missing integration type or type"});
|
||||
return;
|
||||
}
|
||||
|
||||
var byIntegrationType = Integrations.byType[type];
|
||||
if (!byIntegrationType || !byIntegrationType[integrationType]) {
|
||||
res.status(400).send({error: "Unknown integration"});
|
||||
return;
|
||||
}
|
||||
var integrationConfig = byIntegrationType[integrationType];
|
||||
|
||||
res.status(200).send(integrationConfig);
|
||||
}
|
||||
|
||||
_getIntegrations(req, res) {
|
||||
res.setHeader("Content-Type", "application/json");
|
||||
|
||||
var roomId = req.params.roomId;
|
||||
if (!roomId) {
|
||||
res.status(400).send({error: 'Missing room ID'});
|
||||
return;
|
||||
}
|
||||
|
||||
var scalarToken = req.query.scalar_token;
|
||||
this._db.checkToken(scalarToken).then(() => {
|
||||
var integrations = _.map(Integrations.all, i => JSON.parse(JSON.stringify(i))); // clone
|
||||
|
||||
var promises = [];
|
||||
var remove = [];
|
||||
_.forEach(integrations, integration => {
|
||||
try {
|
||||
promises.push(this._findIntegration(integration, roomId, scalarToken).then(builtIntegration => {
|
||||
return builtIntegration.getState().then(state => {
|
||||
var keys = _.keys(state);
|
||||
for (var key of keys) {
|
||||
integration[key] = state[key];
|
||||
}
|
||||
|
||||
return builtIntegration.getUserId();
|
||||
}).then(userId => {
|
||||
integration.userId = userId;
|
||||
});
|
||||
}));
|
||||
} catch (err) {
|
||||
remove.push(integration);
|
||||
log.error("DimensionApi", err);
|
||||
}
|
||||
});
|
||||
|
||||
for (var toRemove of remove) {
|
||||
var idx = integrations.indexOf(toRemove);
|
||||
if (idx === -1) continue;
|
||||
log.warn("DimensionApi", "Disabling integration " + toRemove.name + " due to an error encountered in setup");
|
||||
integrations.splice(idx, 1);
|
||||
}
|
||||
|
||||
Promise.all(promises).then(() => res.send(_.map(integrations, integration => {
|
||||
// Remove sensitive material
|
||||
integration.upstream = undefined;
|
||||
integration.hosted = undefined;
|
||||
return integration;
|
||||
})));
|
||||
}).catch(err => {
|
||||
log.error("DimensionApi", err);
|
||||
console.error(err);
|
||||
res.status(500).send({error: err});
|
||||
});
|
||||
}
|
||||
|
||||
_removeIntegration(req, res) {
|
||||
var roomId = req.params.roomId;
|
||||
var scalarToken = req.query.scalar_token;
|
||||
var type = req.params.type;
|
||||
var integrationType = req.params.integrationType;
|
||||
|
||||
if (!roomId || !scalarToken || !type || !integrationType) {
|
||||
res.status(400).send({error: "Missing room, integration type, type, or token"});
|
||||
return;
|
||||
}
|
||||
|
||||
var integrationConfig = Integrations.byType[type][integrationType];
|
||||
if (!integrationConfig) {
|
||||
res.status(400).send({error: "Unknown integration"});
|
||||
return;
|
||||
}
|
||||
|
||||
log.info("DimensionApi", "Remove requested for " + type + " (" + integrationType + ") in room " + roomId);
|
||||
|
||||
this._db.checkToken(scalarToken).then(() => {
|
||||
return this._findIntegration(integrationConfig, roomId, scalarToken);
|
||||
}).then(integration => integration.removeFromRoom(roomId)).then(() => {
|
||||
res.status(200).send({success: true});
|
||||
}).catch(err => {
|
||||
log.error("DimensionApi", err);
|
||||
console.error(err);
|
||||
res.status(500).send({error: err.message});
|
||||
});
|
||||
}
|
||||
|
||||
_updateIntegrationState(req, res) {
|
||||
var roomId = req.params.roomId;
|
||||
var scalarToken = req.body.scalar_token;
|
||||
var type = req.params.type;
|
||||
var integrationType = req.params.integrationType;
|
||||
|
||||
if (!roomId || !scalarToken || !type || !integrationType) {
|
||||
res.status(400).send({error: "Missing room, integration type, type, or token"});
|
||||
return;
|
||||
}
|
||||
|
||||
var integrationConfig = Integrations.byType[type][integrationType];
|
||||
if (!integrationConfig) {
|
||||
res.status(400).send({error: "Unknown integration"});
|
||||
return;
|
||||
}
|
||||
|
||||
log.info("DimensionApi", "Update state requested for " + type + " (" + integrationType + ") in room " + roomId);
|
||||
|
||||
this._db.checkToken(scalarToken).then(() => {
|
||||
return this._findIntegration(integrationConfig, roomId, scalarToken);
|
||||
}).then(integration => {
|
||||
return integration.updateState(req.body.state);
|
||||
}).then(newState => {
|
||||
res.status(200).send(newState);
|
||||
}).catch(err => {
|
||||
log.error("DimensionApi", err);
|
||||
console.error(err);
|
||||
res.status(500).send({error: err.message});
|
||||
});
|
||||
}
|
||||
|
||||
_getIntegrationState(req, res) {
|
||||
var roomId = req.params.roomId;
|
||||
var scalarToken = req.query.scalar_token;
|
||||
var type = req.params.type;
|
||||
var integrationType = req.params.integrationType;
|
||||
|
||||
if (!roomId || !scalarToken || !type || !integrationType) {
|
||||
res.status(400).send({error: "Missing room, integration type, type, or token"});
|
||||
return;
|
||||
}
|
||||
|
||||
var integrationConfig = Integrations.byType[type][integrationType];
|
||||
if (!integrationConfig) {
|
||||
res.status(400).send({error: "Unknown integration"});
|
||||
return;
|
||||
}
|
||||
|
||||
log.info("DimensionApi", "State requested for " + type + " (" + integrationType + ") in room " + roomId);
|
||||
|
||||
this._db.checkToken(scalarToken).then(() => {
|
||||
return this._findIntegration(integrationConfig, roomId, scalarToken);
|
||||
}).then(integration => {
|
||||
return integration.getState();
|
||||
}).then(state => {
|
||||
res.status(200).send(state);
|
||||
}).catch(err => {
|
||||
log.error("DimensionApi", err);
|
||||
console.error(err);
|
||||
res.status(500).send({error: err.message});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new DimensionApi();
|
@ -1,11 +0,0 @@
|
||||
/**
|
||||
* Serves the purpose of being a documentation endpoint
|
||||
*/
|
||||
class OpenID {
|
||||
access_token = "";
|
||||
token_type = "";
|
||||
matrix_server_name = "";
|
||||
expires_in = 0;
|
||||
}
|
||||
|
||||
module.exports = OpenID;
|
115
src/ScalarApi.js
115
src/ScalarApi.js
@ -1,115 +0,0 @@
|
||||
var MatrixLiteClient = require("./matrix/MatrixLiteClient");
|
||||
var randomString = require("random-string");
|
||||
var ScalarClient = require("./scalar/ScalarClient.js");
|
||||
var _ = require("lodash");
|
||||
var log = require("./util/LogService");
|
||||
var Promise = require("bluebird");
|
||||
var UpstreamConfiguration = require("./UpstreamConfiguration");
|
||||
|
||||
/**
|
||||
* API handler for the Scalar API, as required by Riot
|
||||
*/
|
||||
class ScalarApi {
|
||||
|
||||
/**
|
||||
* Creates a new Scalar API
|
||||
*/
|
||||
constructor() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstraps the Scalar API
|
||||
* @param {*} app the Express application
|
||||
* @param {DimensionStore} db the store to use
|
||||
*/
|
||||
bootstrap(app, db) {
|
||||
this._db = db;
|
||||
|
||||
app.post("/api/v1/scalar/register", this._scalarRegister.bind(this));
|
||||
app.get("/api/v1/scalar/checkToken", this._checkScalarToken.bind(this));
|
||||
app.get("/api/v1/scalar/widgets/title_lookup", this._getWidgetTitle.bind(this));
|
||||
}
|
||||
|
||||
_getWidgetTitle(req, res) {
|
||||
res.setHeader("Content-Type", "application/json");
|
||||
|
||||
var token = req.query.scalar_token;
|
||||
var url = req.query.curl;
|
||||
|
||||
if (!token || !url) {
|
||||
res.status(400).send({error: "Missing token or curl"});
|
||||
return;
|
||||
}
|
||||
|
||||
this._db.checkToken(token).then(() => {
|
||||
MatrixLiteClient.getUrlPreview(url).then(preview => {
|
||||
if (!preview["og:title"]) {
|
||||
res.status(404).send({error:{message:"Could not locate a title for the URL"}});
|
||||
return;
|
||||
}
|
||||
|
||||
// We need to convert the preview response to what Scalar expects
|
||||
res.status(200).send({
|
||||
cached_response: false,
|
||||
page_title_cache_item: {
|
||||
expires: null, // unused
|
||||
cached_response_err: null, // unused
|
||||
cached_title: preview["og:title"],
|
||||
}
|
||||
});
|
||||
}).catch(err => {
|
||||
res.status(500).send({error: {message: "Failed to get preview"}});
|
||||
log.error("ScalarApi", "Failed to get URL preview");
|
||||
log.error("ScalarApi", err);
|
||||
});
|
||||
}).catch(err => {
|
||||
res.status(401).send({error: {message: "Failed to authenticate token"}});
|
||||
log.warn("ScalarApi", "Failed to authenticate token");
|
||||
log.warn("ScalarApi", err);
|
||||
});
|
||||
}
|
||||
|
||||
_checkScalarToken(req, res) {
|
||||
var token = req.query.scalar_token;
|
||||
if (!token) res.sendStatus(400);
|
||||
else this._db.checkToken(token).then(() => {
|
||||
res.sendStatus(200);
|
||||
}).catch(e => {
|
||||
res.sendStatus(401);
|
||||
log.warn("ScalarApi", "Failed to authenticate token");
|
||||
log.verbose("ScalarApi", e);
|
||||
});
|
||||
}
|
||||
|
||||
_scalarRegister(req, res) {
|
||||
res.setHeader("Content-Type", "application/json");
|
||||
|
||||
var tokenInfo = req.body;
|
||||
if (!tokenInfo || !tokenInfo['access_token'] || !tokenInfo['token_type'] || !tokenInfo['matrix_server_name'] || !tokenInfo['expires_in']) {
|
||||
res.status(400).send({error: 'Missing OpenID'});
|
||||
return;
|
||||
}
|
||||
|
||||
var client = new MatrixLiteClient(tokenInfo);
|
||||
var scalarToken = randomString({length: 25});
|
||||
var userId;
|
||||
client.getSelfMxid().then(mxid => {
|
||||
userId = mxid;
|
||||
if (!mxid) throw new Error("Token does not resolve to a matrix user");
|
||||
|
||||
// TODO: Make this part more generic for other upstreams (#22)
|
||||
if (!UpstreamConfiguration.hasUpstream("vector")) return Promise.resolve(null);
|
||||
return ScalarClient.register(tokenInfo);
|
||||
}).then(upstreamToken => {
|
||||
return this._db.createToken(userId, tokenInfo, scalarToken, upstreamToken);
|
||||
}).then(() => {
|
||||
res.setHeader("Content-Type", "application/json");
|
||||
res.send({scalar_token: scalarToken});
|
||||
}).catch(err => {
|
||||
log.error("ScalarApi", err);
|
||||
res.status(500).send({error: err.message});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new ScalarApi();
|
@ -1,50 +0,0 @@
|
||||
var LogService = require("./util/LogService");
|
||||
var _ = require("lodash");
|
||||
var config = require("config");
|
||||
|
||||
/**
|
||||
* Handles all upstream configuration information, such as URLs, tokens, and whether or not they are enabled.
|
||||
*/
|
||||
class UpstreamConfiguration {
|
||||
/**
|
||||
* Creates a new upstream configuration handler
|
||||
*/
|
||||
constructor() {
|
||||
this._upstreams = {};
|
||||
this._loadUpstreams();
|
||||
}
|
||||
|
||||
_loadUpstreams() {
|
||||
for (var upstream of config.upstreams) {
|
||||
var upstreamConfig = upstream;
|
||||
|
||||
if (this._upstreams[upstream.name]) {
|
||||
LogService.warn("UpstreamConfiguration", "Duplicate upstream " + upstream.name +" - skipping");
|
||||
continue;
|
||||
}
|
||||
|
||||
this._upstreams[upstream.name] = upstreamConfig;
|
||||
LogService.info("UpstreamConfiguration", "Loaded upstream '" + upstream.name + "' as: " + JSON.stringify(upstreamConfig));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a particular upstream exists
|
||||
* @param {string} name the name of the upstream
|
||||
* @returns {boolean} true if it is enabled and exists
|
||||
*/
|
||||
hasUpstream(name) {
|
||||
return !!this._upstreams[name];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an upstream's configuration
|
||||
* @param {string} name the upstream name
|
||||
* @returns {{url:string}} the upstream configuration
|
||||
*/
|
||||
getUpstream(name) {
|
||||
return _.clone(this._upstreams[name]);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new UpstreamConfiguration();
|
@ -1,22 +0,0 @@
|
||||
var IntegrationStub = require("./IntegrationStub");
|
||||
|
||||
/**
|
||||
* Represents a bridge. Normally bridges have enhanced configuration and requirements over bots.
|
||||
*/
|
||||
class Bridge extends IntegrationStub {
|
||||
|
||||
/**
|
||||
* Creates a new bridge
|
||||
* @param bridgeConfig the configuration for the bridge
|
||||
*/
|
||||
constructor(bridgeConfig) {
|
||||
super(bridgeConfig);
|
||||
}
|
||||
|
||||
/*override*/
|
||||
getUserId() {
|
||||
return null; // bridges don't have bot users we care about
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Bridge;
|
@ -1,18 +0,0 @@
|
||||
var IntegrationStub = require("./IntegrationStub");
|
||||
|
||||
/**
|
||||
* Represents a bot with additional configuration or setup needs. Normally indicates a bot needs
|
||||
* more than a simple invite to the room.
|
||||
*/
|
||||
class ComplexBot extends IntegrationStub {
|
||||
|
||||
/**
|
||||
* Creates a new complex bot
|
||||
* @param botConfig the configuration for the bot
|
||||
*/
|
||||
constructor(botConfig) {
|
||||
super(botConfig);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ComplexBot;
|
@ -1,44 +0,0 @@
|
||||
/**
|
||||
* Stub for an Integration
|
||||
*/
|
||||
class IntegrationStub {
|
||||
constructor(botConfig) {
|
||||
this._config = botConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the user ID for this bot
|
||||
* @return {Promise<string>} resolves to the user ID
|
||||
*/
|
||||
getUserId() {
|
||||
return Promise.resolve(this._config.userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets state information that represents how this bot is operating.
|
||||
* @return {Promise<*>} resolves to the state information
|
||||
*/
|
||||
getState() {
|
||||
return Promise.resolve({});
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the integration from the given room
|
||||
* @param {string} roomId the room ID to remove the integration from
|
||||
* @returns {Promise<>} resolves when completed
|
||||
*/
|
||||
removeFromRoom(roomId) {
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the state information for this integration. The data passed is an implementation detail.
|
||||
* @param {*} newState the new state
|
||||
* @returns {Promise<*>} resolves when completed, with the new state of the integration
|
||||
*/
|
||||
updateState(newState) {
|
||||
return Promise.resolve({});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = IntegrationStub;
|
@ -1,22 +0,0 @@
|
||||
var IntegrationStub = require("./IntegrationStub");
|
||||
|
||||
/**
|
||||
* Represents a widget. Widgets allow for web applications or rich functionality within the room.
|
||||
*/
|
||||
class Widget extends IntegrationStub {
|
||||
|
||||
/**
|
||||
* Creates a new widget
|
||||
* @param widgetConfig the configuration for the widget
|
||||
*/
|
||||
constructor(widgetConfig) {
|
||||
super(widgetConfig);
|
||||
}
|
||||
|
||||
/*override*/
|
||||
getUserId() {
|
||||
return null; // widgets don't have bot users we care about
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Widget;
|
@ -1,20 +0,0 @@
|
||||
var IntegrationStub = require("../generic_types/IntegrationStub");
|
||||
|
||||
/**
|
||||
* Creates an integration using the given
|
||||
* @param {DimensionStore} db the database
|
||||
* @param {*} integrationConfig the integration configuration
|
||||
* @param {string} roomId the room ID
|
||||
* @param {string} scalarToken the scalar token
|
||||
* @returns {Promise<*>} resolves to the configured integration
|
||||
*/
|
||||
var factory = (db, integrationConfig, roomId, scalarToken) => {
|
||||
factory.validateConfig(integrationConfig);
|
||||
return Promise.resolve(new IntegrationStub(integrationConfig));
|
||||
};
|
||||
|
||||
factory.validateConfig = (integrationConfig) => {
|
||||
// Nothing to do
|
||||
};
|
||||
|
||||
module.exports = factory;
|
@ -1,53 +0,0 @@
|
||||
var ComplexBot = require("../../generic_types/ComplexBot");
|
||||
|
||||
/**
|
||||
* Represents a CircleCI bot
|
||||
*/
|
||||
class CircleCiBot extends ComplexBot {
|
||||
|
||||
/**
|
||||
* Creates a new CircleCI bot
|
||||
* @param botConfig the bot configuration
|
||||
* @param backbone the backbone powering this bot
|
||||
*/
|
||||
constructor(botConfig, backbone) {
|
||||
super(botConfig);
|
||||
this._backbone = backbone;
|
||||
}
|
||||
|
||||
/*override*/
|
||||
getUserId() {
|
||||
return this._backbone.getUserId();
|
||||
}
|
||||
|
||||
/*override*/
|
||||
getState() {
|
||||
var response = {
|
||||
repoTemplates: [],
|
||||
immutableRepoTemplates: [],
|
||||
webhookUrl: ""
|
||||
};
|
||||
return this._backbone.getRepos().then(templates => {
|
||||
response.repoTemplates = templates;
|
||||
return this._backbone.getImmutableRepos();
|
||||
}).then(immutable => {
|
||||
response.immutableRepoTemplates = immutable;
|
||||
return this._backbone.getWebhookUrl();
|
||||
}).then(url => {
|
||||
response.webhookUrl = url;
|
||||
return response;
|
||||
});
|
||||
}
|
||||
|
||||
/*override*/
|
||||
removeFromRoom(roomId) {
|
||||
return this._backbone.removeFromRoom(roomId);
|
||||
}
|
||||
|
||||
/*override*/
|
||||
updateState(newState) {
|
||||
return this._backbone.setRepos(newState.repoTemplates).then(() => this.getState());
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = CircleCiBot;
|
@ -1,20 +0,0 @@
|
||||
var CircleCiBot = require("./CircleCiBot");
|
||||
var VectorCircleCiBackbone = require("./VectorCircleCiBackbone");
|
||||
var UpstreamConfiguration = require("../../../UpstreamConfiguration");
|
||||
|
||||
var factory = (db, integrationConfig, roomId, scalarToken) => {
|
||||
factory.validateConfig(integrationConfig);
|
||||
|
||||
return db.getUpstreamToken(scalarToken).then(upstreamToken => {
|
||||
var backbone = new VectorCircleCiBackbone(roomId, upstreamToken);
|
||||
return new CircleCiBot(integrationConfig, backbone);
|
||||
});
|
||||
};
|
||||
|
||||
factory.validateConfig = (integrationConfig) => {
|
||||
if (!integrationConfig.upstream) throw new Error("Unsupported configuration");
|
||||
if (integrationConfig.upstream.type !== "vector") throw new Error("Unsupported upstream");
|
||||
if (!UpstreamConfiguration.hasUpstream("vector")) throw new Error("Vector upstream not specified");
|
||||
};
|
||||
|
||||
module.exports = factory;
|
@ -1,63 +0,0 @@
|
||||
/**
|
||||
* Stubbed/placeholder CircleCI backbone
|
||||
*/
|
||||
class StubbedCircleCiBackbone {
|
||||
|
||||
/**
|
||||
* Creates a new stubbed CircleCI backbone
|
||||
*/
|
||||
constructor() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the user ID for this backbone
|
||||
* @returns {Promise<string>} resolves to the user ID
|
||||
*/
|
||||
getUserId() {
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the repository templates for this backbone
|
||||
* @returns {Promise<{repoKey:string,template:string}[]>} resolves to the collection of repositories and their templates
|
||||
*/
|
||||
getRepos() {
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the immutable repository templates for this backbone (set by other users)
|
||||
* @returns {Promise<{repoKey:string,template:string,ownerId:string}[]>} resolves to the collection of repositories and their templates
|
||||
*/
|
||||
getImmutableRepos() {
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the new repository templates for this backbone
|
||||
* @param {{repoKey:string,template:string}[]} newRepos the new templates for the repositories
|
||||
* @returns {Promise<>} resolves when complete
|
||||
*/
|
||||
setRepos(newRepos) {
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the webhook url for this backbone
|
||||
* @returns {Promise<string>} resolves to the webhook URL
|
||||
*/
|
||||
getWebhookUrl() {
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the bot from the given room
|
||||
* @param {string} roomId the room ID to remove the bot from
|
||||
* @returns {Promise<>} resolves when completed
|
||||
*/
|
||||
removeFromRoom(roomId) {
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = StubbedCircleCiBackbone;
|
@ -1,108 +0,0 @@
|
||||
var StubbedCircleCiBackbone = require("./StubbedCircleCiBackbone");
|
||||
var VectorScalarClient = require("../../../scalar/VectorScalarClient");
|
||||
var _ = require("lodash");
|
||||
var log = require("../../../util/LogService");
|
||||
|
||||
/**
|
||||
* Backbone for CircleCI bots running on vector.im through scalar
|
||||
*/
|
||||
class VectorCircleCiBackbone extends StubbedCircleCiBackbone {
|
||||
|
||||
/**
|
||||
* Creates a new Vector CircleCI backbone
|
||||
* @param {string} roomId the room ID to manage
|
||||
* @param {string} upstreamScalarToken the vector scalar token
|
||||
*/
|
||||
constructor(roomId, upstreamScalarToken) {
|
||||
super();
|
||||
this._roomId = roomId;
|
||||
this._scalarToken = upstreamScalarToken;
|
||||
this._info = null;
|
||||
this._otherTemplates = [];
|
||||
}
|
||||
|
||||
/*override*/
|
||||
getUserId() {
|
||||
return (this._info ? Promise.resolve() : this._getInfo()).then(() => {
|
||||
return this._info.bot_user_id;
|
||||
});
|
||||
}
|
||||
|
||||
/*override*/
|
||||
getRepos() {
|
||||
return (this._info ? Promise.resolve() : this._getInfo()).then(() => {
|
||||
if (this._info.integrations.length == 0) return [];
|
||||
|
||||
var rooms = _.keys(this._info.integrations[0].config.rooms);
|
||||
if (rooms.indexOf(this._roomId) === -1) return [];
|
||||
|
||||
var repos = _.keys(this._info.integrations[0].config.rooms[this._roomId].repos);
|
||||
return _.map(repos, r => {
|
||||
return {repoKey: r, template: this._info.integrations[0].config.rooms[this._roomId].repos[r].template};
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/*override*/
|
||||
getImmutableRepos() {
|
||||
return (this._info ? Promise.resolve() : this._getInfo()).then(() => {
|
||||
return this._otherTemplates;
|
||||
});
|
||||
}
|
||||
|
||||
/*override*/
|
||||
setRepos(newRepos) {
|
||||
var config = {};
|
||||
config[this._roomId] = {repos: {}};
|
||||
for (var repo of newRepos) config[this._roomId].repos[repo.repoKey] = {template: repo.template};
|
||||
|
||||
return VectorScalarClient.configureIntegration("circleci", this._scalarToken, {
|
||||
rooms: config
|
||||
});
|
||||
}
|
||||
|
||||
/*override*/
|
||||
getWebhookUrl() {
|
||||
// string
|
||||
return (this._info ? Promise.resolve() : this._getInfo()).then(() => {
|
||||
if (this._info.integrations.length == 0) return "";
|
||||
return this._info.integrations[0].config.webhook_url;
|
||||
});
|
||||
}
|
||||
|
||||
_getInfo() {
|
||||
return VectorScalarClient.getIntegrationsForRoom(this._roomId, this._scalarToken).then(integrations => {
|
||||
this._otherTemplates = [];
|
||||
for (var integration of integrations) {
|
||||
if (integration.self) continue; // skip - we're not looking for ones we know about
|
||||
if (integration.type == "circleci") {
|
||||
var roomIds = _.keys(integration.config.rooms);
|
||||
if (roomIds.length === 0) continue;
|
||||
if (roomIds.length !== 1) log.warn("VectorCircleCiBackbone", "Expected 1 room but found " + roomIds.length);
|
||||
|
||||
var roomConfig = integration.config.rooms[roomIds[0]];
|
||||
var repositories = _.keys(roomConfig.repos);
|
||||
|
||||
for (var repo of repositories) {
|
||||
this._otherTemplates.push({
|
||||
repoKey: repo,
|
||||
template: roomConfig.repos[repo].template,
|
||||
ownerId: integration.user_id
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return VectorScalarClient.getIntegration("circleci", this._roomId, this._scalarToken);
|
||||
}).then(info => {
|
||||
this._info = info;
|
||||
});
|
||||
}
|
||||
|
||||
/*override*/
|
||||
removeFromRoom(roomId) {
|
||||
return VectorScalarClient.removeIntegration("circleci", roomId, this._scalarToken);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = VectorCircleCiBackbone;
|
@ -1,46 +0,0 @@
|
||||
var log = require("../../util/LogService");
|
||||
var StubbedFactory = require("./StubbedFactory");
|
||||
var SimpleBotFactory = require("./simple_bot/SimpleBotFactory");
|
||||
var RSSFactory = require("./rss/RSSFactory");
|
||||
var IRCFactory = require("./irc/IRCFactory");
|
||||
var TravisCiFactory = require("./travisci/TravisCiFactory");
|
||||
var CircleCiFactory = require("./circleci/CircleCiFactory");
|
||||
var SimpleWidgetFactory = require("./simple_widget/SimpleWidgetFactory");
|
||||
|
||||
var mapping = {
|
||||
"complex-bot": {
|
||||
"rss": RSSFactory,
|
||||
"travisci": TravisCiFactory,
|
||||
"circleci": CircleCiFactory,
|
||||
},
|
||||
"bridge": {
|
||||
"irc": IRCFactory,
|
||||
}
|
||||
};
|
||||
|
||||
var defaultFactories = {
|
||||
"complex-bot": null,
|
||||
"bot": SimpleBotFactory,
|
||||
"bridge": null,
|
||||
"widget": SimpleWidgetFactory,
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
getFactory: (integrationConfig) => {
|
||||
var opts = mapping[integrationConfig.type];
|
||||
|
||||
if (!opts) {
|
||||
log.verbose("IntegrationImpl", "No option set available for " + integrationConfig.type + " - will attempt defaults");
|
||||
}
|
||||
|
||||
var factory = null;
|
||||
if (!opts) factory = defaultFactories[integrationConfig.type];
|
||||
else factory = opts[integrationConfig.integrationType];
|
||||
if (!factory) {
|
||||
log.verbose("IntegrationImpl", "No factory available for " + integrationConfig.type + " (" + integrationConfig.integrationType + ") - using stub");
|
||||
factory = StubbedFactory;
|
||||
}
|
||||
|
||||
return factory;
|
||||
}
|
||||
};
|
@ -1,114 +0,0 @@
|
||||
var Integrations = require("../../index");
|
||||
var IntegrationImpl = require("../index");
|
||||
var log = require("../../../util/LogService");
|
||||
|
||||
/**
|
||||
* API Handler for the IRC integration
|
||||
*/
|
||||
class IRCApi {
|
||||
|
||||
/**
|
||||
* Creates a new IRC API
|
||||
*/
|
||||
constructor() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstraps the IRC API
|
||||
* @param {*} app the Express application
|
||||
* @param {DimensionStore} db the store to use
|
||||
*/
|
||||
bootstrap(app, db) {
|
||||
if (!Integrations.byType["bridge"] || !Integrations.byType["bridge"]["irc"]) {
|
||||
log.info("IRCApi", "IRC Bridge not enabled - not setting up the API");
|
||||
return;
|
||||
} else log.info("IRCApi", "Setting up IRC API");
|
||||
|
||||
this._db = db;
|
||||
|
||||
app.get("/api/v1/irc/:roomId/ops/:network/:channel", this._getChannelOps.bind(this));
|
||||
app.put("/api/v1/irc/:roomId/channels/:network/:channel", this._addChannel.bind(this));
|
||||
app.delete("/api/v1/irc/:roomId/channels/:network/:channel", this._deleteChannel.bind(this));
|
||||
}
|
||||
|
||||
_getChannelOps(req, res) {
|
||||
this._generalProcessing(req, res).then(ircBridge => {
|
||||
var network = req.params.network;
|
||||
var channel = req.params.channel;
|
||||
return ircBridge.getChannelOps(network, channel).catch(err => {
|
||||
log.error("IRCApi", err);
|
||||
console.error(err);
|
||||
res.status(500).send({error: err});
|
||||
return null;
|
||||
});
|
||||
}).then(ops => {
|
||||
if (ops !== null) res.status(200).send(ops);
|
||||
}).catch(() => null);
|
||||
}
|
||||
|
||||
_addChannel(req, res) {
|
||||
this._generalProcessing(req, res).then(ircBridge => {
|
||||
var network = req.params.network;
|
||||
var channel = req.params.channel;
|
||||
var op = req.query.op;
|
||||
return ircBridge.addChannel(network, channel, op).catch(err => {
|
||||
log.error("IRCApi", err);
|
||||
console.error(err);
|
||||
res.status(500).send({error: err});
|
||||
return null;
|
||||
});
|
||||
}).then(result => {
|
||||
if (result !== null) res.status(200).send({successful: true});
|
||||
}).catch(() => null);
|
||||
}
|
||||
|
||||
_deleteChannel(req, res) {
|
||||
this._generalProcessing(req, res).then(ircBridge => {
|
||||
var network = req.params.network;
|
||||
var channel = req.params.channel;
|
||||
return ircBridge.removeChannel(network, channel).catch(err => {
|
||||
log.error("IRCApi", err);
|
||||
console.error(err);
|
||||
res.status(500).send({error: err});
|
||||
return null;
|
||||
});
|
||||
}).then(result => {
|
||||
if (result !== null) res.status(200).send({successful: true});
|
||||
}).catch(() => null);
|
||||
}
|
||||
|
||||
_generalProcessing(req, res) {
|
||||
return new Promise((resolve, reject) => {
|
||||
res.setHeader("Content-Type", "application/json");
|
||||
|
||||
var roomId = req.params.roomId;
|
||||
var network = req.params.network;
|
||||
var channel = req.params.channel;
|
||||
if (!roomId || !network || !channel) {
|
||||
res.status(400).send({error: 'Missing room ID, network, or channel'});
|
||||
reject();
|
||||
return;
|
||||
}
|
||||
|
||||
var scalarToken = req.query.scalar_token;
|
||||
this._db.checkToken(scalarToken).then(() => {
|
||||
var conf = Integrations.byType["bridge"]["irc"];
|
||||
var factory = IntegrationImpl.getFactory(conf);
|
||||
factory(this._db, conf, roomId, scalarToken).then(resolve).catch(err => {
|
||||
log.error("IRCApi", err);
|
||||
console.error(err);
|
||||
res.status(500).send({error: err});
|
||||
reject();
|
||||
});
|
||||
}).catch(err => {
|
||||
log.error("IRCApi", err);
|
||||
console.error(err);
|
||||
res.status(500).send({error: err});
|
||||
reject();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = new IRCApi();
|
@ -1,75 +0,0 @@
|
||||
var Bridge = require("../../generic_types/Bridge");
|
||||
|
||||
/**
|
||||
* Represents an IRC bridge
|
||||
*/
|
||||
class IRCBridge extends Bridge {
|
||||
|
||||
/**
|
||||
* Creates a new IRC bridge
|
||||
* @param bridgeConfig the bridge configuration
|
||||
* @param backbone the backbone powering this bridge
|
||||
*/
|
||||
constructor(bridgeConfig, backbone) {
|
||||
super(bridgeConfig);
|
||||
this._backbone = backbone;
|
||||
}
|
||||
|
||||
/*override*/
|
||||
getState() {
|
||||
var response = {
|
||||
availableNetworks: [],
|
||||
channels: {}
|
||||
};
|
||||
return this._backbone.getNetworks().then(networks => {
|
||||
response.availableNetworks = networks;
|
||||
return this._backbone.getLinkedChannels();
|
||||
}).then(channels => {
|
||||
response.channels = channels;
|
||||
return response;
|
||||
});
|
||||
}
|
||||
|
||||
/*override*/
|
||||
removeFromRoom(roomId) {
|
||||
return this._backbone.removeFromRoom(roomId);
|
||||
}
|
||||
|
||||
/*override*/
|
||||
updateState(newState) {
|
||||
throw new Error("State cannot be updated for an IRC bridge. Use the IRC API instead.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of operators available in a particular channel on a particular network
|
||||
* @param {string} network the network to look at
|
||||
* @param {string} channel the channel to look in (without prefixed #)
|
||||
* @returns {Promise<string[]>} resolves to a list of operators
|
||||
*/
|
||||
getChannelOps(network, channel) {
|
||||
return this._backbone.getChannelOps(network, channel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Links a channel to the room this bridge controls
|
||||
* @param {string} network the network to link to
|
||||
* @param {string} channel the channel to link to
|
||||
* @param {string} op the channel operator to request permission from
|
||||
* @returns {Promise<>} resolves when complete
|
||||
*/
|
||||
addChannel(network, channel, op) {
|
||||
return this._backbone.addChannel(network, channel, op);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlinks a channel from the room this bridge controls
|
||||
* @param {string} network the network to unlink from
|
||||
* @param {string} channel the channel to unlink
|
||||
* @returns {Promise<>} resolves when complete
|
||||
*/
|
||||
removeChannel(network, channel) {
|
||||
return this._backbone.removeChannel(network, channel);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = IRCBridge;
|
@ -1,20 +0,0 @@
|
||||
var IRCBridge = require("./IRCBridge");
|
||||
var VectorIrcBackbone = require("./VectorIrcBackbone");
|
||||
var UpstreamConfiguration = require("../../../UpstreamConfiguration");
|
||||
|
||||
var factory = (db, integrationConfig, roomId, scalarToken) => {
|
||||
factory.validateConfig(integrationConfig);
|
||||
|
||||
return db.getUpstreamToken(scalarToken).then(upstreamToken => {
|
||||
var backbone = new VectorIrcBackbone(roomId, upstreamToken);
|
||||
return new IRCBridge(integrationConfig, backbone);
|
||||
});
|
||||
};
|
||||
|
||||
factory.validateConfig = (integrationConfig) => {
|
||||
if (!integrationConfig.upstream) throw new Error("Unsupported configuration");
|
||||
if (integrationConfig.upstream.type !== "vector") throw new Error("Unsupported upstream");
|
||||
if (!UpstreamConfiguration.hasUpstream("vector")) throw new Error("Vector upstream not specified");
|
||||
};
|
||||
|
||||
module.exports = factory;
|
@ -1,60 +0,0 @@
|
||||
/**
|
||||
* Stubbed/placeholder IRC backbone
|
||||
*/
|
||||
class StubbedIrcBackbone {
|
||||
|
||||
/**
|
||||
* Creates a new stubbed IRC backbone
|
||||
*/
|
||||
constructor() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of all available networks
|
||||
* @returns {Promise<{name: string, id: string}[]>} resolves to the list of available networks
|
||||
*/
|
||||
getNetworks() {
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a network representation of the linked channels
|
||||
* @returns {Promise<{[string]: string[]}>} resolves to the network representation of linked channels
|
||||
*/
|
||||
getLinkedChannels() {
|
||||
return Promise.resolve({});
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of operators available in a particular channel on a particular network
|
||||
* @param {string} network the network to look at
|
||||
* @param {string} channel the channel to look in (without prefixed #)
|
||||
* @returns {Promise<string[]>} resolves to a list of operators
|
||||
*/
|
||||
getChannelOps(network, channel) {
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Links a channel to the room this backbone controls
|
||||
* @param {string} network the network to link to
|
||||
* @param {string} channel the channel to link to
|
||||
* @param {string} op the channel operator to request permission from
|
||||
* @returns {Promise<>} resolves when complete
|
||||
*/
|
||||
addChannel(network, channel, op) {
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlinks a channel from the room this backbone controls
|
||||
* @param {string} network the network to unlink from
|
||||
* @param {string} channel the channel to unlink
|
||||
* @returns {Promise<>} resolves when complete
|
||||
*/
|
||||
removeChannel(network, channel) {
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = StubbedIrcBackbone;
|
@ -1,120 +0,0 @@
|
||||
var StubbedIrcBackbone = require("./StubbedIrcBackbone");
|
||||
var VectorScalarClient = require("../../../scalar/VectorScalarClient");
|
||||
var _ = require("lodash");
|
||||
var log = require("../../../util/LogService");
|
||||
|
||||
/**
|
||||
* Backbone for IRC bridges running on vector.im through scalar
|
||||
*/
|
||||
class VectorIrcBackbone extends StubbedIrcBackbone {
|
||||
|
||||
/**
|
||||
* Creates a new Vector IRC backbone
|
||||
* @param {string} roomId the room ID to manage
|
||||
* @param {string} upstreamScalarToken the vector scalar token
|
||||
*/
|
||||
constructor(roomId, upstreamScalarToken) {
|
||||
super();
|
||||
this._roomId = roomId;
|
||||
this._scalarToken = upstreamScalarToken;
|
||||
this._lastNetworkResponse = null;
|
||||
}
|
||||
|
||||
/*override*/
|
||||
getNetworks() {
|
||||
return this._getNetworks().then(networks => _.map(networks, n => {
|
||||
return {name: n.title, id: n.id};
|
||||
}));
|
||||
}
|
||||
|
||||
/*override*/
|
||||
getLinkedChannels() {
|
||||
var networks;
|
||||
return this._getNetworks().then(n => {
|
||||
networks = n;
|
||||
return VectorScalarClient.getIrcLinks(this._roomId, this._scalarToken);
|
||||
}).then(links => {
|
||||
var container = {};
|
||||
|
||||
var ridToServerId = {};
|
||||
|
||||
for (var network of networks) {
|
||||
ridToServerId[network.rid] = network.id;
|
||||
container[network.id] = [];
|
||||
}
|
||||
|
||||
for (var link of links) {
|
||||
var server = ridToServerId[link.rid];
|
||||
if (!server) {
|
||||
log.error("VectorIrcBackbone", "Could not find network for RID " + link.rid);
|
||||
throw new Error("Unexpected RID");
|
||||
}
|
||||
|
||||
container[server].push(link.channel);
|
||||
}
|
||||
|
||||
return container;
|
||||
});
|
||||
}
|
||||
|
||||
/*override*/
|
||||
getChannelOps(network, channel) {
|
||||
return this._getNetworks().then(networks => {
|
||||
var networkServer = null;
|
||||
var rid = null;
|
||||
for (var n of networks) {
|
||||
if (n.id === network) {
|
||||
networkServer = n.domain;
|
||||
rid = n.rid;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return VectorScalarClient.getIrcOperators(rid, networkServer, '#' + channel, this._scalarToken);
|
||||
});
|
||||
}
|
||||
|
||||
/*override*/
|
||||
addChannel(network, channel, op) {
|
||||
return this._getNetworks().then(networks => {
|
||||
var networkServer = null;
|
||||
var rid = null;
|
||||
for (var n of networks) {
|
||||
if (n.id === network) {
|
||||
networkServer = n.domain;
|
||||
rid = n.rid;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return VectorScalarClient.addIrcLink(rid, this._roomId, networkServer, '#' + channel, op, this._scalarToken);
|
||||
});
|
||||
}
|
||||
|
||||
/*override*/
|
||||
removeChannel(network, channel) {
|
||||
return this._getNetworks().then(networks => {
|
||||
var networkServer = null;
|
||||
var rid = null;
|
||||
for (var n of networks) {
|
||||
if (n.id === network) {
|
||||
networkServer = n.domain;
|
||||
rid = n.rid;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return VectorScalarClient.removeIrcLink(rid, this._roomId, networkServer, '#' + channel, this._scalarToken);
|
||||
});
|
||||
}
|
||||
|
||||
_getNetworks() {
|
||||
if (this._lastNetworkResponse !== null) return Promise.resolve(this._lastNetworkResponse);
|
||||
return VectorScalarClient.getIrcNetworks(this._scalarToken).then(networks => {
|
||||
this._lastNetworkResponse = networks;
|
||||
return networks;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = VectorIrcBackbone;
|
@ -1,49 +0,0 @@
|
||||
var ComplexBot = require("../../generic_types/ComplexBot");
|
||||
|
||||
/**
|
||||
* Represents an RSS bot
|
||||
*/
|
||||
class RSSBot extends ComplexBot {
|
||||
|
||||
/**
|
||||
* Creates a new RSS bot
|
||||
* @param botConfig the bot configuration
|
||||
* @param backbone the backbone powering this bot
|
||||
*/
|
||||
constructor(botConfig, backbone) {
|
||||
super(botConfig);
|
||||
this._backbone = backbone;
|
||||
}
|
||||
|
||||
/*override*/
|
||||
getUserId() {
|
||||
return this._backbone.getUserId();
|
||||
}
|
||||
|
||||
/*override*/
|
||||
getState() {
|
||||
var response = {
|
||||
feeds: [],
|
||||
immutableFeeds: []
|
||||
};
|
||||
return this._backbone.getFeeds().then(feeds => {
|
||||
response.feeds = feeds;
|
||||
return this._backbone.getImmutableFeeds();
|
||||
}).then(feeds => {
|
||||
response.immutableFeeds = feeds;
|
||||
return response;
|
||||
});
|
||||
}
|
||||
|
||||
/*override*/
|
||||
removeFromRoom(roomId) {
|
||||
return this._backbone.removeFromRoom(roomId);
|
||||
}
|
||||
|
||||
/*override*/
|
||||
updateState(newState) {
|
||||
return this._backbone.setFeeds(newState.feeds).then(() => this.getState());
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = RSSBot;
|
@ -1,20 +0,0 @@
|
||||
var RSSBot = require("./RSSBot");
|
||||
var VectorRssBackbone = require("./VectorRssBackbone");
|
||||
var UpstreamConfiguration = require("../../../UpstreamConfiguration");
|
||||
|
||||
var factory = (db, integrationConfig, roomId, scalarToken) => {
|
||||
factory.validateConfig(integrationConfig);
|
||||
|
||||
return db.getUpstreamToken(scalarToken).then(upstreamToken => {
|
||||
var backbone = new VectorRssBackbone(roomId, upstreamToken);
|
||||
return new RSSBot(integrationConfig, backbone);
|
||||
});
|
||||
};
|
||||
|
||||
factory.validateConfig = (integrationConfig) => {
|
||||
if (!integrationConfig.upstream) throw new Error("Unsupported configuration");
|
||||
if (integrationConfig.upstream.type !== "vector") throw new Error("Unsupported upstream");
|
||||
if (!UpstreamConfiguration.hasUpstream("vector")) throw new Error("Vector upstream not specified");
|
||||
};
|
||||
|
||||
module.exports = factory;
|
@ -1,55 +0,0 @@
|
||||
/**
|
||||
* Stubbed/placeholder RSS backbone
|
||||
*/
|
||||
class StubbedRssBackbone {
|
||||
|
||||
/**
|
||||
* Creates a new stubbed RSS backbone
|
||||
*/
|
||||
constructor() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the user ID for this backbone
|
||||
* @returns {Promise<string>} resolves to the user ID
|
||||
*/
|
||||
getUserId() {
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the feeds for this backbone
|
||||
* @returns {Promise<string[]>} resolves to the collection of feeds
|
||||
*/
|
||||
getFeeds() {
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the new feeds for this backbone
|
||||
* @param {string[]} newFeeds the new feed URLs
|
||||
* @returns {Promise<>} resolves when complete
|
||||
*/
|
||||
setFeeds(newFeeds) {
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the immutable feeds for this backbone
|
||||
* @returns {Promise<{url:string,ownerId:string}>} resolves to the collection of immutable feeds
|
||||
*/
|
||||
getImmutableFeeds() {
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the bot from the given room
|
||||
* @param {string} roomId the room ID to remove the bot from
|
||||
* @returns {Promise<>} resolves when completed
|
||||
*/
|
||||
removeFromRoom(roomId) {
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = StubbedRssBackbone;
|
@ -1,82 +0,0 @@
|
||||
var StubbedRssBackbone = require("./StubbedRssBackbone");
|
||||
var VectorScalarClient = require("../../../scalar/VectorScalarClient");
|
||||
var _ = require("lodash");
|
||||
var log = require("../../../util/LogService");
|
||||
|
||||
/**
|
||||
* Backbone for RSS bots running on vector.im through scalar
|
||||
*/
|
||||
class VectorRssBackbone extends StubbedRssBackbone {
|
||||
|
||||
/**
|
||||
* Creates a new Vector RSS backbone
|
||||
* @param {string} roomId the room ID to manage
|
||||
* @param {string} upstreamScalarToken the vector scalar token
|
||||
*/
|
||||
constructor(roomId, upstreamScalarToken) {
|
||||
super();
|
||||
this._roomId = roomId;
|
||||
this._scalarToken = upstreamScalarToken;
|
||||
this._info = null;
|
||||
this._otherFeeds = [];
|
||||
}
|
||||
|
||||
/*override*/
|
||||
getUserId() {
|
||||
return (this._info ? Promise.resolve() : this._getInfo()).then(() => {
|
||||
return this._info.bot_user_id;
|
||||
});
|
||||
}
|
||||
|
||||
/*override*/
|
||||
getFeeds() {
|
||||
return (this._info ? Promise.resolve() : this._getInfo()).then(() => {
|
||||
if (this._info.integrations.length == 0) return [];
|
||||
return _.keys(this._info.integrations[0].config.feeds);
|
||||
});
|
||||
}
|
||||
|
||||
/*override*/
|
||||
setFeeds(newFeeds) {
|
||||
var feedConfig = {};
|
||||
for (var feed of newFeeds) feedConfig[feed] = {};
|
||||
|
||||
return VectorScalarClient.configureIntegration("rssbot", this._scalarToken, {
|
||||
feeds: feedConfig,
|
||||
room_id: this._roomId
|
||||
});
|
||||
}
|
||||
|
||||
/*override*/
|
||||
getImmutableFeeds() {
|
||||
return (this._info ? Promise.resolve() : this._getInfo()).then(() => {
|
||||
return this._otherFeeds;
|
||||
});
|
||||
}
|
||||
|
||||
_getInfo() {
|
||||
return VectorScalarClient.getIntegrationsForRoom(this._roomId, this._scalarToken).then(integrations => {
|
||||
this._otherFeeds = [];
|
||||
for (var integration of integrations) {
|
||||
if (integration.self) continue; // skip - we're not looking for ones we know about
|
||||
if (integration.type == "rssbot") {
|
||||
var urls = _.keys(integration.config.feeds);
|
||||
for (var url of urls) {
|
||||
this._otherFeeds.push({url: url, ownerId: integration.user_id});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return VectorScalarClient.getIntegration("rssbot", this._roomId, this._scalarToken);
|
||||
}).then(info => {
|
||||
this._info = info;
|
||||
});
|
||||
}
|
||||
|
||||
/*override*/
|
||||
removeFromRoom(roomId) {
|
||||
return VectorScalarClient.removeIntegration("rssbot", roomId, this._scalarToken);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = VectorRssBackbone;
|
@ -1,31 +0,0 @@
|
||||
var sdk = require("matrix-js-sdk");
|
||||
var log = require("../../../util/LogService");
|
||||
var StubbedSimpleBackbone = require("./StubbedSimpleBackbone");
|
||||
|
||||
/**
|
||||
* Standalone (matrix) backbone for simple bots
|
||||
*/
|
||||
class HostedSimpleBackbone extends StubbedSimpleBackbone {
|
||||
|
||||
/**
|
||||
* Creates a new standalone bot backbone
|
||||
* @param {*} botConfig the configuration for the bot
|
||||
*/
|
||||
constructor(botConfig) {
|
||||
super(botConfig);
|
||||
this._config = botConfig;
|
||||
this._client = sdk.createClient({
|
||||
baseUrl: this._config.hosted.homeserverUrl,
|
||||
accessToken: this._config.hosted.accessToken,
|
||||
userId: this._config.userId,
|
||||
});
|
||||
}
|
||||
|
||||
/*override*/
|
||||
removeFromRoom(roomId) {
|
||||
log.info("HostedSimpleBackbone", "Removing " + this._config.userId + " from " + roomId);
|
||||
return this._client.leave(roomId);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = HostedSimpleBackbone;
|
@ -1,24 +0,0 @@
|
||||
var IntegrationStub = require("../../generic_types/IntegrationStub");
|
||||
|
||||
/**
|
||||
* Represents an RSS bot
|
||||
*/
|
||||
class SimpleBot extends IntegrationStub {
|
||||
|
||||
/**
|
||||
* Creates a new RSS bot
|
||||
* @param botConfig the bot configuration
|
||||
* @param backbone the backbone powering this bot
|
||||
*/
|
||||
constructor(botConfig, backbone) {
|
||||
super(botConfig);
|
||||
this._backbone = backbone;
|
||||
}
|
||||
|
||||
/*override*/
|
||||
removeFromRoom(roomId) {
|
||||
return this._backbone.removeFromRoom(roomId);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = SimpleBot;
|
@ -1,27 +0,0 @@
|
||||
var SimpleBot = require("./SimpleBot");
|
||||
var VectorSimpleBackbone = require("./VectorSimpleBackbone");
|
||||
var HostedSimpleBackbone = require("./HostedSimpleBackbone");
|
||||
var UpstreamConfiguration = require("../../../UpstreamConfiguration");
|
||||
|
||||
var factory = (db, integrationConfig, roomId, scalarToken) => {
|
||||
factory.validateConfig(integrationConfig);
|
||||
|
||||
if (integrationConfig.upstream) {
|
||||
return db.getUpstreamToken(scalarToken).then(upstreamToken => {
|
||||
var backbone = new VectorSimpleBackbone(integrationConfig, upstreamToken);
|
||||
return new SimpleBot(integrationConfig, backbone);
|
||||
});
|
||||
} else if (integrationConfig.hosted) {
|
||||
var backbone = new HostedSimpleBackbone(integrationConfig);
|
||||
return Promise.resolve(new SimpleBot(integrationConfig, backbone));
|
||||
}
|
||||
};
|
||||
|
||||
factory.validateConfig = (integrationConfig) => {
|
||||
if (integrationConfig.upstream) {
|
||||
if (integrationConfig.upstream.type !== "vector") throw new Error("Unsupported upstream");
|
||||
if (!UpstreamConfiguration.hasUpstream("vector")) throw new Error("Vector upstream not specified");
|
||||
} else if (!integrationConfig.hosted) throw new Error("Unsupported configuration");
|
||||
};
|
||||
|
||||
module.exports = factory;
|
@ -1,24 +0,0 @@
|
||||
/**
|
||||
* Stubbed backbone for simple bots
|
||||
*/
|
||||
class StubbedSimpleBackbone {
|
||||
|
||||
/**
|
||||
* Creates a new stubbed bot backbone
|
||||
* @param {*} botConfig the configuration for the bot
|
||||
*/
|
||||
constructor(botConfig) {
|
||||
this._config = botConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Leaves a given Matrix room
|
||||
* @param {string} roomId the room to leave
|
||||
* @returns {Promise<>} resolves when completed
|
||||
*/
|
||||
removeFromRoom(roomId) {
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = StubbedSimpleBackbone;
|
@ -1,28 +0,0 @@
|
||||
var VectorScalarClient = require("../../../scalar/VectorScalarClient");
|
||||
var log = require("../../../util/LogService");
|
||||
var StubbedSimpleBackbone = require("./StubbedSimpleBackbone");
|
||||
|
||||
/**
|
||||
* Vector backbone for simple bots
|
||||
*/
|
||||
class VectorSimpleBackbone extends StubbedSimpleBackbone {
|
||||
|
||||
/**
|
||||
* Creates a new vector bot backbone
|
||||
* @param {*} botConfig the configuration for the bot
|
||||
* @param {string} upstreamScalarToken the upstream scalar token
|
||||
*/
|
||||
constructor(botConfig, upstreamScalarToken) {
|
||||
super(botConfig);
|
||||
this._config = botConfig;
|
||||
this._upstreamToken = upstreamScalarToken;
|
||||
}
|
||||
|
||||
/*override*/
|
||||
removeFromRoom(roomId) {
|
||||
log.info("VectorSimpleBackbone", "Removing " + this._config.userId + " from " + roomId);
|
||||
return VectorScalarClient.removeIntegration(this._config.upstream.id, roomId, this._upstreamToken);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = VectorSimpleBackbone;
|
@ -1,17 +0,0 @@
|
||||
var Widget = require("../../generic_types/Widget");
|
||||
|
||||
/**
|
||||
* Represents a simple widget
|
||||
*/
|
||||
class SimpleWidget extends Widget {
|
||||
|
||||
/**
|
||||
* Creates a new simple widget
|
||||
* @param widgetConfig the widget configuration
|
||||
*/
|
||||
constructor(widgetConfig) {
|
||||
super(widgetConfig);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = SimpleWidget;
|
@ -1,13 +0,0 @@
|
||||
var SimpleWidget = require("./SimpleWidget");
|
||||
var Promise = require("bluebird");
|
||||
|
||||
var factory = (db, integrationConfig, roomId, scalarToken) => {
|
||||
factory.validateConfig(integrationConfig);
|
||||
return Promise.resolve(new SimpleWidget(integrationConfig, roomId));
|
||||
};
|
||||
|
||||
factory.validateConfig = (integrationConfig) => {
|
||||
// Nothing to do
|
||||
};
|
||||
|
||||
module.exports = factory;
|
@ -1,63 +0,0 @@
|
||||
/**
|
||||
* Stubbed/placeholder Travis CI backbone
|
||||
*/
|
||||
class StubbedTravisCiBackbone {
|
||||
|
||||
/**
|
||||
* Creates a new stubbed RSS backbone
|
||||
*/
|
||||
constructor() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the user ID for this backbone
|
||||
* @returns {Promise<string>} resolves to the user ID
|
||||
*/
|
||||
getUserId() {
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the repository templates for this backbone
|
||||
* @returns {Promise<{repoKey:string,template:string}[]>} resolves to the collection of repositories and their templates
|
||||
*/
|
||||
getRepos() {
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the immutable repository templates for this backbone (set by other users)
|
||||
* @returns {Promise<{repoKey:string,template:string,ownerId:string}[]>} resolves to the collection of repositories and their templates
|
||||
*/
|
||||
getImmutableRepos() {
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the new repository templates for this backbone
|
||||
* @param {{repoKey:string,template:string}[]} newRepos the new templates for the repositories
|
||||
* @returns {Promise<>} resolves when complete
|
||||
*/
|
||||
setRepos(newRepos) {
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the webhook url for this backbone
|
||||
* @returns {Promise<string>} resolves to the webhook URL
|
||||
*/
|
||||
getWebhookUrl() {
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the bot from the given room
|
||||
* @param {string} roomId the room ID to remove the bot from
|
||||
* @returns {Promise<>} resolves when completed
|
||||
*/
|
||||
removeFromRoom(roomId) {
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = StubbedTravisCiBackbone;
|
@ -1,53 +0,0 @@
|
||||
var ComplexBot = require("../../generic_types/ComplexBot");
|
||||
|
||||
/**
|
||||
* Represents a Travis CI bot
|
||||
*/
|
||||
class TravisCiBot extends ComplexBot {
|
||||
|
||||
/**
|
||||
* Creates a new Travis CI bot
|
||||
* @param botConfig the bot configuration
|
||||
* @param backbone the backbone powering this bot
|
||||
*/
|
||||
constructor(botConfig, backbone) {
|
||||
super(botConfig);
|
||||
this._backbone = backbone;
|
||||
}
|
||||
|
||||
/*override*/
|
||||
getUserId() {
|
||||
return this._backbone.getUserId();
|
||||
}
|
||||
|
||||
/*override*/
|
||||
getState() {
|
||||
var response = {
|
||||
repoTemplates: [],
|
||||
immutableRepoTemplates: [],
|
||||
webhookUrl: ""
|
||||
};
|
||||
return this._backbone.getRepos().then(templates => {
|
||||
response.repoTemplates = templates;
|
||||
return this._backbone.getImmutableRepos();
|
||||
}).then(immutable => {
|
||||
response.immutableRepoTemplates = immutable;
|
||||
return this._backbone.getWebhookUrl();
|
||||
}).then(url => {
|
||||
response.webhookUrl = url;
|
||||
return response;
|
||||
});
|
||||
}
|
||||
|
||||
/*override*/
|
||||
removeFromRoom(roomId) {
|
||||
return this._backbone.removeFromRoom(roomId);
|
||||
}
|
||||
|
||||
/*override*/
|
||||
updateState(newState) {
|
||||
return this._backbone.setRepos(newState.repoTemplates).then(() => this.getState());
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = TravisCiBot;
|
@ -1,20 +0,0 @@
|
||||
var TravisCiBot = require("./TravisCiBot");
|
||||
var VectorTravisCiBackbone = require("./VectorTravisCiBackbone");
|
||||
var UpstreamConfiguration = require("../../../UpstreamConfiguration");
|
||||
|
||||
var factory = (db, integrationConfig, roomId, scalarToken) => {
|
||||
factory.validateConfig(integrationConfig);
|
||||
|
||||
return db.getUpstreamToken(scalarToken).then(upstreamToken => {
|
||||
var backbone = new VectorTravisCiBackbone(roomId, upstreamToken);
|
||||
return new TravisCiBot(integrationConfig, backbone);
|
||||
});
|
||||
};
|
||||
|
||||
factory.validateConfig = (integrationConfig) => {
|
||||
if (!integrationConfig.upstream) throw new Error("Unsupported configuration");
|
||||
if (integrationConfig.upstream.type !== "vector") throw new Error("Unsupported upstream");
|
||||
if (!UpstreamConfiguration.hasUpstream("vector")) throw new Error("Vector upstream not specified");
|
||||
};
|
||||
|
||||
module.exports = factory;
|
@ -1,108 +0,0 @@
|
||||
var StubbedTravisCiBackbone = require("./StubbedTravisCiBackbone");
|
||||
var VectorScalarClient = require("../../../scalar/VectorScalarClient");
|
||||
var _ = require("lodash");
|
||||
var log = require("../../../util/LogService");
|
||||
|
||||
/**
|
||||
* Backbone for Travis CI bots running on vector.im through scalar
|
||||
*/
|
||||
class VectorTravisCiBackbone extends StubbedTravisCiBackbone {
|
||||
|
||||
/**
|
||||
* Creates a new Vector Travis CI backbone
|
||||
* @param {string} roomId the room ID to manage
|
||||
* @param {string} upstreamScalarToken the vector scalar token
|
||||
*/
|
||||
constructor(roomId, upstreamScalarToken) {
|
||||
super();
|
||||
this._roomId = roomId;
|
||||
this._scalarToken = upstreamScalarToken;
|
||||
this._info = null;
|
||||
this._otherTemplates = [];
|
||||
}
|
||||
|
||||
/*override*/
|
||||
getUserId() {
|
||||
return (this._info ? Promise.resolve() : this._getInfo()).then(() => {
|
||||
return this._info.bot_user_id;
|
||||
});
|
||||
}
|
||||
|
||||
/*override*/
|
||||
getRepos() {
|
||||
return (this._info ? Promise.resolve() : this._getInfo()).then(() => {
|
||||
if (this._info.integrations.length == 0) return [];
|
||||
|
||||
var rooms = _.keys(this._info.integrations[0].config.rooms);
|
||||
if (rooms.indexOf(this._roomId) === -1) return [];
|
||||
|
||||
var repos = _.keys(this._info.integrations[0].config.rooms[this._roomId].repos);
|
||||
return _.map(repos, r => {
|
||||
return {repoKey: r, template: this._info.integrations[0].config.rooms[this._roomId].repos[r].template};
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/*override*/
|
||||
getImmutableRepos() {
|
||||
return (this._info ? Promise.resolve() : this._getInfo()).then(() => {
|
||||
return this._otherTemplates;
|
||||
});
|
||||
}
|
||||
|
||||
/*override*/
|
||||
setRepos(newRepos) {
|
||||
var config = {};
|
||||
config[this._roomId] = {repos: {}};
|
||||
for (var repo of newRepos) config[this._roomId].repos[repo.repoKey] = {template: repo.template};
|
||||
|
||||
return VectorScalarClient.configureIntegration("travis-ci", this._scalarToken, {
|
||||
rooms: config
|
||||
});
|
||||
}
|
||||
|
||||
/*override*/
|
||||
getWebhookUrl() {
|
||||
// string
|
||||
return (this._info ? Promise.resolve() : this._getInfo()).then(() => {
|
||||
if (this._info.integrations.length == 0) return "";
|
||||
return this._info.integrations[0].config.webhook_url;
|
||||
});
|
||||
}
|
||||
|
||||
_getInfo() {
|
||||
return VectorScalarClient.getIntegrationsForRoom(this._roomId, this._scalarToken).then(integrations => {
|
||||
this._otherTemplates = [];
|
||||
for (var integration of integrations) {
|
||||
if (integration.self) continue; // skip - we're not looking for ones we know about
|
||||
if (integration.type == "travis-ci") {
|
||||
var roomIds = _.keys(integration.config.rooms);
|
||||
if (roomIds.length === 0) continue;
|
||||
if (roomIds.length !== 1) log.warn("VectorTravisCiBackbone", "Expected 1 room but found " + roomIds.length);
|
||||
|
||||
var roomConfig = integration.config.rooms[roomIds[0]];
|
||||
var repositories = _.keys(roomConfig.repos);
|
||||
|
||||
for (var repo of repositories) {
|
||||
this._otherTemplates.push({
|
||||
repoKey: repo,
|
||||
template: roomConfig.repos[repo].template,
|
||||
ownerId: integration.user_id
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return VectorScalarClient.getIntegration("travis-ci", this._roomId, this._scalarToken);
|
||||
}).then(info => {
|
||||
this._info = info;
|
||||
});
|
||||
}
|
||||
|
||||
/*override*/
|
||||
removeFromRoom(roomId) {
|
||||
return VectorScalarClient.removeIntegration("travis-ci", roomId, this._scalarToken);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = VectorTravisCiBackbone;
|
@ -1,78 +0,0 @@
|
||||
var config = require("config");
|
||||
var log = require("../util/LogService");
|
||||
var fs = require("fs");
|
||||
var path = require("path");
|
||||
var _ = require("lodash");
|
||||
var IntegrationImpl = require("./impl");
|
||||
|
||||
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 = {};
|
||||
var byType = {};
|
||||
|
||||
for (var key of keys) {
|
||||
log.info("Integrations", "Preparing " + key);
|
||||
if (!configs[key].defaults) configs[key].defaults = {};
|
||||
var merged = config.util.extendDeep(configs[key].defaults, configs[key].alt);
|
||||
if (!merged['enabled']) {
|
||||
log.warn("Integrations", "Integration " + key + " is not enabled - skipping");
|
||||
continue;
|
||||
}
|
||||
|
||||
var factory = IntegrationImpl.getFactory(merged);
|
||||
if (!factory) {
|
||||
log.warn("Integrations", "Integration " + key + " does not have an associated factory - skipping");
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
factory.validateConfig(merged);
|
||||
} catch (err) {
|
||||
log.error("Integrations", "Error while validating integration " + key + " - skipping");
|
||||
log.error("Integrations", err);
|
||||
continue;
|
||||
}
|
||||
|
||||
linear.push(merged);
|
||||
if (merged['userId'])
|
||||
byUserId[merged['userId']] = merged;
|
||||
|
||||
if (!byType[merged['type']])
|
||||
byType[merged['type']] = {};
|
||||
if (byType[merged['type']][merged['integrationType']])
|
||||
throw new Error("Duplicate type " + merged['type'] + " (" + merged['integrationType'] + ") at key " + key);
|
||||
byType[merged['type']][merged['integrationType']] = merged;
|
||||
}
|
||||
|
||||
log.info("Integrations", "Loaded " + linear.length + " integrations");
|
||||
|
||||
module.exports = {
|
||||
all: linear,
|
||||
byUserId: byUserId,
|
||||
byType: byType
|
||||
};
|
@ -1,66 +0,0 @@
|
||||
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;
|
@ -1,83 +1,17 @@
|
||||
var request = require('request');
|
||||
var log = require("../util/LogService");
|
||||
var dns = require("dns-then");
|
||||
var Promise = require("bluebird");
|
||||
var config = require("config");
|
||||
|
||||
/**
|
||||
* Represents a lightweight matrix client with minimal functionality
|
||||
*/
|
||||
class MatrixLiteClient {
|
||||
|
||||
/**
|
||||
* Creates a new matrix client
|
||||
* @param {OpenID} openId the open ID to use
|
||||
*/
|
||||
constructor(openId) {
|
||||
this._openId = openId;
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
var helpers_1 = require("./helpers");
|
||||
var MatrixLiteClient = /** @class */ (function () {
|
||||
function MatrixLiteClient(homeserverName, accessToken) {
|
||||
this.homeserverName = homeserverName;
|
||||
this.accessToken = accessToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the Matrix User ID that owns this open ID
|
||||
* @return {Promise<string>} resolves to the mxid
|
||||
*/
|
||||
getSelfMxid() {
|
||||
return this._do("GET", "/_matrix/federation/v1/openid/userinfo", /*qs=*/null, /*body=*/null, /*allowSelfSigned=*/true).then((response, body) => {
|
||||
var json = JSON.parse(response.body);
|
||||
return json['sub'];
|
||||
MatrixLiteClient.prototype.getUrlPreview = function (url) {
|
||||
return helpers_1.doFederatedApiCall("GET", this.homeserverName, "/_matrix/media/r0/preview_url", { access_token: this.accessToken, url: url }).then(function (response) {
|
||||
return response;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a URL preview from the media repo (as provided by the default homeserver)
|
||||
* @param {string} url The URL to get the preview of
|
||||
* @return {Promise<*>} resolves to the raw URL preview
|
||||
*/
|
||||
static getUrlPreview(url) {
|
||||
return MatrixLiteClient._do(config.homeserver.name, config.homeserver.accessToken, "GET", "/_matrix/media/r0/preview_url", {url: url}).then((response, body) => {
|
||||
return JSON.parse(response.body);
|
||||
});
|
||||
}
|
||||
|
||||
_do(method, endpoint, qs = null, body = null, allowSelfSigned = false) {
|
||||
return MatrixLiteClient._do(this._openId.matrix_server_name, this._openId.access_token, method, endpoint, qs, body, allowSelfSigned);
|
||||
}
|
||||
|
||||
static _do(serverName, accessToken, method, endpoint, qs = null, body = null, allowSelfSigned = false) {
|
||||
// HACK: We have to wrap the dns promise in a Bluebird promise just to make sure it works
|
||||
var dnsPromise = dns.resolveSrv("_matrix._tcp." + serverName);
|
||||
return Promise.resolve(dnsPromise).then(records => {
|
||||
if (records && records.length > 0)
|
||||
serverName = records[0].name + ":" + records[0].port;
|
||||
}, err => {
|
||||
log.warn("MatrixLiteClient", "Failed to lookup SRV for " + serverName + " - assuming none available.");
|
||||
log.warn("MatrixLiteClient", err);
|
||||
}).then(() => {
|
||||
var url = "https://" + serverName + endpoint;
|
||||
|
||||
log.verbose("MatrixLiteClient", "Performing request: " + url);
|
||||
|
||||
if (!qs) qs = {};
|
||||
if (accessToken) qs['access_token'] = accessToken;
|
||||
|
||||
var params = {
|
||||
url: url,
|
||||
method: method,
|
||||
form: body,
|
||||
qs: qs,
|
||||
rejectUnauthorized: !allowSelfSigned
|
||||
};
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
request(params, (err, response, body) => {
|
||||
if (err) {
|
||||
log.error("MatrixLiteClient", err);
|
||||
reject(err);
|
||||
} else resolve(response, body);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = MatrixLiteClient;
|
||||
return MatrixLiteClient;
|
||||
}());
|
||||
exports.MatrixLiteClient = MatrixLiteClient;
|
||||
//# sourceMappingURL=MatrixLiteClient.js.map
|
@ -1,58 +1,36 @@
|
||||
var request = require('request');
|
||||
var log = require("../util/LogService");
|
||||
var config = require("config");
|
||||
var UpstreamConfiguration = require("../UpstreamConfiguration");
|
||||
|
||||
/**
|
||||
* Represents a scalar client
|
||||
*/
|
||||
class ScalarClient {
|
||||
|
||||
/**
|
||||
* Creates a new Scalar client
|
||||
*/
|
||||
constructor() {
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
var Promise = require("bluebird");
|
||||
var request = require("request");
|
||||
var matrix_js_snippets_1 = require("matrix-js-snippets");
|
||||
var ScalarClient = /** @class */ (function () {
|
||||
function ScalarClient(upstream) {
|
||||
this.upstream = upstream;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers for a scalar token
|
||||
* @param {OpenID} openId the open ID to register
|
||||
* @returns {Promise<string>} resolves to a scalar token
|
||||
*/
|
||||
register(openId) {
|
||||
return this._do("POST", "/register", null, openId).then((response, body) => {
|
||||
if (response.statusCode !== 200) {
|
||||
log.error("ScalarClient", response.body);
|
||||
return Promise.reject(response.body);
|
||||
}
|
||||
|
||||
return response.body['scalar_token'];
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: Merge this, VectorScalarClient, and MatrixLiteClient into a base class
|
||||
_do(method, endpoint, qs = null, body = null) {
|
||||
// TODO: Generify URL
|
||||
var url = UpstreamConfiguration.getUpstream("vector").url + endpoint;
|
||||
|
||||
log.verbose("ScalarClient", "Performing request: " + url);
|
||||
|
||||
var params = {
|
||||
url: url,
|
||||
method: method,
|
||||
json: body,
|
||||
qs: qs
|
||||
};
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
request(params, (err, response, body) => {
|
||||
ScalarClient.prototype.register = function (openId) {
|
||||
var _this = this;
|
||||
return new Promise(function (resolve, reject) {
|
||||
request({
|
||||
method: "POST",
|
||||
url: _this.upstream.scalarUrl + "/register",
|
||||
json: openId,
|
||||
}, function (err, res, _body) {
|
||||
if (err) {
|
||||
log.error("ScalarClient", err);
|
||||
matrix_js_snippets_1.LogService.error("ScalarClient", "Error registering for token");
|
||||
matrix_js_snippets_1.LogService.error("ScalarClient", err);
|
||||
reject(err);
|
||||
} else resolve(response, body);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new ScalarClient();
|
||||
else if (res.statusCode !== 200) {
|
||||
matrix_js_snippets_1.LogService.error("ScalarClient", "Got status code " + res.statusCode + " while registering for token");
|
||||
reject(new Error("Could not get token"));
|
||||
}
|
||||
else {
|
||||
resolve(res.body);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
return ScalarClient;
|
||||
}());
|
||||
exports.ScalarClient = ScalarClient;
|
||||
//# sourceMappingURL=ScalarClient.js.map
|
@ -1,260 +0,0 @@
|
||||
var request = require('request');
|
||||
var log = require("../util/LogService");
|
||||
var config = require("config");
|
||||
var UpstreamConfiguration = require("../UpstreamConfiguration");
|
||||
|
||||
/**
|
||||
* Represents a scalar client for vector.im
|
||||
*/
|
||||
class VectorScalarClient {
|
||||
|
||||
/**
|
||||
* Creates a new vector.im Scalar client
|
||||
*/
|
||||
constructor() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers for a scalar token
|
||||
* @param {OpenID} openId the open ID to register
|
||||
* @returns {Promise<string>} resolves to a scalar token
|
||||
*/
|
||||
register(openId) {
|
||||
return this._do("POST", "/register", null, openId).then((response, body) => {
|
||||
var json = JSON.parse(response.body);
|
||||
return json['scalar_token'];
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a scalar integration
|
||||
* @param {string} type the type of integration to remove
|
||||
* @param {string} roomId the room ID to remove it from
|
||||
* @param {string} scalarToken the upstream scalar token
|
||||
* @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) => {
|
||||
if (response.statusCode !== 200) {
|
||||
log.error("VectorScalarClient", response.body);
|
||||
return Promise.reject(response.body);
|
||||
}
|
||||
|
||||
// no success processing
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures an Integration on Vector
|
||||
* @param {string} type the integration tpye
|
||||
* @param {string} scalarToken the scalar token
|
||||
* @param {*} config the config to POST to the service
|
||||
* @return {Promise<>} resolves when completed
|
||||
*/
|
||||
configureIntegration(type, scalarToken, config) {
|
||||
return this._do("POST", "/integrations/" + type + "/configureService", {scalar_token: scalarToken}, config).then((response, body) => {
|
||||
if (response.statusCode !== 200) {
|
||||
log.error("VectorScalarClient", response.body);
|
||||
return Promise.reject(response.body);
|
||||
}
|
||||
|
||||
// no success processing
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all of the integrations currently in a room
|
||||
* @param {string} roomId the room ID
|
||||
* @param {string} scalarToken the scalar token to use
|
||||
* @returns {Promise<*[]>} resolves a collection of integrations
|
||||
*/
|
||||
getIntegrationsForRoom(roomId, scalarToken) {
|
||||
return this._do("POST", "/integrations", {scalar_token: scalarToken}, {RoomId: roomId}).then((response, body) => {
|
||||
if (response.statusCode !== 200) {
|
||||
log.error("VectorScalarClient", response.body);
|
||||
return Promise.reject(response.body);
|
||||
}
|
||||
|
||||
return response.body.integrations;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets information for an integration
|
||||
* @param {string} type the type to lookup
|
||||
* @param {string} roomId the room ID to look in
|
||||
* @param {string} scalarToken the scalar token
|
||||
* @return {Promise<{bot_user_id:string,integrations:[]}>} resolves to the integration information
|
||||
*/
|
||||
getIntegration(type, roomId, scalarToken) {
|
||||
return this._do("POST", "/integrations/" + type, {scalar_token: scalarToken}, {room_id: roomId}).then((response, body) => {
|
||||
if (response.statusCode !== 200) {
|
||||
log.error("VectorScalarClient", response.body);
|
||||
return Promise.reject(response.body);
|
||||
}
|
||||
|
||||
return response.body;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of supported IRC networks
|
||||
* @param {string} scalarToken the scalar token
|
||||
* @returns {Promise<{rid: string, title: string, domain: string, id: string}[]>} resolves to the list of IRC networks
|
||||
*/
|
||||
getIrcNetworks(scalarToken) {
|
||||
return this._do("GET", "/bridges/irc/_matrix/provision/querynetworks", {scalar_token: scalarToken}).then((response, body) => {
|
||||
if (response.statusCode !== 200) {
|
||||
log.error("VectorScalarClient", response.body);
|
||||
return Promise.reject(response.body);
|
||||
}
|
||||
|
||||
response.body = JSON.parse(response.body);
|
||||
|
||||
var results = [];
|
||||
for (var network of response.body["replies"]) {
|
||||
var result = {
|
||||
rid: network["rid"],
|
||||
// Assumption: All networks have 1 server from vector
|
||||
id: network["response"]["servers"][0]["network_id"],
|
||||
title: network["response"]["servers"][0]["desc"],
|
||||
domain: network["response"]["servers"][0]["fields"]["domain"]
|
||||
};
|
||||
results.push(result);
|
||||
}
|
||||
|
||||
return results;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of all linked IRC channels for a given room
|
||||
* @param {string} roomId the room ID to look in
|
||||
* @param {string} scalarToken the scalar token
|
||||
* @returns {Promise<{rid: string, server: string, channel: string}>} resolves to a list of linked channels
|
||||
*/
|
||||
getIrcLinks(roomId, scalarToken) {
|
||||
return this._do("GET", "/bridges/irc/_matrix/provision/listlinks/" + roomId, {scalar_token: scalarToken}).then((response, body) => {
|
||||
if (response.statusCode !== 200) {
|
||||
log.error("VectorScalarClient", response.body);
|
||||
return Promise.reject(response.body);
|
||||
}
|
||||
|
||||
response.body = JSON.parse(response.body);
|
||||
|
||||
var results = [];
|
||||
for (var linkContainer of response.body["replies"]) {
|
||||
for (var link of linkContainer["response"]) {
|
||||
results.push({
|
||||
rid: linkContainer["rid"],
|
||||
server: link["remote_room_server"],
|
||||
channel: link["remote_room_channel"]
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of operators in a particular channel on a particular network
|
||||
* @param {string} rid the network ID
|
||||
* @param {string} networkServer the server that has the channel on it
|
||||
* @param {string} channel the channel to look up, with prefix
|
||||
* @param {string} scalarToken the scalar token
|
||||
* @returns {Promise<string[]>} resolves to a list of operators in the channel
|
||||
*/
|
||||
getIrcOperators(rid, networkServer, channel, scalarToken) {
|
||||
return this._do("POST", "/bridges/irc/_matrix/provision/querylink", {scalar_token: scalarToken, rid: rid}, {
|
||||
remote_room_server: networkServer,
|
||||
remote_room_channel: channel
|
||||
}).then((response, body) => {
|
||||
if (response.statusCode !== 200) {
|
||||
log.error("VectorScalarClient", response.body);
|
||||
return Promise.reject(response.body);
|
||||
}
|
||||
|
||||
if (response.body["replies"]) {
|
||||
return response.body["replies"][0]["response"]["operators"];
|
||||
} else return Promise.reject("No operators could be found");
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests an operator for permission to link an IRC channel to a matrix room
|
||||
* @param {string} rid the network ID
|
||||
* @param {string} roomId the matrix room ID
|
||||
* @param {string} networkServer the server that has the channel on it
|
||||
* @param {string} channel the channel to look up, with prefix
|
||||
* @param {string} operator the channel operator's nick
|
||||
* @param {string} scalarToken the scalar token
|
||||
* @returns {Promise<>} resolves when completed
|
||||
*/
|
||||
addIrcLink(rid, roomId, networkServer, channel, operator, scalarToken) {
|
||||
return this._do("POST", "/bridges/irc/_matrix/provision/link", {rid: rid, scalar_token: scalarToken}, {
|
||||
matrix_room_id: roomId,
|
||||
remote_room_channel: channel,
|
||||
remote_room_server: networkServer,
|
||||
op_nick: operator
|
||||
}).then((response, body) => {
|
||||
if (response.statusCode !== 200) {
|
||||
log.error("VectorScalarClient", response.body);
|
||||
return Promise.reject(response.body);
|
||||
}
|
||||
|
||||
return {status: 'ok'};
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a channel link from a Matrix room
|
||||
* @param {string} rid the network ID
|
||||
* @param {string} roomId the matrix room ID
|
||||
* @param {string} networkServer the server that has the channel on it
|
||||
* @param {string} channel the channel to remove, with prefix
|
||||
* @param {string} scalarToken the scalar token
|
||||
* @returns {Promise<>} resolves when completed
|
||||
*/
|
||||
removeIrcLink(rid, roomId, networkServer, channel, scalarToken) {
|
||||
return this._do("POST", "/bridges/irc/_matrix/provision/unlink", {rid: rid, scalar_token: scalarToken}, {
|
||||
matrix_room_id: roomId,
|
||||
remote_room_channel: channel,
|
||||
remote_room_server: networkServer
|
||||
}).then((response, body) => {
|
||||
if (response.statusCode !== 200) {
|
||||
log.error("VectorScalarClient", response.body);
|
||||
return Promise.reject(response.body);
|
||||
}
|
||||
|
||||
return {status: 'ok'};
|
||||
})
|
||||
}
|
||||
|
||||
_do(method, endpoint, qs = null, body = null) {
|
||||
var url = UpstreamConfiguration.getUpstream("vector").url + endpoint;
|
||||
|
||||
log.verbose("VectorScalarClient", "Performing request: " + url);
|
||||
|
||||
var params = {
|
||||
url: url,
|
||||
method: method,
|
||||
json: body,
|
||||
qs: qs
|
||||
};
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
request(params, (err, response, body) => {
|
||||
if (err) {
|
||||
log.error("VectorScalarClient", err);
|
||||
reject(err);
|
||||
} else resolve(response, body);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new VectorScalarClient();
|
@ -1,120 +0,0 @@
|
||||
var DBMigrate = require("db-migrate");
|
||||
var log = require("./../util/LogService");
|
||||
var Sequelize = require('sequelize');
|
||||
var dbConfig = require("../../config/database.json");
|
||||
var moment = require("moment");
|
||||
|
||||
/**
|
||||
* Primary storage for Dimension.
|
||||
*/
|
||||
class DimensionStore {
|
||||
|
||||
constructor() {
|
||||
this._orm = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the store for use
|
||||
*/
|
||||
prepare() {
|
||||
var env = process.env.NODE_ENV || "development";
|
||||
log.info("DimensionStore", "Running migrations");
|
||||
return new Promise((resolve, reject)=> {
|
||||
var dbMigrate = DBMigrate.getInstance(true, {
|
||||
config: "./config/database.json",
|
||||
env: env
|
||||
});
|
||||
dbMigrate.up().then(() => {
|
||||
var dbConfigEnv = dbConfig[env];
|
||||
if (!dbConfigEnv) throw new Error("Could not find DB config for " + env);
|
||||
|
||||
var opts = {
|
||||
host: dbConfigEnv.host || 'localhost',
|
||||
dialect: 'sqlite',
|
||||
pool: {
|
||||
max: 5,
|
||||
min: 0,
|
||||
idle: 10000
|
||||
},
|
||||
storage: dbConfigEnv.filename,
|
||||
logging: i => log.verbose("DimensionStore [SQL]", i)
|
||||
};
|
||||
|
||||
this._orm = new Sequelize(dbConfigEnv.database || 'dimension', dbConfigEnv.username, dbConfigEnv.password, opts);
|
||||
this._bindModels();
|
||||
resolve();
|
||||
}, err => {
|
||||
log.error("DimensionStore", err);
|
||||
reject(err);
|
||||
}).catch(err => {
|
||||
log.error("DimensionStore", err);
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
_bindModels() {
|
||||
// Models
|
||||
this.__Tokens = this._orm.import(__dirname + "/models/tokens");
|
||||
|
||||
// Relationships
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Scalar token
|
||||
* @param {string} mxid the matrix user id
|
||||
* @param {OpenID} openId the open ID
|
||||
* @param {string} scalarToken the token associated with the user
|
||||
* @param {String?} upstreamToken the upstream scalar token (optional)
|
||||
* @returns {Promise<>} resolves when complete
|
||||
*/
|
||||
createToken(mxid, openId, scalarToken, upstreamToken) {
|
||||
return this.__Tokens.create({
|
||||
matrixUserId: mxid,
|
||||
matrixServerName: openId.matrix_server_name,
|
||||
matrixAccessToken: openId.access_token,
|
||||
scalarToken: scalarToken,
|
||||
upstreamToken: upstreamToken,
|
||||
expires: moment().add(openId.expires_in, 'seconds').toDate()
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks to determine if a token is valid or not
|
||||
* @param {string} scalarToken the scalar token to check
|
||||
* @returns {Promise<>} resolves if valid, rejected otherwise
|
||||
*/
|
||||
checkToken(scalarToken) {
|
||||
return this.__Tokens.find({where: {scalarToken: scalarToken}}).then(token => {
|
||||
if (!token) return Promise.reject(new Error("Token not found"));
|
||||
//if (moment().isAfter(moment(token.expires))) return this.__Tokens.destroy({where: {id: token.id}}).then(() => Promise.reject());
|
||||
return Promise.resolve();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the user ID that owns a given token, returning a falsey value if no one owns the token.
|
||||
* @param {string} scalarToken the scalar token to check
|
||||
* @returns {Promise<String>} resolves to the user ID, or a falsey value if no user ID was found
|
||||
*/
|
||||
getTokenOwner(scalarToken) {
|
||||
return this.__Tokens.find({where:{scalarToken: scalarToken}}).then(token => {
|
||||
if (!token) return Promise.reject(new Error("Token not found"));
|
||||
return Promise.resolve(token.matrixUserId);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the upstream token for a given scalar token
|
||||
* @param {string} scalarToken the scalar token to lookup
|
||||
* @returns {Promise<string>} resolves to the upstream token, or null if not found
|
||||
*/
|
||||
getUpstreamToken(scalarToken) {
|
||||
return this.__Tokens.find({where: {scalarToken: scalarToken}}).then(token => {
|
||||
if (!token) return null;
|
||||
return token.upstreamToken;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = DimensionStore;
|
@ -1,45 +0,0 @@
|
||||
module.exports = function (sequelize, DataTypes) {
|
||||
return sequelize.define('tokens', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
autoIncrement: true,
|
||||
primaryKey: true,
|
||||
field: 'id'
|
||||
},
|
||||
matrixUserId: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
field: 'matrixUserId'
|
||||
},
|
||||
matrixServerName: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
field: 'matrixServerName'
|
||||
},
|
||||
matrixAccessToken: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
field: 'matrixAccessToken'
|
||||
},
|
||||
scalarToken: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
field: 'scalarToken'
|
||||
},
|
||||
upstreamToken: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true,
|
||||
field: 'upstreamToken'
|
||||
},
|
||||
expires: {
|
||||
type: DataTypes.TIME,
|
||||
allowNull: false,
|
||||
field: 'expires'
|
||||
}
|
||||
}, {
|
||||
tableName: 'tokens',
|
||||
underscored: false,
|
||||
timestamps: false
|
||||
});
|
||||
};
|
@ -1,100 +0,0 @@
|
||||
var winston = require("winston");
|
||||
var chalk = require("chalk");
|
||||
var config = require("config");
|
||||
var fs = require('fs');
|
||||
var moment = require('moment');
|
||||
|
||||
try {
|
||||
fs.mkdirSync('logs')
|
||||
} catch (err) {
|
||||
if (err.code !== 'EEXIST') throw err
|
||||
}
|
||||
|
||||
const TERM_COLORS = {
|
||||
error: "red",
|
||||
warn: "yellow",
|
||||
info: "blue",
|
||||
verbose: "white",
|
||||
silly: "grey",
|
||||
};
|
||||
|
||||
function winstonColorFormatter(options) {
|
||||
options.level = chalk[TERM_COLORS[options.level]](options.level);
|
||||
return winstonFormatter(options);
|
||||
}
|
||||
|
||||
function winstonFormatter(options) {
|
||||
return options.timestamp() + ' ' + options.level + ' ' + (options.message ? options.message : '') +
|
||||
(options.meta && Object.keys(options.meta).length ? '\n\t' + JSON.stringify(options.meta) : '' );
|
||||
}
|
||||
|
||||
function getTimestamp() {
|
||||
return moment().format('MMM-D-YYYY HH:mm:ss.SSS Z');
|
||||
}
|
||||
|
||||
var loggingConfig = config.get('logging');
|
||||
|
||||
var transports = [];
|
||||
transports.push(new (winston.transports.File)({
|
||||
json: false,
|
||||
name: "file",
|
||||
filename: loggingConfig.file,
|
||||
timestamp: getTimestamp,
|
||||
formatter: winstonFormatter,
|
||||
level: loggingConfig.fileLevel,
|
||||
maxsize: loggingConfig.rotate.size,
|
||||
maxFiles: loggingConfig.rotate.count,
|
||||
zippedArchive: false
|
||||
}));
|
||||
|
||||
if (loggingConfig.console) {
|
||||
transports.push(new (winston.transports.Console)({
|
||||
json: false,
|
||||
name: "console",
|
||||
timestamp: getTimestamp,
|
||||
formatter: winstonColorFormatter,
|
||||
level: loggingConfig.consoleLevel
|
||||
}));
|
||||
}
|
||||
|
||||
var log = new winston.Logger({
|
||||
transports: transports,
|
||||
levels: {
|
||||
error: 0,
|
||||
warn: 1,
|
||||
info: 2,
|
||||
verbose: 3,
|
||||
silly: 4
|
||||
}
|
||||
});
|
||||
|
||||
function doLog(level, module, messageOrObject) {
|
||||
if (typeof(messageOrObject) === 'object' && !(messageOrObject instanceof Error))
|
||||
messageOrObject = JSON.stringify(messageOrObject);
|
||||
var message = "[" + module + "] " + messageOrObject;
|
||||
log.log(level, message);
|
||||
}
|
||||
|
||||
class LogService {
|
||||
static info(module, message) {
|
||||
doLog('info', module, message);
|
||||
}
|
||||
|
||||
static warn(module, message) {
|
||||
doLog('warn', module, message);
|
||||
}
|
||||
|
||||
static error(module, message) {
|
||||
doLog('error', module, message);
|
||||
}
|
||||
|
||||
static verbose(module, message) {
|
||||
doLog('verbose', module, message);
|
||||
}
|
||||
|
||||
static silly(module, message) {
|
||||
doLog('silly', module, message);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = LogService;
|
@ -13,6 +13,6 @@
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"./src-ts/**/*"
|
||||
"./src/**/*"
|
||||
]
|
||||
}
|
Loading…
Reference in New Issue
Block a user