mirror of
https://github.com/turt2live/matrix-dimension.git
synced 2024-10-01 01:05:53 -04:00
parent
e50eb7003e
commit
ebc77b7a07
9
config/integrations/rssbot.yaml
Normal file
9
config/integrations/rssbot.yaml
Normal file
@ -0,0 +1,9 @@
|
||||
type: "complex-bot"
|
||||
integrationType: "rss"
|
||||
enabled: true
|
||||
name: "RSS Bot"
|
||||
about: "Tracks any Atom/RSS feed and sends new items into this room"
|
||||
avatar: "/img/avatars/rssbot.png"
|
||||
upstream:
|
||||
type: "vector"
|
||||
id: "rssbot"
|
@ -4,6 +4,67 @@ Scalar has a server-side component to assist in managing integrations. The known
|
||||
|
||||
None of these are officially documented, and are subject to change.
|
||||
|
||||
## POST `/api/integrations?scalar_token=...`
|
||||
|
||||
**Body**:
|
||||
```
|
||||
{
|
||||
"RoomID": "!JmvocvDuPTYUfuvKgs:t2l.io"
|
||||
}
|
||||
```
|
||||
*Note*: Case difference appears to be intentional.
|
||||
|
||||
**Response**:
|
||||
```
|
||||
{
|
||||
"integrations": [{
|
||||
"type": "rssbot",
|
||||
"user_id": "@travis:t2l.io",
|
||||
"config": {
|
||||
"feeds": {
|
||||
"https://ci.t2l.io/view/all/rssAll": {
|
||||
"poll_interval_mins": 0,
|
||||
"is_failing": false,
|
||||
"last_updated_ts_secs": 1495995601,
|
||||
"rooms": ["!JmvocvDuPTYUfuvKgs:t2l.io"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"self": false
|
||||
},{
|
||||
"type": "rssbot",
|
||||
"user_id": "@travis:tang.ents.ca",
|
||||
"config": {
|
||||
"feeds": {
|
||||
"https://ci.t2l.io/job/java-simple-eventemitter/rssAll": {
|
||||
"poll_interval_mins": 0,
|
||||
"is_failing": false,
|
||||
"last_updated_ts_secs": 1495995618,
|
||||
"rooms": ["!JmvocvDuPTYUfuvKgs:t2l.io"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"self": true
|
||||
},{
|
||||
"type": "travis-ci",
|
||||
"user_id": "@travis:t2l.io",
|
||||
"config": {
|
||||
"webhook_url": "https://scalar.vector.im/api/neb/services/hooks/some_long_string",
|
||||
"rooms": {
|
||||
"!JmvocvDuPTYUfuvKgs:t2l.io": {
|
||||
"repos": {
|
||||
"turt2live/matrix-dimension": {
|
||||
"template": "%{repository}#%{build_number} (%{branch} - %{commit} : %{author}): %{message}\n Change view : %{compare_url}\n Build details : %{build_url}\n"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"self": false
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
## POST `/api/integrations/{type}?scalar_token=...`
|
||||
|
||||
**Params**:
|
||||
|
@ -12,6 +12,7 @@ var integrations = require("./integration");
|
||||
var _ = require("lodash");
|
||||
var UpstreamIntegration = require("./integration/UpstreamIntegration");
|
||||
var HostedIntegration = require("./integration/HostedIntegration");
|
||||
var IntegrationImpl = require("./integration/impl");
|
||||
|
||||
/**
|
||||
* Primary entry point for Dimension
|
||||
@ -52,7 +53,7 @@ class Dimension {
|
||||
this._app.post("/api/v1/scalar/register", this._scalarRegister.bind(this));
|
||||
this._app.get("/api/v1/scalar/checkToken", this._checkScalarToken.bind(this));
|
||||
|
||||
this._app.get("/api/v1/dimension/integrations", this._getIntegrations.bind(this));
|
||||
this._app.get("/api/v1/dimension/integrations/:roomId", this._getIntegrations.bind(this));
|
||||
this._app.post("/api/v1/dimension/removeIntegration", this._removeIntegration.bind(this));
|
||||
}
|
||||
|
||||
@ -89,14 +90,46 @@ class Dimension {
|
||||
_getIntegrations(req, res) {
|
||||
res.setHeader("Content-Type", "application/json");
|
||||
|
||||
var results = _.map(integrations.all, i => {
|
||||
var integration = JSON.parse(JSON.stringify(i));
|
||||
integration.upstream = undefined;
|
||||
integration.hosted = undefined;
|
||||
return integration;
|
||||
});
|
||||
var scalarToken = req.query.scalar_token;
|
||||
this._db.checkToken(scalarToken).then(() => {
|
||||
var roomId = req.params.roomId;
|
||||
if (!roomId) {
|
||||
res.status(400).send({error: 'Missing room ID'});
|
||||
return;
|
||||
}
|
||||
|
||||
res.send(results);
|
||||
var results = _.map(integrations.all, i => JSON.parse(JSON.stringify(i)));
|
||||
|
||||
var promises = [];
|
||||
_.forEach(results, i => {
|
||||
if (IntegrationImpl[i.type]) {
|
||||
var confs = IntegrationImpl[i.type];
|
||||
if (confs[i.integrationType]) {
|
||||
log.info("Dimension", "Using special configuration for " + i.type + " (" + i.integrationType + ")");
|
||||
|
||||
promises.push(confs[i.integrationType](this._db, i, roomId, scalarToken).then(integration => {
|
||||
return integration.getUserId().then(userId=> {
|
||||
i.userId = userId;
|
||||
return integration.getState();
|
||||
}).then(state=> {
|
||||
for (var key in state) {
|
||||
i[key] = state[key];
|
||||
}
|
||||
});
|
||||
}))
|
||||
} else log.verbose("Dimension", "No special configuration needs for " + i.type + " (" + i.integrationType + ")");
|
||||
} else log.verbose("Dimension", "No special implementation type for " + i.type);
|
||||
});
|
||||
|
||||
Promise.all(promises).then(() => res.send(_.map(results, integration => {
|
||||
integration.upstream = undefined;
|
||||
integration.hosted = undefined;
|
||||
return integration;
|
||||
})));
|
||||
}).catch(err => {
|
||||
log.error("Dimension", err);
|
||||
res.status(500).send({error: err});
|
||||
});
|
||||
}
|
||||
|
||||
_checkScalarToken(req, res) {
|
||||
|
11
src/integration/impl/StubbedFactory.js
Normal file
11
src/integration/impl/StubbedFactory.js
Normal file
@ -0,0 +1,11 @@
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
module.exports = (db, integrationConfig, roomId, scalarToken) => {
|
||||
throw new Error("Not implemented");
|
||||
};
|
7
src/integration/impl/index.js
Normal file
7
src/integration/impl/index.js
Normal file
@ -0,0 +1,7 @@
|
||||
var RSSFactory = require("./rss/RSSFactory");
|
||||
|
||||
module.exports = {
|
||||
"complex-bot": {
|
||||
"rss": RSSFactory
|
||||
}
|
||||
};
|
35
src/integration/impl/rss/RSSBot.js
Normal file
35
src/integration/impl/rss/RSSBot.js
Normal file
@ -0,0 +1,35 @@
|
||||
var ComplexBot = require("../../type/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();
|
||||
}
|
||||
|
||||
getFeeds() {
|
||||
return this._backbone.getFeeds();
|
||||
}
|
||||
|
||||
/*override*/
|
||||
getState() {
|
||||
return this.getFeeds().then(feeds => {
|
||||
return {feeds: feeds};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = RSSBot;
|
12
src/integration/impl/rss/RSSFactory.js
Normal file
12
src/integration/impl/rss/RSSFactory.js
Normal file
@ -0,0 +1,12 @@
|
||||
var RSSBot = require("./RSSBot");
|
||||
var VectorRssBackbone = require("./VectorRssBackbone");
|
||||
|
||||
module.exports = (db, integrationConfig, roomId, scalarToken) => {
|
||||
if (integrationConfig.upstream) {
|
||||
if (integrationConfig.upstream.type !== "vector") throw new Error("Unsupported upstream");
|
||||
return db.getUpstreamToken(scalarToken).then(upstreamToken => {
|
||||
var backbone = new VectorRssBackbone(roomId, upstreamToken);
|
||||
return new RSSBot(integrationConfig, backbone);
|
||||
});
|
||||
} else throw new Error("Unsupported config");
|
||||
};
|
29
src/integration/impl/rss/StubbedRssBackbone.js
Normal file
29
src/integration/impl/rss/StubbedRssBackbone.js
Normal file
@ -0,0 +1,29 @@
|
||||
/**
|
||||
* 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");
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = StubbedRssBackbone;
|
45
src/integration/impl/rss/VectorRssBackbone.js
Normal file
45
src/integration/impl/rss/VectorRssBackbone.js
Normal file
@ -0,0 +1,45 @@
|
||||
var StubbedRssBackbone = require("./StubbedRssBackbone");
|
||||
var VectorScalarClient = require("../../../scalar/VectorScalarClient");
|
||||
var _ = require("lodash");
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/*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);
|
||||
});
|
||||
}
|
||||
|
||||
_getInfo() {
|
||||
return VectorScalarClient.getIntegration("rssbot", this._roomId, this._scalarToken).then(info => {
|
||||
this._info = info;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = VectorRssBackbone;
|
18
src/integration/type/ComplexBot.js
Normal file
18
src/integration/type/ComplexBot.js
Normal file
@ -0,0 +1,18 @@
|
||||
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;
|
26
src/integration/type/IntegrationStub.js
Normal file
26
src/integration/type/IntegrationStub.js
Normal file
@ -0,0 +1,26 @@
|
||||
/**
|
||||
* 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({});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = IntegrationStub;
|
@ -46,6 +46,42 @@ class VectorScalarClient {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 information on
|
||||
* @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;
|
||||
});
|
||||
}
|
||||
|
||||
_do(method, endpoint, qs = null, body = null) {
|
||||
var url = config.get("upstreams.vector") + endpoint;
|
||||
|
||||
|
@ -86,7 +86,7 @@ class DimensionStore {
|
||||
*/
|
||||
checkToken(scalarToken) {
|
||||
return this.__Tokens.find({where: {scalarToken: scalarToken}}).then(token => {
|
||||
if (!token) return Promise.reject();
|
||||
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();
|
||||
});
|
||||
|
@ -40,7 +40,7 @@ export class RiotComponent {
|
||||
}
|
||||
|
||||
private init() {
|
||||
this.api.getIntegrations().then(integrations => {
|
||||
this.api.getIntegrations(this.roomId, this.scalarToken).then(integrations => {
|
||||
this.integrations = integrations;
|
||||
let promises = integrations.map(b => this.updateIntegrationState(b));
|
||||
return Promise.all(promises);
|
||||
|
@ -7,18 +7,22 @@ export class ApiService {
|
||||
constructor(private http: Http) {
|
||||
}
|
||||
|
||||
checkScalarToken(token): Promise<boolean> {
|
||||
return this.http.get("/api/v1/scalar/checkToken", {params: {scalar_token: token}})
|
||||
checkScalarToken(scalarToken): Promise<boolean> {
|
||||
return this.http.get("/api/v1/scalar/checkToken", {params: {scalar_token: scalarToken}})
|
||||
.map(res => res.status === 200).toPromise();
|
||||
}
|
||||
|
||||
getIntegrations(): Promise<Integration[]> {
|
||||
return this.http.get("/api/v1/dimension/integrations")
|
||||
getIntegrations(roomId, scalarToken): Promise<Integration[]> {
|
||||
return this.http.get("/api/v1/dimension/integrations/" + roomId, {params: {scalar_token: scalarToken}})
|
||||
.map(res => res.json()).toPromise();
|
||||
}
|
||||
|
||||
removeIntegration(roomId: string, userId: string, scalarToken: string): Promise<any> {
|
||||
return this.http.post("/api/v1/dimension/removeIntegration", {roomId: roomId, userId: userId, scalarToken: scalarToken})
|
||||
return this.http.post("/api/v1/dimension/removeIntegration", {
|
||||
roomId: roomId,
|
||||
userId: userId,
|
||||
scalarToken: scalarToken
|
||||
})
|
||||
.map(res => res.json()).toPromise();
|
||||
}
|
||||
}
|
||||
|
BIN
web/public/img/avatars/rssbot.png
Normal file
BIN
web/public/img/avatars/rssbot.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.9 KiB |
BIN
web/public/img/avatars/travisci.png
Normal file
BIN
web/public/img/avatars/travisci.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 45 KiB |
Loading…
Reference in New Issue
Block a user