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)
### 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
* Fix spanish translation.
* Hide secret keys in diagnostic tool.
## 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-expiration':
case 'prosody-c2s':
case 'prosody-component-port':
return options.formValues['chat-type'] !== ('builtin-prosody' as ChatType)
case 'prosody-c2s-port':
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
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==",
"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": {
"version": "3.2.9",
"resolved": "https://registry.npmjs.org/@types/async/-/async-3.2.9.tgz",
@ -220,24 +211,6 @@
"@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": {
"version": "4.17.13",
"resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.13.tgz",
@ -281,23 +254,11 @@
"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": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.0.tgz",
"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": {
"version": "7.0.9",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz",
@ -310,12 +271,6 @@
"integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=",
"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": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.1.tgz",
@ -324,37 +279,6 @@
"@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": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz",
@ -429,119 +353,6 @@
"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": {
"version": "4.29.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.29.0.tgz",
@ -879,108 +690,6 @@
"@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",
@ -3083,7 +2792,8 @@
"events": {
"version": "3.3.0",
"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": {
"version": "1.0.3",
@ -4497,11 +4207,6 @@
"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",
@ -4615,11 +4320,6 @@
"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",

View File

@ -31,8 +31,6 @@
"dist/assets/style.css"
],
"dependencies": {
"@xmpp/component": "^0.13.0",
"@xmpp/jid": "^0.13.0",
"async": "^3.2.2",
"body-parser": "^1.19.0",
"decache": "^4.6.0",
@ -50,8 +48,6 @@
"@types/got": "^9.6.12",
"@types/node": "^16.11.6",
"@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/parser": "^4.29.0",
"eslint": "^7.32.0",
@ -86,7 +82,6 @@
"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",
@ -94,7 +89,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: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:script": "npx eslint --ext .js --ext .ts .",
"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.
*/
async function _getKey ({ storageManager }: RegisterServerOptions, key: string): Promise<string> {
let value: string = await storageManager.getData(key)
async function getAPIKey ({ storageManager }: RegisterServerOptions): Promise<string> {
let value: string = await storageManager.getData('APIKEY')
if (!value) {
value = Math.random().toString(36).slice(2, 12)
await storageManager.storeData(key, value)
await storageManager.storeData('APIKEY', 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 {
getAPIKey,
getExternalComponentKey
getAPIKey
}

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

View File

@ -4,9 +4,8 @@ import { getBaseRouterRoute } from '../helpers'
import { ProsodyFilePaths } from './config/paths'
import { ConfigLogExpiration, ProsodyConfigContent } from './config/content'
import { getProsodyDomain } from './config/domain'
import { getAPIKey, getExternalComponentKey } from '../apikey'
import { getAPIKey } from '../apikey'
import type { ProsodyLogLevel } from './config/content'
import { parseConfigDemoBotUUIDs } from './config/bots'
async function getWorkingDir (options: RegisterServerOptions): Promise<string> {
const peertubeHelpers = options.peertubeHelpers
@ -24,9 +23,9 @@ async function getWorkingDir (options: RegisterServerOptions): Promise<string> {
/**
* 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
logger.debug('Calling ensureworkingDirs')
logger.debug('Calling ensureworkingDir')
const paths = await getProsodyFilePaths(options)
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
logger.debug(`Write access ok on ${dir}`)
for (const path of [paths.data, paths.bots.dir]) {
if (!fs.existsSync(path)) {
logger.info(`The data dir ${path} does not exists, trying to create it`)
await fs.promises.mkdir(path)
logger.debug(`Working dir ${path} was created`)
}
if (!fs.existsSync(paths.data)) {
logger.info(`The data dir ${paths.data} does not exists, trying to create it`)
await fs.promises.mkdir(paths.data)
logger.debug(`Working dir ${paths.data} was created`)
}
return dir
@ -62,61 +59,25 @@ async function getProsodyFilePaths (options: RegisterServerOptions): Promise<Pro
log: path.resolve(dir, 'prosody.log'),
config: path.resolve(dir, 'prosody.cfg.lua'),
data: path.resolve(dir, 'data'),
bots: {
dir: path.resolve(dir, 'bots'),
demobot: path.resolve(dir, 'bots', 'demobot.js')
},
modules: path.resolve(__dirname, '../../prosody-modules')
}
}
interface ProsodyConfigBots {
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
interface ProsodyConfig {
content: string
}>
class ProsodyConfig {
constructor (
private readonly configFiles: ProsodyConfigFiles,
public paths: ProsodyFilePaths,
public host: string,
public port: 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
}
paths: ProsodyFilePaths
host: string
port: string
baseApiUrl: string
roomType: 'video' | 'channel'
logByDefault: boolean
logExpiration: ConfigLogExpiration
valuesToHideInDiagnostic: {[key: string]: string}
}
async function getProsodyConfig (options: RegisterServerOptions): Promise<ProsodyConfig> {
const logger = options.peertubeHelpers.logger
logger.debug('Calling getProsodyConfig')
let useExternalComponents = false
const bots: ProsodyConfigBots = {}
const valuesToHideInDiagnostic: {[key: string]: string} = {}
const settings = await options.settingsManager.getSettings([
'prosody-port',
'prosody-muc-log-by-default',
@ -124,25 +85,20 @@ async function getProsodyConfig (options: RegisterServerOptions): Promise<Prosod
'prosody-c2s',
'prosody-room-type',
'prosody-peertube-uri',
'prosody-c2s-port',
'prosody-component-port',
'chat-videos-list'
'prosody-c2s-port'
])
const valuesToHideInDiagnostic: {[key: string]: string} = {}
const port = (settings['prosody-port'] as string) || '52800'
if (!/^\d+$/.test(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 logExpirationSetting = (settings['prosody-muc-expiration'] as string) ?? DEFAULTLOGEXPIRATION
const enableC2s = (settings['prosody-c2s'] as boolean) || false
const prosodyDomain = await getProsodyDomain(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)
valuesToHideInDiagnostic.APIKey = apikey
@ -196,57 +152,19 @@ async function getProsodyConfig (options: RegisterServerOptions): Promise<Prosod
logLevel = 'info'
}
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()
return new ProsodyConfig(
[
{
key: 'prosody',
path: paths.config,
content: content
},
{
key: 'demobot',
path: paths.bots.demobot,
content: demoBotContent
}
],
return {
content,
paths,
prosodyDomain,
port,
baseApiUrl,
host: prosodyDomain,
roomType,
logByDefault,
logExpiration,
bots,
valuesToHideInDiagnostic
)
}
}
async function writeProsodyConfig (options: RegisterServerOptions): Promise<ProsodyConfig> {
@ -254,19 +172,15 @@ async function writeProsodyConfig (options: RegisterServerOptions): Promise<Pros
logger.debug('Calling writeProsodyConfig')
logger.debug('Ensuring that the working dir exists')
await ensureWorkingDirs(options)
await ensureWorkingDir(options)
logger.debug('Computing the Prosody config content')
const config = await getProsodyConfig(options)
const content = config.content
const fileName = config.paths.config
const configFiles = config.getConfigFiles()
for (const configFile of configFiles) {
const content = configFile.content
const fileName = configFile.path
logger.info(`Writing prosody configuration file '${configFile.key}' to ${fileName}.`)
logger.info(`Writing prosody configuration file to ${fileName}`)
await fs.promises.writeFile(fileName, content)
logger.debug(`Prosody configuration file '${configFile.key}' writen.`)
}
logger.debug('Prosody configuration file writen')
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 {
getProsodyConfig,
getWorkingDir,
ensureWorkingDirs,
ensureWorkingDir,
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,20 +102,17 @@ class ProsodyConfigVirtualHost extends ProsodyConfigBlock {
class ProsodyConfigComponent extends ProsodyConfigBlock {
name: string
type?: string
type: string
constructor (name: string, type?: string) {
constructor (type: string, name: string) {
super(' ')
this.type = type
this.name = name
}
write (): string {
if (this.type !== undefined) {
return `Component "${this.name}" "${this.type}"\n` + super.write()
}
return `Component "${this.name}"\n` + super.write()
}
}
type ProsodyLogLevel = 'debug' | 'info' | 'warn' | 'error'
@ -126,7 +123,6 @@ class ProsodyConfigContent {
authenticated?: ProsodyConfigVirtualHost
anon: ProsodyConfigVirtualHost
muc: ProsodyConfigComponent
externalComponents: ProsodyConfigComponent[] = []
log: string
prosodyDomain: string
@ -136,7 +132,7 @@ class ProsodyConfigContent {
this.log = ''
this.prosodyDomain = 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('allow_registration', false)
@ -285,21 +281,6 @@ class ProsodyConfigContent {
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 {
let log = ''
log += 'log = {\n'
@ -328,11 +309,6 @@ class ProsodyConfigContent {
content += '\n\n'
content += this.muc.write()
content += '\n\n'
this.externalComponents.forEach((externalComponent) => {
content += '\n\n'
content += externalComponent.write()
content += '\n\n'
})
return content
}
}

View File

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

View File

@ -113,24 +113,21 @@ async function testProsodyCorrectlyRunning (options: RegisterServerOptions): Pro
try {
const wantedConfig = await getProsodyConfig(options)
const configFiles = wantedConfig.getConfigFiles()
for (const configFile of configFiles) {
const filePath = configFile.path
const filePath = wantedConfig.paths.config
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, {
encoding: 'utf-8'
})
const wantedContent = configFile.content
const wantedContent = wantedConfig.content
if (actualContent === wantedContent) {
result.messages.push(`Prosody configuration file '${configFile.key}' content is correct.`)
result.messages.push('Prosody configuration file content is correct.')
} 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
}
}
} catch (error) {
result.messages.push('Error when requiring the prosody config file: ' + (error as string))
return result

View File

@ -359,22 +359,6 @@ archiving for a specific room, by editing its properties.
</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({
name: 'prosody-c2s',
label: 'Enable client to server connections',