Initial commit
This commit is contained in:
commit
78c733398f
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
/node_modules/
|
||||||
|
/.out/
|
||||||
|
deployment.json
|
||||||
|
*.js
|
||||||
|
*.d.ts
|
||||||
|
*.tsbuildinfo
|
9
matrix/plugin.json
Normal file
9
matrix/plugin.json
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"main": "plugin.js",
|
||||||
|
"name": "matrix",
|
||||||
|
"description": "Matrix.org Service for Squeebot 3",
|
||||||
|
"tags": ["service", "matrix"],
|
||||||
|
"version": "1.0.0",
|
||||||
|
"dependencies": ["control?"],
|
||||||
|
"npmDependencies": ["matrix-bot-sdk@0.5.12"]
|
||||||
|
}
|
260
matrix/plugin.ts
Normal file
260
matrix/plugin.ts
Normal file
@ -0,0 +1,260 @@
|
|||||||
|
import {
|
||||||
|
Plugin,
|
||||||
|
Configurable,
|
||||||
|
EventListener,
|
||||||
|
InjectService
|
||||||
|
} from '@squeebot/core/lib/plugin';
|
||||||
|
|
||||||
|
import util from 'util';
|
||||||
|
|
||||||
|
import {
|
||||||
|
MatrixClient,
|
||||||
|
AutojoinRoomsMixin,
|
||||||
|
RichReply,
|
||||||
|
SimpleFsStorageProvider,
|
||||||
|
LogService,
|
||||||
|
LogLevel,
|
||||||
|
} from 'matrix-bot-sdk';
|
||||||
|
|
||||||
|
import { logger } from '@squeebot/core/lib/core';
|
||||||
|
import {
|
||||||
|
EMessageType,
|
||||||
|
Formatter,
|
||||||
|
HTMLFormatter,
|
||||||
|
IMessage,
|
||||||
|
IMessageTarget,
|
||||||
|
Protocol
|
||||||
|
} from '@squeebot/core/lib/types';
|
||||||
|
|
||||||
|
class MatrixMessageAdapter implements IMessage {
|
||||||
|
public time: Date = new Date();
|
||||||
|
public resolved = false;
|
||||||
|
public direct = false;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public type: EMessageType,
|
||||||
|
public data: any,
|
||||||
|
public source: Protocol,
|
||||||
|
public sender: IMessageTarget,
|
||||||
|
public target?: IMessageTarget) {}
|
||||||
|
|
||||||
|
public get fullSenderID(): string {
|
||||||
|
return this.source.fullName + '/' + this.sender.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get fullRoomID(): string {
|
||||||
|
if (!this.target) {
|
||||||
|
return this.source.fullName;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.source.fullName + '/' + this.target.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get text(): string {
|
||||||
|
return this.data.content.body;
|
||||||
|
}
|
||||||
|
|
||||||
|
public resolve(...args: any[]): void {
|
||||||
|
this.resolved = true;
|
||||||
|
this.source.resolve(this, ...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
public reject(error: Error): void {
|
||||||
|
this.resolved = true;
|
||||||
|
this.source.resolve(this, error.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public mention(user: IMessageTarget): string {
|
||||||
|
return `${user.id}:`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MatrixProtocol extends Protocol {
|
||||||
|
public format: Formatter = new HTMLFormatter();
|
||||||
|
public type = 'MatrixProtocol';
|
||||||
|
public nameCache: Map<string, string> = new Map<string, string>();
|
||||||
|
|
||||||
|
private client?: MatrixClient;
|
||||||
|
|
||||||
|
public async handler(roomId: any, event: any): Promise<void> {
|
||||||
|
// Don't handle events that don't have contents (they were probably redacted)
|
||||||
|
if (!this.client || !this.me || !event.content) { return; }
|
||||||
|
|
||||||
|
// Don't handle non-text events
|
||||||
|
if (event.content.msgtype !== 'm.text') { return; }
|
||||||
|
const msg = event.content.body;
|
||||||
|
|
||||||
|
// filter out events sent by the bot itself
|
||||||
|
if (event.sender === this.me.id || !msg) { return; }
|
||||||
|
const senderName = await this.getUserDisplayName(event.sender);
|
||||||
|
const roomName = await this.getRoomDisplayName(roomId);
|
||||||
|
|
||||||
|
const newMessage = new MatrixMessageAdapter(
|
||||||
|
EMessageType.message,
|
||||||
|
event,
|
||||||
|
this,
|
||||||
|
{ id: event.sender, name: senderName, },
|
||||||
|
{ id: roomId, name: roomName, }
|
||||||
|
);
|
||||||
|
this.plugin.stream.emitTo('channel', 'message', newMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getUserDisplayName(id: string): Promise<string> {
|
||||||
|
if (this.nameCache.has(id)) {
|
||||||
|
return this.nameCache.get(id) as string;
|
||||||
|
}
|
||||||
|
const profile = await this.client?.getUserProfile(id);
|
||||||
|
if (!profile || !profile.displayname) {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
this.nameCache.set(id, profile.displayname);
|
||||||
|
return profile.displayname;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getRoomDisplayName(id: string): Promise<string> {
|
||||||
|
if (this.nameCache.has(id)) {
|
||||||
|
return this.nameCache.get(id) as string;
|
||||||
|
}
|
||||||
|
const roomState = await this.client?.getRoomStateEvent(id, 'm.room.name', '');
|
||||||
|
if (!roomState || !roomState.name) {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
this.nameCache.set(id, roomState.name);
|
||||||
|
return roomState.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getSelf(): Promise<void> {
|
||||||
|
if (!this.client) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const id = await this.client.getUserId();
|
||||||
|
const name = await this.getUserDisplayName(id);
|
||||||
|
|
||||||
|
this.me = { id, name };
|
||||||
|
}
|
||||||
|
|
||||||
|
public start(...args: any[]): void {
|
||||||
|
const stpath = `.matrix-${this.config.name}.db.json`;
|
||||||
|
const storage = new SimpleFsStorageProvider(stpath);
|
||||||
|
|
||||||
|
this.client = new MatrixClient(this.config.homeserver, this.config.token, storage);
|
||||||
|
AutojoinRoomsMixin.setupOnClient(this.client);
|
||||||
|
LogService.setLevel(LogLevel.ERROR);
|
||||||
|
|
||||||
|
this.client.on('room.message', (...dargs) =>
|
||||||
|
this.handler(dargs[0], dargs[1])
|
||||||
|
.catch(e => logger.error(e)));
|
||||||
|
|
||||||
|
this.client.start()
|
||||||
|
.then(() => {
|
||||||
|
this.running = true;
|
||||||
|
logger.info('[%s] Protocol "%s" ready', this.plugin.manifest.name, this.config.name);
|
||||||
|
this.getSelf();
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
this.stop(true);
|
||||||
|
logger.error('[%s] Protocol "%s" failed to start',
|
||||||
|
this.plugin.manifest.name, this.config.name);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public stop(force = false): void {
|
||||||
|
if (!this.running && !force) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.client) {
|
||||||
|
this.client.stop();
|
||||||
|
this.client = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.running = false;
|
||||||
|
this.stopped = true;
|
||||||
|
|
||||||
|
if (force) {
|
||||||
|
this.failed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.emit('stopped');
|
||||||
|
}
|
||||||
|
|
||||||
|
public resolve(msg: MatrixMessageAdapter, ...data: any[]): void {
|
||||||
|
let response = util.format(data[0], ...data.slice(1));
|
||||||
|
if (!response || !msg.target) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(data[0])) {
|
||||||
|
try {
|
||||||
|
response = this.format.compose(data[0]);
|
||||||
|
} catch (e) {
|
||||||
|
logger.error('[%s] Failed to compose message:', this.fullName, e.message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.client?.sendMessage(msg.target.id, {
|
||||||
|
msgtype: 'm.text',
|
||||||
|
body: this.format.strip(response),
|
||||||
|
format: 'org.matrix.custom.html',
|
||||||
|
formatted_body: response.replace('\n', '<br />'),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@InjectService(MatrixProtocol)
|
||||||
|
@Configurable({instances: []})
|
||||||
|
class MatrixServicePlugin extends Plugin {
|
||||||
|
initialize(): void {
|
||||||
|
const protoList = this.validateConfiguration();
|
||||||
|
this.startAll(protoList);
|
||||||
|
}
|
||||||
|
|
||||||
|
private startAll(list: any[]): void {
|
||||||
|
for (const ins of list) {
|
||||||
|
const newProto = new MatrixProtocol(this, ins);
|
||||||
|
logger.log('[%s] Starting Matrix service "%s".', this.name, ins.name);
|
||||||
|
this.monitor(newProto);
|
||||||
|
this.service?.use(newProto, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private monitor(proto: Protocol): void {
|
||||||
|
proto.on('running', () => this.emit('protocolNew', proto));
|
||||||
|
proto.on('stopped', () => this.emit('protocolExit', proto));
|
||||||
|
}
|
||||||
|
|
||||||
|
private validateConfiguration(): any[] {
|
||||||
|
if (!this.config.config.instances) {
|
||||||
|
throw new Error('Configuration incomplete!');
|
||||||
|
}
|
||||||
|
|
||||||
|
const instances = this.config.config.instances;
|
||||||
|
const runnables: any[] = [];
|
||||||
|
for (const ins of instances) {
|
||||||
|
if (ins.enabled === false) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ins.name || !ins.token || !ins.homeserver) {
|
||||||
|
throw new Error('Invalid instance configuration!');
|
||||||
|
}
|
||||||
|
|
||||||
|
runnables.push(ins);
|
||||||
|
}
|
||||||
|
|
||||||
|
return runnables;
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventListener('pluginUnload')
|
||||||
|
public unloadEventHandler(plugin: string | Plugin): void {
|
||||||
|
if (plugin === this.name || plugin === this) {
|
||||||
|
this.config.save().then(() =>
|
||||||
|
this.service?.stopAll().then(() =>
|
||||||
|
this.emit('pluginUnloaded', this)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = MatrixServicePlugin;
|
1255
package-lock.json
generated
Normal file
1255
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
18
package.json
Normal file
18
package.json
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"name": "service-matrix",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsc",
|
||||||
|
"watch": "tsc -w"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"@squeebot/core": "^3.1.1",
|
||||||
|
"matrix-bot-sdk": "^0.5.12",
|
||||||
|
"typescript": "^4.1.3"
|
||||||
|
}
|
||||||
|
}
|
10
squeebot.repo.json
Normal file
10
squeebot.repo.json
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"name": "service-matrix",
|
||||||
|
"plugins": [
|
||||||
|
{
|
||||||
|
"name": "matrix",
|
||||||
|
"version": "1.0.0"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"typescript": true
|
||||||
|
}
|
1
tsconfig.json
Normal file
1
tsconfig.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"compilerOptions":{"downlevelIteration":true,"esModuleInterop":true,"experimentalDecorators":true,"forceConsistentCasingInFileNames":true,"skipLibCheck":true,"sourceMap":false,"strict":true,"target":"es5"}}
|
122
tslint.json
Normal file
122
tslint.json
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
{
|
||||||
|
"extends": "tslint:recommended",
|
||||||
|
"rules": {
|
||||||
|
"align": {
|
||||||
|
"options": [
|
||||||
|
"parameters",
|
||||||
|
"statements"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"array-type": false,
|
||||||
|
"arrow-return-shorthand": true,
|
||||||
|
"curly": true,
|
||||||
|
"deprecation": {
|
||||||
|
"severity": "warning"
|
||||||
|
},
|
||||||
|
"eofline": true,
|
||||||
|
"import-spacing": true,
|
||||||
|
"indent": {
|
||||||
|
"options": [
|
||||||
|
"spaces"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"max-classes-per-file": false,
|
||||||
|
"max-line-length": [
|
||||||
|
true,
|
||||||
|
140
|
||||||
|
],
|
||||||
|
"member-ordering": [
|
||||||
|
true,
|
||||||
|
{
|
||||||
|
"order": [
|
||||||
|
"static-field",
|
||||||
|
"instance-field",
|
||||||
|
"static-method",
|
||||||
|
"instance-method"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"no-console": [
|
||||||
|
true,
|
||||||
|
"debug",
|
||||||
|
"info",
|
||||||
|
"time",
|
||||||
|
"timeEnd",
|
||||||
|
"trace"
|
||||||
|
],
|
||||||
|
"no-empty": false,
|
||||||
|
"no-inferrable-types": [
|
||||||
|
true,
|
||||||
|
"ignore-params"
|
||||||
|
],
|
||||||
|
"no-non-null-assertion": false,
|
||||||
|
"no-redundant-jsdoc": true,
|
||||||
|
"no-switch-case-fall-through": true,
|
||||||
|
"no-var-requires": false,
|
||||||
|
"object-literal-key-quotes": [
|
||||||
|
true,
|
||||||
|
"as-needed"
|
||||||
|
],
|
||||||
|
"quotemark": [
|
||||||
|
true,
|
||||||
|
"single"
|
||||||
|
],
|
||||||
|
"semicolon": {
|
||||||
|
"options": [
|
||||||
|
"always"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"space-before-function-paren": {
|
||||||
|
"options": {
|
||||||
|
"anonymous": "never",
|
||||||
|
"asyncArrow": "always",
|
||||||
|
"constructor": "never",
|
||||||
|
"method": "never",
|
||||||
|
"named": "never"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"typedef": [
|
||||||
|
true,
|
||||||
|
"call-signature"
|
||||||
|
],
|
||||||
|
"forin": false,
|
||||||
|
"ban-types": {
|
||||||
|
"function": false
|
||||||
|
},
|
||||||
|
"typedef-whitespace": {
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"call-signature": "nospace",
|
||||||
|
"index-signature": "nospace",
|
||||||
|
"parameter": "nospace",
|
||||||
|
"property-declaration": "nospace",
|
||||||
|
"variable-declaration": "nospace"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"call-signature": "onespace",
|
||||||
|
"index-signature": "onespace",
|
||||||
|
"parameter": "onespace",
|
||||||
|
"property-declaration": "onespace",
|
||||||
|
"variable-declaration": "onespace"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"variable-name": {
|
||||||
|
"options": [
|
||||||
|
"ban-keywords",
|
||||||
|
"check-format",
|
||||||
|
"allow-pascal-case"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"whitespace": {
|
||||||
|
"options": [
|
||||||
|
"check-branch",
|
||||||
|
"check-decl",
|
||||||
|
"check-operator",
|
||||||
|
"check-separator",
|
||||||
|
"check-type",
|
||||||
|
"check-typecast"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user