Reverting work on DemoBot (it is now an external package).

This commit is contained in:
John Livingston 2021-12-11 17:12:04 +01:00
parent d01d13a69e
commit 2e7cec04d9
No known key found for this signature in database
GPG Key ID: B17B5640CE66CDBC
23 changed files with 89 additions and 1221 deletions

View File

@ -2,14 +2,10 @@
## (unreleased yet) ## (unreleased yet)
### Features
* Adding Bots to builtin Prosody mode!
* The DemoBot: it is a bot than can join rooms to demonstrate the plugin (it is an hidden feature).
### Fixes ### Fixes
* Fix spanish translation. * Fix spanish translation.
* Hide secret keys in diagnostic tool.
## v5.0.2 ## v5.0.2

View File

@ -1,40 +0,0 @@
{
"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"
}
}

View File

@ -1,70 +0,0 @@
import { BotsConfig } from './lib/config'
import { logger } from './lib/logger'
import { BotComponent } from './lib/bot/component'
import { BotHandlerDemo } from './lib/bot/handlers/demo'
if (!process.argv[2]) {
throw new Error('Missing parameter: the demobot configuration file path')
}
const botsConfig = new BotsConfig(process.argv[2])
const runningBots: BotComponent[] = []
async function start (botsConfig: BotsConfig): Promise<void> {
await botsConfig.load()
let atLeastOne: boolean = false
if (botsConfig.useDemoBot()) {
atLeastOne = true
logger.info('Starting DemoBot...')
const config = botsConfig.getDemoBotConfig()
const instance = new BotComponent(
'DemoBot',
{
service: config.service,
domain: config.domain,
password: config.password
},
config.mucDomain
)
runningBots.push(instance)
instance.connect().then(async () => {
for (const roomId of config.rooms) {
const room = await instance.joinRoom(roomId, 'DemoBot')
room.attachHandler(new BotHandlerDemo(room))
}
}).catch(err => { throw err })
}
if (!atLeastOne) {
logger.info('No bot to launch, exiting.')
process.exit(0)
}
}
async function shutdown (): Promise<void> {
logger.info('Shutdown...')
for (const bot of runningBots) {
logger.info('Stopping the bot ' + bot.botName + '...')
await bot.disconnect()
}
process.exit(0)
}
// catching signals and do something before exit
['SIGHUP', 'SIGINT', 'SIGQUIT', 'SIGILL', 'SIGTRAP', 'SIGABRT',
'SIGBUS', 'SIGFPE', 'SIGUSR1', 'SIGSEGV', 'SIGUSR2', 'SIGTERM'
].forEach((sig) => {
process.on(sig, () => {
logger.debug('Receiving signal: ' + sig)
shutdown().catch((err) => {
logger.error(`Error on shutting down: ${err as string}`)
})
})
})
start(botsConfig).catch((err) => {
logger.error(`Function start failed: ${err as string}`)
process.exit(1)
})

View File

@ -1,115 +0,0 @@
import type { XMPPStanza, XMPPStanzaType } from './types'
import type { Node } from '@xmpp/xml'
import { logger } from '../logger'
import { component, xml, Component, Options } from '@xmpp/component'
import { parse, JID } from '@xmpp/jid'
import { BotRoom } from './room'
class BotComponent {
protected xmpp?: Component
protected address?: JID
public readonly xml = xml
protected rooms: Map<string, BotRoom> = new Map()
constructor (
public readonly botName: string,
protected readonly connectionConfig: Options,
protected readonly mucDomain: string
) {}
public async connect (): Promise<void> {
this.xmpp = component({
service: this.connectionConfig.service,
domain: this.connectionConfig.domain,
password: this.connectionConfig.password
})
this.xmpp.on('error', (err: any) => {
logger.error(err)
})
this.xmpp.on('offline', () => {
logger.info(`${this.botName} is now offline.`)
})
this.xmpp.on('stanza', (stanza: XMPPStanza) => {
logger.debug('stanza received' + stanza.toString())
if (!stanza.attrs.from) { return }
const jid = parse(stanza.attrs.from)
const roomJid = jid.bare() // removing the «resource» part of the jid.
const room = this.rooms.get(roomJid.toString())
if (!room) {
return
}
room.emit('stanza', stanza, jid.getResource())
})
this.xmpp.on('online', (address) => {
logger.debug('Online with address' + address.toString())
this.address = address
// 'online' is emitted at reconnection, so we must reset rooms rosters
this.rooms.forEach(room => room.emit('reset'))
})
this.xmpp.on('offline', () => {
logger.info(`Stoppping process: ${this.botName} is now offline.`)
})
await this.xmpp.start()
}
public async disconnect (): Promise<any> {
for (const [roomId, room] of this.rooms) {
logger.debug(`Leaving room ${roomId}...`)
await room.detachHandlers()
await room.part()
}
await this.xmpp?.stop()
this.xmpp = undefined
}
public async sendStanza (
type: XMPPStanzaType,
attrs: object,
...children: Node[]
): Promise<void> {
attrs = Object.assign({
from: this.address?.toString()
}, attrs)
const stanza = this.xml(type, attrs, ...children)
logger.debug('stanza to emit: ' + stanza.toString())
await this.xmpp?.send(stanza)
}
public async joinRoom (roomId: string, nick: string): Promise<BotRoom> {
const roomJID = new JID(roomId, this.mucDomain)
const roomJIDstr = roomJID.toString()
let room: BotRoom | undefined = this.rooms.get(roomJIDstr)
if (!room) {
room = new BotRoom(this, roomJID)
this.rooms.set(roomJIDstr, room)
}
await room.join(nick)
return room
}
public async partRoom (roomId: string): Promise<void> {
const roomJID = new JID(roomId, this.mucDomain)
const room = this.rooms.get(roomJID.toString())
if (!room) {
return
}
await room.part()
}
public getAddress (): JID | undefined {
return this.address
}
}
export {
BotComponent
}

View File

@ -1,12 +0,0 @@
import type { BotRoom } from '../room'
export abstract class BotHandler {
constructor (
protected readonly room: BotRoom
) {
this.init()
}
protected abstract init (): void
public abstract stop (): void
}

View File

@ -1,77 +0,0 @@
import type { XMPPUser } from '../types'
import { logger } from '../../logger'
import { BotHandler } from './base'
const RANDOM_MESSAGES: string[] = [
'🎵🎶',
'🎵🎶 I\'m just a bot, I\'m just a bot in the world. 🎵🎶',
'You can see who is connected by opening the right panel.',
'This is a random message.',
'Oh, yet another random message.',
'You can mention a user using a @ in front of a user\'s nick. Try to mention me.'
]
export class BotHandlerDemo extends BotHandler {
protected readonly lastHellos: Map<string, Date> = new Map()
protected randomCount: number = 0
protected randomTimeout: NodeJS.Timeout | undefined
protected init (): void {
const room = this.room
room.on('room_join', (user: XMPPUser) => {
if (user.isMe) {
return
}
if (!room.isOnline()) {
return
}
const lastHello = this.lastHellos.get(user.nick)
const now = new Date()
let msg: string
if (lastHello) {
logger.debug(`The user ${user.nick} was already seen at ${lastHello.toString()}`)
if ((now.getTime() - lastHello.getTime()) < 3600 * 1000) { // no more than one hello per hour
logger.info(`The user ${user.nick} was seen to recently, no message to send.`)
return
}
logger.info(`The user ${user.nick} was seen a long time ago, sending a message.`)
msg = `Hello ${user.nick}! Happy to see you again.`
} else {
logger.info(`The user ${user.nick} is here for the first time. Sending a message.`)
msg = `Hello ${user.nick}! I'm the DemoBot, I'm here to demonstrate the chatroom.`
}
this.lastHellos.set(user.nick, now)
room.sendGroupchat(msg).catch(() => {})
})
room.on('room_message', (msg: string, user?: XMPPUser, mentionned?: boolean) => {
if (!user || user.isMe) { return }
if (!room.isOnline()) { return }
if (!mentionned) { return }
room.sendGroupchat(`Yep @${user.nick}?`).catch(() => {})
})
this.randomTimeout = setInterval(() => {
this.sendRandomMessage()
}, 60 * 1000)
}
public stop (): void {
if (this.randomTimeout) {
clearInterval(this.randomTimeout)
}
}
protected sendRandomMessage (): void {
const room = this.room
if (!room.isOnline()) { return }
// checking if there is someone to listen...
const onlineUserCount = this.room.onlineUserCount()
logger.debug(`Online user count in room: ${onlineUserCount}`)
if (onlineUserCount < 2) { return }
const cpt = this.randomCount++
logger.info(`Emitting the random message number ${cpt}.`)
this.room.sendGroupchat(RANDOM_MESSAGES[cpt % RANDOM_MESSAGES.length]).catch(() => {})
}
}

View File

@ -1,189 +0,0 @@
import type { BotComponent } from './component'
import type { BotHandler } from './handlers/base'
import type { XMPPStanza, XMPPUser } from './types'
import EventEmitter from 'events'
import { JID } from '@xmpp/jid'
import { logger } from '../logger'
export class BotRoom extends EventEmitter {
protected state: 'offline' | 'online' = 'offline'
protected userJID: JID | undefined
protected readonly roster: Map<string, XMPPUser> = new Map()
protected readonly handlers: BotHandler[] = []
constructor (
protected readonly component: BotComponent,
protected readonly roomJID: JID
) {
super()
this.on('reset', () => {
this.state = 'offline'
this.roster.clear()
})
this.on('stanza', (stanza: XMPPStanza, resource?: string) => {
this.receiveStanza(stanza, resource)
})
}
public isOnline (): boolean {
return this.state === 'online'
}
public onlineUserCount (): number {
let count = 0
this.roster.forEach(user => {
if (user.state === 'online') { count++ }
})
return count
}
public async join (nick: string): Promise<void> {
this.userJID = new JID(this.roomJID.getLocal(), this.roomJID.getDomain(), nick)
logger.debug(`Emitting a presence for room ${this.roomJID.toString()}...`)
await this.component.sendStanza('presence',
{
to: this.userJID.toString()
},
this.component.xml('x', {
xmlns: 'http://jabber.org/protocol/muc'
})
)
// FIXME: should wait for a presence stanza from the server.
// FIXME: should handle used nick errors.
}
public async part (): Promise<void> {
if (!this.userJID) { return }
logger.debug(`Emitting a presence=unavailable for room ${this.roomJID.toString()}...`)
await this.component.sendStanza('presence', {
to: this.userJID.toString(),
type: 'unavailable'
})
// FIXME: should wait for a presence stanza from the server.
}
public async sendGroupchat (msg: string): Promise<void> {
if (!this.userJID) { return }
logger.debug(`Emitting a groupchat message for room ${this.roomJID.toString()}...`)
await this.component.sendStanza(
'message',
{
type: 'groupchat',
to: this.roomJID.toString()
},
this.component.xml('body', {}, msg)
)
}
public receiveStanza (stanza: XMPPStanza, fromResource?: string): void {
if (stanza.name === 'presence') {
this.receivePresenceStanza(stanza, fromResource)
}
if (stanza.name === 'message') {
this.receiveMessageStanza(stanza, fromResource)
}
}
public receivePresenceStanza (stanza: XMPPStanza, fromResource?: string): void {
if (!fromResource) {
return
}
const isPresent = stanza.attrs.type !== 'unavailable'
const statusElems = stanza.getChild('x')?.getChildren('status')
const statusCodes = []
if (statusElems) {
for (const s of statusElems) {
statusCodes.push(parseInt(s.attrs.code))
}
}
const isMe = statusCodes.includes(110) // status 110 means that is concern the current user.
let user = this.roster.get(fromResource)
const previousState = user?.state
if (!isPresent) {
if (!user) {
return
}
user.state = 'offline'
if (isMe) {
this.state = 'offline'
}
if (previousState === 'online') {
this.emit('room_part', user)
}
} else {
if (!user) {
user = {
state: 'online',
nick: fromResource,
isMe: isMe
}
this.roster.set(fromResource, user)
} else {
user.state = 'online'
}
if (isMe) {
this.state = 'online'
}
if (previousState !== 'online') {
this.emit('room_join', user)
}
}
}
protected receiveMessageStanza (stanza: XMPPStanza, fromResource?: string): void {
if (stanza.attrs.type !== 'groupchat') {
return
}
// ignoring messages send by the bot himself
if (stanza.attrs.from === this.userJID?.toString()) {
return
}
// ignoring history messages
if (stanza.getChild('delay')) {
return
}
const body = stanza.getChild('body')
// ignoring message without body (subject, ...)
if (!body) {
return
}
let mentionned: boolean = false // I'm I mentionned?
// TODO: fix this ugly code.
if (this.userJID) {
const references = stanza.getChildren('reference')
for (const reference of references) {
if (reference.attrs.type === 'mention') {
if (reference.attrs.uri === 'xmpp:' + this.userJID.toString()) {
mentionned = true
} else {
const addr = this.component.getAddress()
if (addr) {
if (reference.attrs.uri === 'xmpp:' + addr.toString()) {
mentionned = true
}
}
}
}
}
}
const user = fromResource ? this.roster.get(fromResource) : undefined
this.emit('room_message', body.toString(), user, mentionned)
}
public attachHandler (handler: BotHandler): void {
this.handlers.push(handler)
}
public detachHandlers (): void {
for (const handler of this.handlers) {
handler.stop()
}
}
}

View File

@ -1,13 +0,0 @@
import type { Element } from '@xmpp/xml'
export type XMPPStanzaType = 'message' | 'iq' | 'presence'
export interface XMPPStanza extends Element {
name: XMPPStanzaType
}
export interface XMPPUser {
state: 'offline' | 'online'
nick: string
isMe: boolean
}

View File

@ -1,88 +0,0 @@
import * as path from 'path'
import * as fs from 'fs'
import decache from 'decache'
import { logger } from '../lib/logger'
interface DemoBotConfig {
rooms: string[]
service: string
domain: string
mucDomain: string
password: string
}
class BotsConfig {
protected readonly configDir: string
protected configs: {
demobot?: DemoBotConfig
}
constructor (configDir: string) {
this.configDir = configDir = path.resolve(configDir)
// Not necessary, but just in case: perform some path checking... (to limit code injection risks)
const parts = configDir.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('Bots configuration dir seems invalid (not in peertube-plugin-livechat folder).')
}
this.configs = {}
}
public async load (): Promise<void> {
await this.loadDemoBot()
}
protected async loadDemoBot (): Promise<void> {
const configPath = path.resolve(this.configDir, 'demobot.js')
logger.debug(`Loading DemoBot config from file ${configPath}`)
if (!fs.existsSync(configPath)) {
logger.debug('The config file for DemoBot does not exist.')
delete this.configs.demobot
return
}
decache(configPath)
logger.debug('require DemoBot config file...')
const conf = require(configPath).getConf() as DemoBotConfig | null
if (!conf) {
logger.debug('getConf() returned null for the DemoBot.')
delete this.configs.demobot
return
}
if (!conf.rooms || !conf.domain || !conf.mucDomain || !conf.password || !conf.service) {
logger.error('Invalid DemoBot configuration: ' + JSON.stringify(conf))
delete this.configs.demobot
return
}
// Conf seems legit. But if there is no rooms, no need to keep it.
if (!conf.rooms.length) {
logger.debug('No room in DemoBot config.')
delete this.configs.demobot
return
}
// TODO: detect changes? avoid reloading when not needed? or should it be by the caller?
logger.debug('Config loaded for demobot: ' + JSON.stringify(conf))
this.configs.demobot = conf
}
public useDemoBot (): boolean {
return (this.configs.demobot?.rooms?.length ?? 0) > 0
}
public getDemoBotConfig (): DemoBotConfig {
if (!this.configs.demobot) {
throw new Error('Should not call getDemoBotConfig when useDemoBot is false.')
}
return this.configs.demobot
}
}
export {
BotsConfig
}

View File

@ -1,23 +0,0 @@
class Logger {
public debug (s: string): void {
console.log(s)
}
public info (s: string): void {
console.info(s)
}
public warn (s: string): void {
console.warn(s)
}
public error (s: string): void {
console.error(s)
}
}
const logger = new Logger()
export {
logger
}

View File

@ -1,26 +0,0 @@
{
"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": []
}

View File

@ -211,7 +211,6 @@ function register ({ registerHook, registerSettingsScript, peertubeHelpers }: Re
case 'prosody-muc-log-by-default': case 'prosody-muc-log-by-default':
case 'prosody-muc-expiration': case 'prosody-muc-expiration':
case 'prosody-c2s': case 'prosody-c2s':
case 'prosody-component-port':
return options.formValues['chat-type'] !== ('builtin-prosody' as ChatType) return options.formValues['chat-type'] !== ('builtin-prosody' as ChatType)
case 'prosody-c2s-port': case 'prosody-c2s-port':
return !( return !(

View File

@ -122,8 +122,3 @@ When you open the chat room in full screen, there will also be a menu with dedic
### OBS Overlay using Matterbridge ### OBS Overlay using Matterbridge
Here is a tutorial to use Matterbridge with the plugin: <https://gitlab.com/refrac/obs-matterbridge-overlay/-/blob/master/documentation/peertube.md> Here is a tutorial to use Matterbridge with the plugin: <https://gitlab.com/refrac/obs-matterbridge-overlay/-/blob/master/documentation/peertube.md>
### Demobot
This is a hidden feature. It is a bot that can join rooms, and demonstrate the plugin capacities.
This is not documented for now.

304
package-lock.json generated
View File

@ -175,15 +175,6 @@
"integrity": "sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==", "integrity": "sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==",
"dev": true "dev": true
}, },
"@types/accepts": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/@types/accepts/-/accepts-1.3.5.tgz",
"integrity": "sha512-jOdnI/3qTpHABjM5cx1Hc0sKsPoYCp+DP/GJRGtDlPd7fiV9oXGGIcjW/ZOxLIvjGz8MA+uMZI9metHlgqbgwQ==",
"dev": true,
"requires": {
"@types/node": "*"
}
},
"@types/async": { "@types/async": {
"version": "3.2.9", "version": "3.2.9",
"resolved": "https://registry.npmjs.org/@types/async/-/async-3.2.9.tgz", "resolved": "https://registry.npmjs.org/@types/async/-/async-3.2.9.tgz",
@ -220,24 +211,6 @@
"@types/node": "*" "@types/node": "*"
} }
}, },
"@types/content-disposition": {
"version": "0.5.4",
"resolved": "https://registry.npmjs.org/@types/content-disposition/-/content-disposition-0.5.4.tgz",
"integrity": "sha512-0mPF08jn9zYI0n0Q/Pnz7C4kThdSt+6LD4amsrYDDpgBfrVWa3TcCOxKX1zkGgYniGagRv8heN2cbh+CAn+uuQ==",
"dev": true
},
"@types/cookies": {
"version": "0.7.7",
"resolved": "https://registry.npmjs.org/@types/cookies/-/cookies-0.7.7.tgz",
"integrity": "sha512-h7BcvPUogWbKCzBR2lY4oqaZbO3jXZksexYJVFvkrFeLgbZjQkU4x8pRq6eg2MHXQhY0McQdqmmsxRWlVAHooA==",
"dev": true,
"requires": {
"@types/connect": "*",
"@types/express": "*",
"@types/keygrip": "*",
"@types/node": "*"
}
},
"@types/express": { "@types/express": {
"version": "4.17.13", "version": "4.17.13",
"resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.13.tgz", "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.13.tgz",
@ -281,23 +254,11 @@
"form-data": "^2.5.0" "form-data": "^2.5.0"
} }
}, },
"@types/http-assert": {
"version": "1.5.3",
"resolved": "https://registry.npmjs.org/@types/http-assert/-/http-assert-1.5.3.tgz",
"integrity": "sha512-FyAOrDuQmBi8/or3ns4rwPno7/9tJTijVW6aQQjK02+kOQ8zmoNg2XJtAuQhvQcy1ASJq38wirX5//9J1EqoUA==",
"dev": true
},
"@types/http-cache-semantics": { "@types/http-cache-semantics": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.0.tgz", "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.0.tgz",
"integrity": "sha512-c3Xy026kOF7QOTn00hbIllV1dLR9hG9NkSrLQgCVs8NF6sBU+VGWjD3wLPhmh1TYAc7ugCFsvHYMN4VcBN1U1A==" "integrity": "sha512-c3Xy026kOF7QOTn00hbIllV1dLR9hG9NkSrLQgCVs8NF6sBU+VGWjD3wLPhmh1TYAc7ugCFsvHYMN4VcBN1U1A=="
}, },
"@types/http-errors": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-1.8.1.tgz",
"integrity": "sha512-e+2rjEwK6KDaNOm5Aa9wNGgyS9oSZU/4pfSMMPYNOfjvFI0WVXm29+ITRFr6aKDvvKo7uU1jV68MW4ScsfDi7Q==",
"dev": true
},
"@types/json-schema": { "@types/json-schema": {
"version": "7.0.9", "version": "7.0.9",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz",
@ -310,12 +271,6 @@
"integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=",
"dev": true "dev": true
}, },
"@types/keygrip": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@types/keygrip/-/keygrip-1.0.2.tgz",
"integrity": "sha512-GJhpTepz2udxGexqos8wgaBx4I/zWIDPh/KOGEwAqtuGDkOUJu5eFvwmdBX4AmB8Odsr+9pHCQqiAqDL/yKMKw==",
"dev": true
},
"@types/keyv": { "@types/keyv": {
"version": "3.1.1", "version": "3.1.1",
"resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.1.tgz", "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.1.tgz",
@ -324,37 +279,6 @@
"@types/node": "*" "@types/node": "*"
} }
}, },
"@types/koa": {
"version": "2.13.4",
"resolved": "https://registry.npmjs.org/@types/koa/-/koa-2.13.4.tgz",
"integrity": "sha512-dfHYMfU+z/vKtQB7NUrthdAEiSvnLebvBjwHtfFmpZmB7em2N3WVQdHgnFq+xvyVgxW5jKDmjWfLD3lw4g4uTw==",
"dev": true,
"requires": {
"@types/accepts": "*",
"@types/content-disposition": "*",
"@types/cookies": "*",
"@types/http-assert": "*",
"@types/http-errors": "*",
"@types/keygrip": "*",
"@types/koa-compose": "*",
"@types/node": "*"
}
},
"@types/koa-compose": {
"version": "3.2.5",
"resolved": "https://registry.npmjs.org/@types/koa-compose/-/koa-compose-3.2.5.tgz",
"integrity": "sha512-B8nG/OoE1ORZqCkBVsup/AKcvjdgoHnfi4pZMn5UwAPCbhk/96xyv284eBYW8JlQbQ7zDmnpFr68I/40mFoIBQ==",
"dev": true,
"requires": {
"@types/koa": "*"
}
},
"@types/ltx": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@types/ltx/-/ltx-3.0.1.tgz",
"integrity": "sha512-X+1EoqEcSZ45MYJmg0rfMvEyQPGydLT00HJcPant+5J3+OM0N+ZVL6BdZ1Iy4K3dA+JBGe1WP7PvTM/GtxN/XA==",
"dev": true
},
"@types/mime": { "@types/mime": {
"version": "1.3.2", "version": "1.3.2",
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz",
@ -429,119 +353,6 @@
"winston": "*" "winston": "*"
} }
}, },
"@types/xmpp__component": {
"version": "0.13.0",
"resolved": "https://registry.npmjs.org/@types/xmpp__component/-/xmpp__component-0.13.0.tgz",
"integrity": "sha512-4vKLiicgkZwW8bKofmmy0BJpw3MuOW73c5hVPhUtgBPDTh9hj7wQezhpOLX3AhQFto97YpLg2GwWzhnwfSl1BA==",
"dev": true,
"requires": {
"@types/xmpp__component-core": "*",
"@types/xmpp__iq": "*",
"@types/xmpp__middleware": "*",
"@types/xmpp__reconnect": "*"
}
},
"@types/xmpp__component-core": {
"version": "0.13.0",
"resolved": "https://registry.npmjs.org/@types/xmpp__component-core/-/xmpp__component-core-0.13.0.tgz",
"integrity": "sha512-K9l6SLG91kTcchW/Nt5TL9Kfe5aWDyDcHWvoFgnwvGoF4g0K737HdZMzD0DN1TP7Gb2g/JNCiK245BuDYegAbw==",
"dev": true,
"requires": {
"@types/xmpp__connection-tcp": "*",
"@types/xmpp__jid": "*",
"@types/xmpp__xml": "*"
}
},
"@types/xmpp__connection": {
"version": "0.13.0",
"resolved": "https://registry.npmjs.org/@types/xmpp__connection/-/xmpp__connection-0.13.0.tgz",
"integrity": "sha512-YsvLhgOfxY3TbDTeTT0ZrToqh3IsA0nKnXk/NxTES2O6wTxn9lQDRBYNgB6lkq+D50nA8nmT3d53acb0f4Rycw==",
"dev": true,
"requires": {
"@types/xmpp__error": "*",
"@types/xmpp__events": "*",
"@types/xmpp__jid": "*",
"@types/xmpp__xml": "*"
}
},
"@types/xmpp__connection-tcp": {
"version": "0.13.0",
"resolved": "https://registry.npmjs.org/@types/xmpp__connection-tcp/-/xmpp__connection-tcp-0.13.0.tgz",
"integrity": "sha512-yHvAWck6JVs0H/E2tnoUVOsFPylLj1TX4ARdm1/jFRqOPWynw36B/RU0UW1KNSC8dKA6VAhl0mTICnGUZVtcug==",
"dev": true,
"requires": {
"@types/xmpp__connection": "*",
"@types/xmpp__xml": "*"
}
},
"@types/xmpp__error": {
"version": "0.13.0",
"resolved": "https://registry.npmjs.org/@types/xmpp__error/-/xmpp__error-0.13.0.tgz",
"integrity": "sha512-W+tM0UDj3toruhdjhn/VK1mtjOF+YMz+FdxgkMVi6lwCXA/uDW79elW6WbeM8zhiM92ZoVPSgD2zt9YXmrkiZQ==",
"dev": true,
"requires": {
"@types/xmpp__xml": "*"
}
},
"@types/xmpp__events": {
"version": "0.13.0",
"resolved": "https://registry.npmjs.org/@types/xmpp__events/-/xmpp__events-0.13.0.tgz",
"integrity": "sha512-somi0EF9BwaBPmDQk6r1hE6dtXXjv2ztSNk/hStcfGVY9NfD9ErcopWgzzbGdeQg2/WcMNlVwfYXQfIm6w3w+A==",
"dev": true,
"requires": {
"@types/node": "*"
}
},
"@types/xmpp__iq": {
"version": "0.13.0",
"resolved": "https://registry.npmjs.org/@types/xmpp__iq/-/xmpp__iq-0.13.0.tgz",
"integrity": "sha512-jy3aTixRMi8uqiIfhbkIxeWB62NTFGXKdZsYOwlgLNQ9BUimnbGR8BmZGSic5meUTPUaEEpCx/xp3AnVYADICQ==",
"dev": true,
"requires": {
"@types/koa-compose": "*",
"@types/xmpp__events": "*",
"@types/xmpp__middleware": "*",
"@types/xmpp__xml": "*"
}
},
"@types/xmpp__jid": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/@types/xmpp__jid/-/xmpp__jid-1.3.2.tgz",
"integrity": "sha512-zh5mdcBY1zNzI9XxXZxsuq/XGd6YeSwZzwQJpV5NQEtZUiSJ1+YW19+w2pELLrlV2hoMOcSf8PfPwB9ocPwIDg==",
"dev": true
},
"@types/xmpp__middleware": {
"version": "0.13.0",
"resolved": "https://registry.npmjs.org/@types/xmpp__middleware/-/xmpp__middleware-0.13.0.tgz",
"integrity": "sha512-bgwIFdl5khKt/UQY4f6Ca7pEIUGQPCN3CvZ4ZuYSwp5PY9EpH32Tj/akUwfWMuMqGsybvdTeuq7ewT1ic7hsZQ==",
"dev": true,
"requires": {
"@types/koa-compose": "*",
"@types/xmpp__connection": "*",
"@types/xmpp__error": "*",
"@types/xmpp__jid": "*",
"@types/xmpp__xml": "*"
}
},
"@types/xmpp__reconnect": {
"version": "0.13.0",
"resolved": "https://registry.npmjs.org/@types/xmpp__reconnect/-/xmpp__reconnect-0.13.0.tgz",
"integrity": "sha512-MGuq9Dl24iU/t1nuGp/5yUsv4yAvQk5DOARw/iPXpAjB5hCBCzzvsN2ttkw8vAVsQ5DSbpgPWI33GQ2xF2MaSQ==",
"dev": true,
"requires": {
"@types/xmpp__connection": "*",
"@types/xmpp__events": "*"
}
},
"@types/xmpp__xml": {
"version": "0.13.1",
"resolved": "https://registry.npmjs.org/@types/xmpp__xml/-/xmpp__xml-0.13.1.tgz",
"integrity": "sha512-pxRGht/JVPhIwvcFkqv3fsXc1V/qj/C+vkTD75S1whpaNslJJbmA4hphOcbynvIegKdQHxfa56d22sOtHWjDsg==",
"dev": true,
"requires": {
"@types/ltx": "*"
}
},
"@typescript-eslint/eslint-plugin": { "@typescript-eslint/eslint-plugin": {
"version": "4.29.0", "version": "4.29.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.29.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.29.0.tgz",
@ -879,108 +690,6 @@
"@xtuc/long": "4.2.2" "@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": { "@xtuc/ieee754": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz",
@ -3083,7 +2792,8 @@
"events": { "events": {
"version": "3.3.0", "version": "3.3.0",
"resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
"integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==" "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==",
"dev": true
}, },
"evp_bytestokey": { "evp_bytestokey": {
"version": "1.0.3", "version": "1.0.3",
@ -4497,11 +4207,6 @@
"integrity": "sha512-h9ivI88e1lFNmTT4HovBN33Ysn0OIJG7IPG2mkpx2uniQXFWqo35QdiX7w0TovlUFXfW8aPFblP5/q0jlOr2sA==", "integrity": "sha512-h9ivI88e1lFNmTT4HovBN33Ysn0OIJG7IPG2mkpx2uniQXFWqo35QdiX7w0TovlUFXfW8aPFblP5/q0jlOr2sA==",
"dev": true "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": { "kuler": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz",
@ -4615,11 +4320,6 @@
"yallist": "^4.0.0" "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": { "make-dir": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz",

View File

@ -31,8 +31,6 @@
"dist/assets/style.css" "dist/assets/style.css"
], ],
"dependencies": { "dependencies": {
"@xmpp/component": "^0.13.0",
"@xmpp/jid": "^0.13.0",
"async": "^3.2.2", "async": "^3.2.2",
"body-parser": "^1.19.0", "body-parser": "^1.19.0",
"decache": "^4.6.0", "decache": "^4.6.0",
@ -50,8 +48,6 @@
"@types/got": "^9.6.12", "@types/got": "^9.6.12",
"@types/node": "^16.11.6", "@types/node": "^16.11.6",
"@types/winston": "^2.4.4", "@types/winston": "^2.4.4",
"@types/xmpp__component": "^0.13.0",
"@types/xmpp__jid": "^1.3.2",
"@typescript-eslint/eslint-plugin": "^4.29.0", "@typescript-eslint/eslint-plugin": "^4.29.0",
"@typescript-eslint/parser": "^4.29.0", "@typescript-eslint/parser": "^4.29.0",
"eslint": "^7.32.0", "eslint": "^7.32.0",
@ -86,7 +82,6 @@
"clean": "rm -rf dist/* build/*", "clean": "rm -rf dist/* build/*",
"clean:light": "rm -rf dist/*", "clean:light": "rm -rf dist/*",
"prepare": "npm run clean && npm run build", "prepare": "npm run clean && npm run build",
"build:bots": "npx tsc --build bots/tsconfig.json",
"build:converse": "bash conversejs/build-conversejs.sh", "build:converse": "bash conversejs/build-conversejs.sh",
"build:images": "mkdir -p dist/client/images && npx svgo -f public/images/ -o dist/client/images/", "build:images": "mkdir -p dist/client/images && npx svgo -f public/images/ -o dist/client/images/",
"build:webpack": "webpack --mode=production", "build:webpack": "webpack --mode=production",
@ -94,7 +89,7 @@
"build:serverconverse": "mkdir -p dist/server/conversejs && cp conversejs/index.html dist/server/conversejs/", "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:prosodymodules": "mkdir -p dist/server/prosody-modules && cp -r prosody-modules/* dist/server/prosody-modules/",
"build:styles": "sass assets:dist/assets", "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:bots", "build": "npm-run-all -s clean:light -p build:converse build:images build:webpack build:server build:serverconverse build:prosodymodules build:styles",
"lint": "npm-run-all -s lint:script lint:styles", "lint": "npm-run-all -s lint:script lint:styles",
"lint:script": "npx eslint --ext .js --ext .ts .", "lint:script": "npx eslint --ext .js --ext .ts .",
"lint:styles": "stylelint 'conversejs/**/*.scss' 'assets/**/*.css'", "lint:styles": "stylelint 'conversejs/**/*.scss' 'assets/**/*.css'",

View File

@ -3,28 +3,15 @@ For internal API, we will generate an api Key that must be provided as
GET parameter for every API call. GET parameter for every API call.
*/ */
async function _getKey ({ storageManager }: RegisterServerOptions, key: string): Promise<string> { async function getAPIKey ({ storageManager }: RegisterServerOptions): Promise<string> {
let value: string = await storageManager.getData(key) let value: string = await storageManager.getData('APIKEY')
if (!value) { if (!value) {
value = Math.random().toString(36).slice(2, 12) value = Math.random().toString(36).slice(2, 12)
await storageManager.storeData(key, value) await storageManager.storeData('APIKEY', value)
} }
return value return value
} }
async function getAPIKey (options: RegisterServerOptions): Promise<string> {
return _getKey(options, 'APIKEY')
}
async function getExternalComponentKey (options: RegisterServerOptions, componentName: string): Promise<string> {
if (!/^[A-Z]+$/.test(componentName)) {
throw new Error('Invalid component name: ' + componentName)
}
const key = 'EXTERNALCOMPONENTKEY_' + componentName
return _getKey(options, key)
}
export { export {
getAPIKey, getAPIKey
getExternalComponentKey
} }

View File

@ -1,4 +1,4 @@
import { getProsodyConfig, getWorkingDir } from '../prosody/config' import { getProsodyConfig, getProsodyConfigContentForDiagnostic, getWorkingDir } from '../prosody/config'
import { getProsodyAbout, testProsodyCorrectlyRunning } from '../prosody/ctl' import { getProsodyAbout, testProsodyCorrectlyRunning } from '../prosody/ctl'
import { newResult, TestResult } from './utils' import { newResult, TestResult } from './utils'
import { getAPIKey } from '../apikey' import { getAPIKey } from '../apikey'
@ -24,6 +24,7 @@ export async function diagProsody (test: string, options: RegisterServerOptions)
let prosodyHost: string let prosodyHost: string
try { try {
const wantedConfig = await getProsodyConfig(options) const wantedConfig = await getProsodyConfig(options)
const filePath = wantedConfig.paths.config
result.messages.push(`Prosody will run on port '${wantedConfig.port}'`) result.messages.push(`Prosody will run on port '${wantedConfig.port}'`)
prosodyPort = wantedConfig.port prosodyPort = wantedConfig.port
@ -49,44 +50,34 @@ export async function diagProsody (test: string, options: RegisterServerOptions)
} }
result.messages.push(`Room content will be saved for '${wantedConfig.logExpiration.value}'`) result.messages.push(`Room content will be saved for '${wantedConfig.logExpiration.value}'`)
if (wantedConfig.bots.demobot) { await fs.promises.access(filePath, fs.constants.R_OK) // throw an error if file does not exist.
result.messages.push(`The Demo bot is active for videos: ${wantedConfig.bots.demobot.join(', ')}`) result.messages.push(`The prosody configuration file (${filePath}) exists`)
} const actualContent = await fs.promises.readFile(filePath, {
encoding: 'utf-8'
})
const configFiles = wantedConfig.getConfigFiles() result.debug.push({
for (const configFile of configFiles) { title: 'Current prosody configuration',
const filePath = configFile.path // we have to hide secret keys and other values.
const configFileKey = configFile.key // But here, we haven't them for actualContent.
// So we will use values in wantedConfig, hopping it is enough.
await fs.promises.access(filePath, fs.constants.R_OK) // throw an error if file does not exist. message: getProsodyConfigContentForDiagnostic(wantedConfig, actualContent)
result.messages.push(`The prosody '${configFileKey}' configuration file (${filePath}) exists`) })
const actualContent = await fs.promises.readFile(filePath, {
encoding: 'utf-8'
})
const wantedContent = wantedConfig.content
if (actualContent === wantedContent) {
result.messages.push('Prosody configuration file content is correct.')
} else {
result.messages.push('Prosody configuration file content is not correct.')
result.debug.push({ result.debug.push({
title: `Current prosody '${configFileKey}' configuration`, title: 'Prosody configuration should be',
// we have to hide secret keys and other values. // we have to hide secret keys and other values:
// But here, we haven't them for actualContent. message: getProsodyConfigContentForDiagnostic(wantedConfig)
// So we will use values in wantedConfig, hopping it is enough.
message: wantedConfig.contentForDiagnostic(actualContent)
}) })
return result
const wantedContent = configFile.content
if (actualContent === wantedContent) {
result.messages.push(`Prosody configuration file '${configFileKey}' content is correct.`)
} else {
result.messages.push(`Prosody configuration file '${configFileKey}'' content is not correct.`)
result.debug.push({
title: `Prosody configuration '${configFileKey}' should be`,
// we have to hide secret keys and other values:
message: wantedConfig.contentForDiagnostic(wantedContent)
})
return result
}
} }
} catch (error) { } catch (error) {
result.messages.push('Error when testing the prosody config: ' + (error as string)) result.messages.push('Error when requiring the prosody config file: ' + (error as string))
return result return result
} }

View File

@ -4,9 +4,8 @@ import { getBaseRouterRoute } from '../helpers'
import { ProsodyFilePaths } from './config/paths' import { ProsodyFilePaths } from './config/paths'
import { ConfigLogExpiration, ProsodyConfigContent } from './config/content' import { ConfigLogExpiration, ProsodyConfigContent } from './config/content'
import { getProsodyDomain } from './config/domain' import { getProsodyDomain } from './config/domain'
import { getAPIKey, getExternalComponentKey } from '../apikey' import { getAPIKey } from '../apikey'
import type { ProsodyLogLevel } from './config/content' import type { ProsodyLogLevel } from './config/content'
import { parseConfigDemoBotUUIDs } from './config/bots'
async function getWorkingDir (options: RegisterServerOptions): Promise<string> { async function getWorkingDir (options: RegisterServerOptions): Promise<string> {
const peertubeHelpers = options.peertubeHelpers const peertubeHelpers = options.peertubeHelpers
@ -24,9 +23,9 @@ async function getWorkingDir (options: RegisterServerOptions): Promise<string> {
/** /**
* Creates the working dir if needed, and returns it. * Creates the working dir if needed, and returns it.
*/ */
async function ensureWorkingDirs (options: RegisterServerOptions): Promise<string> { async function ensureWorkingDir (options: RegisterServerOptions): Promise<string> {
const logger = options.peertubeHelpers.logger const logger = options.peertubeHelpers.logger
logger.debug('Calling ensureworkingDirs') logger.debug('Calling ensureworkingDir')
const paths = await getProsodyFilePaths(options) const paths = await getProsodyFilePaths(options)
const dir = paths.dir const dir = paths.dir
@ -39,12 +38,10 @@ async function ensureWorkingDirs (options: RegisterServerOptions): Promise<strin
await fs.promises.access(dir, fs.constants.W_OK) // will throw an error if no access await fs.promises.access(dir, fs.constants.W_OK) // will throw an error if no access
logger.debug(`Write access ok on ${dir}`) logger.debug(`Write access ok on ${dir}`)
for (const path of [paths.data, paths.bots.dir]) { if (!fs.existsSync(paths.data)) {
if (!fs.existsSync(path)) { logger.info(`The data dir ${paths.data} does not exists, trying to create it`)
logger.info(`The data dir ${path} does not exists, trying to create it`) await fs.promises.mkdir(paths.data)
await fs.promises.mkdir(path) logger.debug(`Working dir ${paths.data} was created`)
logger.debug(`Working dir ${path} was created`)
}
} }
return dir return dir
@ -62,61 +59,25 @@ async function getProsodyFilePaths (options: RegisterServerOptions): Promise<Pro
log: path.resolve(dir, 'prosody.log'), log: path.resolve(dir, 'prosody.log'),
config: path.resolve(dir, 'prosody.cfg.lua'), config: path.resolve(dir, 'prosody.cfg.lua'),
data: path.resolve(dir, 'data'), data: path.resolve(dir, 'data'),
bots: {
dir: path.resolve(dir, 'bots'),
demobot: path.resolve(dir, 'bots', 'demobot.js')
},
modules: path.resolve(__dirname, '../../prosody-modules') modules: path.resolve(__dirname, '../../prosody-modules')
} }
} }
interface ProsodyConfigBots { interface ProsodyConfig {
demobot?: string[] // if the demo bot is activated, here are the video UUIDS where it will be.
}
type ProsodyConfigFilesKey = 'prosody' | 'demobot'
type ProsodyConfigFiles = Array<{
key: ProsodyConfigFilesKey
path: string
content: string content: string
}> paths: ProsodyFilePaths
host: string
class ProsodyConfig { port: string
constructor ( baseApiUrl: string
private readonly configFiles: ProsodyConfigFiles, roomType: 'video' | 'channel'
public paths: ProsodyFilePaths, logByDefault: boolean
public host: string, logExpiration: ConfigLogExpiration
public port: string, valuesToHideInDiagnostic: {[key: string]: string}
public baseApiUrl: string,
public roomType: 'video' | 'channel',
public logByDefault: boolean,
public logExpiration: ConfigLogExpiration,
public bots: ProsodyConfigBots,
public valuesToHideInDiagnostic: {[key: string]: string}
) {}
public getConfigFiles (): ProsodyConfigFiles {
return this.configFiles
}
public contentForDiagnostic (content: string): string {
let r: string = content
for (const key in this.valuesToHideInDiagnostic) {
// replaceAll not available, using trick:
r = r.split(this.valuesToHideInDiagnostic[key]).join(`***${key}***`)
}
return r
}
} }
async function getProsodyConfig (options: RegisterServerOptions): Promise<ProsodyConfig> { async function getProsodyConfig (options: RegisterServerOptions): Promise<ProsodyConfig> {
const logger = options.peertubeHelpers.logger const logger = options.peertubeHelpers.logger
logger.debug('Calling getProsodyConfig') logger.debug('Calling getProsodyConfig')
let useExternalComponents = false
const bots: ProsodyConfigBots = {}
const valuesToHideInDiagnostic: {[key: string]: string} = {}
const settings = await options.settingsManager.getSettings([ const settings = await options.settingsManager.getSettings([
'prosody-port', 'prosody-port',
'prosody-muc-log-by-default', 'prosody-muc-log-by-default',
@ -124,25 +85,20 @@ async function getProsodyConfig (options: RegisterServerOptions): Promise<Prosod
'prosody-c2s', 'prosody-c2s',
'prosody-room-type', 'prosody-room-type',
'prosody-peertube-uri', 'prosody-peertube-uri',
'prosody-c2s-port', 'prosody-c2s-port'
'prosody-component-port',
'chat-videos-list'
]) ])
const valuesToHideInDiagnostic: {[key: string]: string} = {}
const port = (settings['prosody-port'] as string) || '52800' const port = (settings['prosody-port'] as string) || '52800'
if (!/^\d+$/.test(port)) { if (!/^\d+$/.test(port)) {
throw new Error('Invalid port') throw new Error('Invalid port')
} }
const externalComponentsPort = (settings['prosody-component-port'] as string) || '53470'
if (!/^\d+$/.test(externalComponentsPort)) {
throw new Error('Invalid external components port')
}
const logByDefault = (settings['prosody-muc-log-by-default'] as boolean) ?? true const logByDefault = (settings['prosody-muc-log-by-default'] as boolean) ?? true
const logExpirationSetting = (settings['prosody-muc-expiration'] as string) ?? DEFAULTLOGEXPIRATION const logExpirationSetting = (settings['prosody-muc-expiration'] as string) ?? DEFAULTLOGEXPIRATION
const enableC2s = (settings['prosody-c2s'] as boolean) || false const enableC2s = (settings['prosody-c2s'] as boolean) || false
const prosodyDomain = await getProsodyDomain(options) const prosodyDomain = await getProsodyDomain(options)
const paths = await getProsodyFilePaths(options) const paths = await getProsodyFilePaths(options)
const roomType = (settings['prosody-room-type']) === 'channel' ? 'channel' : 'video' const roomType = settings['prosody-room-type'] === 'channel' ? 'channel' : 'video'
const apikey = await getAPIKey(options) const apikey = await getAPIKey(options)
valuesToHideInDiagnostic.APIKey = apikey valuesToHideInDiagnostic.APIKey = apikey
@ -196,57 +152,19 @@ async function getProsodyConfig (options: RegisterServerOptions): Promise<Prosod
logLevel = 'info' logLevel = 'info'
} }
config.setLog(logLevel) config.setLog(logLevel)
const demoBotUUIDs = parseConfigDemoBotUUIDs((settings['chat-videos-list'] as string) || '')
let demoBotContentObj: string = 'null'
if (demoBotUUIDs?.length > 0) {
useExternalComponents = true
const componentSecret = await getExternalComponentKey(options, 'DEMOBOT')
valuesToHideInDiagnostic.ComponentSecret = componentSecret
config.useDemoBot(componentSecret)
bots.demobot = demoBotUUIDs
demoBotContentObj = JSON.stringify({
rooms: 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) {
config.useExternalComponents(externalComponentsPort)
}
const content = config.write() const content = config.write()
return new ProsodyConfig( return {
[ content,
{
key: 'prosody',
path: paths.config,
content: content
},
{
key: 'demobot',
path: paths.bots.demobot,
content: demoBotContent
}
],
paths, paths,
prosodyDomain,
port, port,
baseApiUrl, baseApiUrl,
host: prosodyDomain,
roomType, roomType,
logByDefault, logByDefault,
logExpiration, logExpiration,
bots,
valuesToHideInDiagnostic valuesToHideInDiagnostic
) }
} }
async function writeProsodyConfig (options: RegisterServerOptions): Promise<ProsodyConfig> { async function writeProsodyConfig (options: RegisterServerOptions): Promise<ProsodyConfig> {
@ -254,19 +172,15 @@ async function writeProsodyConfig (options: RegisterServerOptions): Promise<Pros
logger.debug('Calling writeProsodyConfig') logger.debug('Calling writeProsodyConfig')
logger.debug('Ensuring that the working dir exists') logger.debug('Ensuring that the working dir exists')
await ensureWorkingDirs(options) await ensureWorkingDir(options)
logger.debug('Computing the Prosody config content') logger.debug('Computing the Prosody config content')
const config = await getProsodyConfig(options) const config = await getProsodyConfig(options)
const content = config.content
const fileName = config.paths.config
const configFiles = config.getConfigFiles() logger.info(`Writing prosody configuration file to ${fileName}`)
for (const configFile of configFiles) { await fs.promises.writeFile(fileName, content)
const content = configFile.content logger.debug('Prosody configuration file writen')
const fileName = configFile.path
logger.info(`Writing prosody configuration file '${configFile.key}' to ${fileName}.`)
await fs.promises.writeFile(fileName, content)
logger.debug(`Prosody configuration file '${configFile.key}' writen.`)
}
return config return config
} }
@ -322,10 +236,20 @@ function readLogExpiration (options: RegisterServerOptions, logExpiration: strin
} }
} }
function getProsodyConfigContentForDiagnostic (config: ProsodyConfig, content?: string): string {
let r: string = content ?? config.content
for (const key in config.valuesToHideInDiagnostic) {
// replaceAll not available, using trick:
r = r.split(config.valuesToHideInDiagnostic[key]).join(`***${key}***`)
}
return r
}
export { export {
getProsodyConfig, getProsodyConfig,
getWorkingDir, getWorkingDir,
ensureWorkingDirs, ensureWorkingDir,
getProsodyFilePaths, getProsodyFilePaths,
writeProsodyConfig writeProsodyConfig,
getProsodyConfigContentForDiagnostic
} }

View File

@ -1,19 +0,0 @@
function parseConfigDemoBotUUIDs (s: string): string[] {
if (!s) {
return []
}
let a = s.split('\n')
// find lines that are like:
// 6432f147-83c7-4fa3-b3b5-e49c2590e825 #!demobot
a = a.filter(line => /#!demobot\b/.test(line))
a = a.map(line => {
return line.replace(/#.*$/, '')
.replace(/^\s+/, '')
.replace(/\s+$/, '')
})
return a.filter(line => line !== '')
}
export {
parseConfigDemoBotUUIDs
}

View File

@ -102,19 +102,16 @@ class ProsodyConfigVirtualHost extends ProsodyConfigBlock {
class ProsodyConfigComponent extends ProsodyConfigBlock { class ProsodyConfigComponent extends ProsodyConfigBlock {
name: string name: string
type?: string type: string
constructor (name: string, type?: string) { constructor (type: string, name: string) {
super(' ') super(' ')
this.type = type this.type = type
this.name = name this.name = name
} }
write (): string { write (): string {
if (this.type !== undefined) { return `Component "${this.name}" "${this.type}"\n` + super.write()
return `Component "${this.name}" "${this.type}"\n` + super.write()
}
return `Component "${this.name}"\n` + super.write()
} }
} }
@ -126,7 +123,6 @@ class ProsodyConfigContent {
authenticated?: ProsodyConfigVirtualHost authenticated?: ProsodyConfigVirtualHost
anon: ProsodyConfigVirtualHost anon: ProsodyConfigVirtualHost
muc: ProsodyConfigComponent muc: ProsodyConfigComponent
externalComponents: ProsodyConfigComponent[] = []
log: string log: string
prosodyDomain: string prosodyDomain: string
@ -136,7 +132,7 @@ class ProsodyConfigContent {
this.log = '' this.log = ''
this.prosodyDomain = prosodyDomain this.prosodyDomain = prosodyDomain
this.anon = new ProsodyConfigVirtualHost('anon.' + prosodyDomain) this.anon = new ProsodyConfigVirtualHost('anon.' + prosodyDomain)
this.muc = new ProsodyConfigComponent('room.' + prosodyDomain, 'muc') this.muc = new ProsodyConfigComponent('muc', 'room.' + prosodyDomain)
this.global.set('daemonize', false) this.global.set('daemonize', false)
this.global.set('allow_registration', false) this.global.set('allow_registration', false)
@ -285,21 +281,6 @@ class ProsodyConfigContent {
this.muc.set('peertubelivechat_test_peertube_api_url', apiurl) this.muc.set('peertubelivechat_test_peertube_api_url', apiurl)
} }
useExternalComponents (componentsPort: string): void {
this.global.set('component_ports', [componentsPort])
this.global.set('component_interfaces', ['127.0.0.1', '::1'])
}
useDemoBot (componentSecret: string): void {
const demoBotComponent = new ProsodyConfigComponent('demobot.' + this.prosodyDomain)
demoBotComponent.set('component_secret', componentSecret)
// If we want the bot to be moderator, should do the trick:
// this.global.add('admins', 'demobot.' + this.prosodyDomain)
this.externalComponents.push(demoBotComponent)
}
setLog (level: ProsodyLogLevel, syslog?: ProsodyLogLevel[]): void { setLog (level: ProsodyLogLevel, syslog?: ProsodyLogLevel[]): void {
let log = '' let log = ''
log += 'log = {\n' log += 'log = {\n'
@ -328,11 +309,6 @@ class ProsodyConfigContent {
content += '\n\n' content += '\n\n'
content += this.muc.write() content += this.muc.write()
content += '\n\n' content += '\n\n'
this.externalComponents.forEach((externalComponent) => {
content += '\n\n'
content += externalComponent.write()
content += '\n\n'
})
return content return content
} }
} }

View File

@ -5,10 +5,6 @@ interface ProsodyFilePaths {
log: string log: string
config: string config: string
data: string data: string
bots: {
dir: string
demobot: string
}
modules: string modules: string
} }

View File

@ -113,23 +113,20 @@ async function testProsodyCorrectlyRunning (options: RegisterServerOptions): Pro
try { try {
const wantedConfig = await getProsodyConfig(options) const wantedConfig = await getProsodyConfig(options)
const configFiles = wantedConfig.getConfigFiles() const filePath = wantedConfig.paths.config
for (const configFile of configFiles) {
const filePath = configFile.path
await fs.promises.access(filePath, fs.constants.R_OK) // throw an error if file does not exist. await fs.promises.access(filePath, fs.constants.R_OK) // throw an error if file does not exist.
result.messages.push(`The prosody configuration file (${configFile.key}: ${filePath}) exists`) result.messages.push(`The prosody configuration file (${filePath}) exists`)
const actualContent = await fs.promises.readFile(filePath, { const actualContent = await fs.promises.readFile(filePath, {
encoding: 'utf-8' encoding: 'utf-8'
}) })
const wantedContent = configFile.content const wantedContent = wantedConfig.content
if (actualContent === wantedContent) { if (actualContent === wantedContent) {
result.messages.push(`Prosody configuration file '${configFile.key}' content is correct.`) result.messages.push('Prosody configuration file content is correct.')
} else { } else {
result.messages.push(`Prosody configuration file '${configFile.key}' content is not correct.`) result.messages.push('Prosody configuration file content is not correct.')
return result return result
}
} }
} catch (error) { } catch (error) {
result.messages.push('Error when requiring the prosody config file: ' + (error as string)) result.messages.push('Error when requiring the prosody config file: ' + (error as string))

View File

@ -359,22 +359,6 @@ archiving for a specific room, by editing its properties.
</ul>` </ul>`
}) })
registerSetting({
name: 'prosody-component-port',
label: 'The port to be use for external components',
type: 'input',
default: '53470',
private: true,
descriptionHTML:
`The port that will be used for extra components used by the builtin Prosody server.<br>
This is only used when one of these special features is used:<br>
<ul>
<li>Demo bot: this is a hidden feature, for demonstration purposes. See the documentation for more information.</li>
</ul><br>
Change it if this port is already in use on your server.
`
})
registerSetting({ registerSetting({
name: 'prosody-c2s', name: 'prosody-c2s',
label: 'Enable client to server connections', label: 'Enable client to server connections',