Merge branch 'travis/irc'

This commit is contained in:
turt2live 2017-06-10 14:58:37 -06:00
commit 03e6350d17
34 changed files with 853 additions and 113 deletions

View file

@ -4,7 +4,7 @@ enabled: false
userId: "@dimension:t2bot.io"
name: "Demo Bot"
about: "A bot that has no functionality. This is just a demonstration on the config."
avatar: "/img/avatars/demobot.png"
avatar: "img/avatars/demobot.png"
hosted:
homeserverUrl: "https://t2bot.io"
accessToken: "your_matrix_access_token_here"

View file

@ -4,7 +4,7 @@ enabled: true
userId: "@neb_giphy:matrix.org"
name: "Giphy"
about: "Use `!giphy query` to find an animated GIF on demand"
avatar: "/img/avatars/giphy.png"
avatar: "img/avatars/giphy.png"
upstream:
type: "vector"
id: "giphy"

View file

@ -4,7 +4,7 @@ enabled: true
userId: "@_neb_google:matrix.org"
name: "Google"
about: "Use `!google image query` to find an image from Google"
avatar: "/img/avatars/google.png"
avatar: "img/avatars/google.png"
upstream:
type: "vector"
id: "google"

View file

@ -4,7 +4,7 @@ enabled: true
userId: "@_neb_guggy:matrix.org"
name: "Guggy"
about: "Use `!guggy sentence` to create an animated GIF from a sentence"
avatar: "/img/avatars/guggy.png"
avatar: "img/avatars/guggy.png"
upstream:
type: "vector"
id: "guggy"

View file

@ -4,7 +4,7 @@ enabled: true
userId: "@_neb_imgur:matrix.org"
name: "Imgur"
about: "Use `!imgur query` to find an image from Imgur"
avatar: "/img/avatars/imgur.png"
avatar: "img/avatars/imgur.png"
upstream:
type: "vector"
id: "imgur"

View file

@ -0,0 +1,10 @@
type: "bridge"
integrationType: "irc"
enabled: true
name: "IRC"
about: "Bridges IRC channels to the room"
avatar: "img/avatars/irc.png"
requirements:
joinRule: 'public'
upstream:
type: "vector"

View file

@ -4,7 +4,7 @@ enabled: false
userId: "@pollbot:t2bot.io"
name: "Poll Bot"
about: "A bot to poll users in a room. Use `!pollhelp` for more information"
avatar: "/img/avatars/pollbot.png"
avatar: "img/avatars/pollbot.png"
hosted:
homeserverUrl: "https://t2bot.io"
accessToken: "your_matrix_access_token_here"

View file

@ -3,6 +3,6 @@ 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"
avatar: "img/avatars/rssbot.png"
upstream:
type: "vector"

View file

@ -4,7 +4,7 @@ enabled: true
userId: "@_neb_wikipedia:matrix.org"
name: "Wikipedia"
about: "Use `!wikipedia query` to find something from Wikipedia"
avatar: "/img/avatars/wikipedia.png"
avatar: "img/avatars/wikipedia.png"
upstream:
type: "vector"
id: "wikipedia"

View file

@ -1,5 +1,121 @@
# Dimension API
This document describes the various endpoints of Dimension and how to interact with the API.
Dimension has its own API that allows for management of integrations in Riot/Matrix.
Coming soon?
## Types of integrations
### Simple Bots
* Can only be in a room or not
* No state information held
### Complex Bots
* Simple Bots that hold state information
### Bridges
* Manage their own state through dedicated API endpoints
## Endpoints
### `GET /api/v1/dimension/integrations/{roomId}?scalar_token=your_token_here`
**Parameters**
* `{roomId}` - The room ID to get integrations for
* `scalar_token` - The scalar (dimension) token to authenticate with
**Example Response**
```
TODO
```
### `DELETE /api/v1/dimension/integrations/{roomId}/{type}/{integrationType}?scalar_token=your_token_here`
**Parameters**
* `{roomId}` - The room ID to remove the integration from
* `{type}` - The integration type (eg: `bot`, `complex-bot`, `bridge`, etc)
* `{integrationType}` - The integration subtype (eg: `irc`, `rssbot`, `giphy`, etc)
* `scalar_token` - The scalar (dimension) token to authenticate with
**Example Response**
```
TODO
```
### `PUT /api/v1/dimension/integrations/{roomId}/{type}/{integrationType}/state`
**Parameters**
* `{roomId}` - The room ID to update the integration state in
* `{type}` - The integration type (eg: `bot`, `complex-bot`, `bridge`, etc)
* `{integrationType}` - The integration subtype (eg: `irc`, `rssbot`, `giphy`, etc)
**Example Body**
```
{
"scalar_token": "your_token_here",
"state": {
// integration specific state goes here
}
}
```
### `GET /api/v1/dimension/integrations/{roomId}/{type}/{integrationType}/state`
**Parameters**
* `{roomId}` - The room ID to get the integration state in
* `{type}` - The integration type (eg: `bot`, `complex-bot`, `bridge`, etc)
* `{integrationType}` - The integration subtype (eg: `irc`, `rssbot`, `giphy`, etc)
**Response**
An object representing the integration-specific state. See the documentation for the desired integration for more information.
## Integration State Information
### Simple Bots
Do not hold state.
### Complex Bots
#### RSS Bot
```
{
// Mutable using state API
"feeds": [
"https://some.domain.com/feed.rss",
"https://some.domain.com/another_feed.rss"
],
// Read only. Controlled by other users.
"immutableFeeds": [
"https://some.domain.com/third_feed.rss",
"https://some.domain.com/fourth_feed.rss"
]
}
```
### Bridges
#### IRC
```
{
// Read only
"availableNetworks": [
{"name": "Freenode", "id": "freenode"},
{"name": "EsperNet", "id": "espernet"},
{"name": "OFTC", "id": "oftc"}
],
// Read only. Use IRC API to mutate
"channels": {
"freenode": [
"#dimensiontesting",
"#dimensiontest"
],
"espernet": [],
"oftc": []
}
}
```

View file

@ -0,0 +1,47 @@
# Dimension IRC Bridge API
As with most bridges, the IRC bridge uses a dedicated set of API endpoints to manage the state of the bridge. The IRC bridge still uses the state API provided by Dimension to report basic state information, but does not allow edits through the regular API. Instead, it is expected that the IRC API be used to mutate the state.
## Getting available networks/bridged channels
Make a call to the Dimension state API: `GET /api/v1/dimension/integrations/{roomId}/bridge/irc/state?scalar_token=...`.
*Example state*
```
{
"availableNetworks": [
{"name": "Freenode", "id": "freenode"},
{"name": "Espernet", "id": "espernet"},
{"name": "OFTC", "id": "oftc"}
],
"channels": {
"freenode": [
"#dimensiontesting",
"#dimensiontest"
],
"espernet": [],
"oftc": []
}
}
```
## Getting the OPs in a channel
IRC API Endpoint: `GET /api/v1/irc/{network}/{channel}/ops?scalar_token=...`. Be sure to encode the channel parameter.
*Example response*
```
["turt2live", "johndoe"]
```
## Linking a new channel
IRC API Endpoint: `PUT /api/v1/irc/{roomId}/channels/{network}/{channel}?op=turt2live&scalar_token=...`. Be sure to encode the channel parameter.
A 200 OK is returned if the request to add the channel was sent. The channel will not appear in the state information until the op has approved the bridge.
## Unlinking a channel
IRC API Endpoint: `DELETE /api/v1/irc/{roomId}/channels/{network}/{channel}?scalar_token=...`. Be sure to encode the channel parameter.
A 200 OK is returned if the delete was successful.

View file

@ -178,4 +178,146 @@ None of these are officially documented, and are subject to change.
}
}
}
```
## GET `/api/bridges/irc/_matrix/provision/querynetworks?scalar_token=...`
**Response**
```
{
"replies": [
{
"rid": "...",
"response": {
"servers": [
{
"bot_user_id": "@appservice-irc:matrix.org",
"desc": "Freenode",
"fields": {
"domain": "chat.freenode.net"
},
"icon": "https:\/\/matrix.org\/_matrix\/media\/v1\/download\/matrix.org\/DHLHpDDgWNNejFmrewvwEAHX",
"network_id": "freenode"
}
]
}
},
{
"rid": "...",
"response": {
"servers": [
{
"bot_user_id": "@mozilla-irc:matrix.org",
"desc": "Moznet",
"fields": {
"domain": "irc.mozilla.org"
},
"icon": "https:\/\/matrix.org\/_matrix\/media\/v1\/download\/matrix.org\/DHLHpDDgWNNejFmrewvwEAHX",
"network_id": "mozilla"
}
]
}
}
]
}
```
## POST `/api/bridges/irc/_matrix/provision/querylink?rid=...&scalar_token=...`
**Body**
```
{
"remote_room_channel": "#dimensiontesting",
"remote_room_server": "chat.freenode.net"
}
```
**Response**
```
{
"replies": [{
"rid": "...",
"response": {
"operators": ["travis-test"]
}
}]
}
```
## POST `/api/bridges/irc/_matrix/provision/link?rid=...&scalar_token=...`
**Body**
```
{
"matrix_room_id": "!JmvocvDuPTYUfuvKgs:t2l.io",
"remote_room_channel": "#dimensiontesting",
"remote_room_server": "chat.freenode.net",
"op_nick": "travis-test",
"key": ""
}
```
**Response**
```
{
"replies": [{
"rid": "...",
"response":{}
}]
}
```
*Note*: This returns 200 OK after sending the request to link. If the link succeeds, `listlinks` will show as such.
## GET `/api/bridges/irc/_matrix/provision/listlinks/{roomId}?scalar_token=...`
**Params**
* `{roomId}` - the matrix room id (ie: `!JmvocvDuPTYUfuvKgs:t2l.io`)
**Response**
```
{
"replies": [
{
"rid": "...",
"response": [
{
"matrix_room_id": "!JmvocvDuPTYUfuvKgs:t2l.io",
"remote_room_channel": "#dimensiontesting",
"remote_room_server": "chat.freenode.net"
}
]
},
{
"rid": "...",
"response": []
}
]
}
```
*Note*: This is called on a timer in Scalar to show when a user has approved a link. Called every few seconds.
## POST `/api/bridges/irc/_matrix/provision/unlink?rid=...&scalar_token=...`
**Body**
```
{
"matrix_room_id": "!JmvocvDuPTYUfuvKgs:t2l.io",
"remote_room_channel": "#dimensiontest",
"remote_room_server": "chat.freenode.net",
"rid": "..."
}
```
**Response**
```
{
"replies": [
{
"rid": "..",
"response": {}
}
]
}
```

219
package-lock.json generated
View file

@ -4,48 +4,57 @@
"lockfileVersion": 1,
"dependencies": {
"@angular/animations": {
"version": "https://registry.npmjs.org/@angular/animations/-/animations-4.1.3.tgz",
"integrity": "sha1-bomh4Pv9bQ6Qvk8q4ZCqxn+DpBE=",
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/@angular/animations/-/animations-4.2.1.tgz",
"integrity": "sha1-TIeIGS8Ux06wKg/I3Dhxd6ux/yQ=",
"dev": true
},
"@angular/common": {
"version": "https://registry.npmjs.org/@angular/common/-/common-4.1.3.tgz",
"integrity": "sha1-58R5HjITHPdMI5QowqZ9qrLu8Bc=",
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/@angular/common/-/common-4.2.1.tgz",
"integrity": "sha1-yzNOO3H00+nzS3rZQsVI0/k238g=",
"dev": true
},
"@angular/compiler": {
"version": "https://registry.npmjs.org/@angular/compiler/-/compiler-4.1.3.tgz",
"integrity": "sha1-0t0whTsM9KVHWLSjFGMsIx+clMM=",
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-4.2.1.tgz",
"integrity": "sha1-jXe0WOBLiHlH2FrczXVG5KmH1LA=",
"dev": true
},
"@angular/core": {
"version": "https://registry.npmjs.org/@angular/core/-/core-4.1.3.tgz",
"integrity": "sha1-KFSY64arfQtvmC+Pn0h+9hABOzU=",
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/@angular/core/-/core-4.2.1.tgz",
"integrity": "sha1-CGUwSz30MEaRm2RvAv3tqFoMRoU=",
"dev": true
},
"@angular/forms": {
"version": "https://registry.npmjs.org/@angular/forms/-/forms-4.1.3.tgz",
"integrity": "sha1-OAq0w6+ExdHXSMKn0EFRx9yOSYI=",
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/@angular/forms/-/forms-4.2.1.tgz",
"integrity": "sha1-rRcs/TBwdDsqeoFh0sdax/W2H2I=",
"dev": true
},
"@angular/http": {
"version": "https://registry.npmjs.org/@angular/http/-/http-4.1.3.tgz",
"integrity": "sha1-650cMCoBcoFfmlczENm+C964Ra4=",
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/@angular/http/-/http-4.2.1.tgz",
"integrity": "sha1-2ebjSMh1PTGMdh1/S02fBf7EDvk=",
"dev": true
},
"@angular/platform-browser": {
"version": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-4.1.3.tgz",
"integrity": "sha1-T6HbURndF4sxXdrlsym+4akypb0=",
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-4.2.1.tgz",
"integrity": "sha1-HwHU0x+jkCEYJiZs9uOj9imGDx0=",
"dev": true
},
"@angular/platform-browser-dynamic": {
"version": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-4.1.3.tgz",
"integrity": "sha1-PBP9z1kdSH9u/cHUaRPygMbYwuw=",
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-4.2.1.tgz",
"integrity": "sha1-CDLSG6NIHZH0BWxqlUKfn892Ukw=",
"dev": true
},
"@angular/router": {
"version": "https://registry.npmjs.org/@angular/router/-/router-4.1.3.tgz",
"integrity": "sha1-3a/UaufMyLH3SQT/tF85TkRiUhY=",
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/@angular/router/-/router-4.2.1.tgz",
"integrity": "sha1-tkoNh+XNAi89PFLsi3urkLLj8Zg=",
"dev": true
},
"@angularclass/hmr": {
@ -59,7 +68,8 @@
"dev": true
},
"@ng-bootstrap/ng-bootstrap": {
"version": "https://registry.npmjs.org/@ng-bootstrap/ng-bootstrap/-/ng-bootstrap-1.0.0-alpha.26.tgz",
"version": "1.0.0-alpha.26",
"resolved": "https://registry.npmjs.org/@ng-bootstrap/ng-bootstrap/-/ng-bootstrap-1.0.0-alpha.26.tgz",
"integrity": "sha1-89nha1aC7CDts/E5fT1FpXbz9qQ=",
"dev": true
},
@ -68,8 +78,9 @@
"integrity": "sha1-sC0QqwKOKSisWSoFGqpJgaGUHQM="
},
"@types/node": {
"version": "https://registry.npmjs.org/@types/node/-/node-7.0.22.tgz",
"integrity": "sha1-RZP02Ci91hKSlHjqQMZ7T0A8olU=",
"version": "7.0.29",
"resolved": "https://registry.npmjs.org/@types/node/-/node-7.0.29.tgz",
"integrity": "sha512-+8JrLZny/uR+d/jLK9eaV63buRM7X/gNzQk57q76NS4KNKLSKOmxJYFIlwuP2zDvA7wqZj05POPhSd9Z1hYQpQ==",
"dev": true
},
"abbrev": {
@ -533,6 +544,11 @@
"integrity": "sha1-TnPdCen7lxzDhnDF3O2cGJZIHMY=",
"dev": true
},
"cls-bluebird": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/cls-bluebird/-/cls-bluebird-2.0.1.tgz",
"integrity": "sha1-wlmkgK4CwOUGE0MHuxPbMERu4uc="
},
"co": {
"version": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
"integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ="
@ -720,17 +736,20 @@
"dev": true
},
"css-loader": {
"version": "https://registry.npmjs.org/css-loader/-/css-loader-0.28.3.tgz",
"integrity": "sha1-n9XguMQFtt+Se6EQOIcBXTYGQM4=",
"version": "0.28.4",
"resolved": "https://registry.npmjs.org/css-loader/-/css-loader-0.28.4.tgz",
"integrity": "sha1-bPNXkZLONV6LONX0Ldeh8uyJjQ8=",
"dev": true,
"dependencies": {
"json5": {
"version": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz",
"version": "0.5.1",
"resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz",
"integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=",
"dev": true
},
"loader-utils": {
"version": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.1.0.tgz",
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.1.0.tgz",
"integrity": "sha1-yYrvSIvM7aL/teLeZG1qdUQp9c0=",
"dev": true
}
@ -920,10 +939,6 @@
"version": "https://registry.npmjs.org/dotenv/-/dotenv-2.0.0.tgz",
"integrity": "sha1-vXWcNXqqcDZeAclrewvsCKbg2Uk="
},
"dottie": {
"version": "https://registry.npmjs.org/dottie/-/dottie-1.1.1.tgz",
"integrity": "sha1-RcKj9IvWUo7u0memmoSOqspvqmo="
},
"ecc-jsbn": {
"version": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz",
"integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=",
@ -962,6 +977,11 @@
"integrity": "sha1-blwtClYhtdra7O+AuQ7ftc13cvA=",
"dev": true
},
"env-cmd": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/env-cmd/-/env-cmd-5.1.0.tgz",
"integrity": "sha1-AjbbOTw/AzAFIE/NCpLuQHI6nJ4="
},
"errno": {
"version": "https://registry.npmjs.org/errno/-/errno-0.1.4.tgz",
"integrity": "sha1-uJbiOp5ei6M4cfyZar02NfyaHH0=",
@ -1089,17 +1109,20 @@
"dev": true
},
"file-loader": {
"version": "https://registry.npmjs.org/file-loader/-/file-loader-0.11.1.tgz",
"integrity": "sha1-azKO4SNKcp5OR9Njdd1tNcDh24Q=",
"version": "0.11.2",
"resolved": "https://registry.npmjs.org/file-loader/-/file-loader-0.11.2.tgz",
"integrity": "sha512-N+uhF3mswIFeziHQjGScJ/yHXYt3DiLBeC+9vWW+WjUBiClMSOlV1YrXQi+7KM2aA3Rn4Bybgv+uXFQbfkzpvg==",
"dev": true,
"dependencies": {
"json5": {
"version": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz",
"version": "0.5.1",
"resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz",
"integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=",
"dev": true
},
"loader-utils": {
"version": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.1.0.tgz",
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.1.0.tgz",
"integrity": "sha1-yYrvSIvM7aL/teLeZG1qdUQp9c0=",
"dev": true
}
@ -1903,10 +1926,6 @@
"integrity": "sha1-hHIkZ3rbiHDWeSV+0ziP22HkAQU=",
"dev": true
},
"generic-pool": {
"version": "https://registry.npmjs.org/generic-pool/-/generic-pool-2.4.2.tgz",
"integrity": "sha1-iGvFvwvrfblugby7oHiBjeWmJoM="
},
"get-caller-file": {
"version": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.2.tgz",
"integrity": "sha1-9wLmMSfn4jHBYKgMFVSstw1QR+U=",
@ -2137,6 +2156,26 @@
"integrity": "sha1-Bupvg2ead0njhs/h/oEq5dsiPe0=",
"dev": true
},
"icss-utils": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-2.1.0.tgz",
"integrity": "sha1-g/Cg7DeL8yRheLbCrZE28TWxyWI=",
"dev": true,
"dependencies": {
"postcss": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.1.tgz",
"integrity": "sha1-AA29H47vIXqjaLmiEsX8QLKo8/I=",
"dev": true
},
"supports-color": {
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz",
"integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=",
"dev": true
}
}
},
"ieee754": {
"version": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.8.tgz",
"integrity": "sha1-vjPUCsEO8ZJnAfbwii2G+/0a0+Q=",
@ -2208,6 +2247,11 @@
"integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=",
"dev": true
},
"is-bluebird": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/is-bluebird/-/is-bluebird-1.0.2.tgz",
"integrity": "sha1-CWQ5Bg9KpBGr7hkUOoTWpVNG1uI="
},
"is-buffer": {
"version": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.5.tgz",
"integrity": "sha1-Hzsm72E7IUuIy8ojzGwB2Hlh7sw=",
@ -2531,8 +2575,9 @@
"dev": true
},
"matrix-js-sdk": {
"version": "https://registry.npmjs.org/matrix-js-sdk/-/matrix-js-sdk-0.7.8.tgz",
"integrity": "sha1-TUZSCMvzZCZzw2/l8ZFhHFkW8vk="
"version": "0.7.10",
"resolved": "https://registry.npmjs.org/matrix-js-sdk/-/matrix-js-sdk-0.7.10.tgz",
"integrity": "sha1-VPo8xuOwqlkkMOmEnuVl2vfGQEk="
},
"media-typer": {
"version": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
@ -2779,11 +2824,6 @@
"integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=",
"dev": true
},
"olm": {
"version": "https://matrix.org/packages/npm/olm/olm-2.2.1.tgz",
"integrity": "sha1-Xl21DQoUK3x6BlDZs9isw9N+aXs=",
"optional": true
},
"on-finished": {
"version": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
"integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc="
@ -3815,12 +3855,39 @@
}
},
"sequelize": {
"version": "https://registry.npmjs.org/sequelize/-/sequelize-3.30.4.tgz",
"integrity": "sha1-vaLfHjGFSwmeQUmhEen8Clyh0aQ=",
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/sequelize/-/sequelize-4.0.0.tgz",
"integrity": "sha512-sKJHt36Leyzlhsy2g8b2Q5DQxM4F8aIVFOohjnIC6d6rtutWmLjhW/7H2ZEqG8n/3hbK1mfSBnKqZCO7+1sJYA==",
"dependencies": {
"lodash": {
"version": "https://registry.npmjs.org/lodash/-/lodash-4.12.0.tgz",
"integrity": "sha1-K9bcRqBA9Z5obJcu0h2T3FkFMlg="
"@types/node": {
"version": "6.0.78",
"resolved": "https://registry.npmjs.org/@types/node/-/node-6.0.78.tgz",
"integrity": "sha512-+vD6E8ixntRzzZukoF3uP1iV+ZjVN3koTcaeK+BEoc/kSfGbLDIGC7RmCaUgVpUfN6cWvfczFRERCyKM9mkvXg=="
},
"dottie": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/dottie/-/dottie-2.0.0.tgz",
"integrity": "sha1-2hkZgci41xPKARXViYzzl8Lw3dA="
},
"generic-pool": {
"version": "3.1.7",
"resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.1.7.tgz",
"integrity": "sha1-2sIrLHp6BOQXMvfY0tJaMDyI9mI="
},
"inflection": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/inflection/-/inflection-1.10.0.tgz",
"integrity": "sha1-W//LEZetPoEFD44X4hZoCH7p6y8="
},
"validator": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/validator/-/validator-6.3.0.tgz",
"integrity": "sha1-R84j7Y1Ord+p1LjvAHG2zxB418g="
},
"wkx": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/wkx/-/wkx-0.4.1.tgz",
"integrity": "sha1-L8FxtenLVcYlb+9L3h8hvkE77+4="
}
}
},
@ -3877,8 +3944,9 @@
"integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM="
},
"shelljs": {
"version": "https://registry.npmjs.org/shelljs/-/shelljs-0.7.7.tgz",
"integrity": "sha1-svXHfvlxSPS09uImguELuoZnz/E=",
"version": "0.7.8",
"resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.7.8.tgz",
"integrity": "sha1-3svPh0sNHl+3LhSxZKloMEjprLM=",
"dev": true
},
"shimmer": {
@ -4018,17 +4086,20 @@
"integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo="
},
"style-loader": {
"version": "https://registry.npmjs.org/style-loader/-/style-loader-0.18.1.tgz",
"integrity": "sha1-avyolTyEKDDl4tyEeWMJiAqX9+g=",
"version": "0.18.2",
"resolved": "https://registry.npmjs.org/style-loader/-/style-loader-0.18.2.tgz",
"integrity": "sha512-WPpJPZGUxWYHWIUMNNOYqql7zh85zGmr84FdTVWq52WTIkqlW9xSxD3QYWi/T31cqn9UNSsietVEgGn2aaSCzw==",
"dev": true,
"dependencies": {
"json5": {
"version": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz",
"version": "0.5.1",
"resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz",
"integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=",
"dev": true
},
"loader-utils": {
"version": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.1.0.tgz",
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.1.0.tgz",
"integrity": "sha1-yYrvSIvM7aL/teLeZG1qdUQp9c0=",
"dev": true
}
@ -4126,9 +4197,18 @@
"dev": true
},
"tslint": {
"version": "https://registry.npmjs.org/tslint/-/tslint-5.3.2.tgz",
"integrity": "sha1-5WRZ+wlacwfxA7hAUhdPXju+9u0=",
"dev": true
"version": "5.4.3",
"resolved": "https://registry.npmjs.org/tslint/-/tslint-5.4.3.tgz",
"integrity": "sha1-dhyEArgONHt3M6BDkKdXslNYBGc=",
"dev": true,
"dependencies": {
"tsutils": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.4.0.tgz",
"integrity": "sha1-rUzm26Dlo+2934Ymt8oEB4IYn+o=",
"dev": true
}
}
},
"tslint-loader": {
"version": "https://registry.npmjs.org/tslint-loader/-/tslint-loader-3.5.3.tgz",
@ -4147,11 +4227,6 @@
}
}
},
"tsutils": {
"version": "https://registry.npmjs.org/tsutils/-/tsutils-2.2.0.tgz",
"integrity": "sha1-IYYUZX8hxnfkU2tLp12vjrzhs2c=",
"dev": true
},
"tty-browserify": {
"version": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz",
"integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=",
@ -4175,8 +4250,9 @@
"integrity": "sha1-yrEPtJCeRByChC6v4a1kbIGARBA="
},
"typescript": {
"version": "https://registry.npmjs.org/typescript/-/typescript-2.3.3.tgz",
"integrity": "sha1-ljnzw7QBSOjKl/4IpR3RiRu2viI=",
"version": "2.3.4",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-2.3.4.tgz",
"integrity": "sha1-PTgyGCgjHkNPKHUUlZw3qCtin0I=",
"dev": true
},
"uglify-js": {
@ -4291,10 +4367,6 @@
"integrity": "sha1-KAS6vnEq0zeUWaz74kdGqywwP7w=",
"dev": true
},
"validator": {
"version": "https://registry.npmjs.org/validator/-/validator-5.7.0.tgz",
"integrity": "sha1-eoelgUa2laxIYHEUHAxJ1n2gXlw="
},
"vary": {
"version": "https://registry.npmjs.org/vary/-/vary-1.1.1.tgz",
"integrity": "sha1-Z1Neu2lMHVIldFeYRmUyP1h+jTc="
@ -4474,10 +4546,6 @@
}
}
},
"wkx": {
"version": "https://registry.npmjs.org/wkx/-/wkx-0.2.0.tgz",
"integrity": "sha1-dsJPFqzQzY+TzTSqMx4PeWElboQ="
},
"wordwrap": {
"version": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz",
"integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc="
@ -4535,8 +4603,9 @@
}
},
"zone.js": {
"version": "https://registry.npmjs.org/zone.js/-/zone.js-0.8.11.tgz",
"integrity": "sha1-dCvvsX+8SaVxcSuMfYfljKJv2IY=",
"version": "0.8.12",
"resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.8.12.tgz",
"integrity": "sha1-hv9QU8mK7CkaC/S7rFAdaUoFz7s=",
"dev": true
}
}

View file

@ -22,7 +22,7 @@
"express": "^4.15.2",
"js-yaml": "^3.8.2",
"lodash": "^4.17.4",
"matrix-js-sdk": "^0.7.8",
"matrix-js-sdk": "^0.7.10",
"moment": "^2.18.1",
"random-string": "^0.2.0",
"request": "^2.81.0",
@ -31,19 +31,19 @@
"winston": "^2.3.1"
},
"devDependencies": {
"@angular/animations": "^4.1.3",
"@angular/common": "4.1.3",
"@angular/compiler": "4.1.3",
"@angular/core": "4.1.3",
"@angular/forms": "4.1.3",
"@angular/http": "4.1.3",
"@angular/platform-browser": "4.1.3",
"@angular/platform-browser-dynamic": "4.1.3",
"@angular/router": "4.1.3",
"@angular/animations": "^4.2.0",
"@angular/common": "^4.2.1",
"@angular/compiler": "^4.2.1",
"@angular/core": "^4.2.1",
"@angular/forms": "^4.2.1",
"@angular/http": "^4.2.1",
"@angular/platform-browser": "^4.2.1",
"@angular/platform-browser-dynamic": "^4.2.1",
"@angular/router": "^4.2.1",
"@angularclass/hmr": "^1.2.2",
"@angularclass/hmr-loader": "^3.0.2",
"@ng-bootstrap/ng-bootstrap": "^1.0.0-alpha.22",
"@types/node": "^7.0.18",
"@ng-bootstrap/ng-bootstrap": "^1.0.0-alpha.26",
"@types/node": "^7.0.29",
"angular2-modal": "^2.0.3",
"angular2-template-loader": "^0.6.2",
"angular2-toaster": "^4.0.0",
@ -53,10 +53,10 @@
"codelyzer": "^3.0.1",
"copy-webpack-plugin": "^4.0.1",
"core-js": "^2.4.1",
"css-loader": "^0.28.0",
"css-loader": "^0.28.4",
"cssnano": "^3.10.0",
"extract-text-webpack-plugin": "^2.1.2",
"file-loader": "^0.11.1",
"file-loader": "^0.11.2",
"html-loader": "^0.4.5",
"html-webpack-plugin": "^2.28.0",
"jquery": "^3.2.1",
@ -71,14 +71,14 @@
"rimraf": "^2.6.1",
"rxjs": "^5.2.0",
"sass-loader": "^6.0.3",
"shelljs": "^0.7.0",
"style-loader": "^0.18.1",
"shelljs": "^0.7.8",
"style-loader": "^0.18.2",
"ts-helpers": "^1.1.2",
"tslint": "^5.2.0",
"tslint": "^5.4.3",
"tslint-loader": "^3.4.3",
"typescript": "^2.2.2",
"typescript": "^2.3.4",
"url-loader": "^0.5.8",
"webpack": "^2.3.2",
"zone.js": "^0.8.5"
"zone.js": "^0.8.12"
}
}

View file

@ -50,14 +50,15 @@ class DimensionApi {
var promises = [];
_.forEach(integrations, integration => {
promises.push(this._getIntegration(integration, roomId, scalarToken).then(builtIntegration => {
return builtIntegration.getUserId().then(userId => {
integration.userId = userId;
return builtIntegration.getState();
}).then(state => {
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;
});
}));
});

View file

@ -0,0 +1,30 @@
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);
}
/**
* Registers the API routes for this bridge with the given app.
* @param app the app to register the routes on
*/
registerApi(app) {
// nothing
}
/*override*/
getUserId() {
return null; // bridges don't have bot users we care about
}
}
module.exports = Bridge;

View file

@ -2,16 +2,21 @@ 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 mapping = {
"complex-bot": {
"rss": RSSFactory
},
"bridge": {
"irc": IRCFactory
}
};
var defaultFactories = {
"complex-bot": null,
"bot": SimpleBotFactory
"bot": SimpleBotFactory,
"bridge": null
};
module.exports = {

View file

@ -0,0 +1,44 @@
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.");
}
}
module.exports = IRCBridge;

View file

@ -0,0 +1,12 @@
var IRCBridge = require("./IRCBridge");
var VectorIrcBackbone = require("./VectorIrcBackbone");
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 VectorIrcBackbone(roomId, upstreamToken);
return new IRCBridge(integrationConfig, backbone);
});
} else throw new Error("Unsupported config");
};

View file

@ -0,0 +1,29 @@
/**
* 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({});
}
}
module.exports = StubbedIrcBackbone;

View file

@ -0,0 +1,69 @@
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.id].push(link.channel);
}
return container;
});
}
_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;

View file

@ -82,7 +82,7 @@ class VectorScalarClient {
}
/**
* Gets information on
* 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
@ -99,6 +99,66 @@ class VectorScalarClient {
});
}
/**
* 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;
});
}
_do(method, endpoint, qs = null, body = null) {
var url = config.get("upstreams.vector") + endpoint;

View file

@ -19,6 +19,7 @@ import { IntegrationService } from "./shared/integration.service";
import { BootstrapModalModule } from "angular2-modal/plugins/bootstrap";
import { ModalModule } from "angular2-modal";
import { RssConfigComponent } from "./configs/rss/rss-config.component";
import { IrcConfigComponent } from "./configs/irc/irc-config.component";
@NgModule({
imports: [
@ -40,6 +41,7 @@ import { RssConfigComponent } from "./configs/rss/rss-config.component";
IntegrationComponent,
ScalarCloseComponent,
RssConfigComponent,
IrcConfigComponent,
// Vendor
],
@ -53,6 +55,7 @@ import { RssConfigComponent } from "./configs/rss/rss-config.component";
bootstrap: [AppComponent],
entryComponents: [
RssConfigComponent,
IrcConfigComponent,
]
})
export class AppModule {

View file

@ -0,0 +1,10 @@
<div class="config-wrapper">
<img src="/img/close.svg" (click)="dialog.close()" class="close-icon">
<div class="config-header">
<img src="/img/avatars/irc.png">
<h4>Configure IRC Bridge</h4>
</div>
<div class="config-content">
<pre>{{ integration | json }}</pre>
</div>
</div>

View file

@ -0,0 +1 @@
// component styles are encapsulated and only applied to their components

View file

@ -0,0 +1,25 @@
import { Component } from "@angular/core";
import { IRCIntegration } from "../../shared/models/integration";
import { ModalComponent, DialogRef } from "angular2-modal";
import { ConfigModalContext } from "../../integration/integration.component";
@Component({
selector: 'my-irc-config',
templateUrl: './irc-config.component.html',
styleUrls: ['./irc-config.component.scss', './../config.component.scss'],
})
export class IrcConfigComponent implements ModalComponent<ConfigModalContext> {
public integration: IRCIntegration;
private roomId: string;
private scalarToken: string;
constructor(public dialog: DialogRef<ConfigModalContext>) {// ,
// private toaster: ToasterService,
// private api: ApiService) {
this.integration = <IRCIntegration>dialog.context.integration;
this.roomId = dialog.context.roomId;
this.scalarToken = dialog.context.scalarToken;
}
}

View file

@ -3,9 +3,12 @@
<div class="title">
<b>{{ integration.name }}</b>
<div style="display: flex;">
<div class="switch">
<div class="switch" *ngIf="integration.type !== 'bridge'">
<ui-switch [checked]="integration.isEnabled" size="small" [disabled]="integration.isBroken" (change)="update()"></ui-switch>
</div>
<div class="switch" *ngIf="integration.type == 'bridge' && !integration.isEnabled">
<i class="fa fa-warning text-warning" ngbTooltip="{{ integration.bridgeError }}"></i>
</div>
<div class="toolbar">
<i class="fa fa-question-circle text-info" ngbTooltip="{{integration.about}}" *ngIf="integration.about"></i>
<i class="fa fa-cog text-info config-icon" (click)="configureIntegration()" *ngIf="integration.isEnabled && integration.hasConfig"></i>

View file

@ -54,6 +54,26 @@ export class RiotComponent {
private updateIntegrationState(integration: Integration) {
integration.hasConfig = IntegrationService.hasConfig(integration);
if (integration.requirements) {
let keys = _.keys(integration.requirements);
let promises = [];
for (let key of keys) {
let requirement = this.checkRequirement(integration, key);
promises.push(requirement);
}
return Promise.all(promises).then(() => {
integration.isEnabled = true;
integration.isBroken = false;
}, error => {
console.error(error);
integration.bridgeError = error.message || error;
integration.isEnabled = false;
integration.isBroken = false;
});
}
return this.scalar.getMembershipState(this.roomId, integration.userId).then(payload => {
integration.isBroken = false;
@ -70,6 +90,24 @@ export class RiotComponent {
});
}
private checkRequirement(integration: Integration, key: string) {
let requirement = integration.requirements[key];
switch (key) {
case "joinRule":
return this.scalar.getJoinRule(this.roomId).then(payload => {
if (!payload.response) {
return Promise.reject("Could not communicate with Riot");
}
return payload.response.join_rule === requirement
? Promise.resolve()
: Promise.reject(new Error("The room must be " + requirement + " to use this integration."));
});
default:
return Promise.reject(new Error("Requirement '" + key + "' not found"));
}
}
public updateIntegration(integration: Integration) {
let promise = null;

View file

@ -2,6 +2,7 @@ import { Injectable } from "@angular/core";
import { Integration } from "./models/integration";
import { RssConfigComponent } from "../configs/rss/rss-config.component";
import { ContainerContent } from "angular2-modal";
import { IrcConfigComponent } from "../configs/irc/irc-config.component";
@Injectable()
export class IntegrationService {
@ -10,12 +11,18 @@ export class IntegrationService {
"bot": true,
"complex-bot": {
"rss": true
},
"bridge": {
"irc": true
}
};
private static components = {
"complex-bot": {
"rss": RssConfigComponent
},
"bridge": {
"irc": IrcConfigComponent
}
};

View file

@ -8,9 +8,16 @@ export interface Integration {
isEnabled: boolean;
isBroken: boolean;
hasConfig: boolean;
requirements?: any; // nullable
bridgeError: string; // nullable
}
export interface RSSIntegration extends Integration {
feeds: string[];
immutableFeeds: {url: string, ownerId: string}[];
}
export interface IRCIntegration extends Integration {
availableNetworks: {name: string, id: string};
channels: {[networkId: string]: string[]};
}

View file

@ -24,4 +24,10 @@ export interface MembershipStateResponse extends ScalarUserResponse {
avatar_url: string;
displayname: string;
};
}
export interface JoinRuleStateResponse extends ScalarRoomResponse {
response: {
join_rule: string;
};
}

View file

@ -1,6 +1,6 @@
import { Injectable } from "@angular/core";
import * as randomString from "random-string";
import { MembershipStateResponse, ScalarSuccessResponse } from "./models/scalar_responses";
import { MembershipStateResponse, ScalarSuccessResponse, JoinRuleStateResponse } from "./models/scalar_responses";
@Injectable()
export class ScalarService {
@ -30,6 +30,12 @@ export class ScalarService {
});
}
public getJoinRule(roomId: string): Promise<JoinRuleStateResponse> {
return this.callAction("join_rules_state", {
room_id: roomId
});
}
public close(): void {
this.callAction("close_scalar", {});
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

View file

@ -122,7 +122,7 @@ module.exports = function () {
config.devServer = {
contentBase: './web/public',
historyApiFallback: true,
quiet: true,
quiet: false,
stats: 'minimal',
proxy: {
'/api': {