From 978ee83eeea086ac9a64cc0948ae888ff356b804 Mon Sep 17 00:00:00 2001 From: John Livingston Date: Tue, 7 Dec 2021 13:14:01 +0100 Subject: [PATCH] Demo Bot: first proof of concept. --- bots/.eslintrc.json | 40 ++++++++++ bots/bots.ts | 78 ++++++++++++++++++ bots/tsconfig.json | 26 ++++++ package-lock.json | 115 ++++++++++++++++++++++++++- package.json | 4 +- server/lib/diagnostic/prosody.ts | 4 +- server/lib/prosody/config.ts | 54 +++++++++---- server/lib/prosody/config/content.ts | 6 +- server/lib/prosody/config/paths.ts | 5 +- 9 files changed, 307 insertions(+), 25 deletions(-) create mode 100644 bots/.eslintrc.json create mode 100644 bots/bots.ts create mode 100644 bots/tsconfig.json diff --git a/bots/.eslintrc.json b/bots/.eslintrc.json new file mode 100644 index 00000000..de818741 --- /dev/null +++ b/bots/.eslintrc.json @@ -0,0 +1,40 @@ +{ + "root": true, + "env": { + "browser": false, + "es6": true + }, + "extends": [ + "standard-with-typescript" + ], + "globals": {}, + "parser": "@typescript-eslint/parser", + "parserOptions": { + "ecmaVersion": 2018, + "project": [ + "./bots/tsconfig.json" + ] + }, + "plugins": [ + "@typescript-eslint" + ], + "ignorePatterns": [], + "rules": { + "@typescript-eslint/no-unused-vars": [2, {"argsIgnorePattern": "^_"}], + "@typescript-eslint/no-floating-promises": "error", + "@typescript-eslint/no-misused-promises": "error", + "@typescript-eslint/no-var-requires": "off", + "@typescript-eslint/strict-boolean-expressions": "off", + "@typescript-eslint/return-await": [2, "in-try-catch"], // FIXME: correct? + "@typescript-eslint/no-invalid-void-type": "off", + "@typescript-eslint/triple-slash-reference": "off", + "max-len": [ + "error", + { + "code": 120, + "comments": 120 + } + ], + "no-unused-vars": "off" + } +} diff --git a/bots/bots.ts b/bots/bots.ts new file mode 100644 index 00000000..5bc547e8 --- /dev/null +++ b/bots/bots.ts @@ -0,0 +1,78 @@ +import * as path from 'path' + +let demoBotConfigFile = process.argv[2] +if (!demoBotConfigFile) { + throw new Error('Missing parameter: the demobot configuration file path') +} +demoBotConfigFile = path.resolve(demoBotConfigFile) + +// Not necessary, but just in case: perform some path checking... +function checkBotConfigFilePath (configPath: string): void { + const parts = configPath.split(path.sep) + if (!parts.includes('peertube-plugin-livechat')) { + // Indeed, the path should contain the plugin name + // (/var/www/peertube/storage/plugins/data/peertube-plugin-livechat/...) + throw new Error('demobot configuration file path seems invalid (not in peertube-plugin-livechat folder).') + } + if (parts[parts.length - 1] !== 'demobot.js') { + throw new Error('demobot configuration file path seems invalid (filename is not demobot.js).') + } +} +checkBotConfigFilePath(demoBotConfigFile) + +const demoBotConf = require(demoBotConfigFile).getConf() +if (!demoBotConf || !demoBotConf.UUIDs || !demoBotConf.UUIDs.length) { + process.exit(0) +} + +const { component, xml } = require('@xmpp/component') +const xmpp = component({ + service: demoBotConf.service, + domain: demoBotConf.domain, + password: demoBotConf.password +}) +const roomId = `${demoBotConf.UUIDs[0] as string}@${demoBotConf.mucDomain as string}` + +xmpp.on('error', (err: any) => { + console.error(err) +}) + +xmpp.on('offline', () => { + console.log('offline') +}) + +xmpp.on('stanza', async (stanza: any) => { + console.log('stanza received' + (stanza?.toString ? ': ' + (stanza.toString() as string) : '')) + // if (stanza.is('message')) { + // console.log('stanza was a message: ' + (stanza.toString() as string)) + // } +}) + +xmpp.on('online', async (address: any) => { + console.log('Online with address: ' + JSON.stringify(address)) + + const presence = xml( + 'presence', + { + from: address.toString(), + to: roomId + '/DemoBot' + }, + xml('x', { + xmlns: 'http://jabber.org/protocol/muc' + }) + ) + console.log('Sending presence...: ' + (presence.toString() as string)) + await xmpp.send(presence) + + setTimeout(() => { + const message = xml( + 'message', + { type: 'groupchat', to: roomId, from: address.toString() }, + xml('body', {}, 'Hello world') + ) + console.log('Sending message...: ' + (message.toString() as string)) + xmpp.send(message) + }, 1000) +}) + +xmpp.start().catch(console.error) diff --git a/bots/tsconfig.json b/bots/tsconfig.json new file mode 100644 index 00000000..5872a594 --- /dev/null +++ b/bots/tsconfig.json @@ -0,0 +1,26 @@ +{ + "extends": "@tsconfig/node12/tsconfig.json", + "compilerOptions": { + "moduleResolution": "node", // Tell tsc to look in node_modules for modules + "strict": true, // That implies alwaysStrict, noImplicitAny, noImplicitThis + + "alwaysStrict": true, // should already be true because of strict:true + "noImplicitAny": true, // should already be true because of strict:true + "noImplicitThis": true, // should already be true because of strict:true + "noImplicitReturns": true, + "strictBindCallApply": true, // should already be true because of strict:true + "noUnusedLocals": true, + + "removeComments": true, + "sourceMap": true, + + "baseUrl": "./", + "outDir": "../dist/", + "paths": {} + }, + "include": [ + "./**/*", + "../shared/**/*" + ], + "exclude": [] +} diff --git a/package-lock.json b/package-lock.json index 844d9fab..130dea94 100644 --- a/package-lock.json +++ b/package-lock.json @@ -690,6 +690,108 @@ "@xtuc/long": "4.2.2" } }, + "@xmpp/component": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@xmpp/component/-/component-0.13.0.tgz", + "integrity": "sha512-xl2dCJiM7GH98ncdU86JjiKzGfP7ykTJZW6iSKiAaniUKDRixLDMaKM/X0CR+4sXm3rqvRUTYyzndCmCi8CUpg==", + "requires": { + "@xmpp/component-core": "^0.13.0", + "@xmpp/iq": "^0.13.0", + "@xmpp/middleware": "^0.13.0", + "@xmpp/reconnect": "^0.13.0" + } + }, + "@xmpp/component-core": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@xmpp/component-core/-/component-core-0.13.0.tgz", + "integrity": "sha512-/stz9Eo11Q79z1lJ0yWNv0FsSf8AAYko6ctRjHRlHEGkLhQDw959v4k5eB82YrtApoHLoHCxtJMxDwwWAtlprA==", + "requires": { + "@xmpp/connection-tcp": "^0.13.0", + "@xmpp/jid": "^0.13.0", + "@xmpp/xml": "^0.13.0" + } + }, + "@xmpp/connection": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@xmpp/connection/-/connection-0.13.0.tgz", + "integrity": "sha512-8aLM+XsHYfI/Q7DsOAClEgA825eHIztCZVP4z+diAYuyhyN1P0e4en1dQjK7QOVvOg+DsA8qTcZ8C0b3pY7EFw==", + "requires": { + "@xmpp/error": "^0.13.0", + "@xmpp/events": "^0.13.0", + "@xmpp/jid": "^0.13.0", + "@xmpp/xml": "^0.13.0" + } + }, + "@xmpp/connection-tcp": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@xmpp/connection-tcp/-/connection-tcp-0.13.0.tgz", + "integrity": "sha512-qsP+/ILYWA6D5MrZfS/7nNtaO469EAPAJ7P9gNA9hj5ZOu5mX6LwGecSBegpnXXP5b378iSlqOLskkVDUmSahg==", + "requires": { + "@xmpp/connection": "^0.13.0", + "@xmpp/xml": "^0.13.0" + } + }, + "@xmpp/error": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@xmpp/error/-/error-0.13.0.tgz", + "integrity": "sha512-cTyGMrXzuEulRiG29vvHhaU0vTpOxDQS49dyUAW+2Rj5ex9OXXGiWWbJDodEO9B/rHiUXr1U63818Yv4lxZJBA==" + }, + "@xmpp/events": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@xmpp/events/-/events-0.13.0.tgz", + "integrity": "sha512-G+9NczMWWOawn62r1JIv/N413G2biI+hURiN4iH74FGvjagXwassUeJgPnDUEFp2FTKX3dIrJDkXH49ZcFuo/g==", + "requires": { + "events": "^3.3.0" + } + }, + "@xmpp/id": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@xmpp/id/-/id-0.13.0.tgz", + "integrity": "sha512-6m9KAreJ13/FnonnLCeK1a6jJx8PqpdLZfRWxUfQu1Wg4nAlgYrcDSYny+/BUm5ICkAEILjvBtOh/EmJ3wMNmA==" + }, + "@xmpp/iq": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@xmpp/iq/-/iq-0.13.0.tgz", + "integrity": "sha512-3fH7lLIgQ4I/I9nKst+YqFP4WIjV24TVnTDxGQthj7POkmvl2MFo63rlTvA4PV1uRn8FmlyetgP/vbGo+c7yuQ==", + "requires": { + "@xmpp/events": "^0.13.0", + "@xmpp/id": "^0.13.0", + "@xmpp/middleware": "^0.13.0", + "@xmpp/xml": "^0.13.0" + } + }, + "@xmpp/jid": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@xmpp/jid/-/jid-0.13.0.tgz", + "integrity": "sha512-R8XkQOLK7V+wDiXozc9VzoACb4+XPR6K8zno1fur9le7AnUrX/vUvb8/ZcvenFNWVYplvZS6h9GkZPPEGvmUyQ==" + }, + "@xmpp/middleware": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@xmpp/middleware/-/middleware-0.13.0.tgz", + "integrity": "sha512-ZUaArnur2q74nTvwbBckflsxGo73VqEBKk/GaQv0q9Lgg6FjQO/BA6lTlZ597h3V5MBi7SGHPcJ335p1/Rd0uw==", + "requires": { + "@xmpp/error": "^0.13.0", + "@xmpp/jid": "^0.13.0", + "@xmpp/xml": "^0.13.0", + "koa-compose": "^4.1.0" + } + }, + "@xmpp/reconnect": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@xmpp/reconnect/-/reconnect-0.13.0.tgz", + "integrity": "sha512-I0uxzGb6Mr6QlCPjgIGb8eBbPYJc2FauOfpoZ/O7Km+i41MxLmVyNaKP0aY2JhWIxls727X9VMMtjTlK8vE5RQ==", + "requires": { + "@xmpp/events": "^0.13.0" + } + }, + "@xmpp/xml": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@xmpp/xml/-/xml-0.13.0.tgz", + "integrity": "sha512-bgKaUzzJXp8nXCQPzVRJLy1XZQlLrcmjzUe1V7127NcXJddEgk1Ie/esVhh1BUMlPgRdl7BCRQkYe40S6KuuXw==", + "requires": { + "ltx": "^3.0.0" + } + }, "@xtuc/ieee754": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", @@ -2792,8 +2894,7 @@ "events": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "dev": true + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==" }, "evp_bytestokey": { "version": "1.0.3", @@ -4207,6 +4308,11 @@ "integrity": "sha512-h9ivI88e1lFNmTT4HovBN33Ysn0OIJG7IPG2mkpx2uniQXFWqo35QdiX7w0TovlUFXfW8aPFblP5/q0jlOr2sA==", "dev": true }, + "koa-compose": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/koa-compose/-/koa-compose-4.1.0.tgz", + "integrity": "sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw==" + }, "kuler": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", @@ -4320,6 +4426,11 @@ "yallist": "^4.0.0" } }, + "ltx": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ltx/-/ltx-3.0.0.tgz", + "integrity": "sha512-bu3/4/ApUmMqVNuIkHaRhqVtEi6didYcBDIF56xhPRCzVpdztCipZ62CUuaxMlMBUzaVL93+4LZRqe02fuAG6A==" + }, "make-dir": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", diff --git a/package.json b/package.json index 99a07a5b..1b2bd2ae 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "dist/assets/style.css" ], "dependencies": { + "@xmpp/component": "^0.13.0", "async": "^3.2.2", "body-parser": "^1.19.0", "decache": "^4.6.0", @@ -82,6 +83,7 @@ "clean": "rm -rf dist/* build/*", "clean:light": "rm -rf dist/*", "prepare": "npm run clean && npm run build", + "build:bots": "npx tsc --build bots/tsconfig.json", "build:converse": "bash conversejs/build-conversejs.sh", "build:images": "mkdir -p dist/client/images && npx svgo -f public/images/ -o dist/client/images/", "build:webpack": "webpack --mode=production", @@ -89,7 +91,7 @@ "build:serverconverse": "mkdir -p dist/server/conversejs && cp conversejs/index.html dist/server/conversejs/", "build:prosodymodules": "mkdir -p dist/server/prosody-modules && cp -r prosody-modules/* dist/server/prosody-modules/", "build:styles": "sass assets:dist/assets", - "build": "npm-run-all -s clean:light -p build:converse build:images build:webpack build:server build:serverconverse build:prosodymodules build:styles", + "build": "npm-run-all -s clean:light -p build:converse build:images build:webpack build:server build:serverconverse build:prosodymodules build:styles build:bots", "lint": "npm-run-all -s lint:script lint:styles", "lint:script": "npx eslint --ext .js --ext .ts .", "lint:styles": "stylelint 'conversejs/**/*.scss' 'assets/**/*.css'", diff --git a/server/lib/diagnostic/prosody.ts b/server/lib/diagnostic/prosody.ts index d4bfc0ef..fafb29b5 100644 --- a/server/lib/diagnostic/prosody.ts +++ b/server/lib/diagnostic/prosody.ts @@ -49,8 +49,8 @@ export async function diagProsody (test: string, options: RegisterServerOptions) } result.messages.push(`Room content will be saved for '${wantedConfig.logExpiration.value}'`) - if (wantedConfig.bots.demo) { - result.messages.push(`The Demo bot is active for videos: ${wantedConfig.bots.demo.join(', ')}`) + if (wantedConfig.bots.demobot) { + result.messages.push(`The Demo bot is active for videos: ${wantedConfig.bots.demobot.join(', ')}`) } const configFiles = wantedConfig.getConfigFiles() diff --git a/server/lib/prosody/config.ts b/server/lib/prosody/config.ts index 3503a4c0..ec89834a 100644 --- a/server/lib/prosody/config.ts +++ b/server/lib/prosody/config.ts @@ -24,9 +24,9 @@ async function getWorkingDir (options: RegisterServerOptions): Promise { /** * Creates the working dir if needed, and returns it. */ -async function ensureWorkingDir (options: RegisterServerOptions): Promise { +async function ensureWorkingDirs (options: RegisterServerOptions): Promise { const logger = options.peertubeHelpers.logger - logger.debug('Calling ensureworkingDir') + logger.debug('Calling ensureworkingDirs') const paths = await getProsodyFilePaths(options) const dir = paths.dir @@ -39,10 +39,12 @@ async function ensureWorkingDir (options: RegisterServerOptions): Promise 0) { useExternalComponents = true const componentSecret = await getExternalComponentKey(options, 'DEMOBOT') valuesToHideInDiagnostic.ComponentSecret = componentSecret config.useDemoBot(componentSecret) - bots.demo = demoBotUUIDs + bots.demobot = demoBotUUIDs + demoBotContentObj = JSON.stringify({ + UUIDs: demoBotUUIDs, + service: 'xmpp://127.0.0.1:' + externalComponentsPort, + domain: 'demobot.' + prosodyDomain, + mucDomain: 'room.' + prosodyDomain, + password: componentSecret + }) } + let demoBotContent = '"use strict";\n' + demoBotContent += 'Object.defineProperty(exports, "__esModule", { value: true });\n' + demoBotContent += `function getConf () { return ${demoBotContentObj}; }` + '\n' + demoBotContent += 'exports.getConf = getConf;\n' if (useExternalComponents) { - const externalComponentsPort = (settings['prosody-component-port'] as string) || '53470' - if (!/^\d+$/.test(externalComponentsPort)) { - throw new Error('Invalid external components port') - } config.useExternalComponents(externalComponentsPort) } @@ -213,6 +230,11 @@ async function getProsodyConfig (options: RegisterServerOptions): Promise