peertube-plugin-livechat/server/lib/configuration/channel/storage.ts

231 lines
6.8 KiB
TypeScript
Raw Normal View History

2023-08-09 14:16:02 +00:00
import type { RegisterServerOptions } from '@peertube/peertube-types'
2023-09-18 10:23:35 +00:00
import type { ChannelConfigurationOptions } from '../../../../shared/lib/types'
import type { ChannelCommonRoomConf } from '../../configuration/bot'
import { RoomChannel } from '../../room-channel'
import { sanitizeChannelConfigurationOptions } from '../../configuration/channel/sanitize'
import * as fs from 'fs'
import * as path from 'path'
2023-08-09 14:16:02 +00:00
2023-09-19 18:49:14 +00:00
// FIXME: should be exported by xmppjs-chat-bot
type ConfigHandlers = ChannelCommonRoomConf['handlers']
type ConfigHandler = ConfigHandlers[0]
/**
* Get saved configuration options for the given channel.
* Can throw an exception.
* @param options Peertube server options
* @param channelInfos Info from channel from which we want to get infos
2023-09-18 10:23:35 +00:00
* @returns Channel configuration data, or null if nothing is stored
*/
async function getChannelConfigurationOptions (
2023-08-09 14:16:02 +00:00
options: RegisterServerOptions,
2023-09-18 10:23:35 +00:00
channelId: number | string
): Promise<ChannelConfigurationOptions | null> {
const logger = options.peertubeHelpers.logger
2023-09-18 10:23:35 +00:00
const filePath = _getFilePath(options, channelId)
if (!fs.existsSync(filePath)) {
logger.debug('No stored data for channel, returning default values')
2023-09-18 10:23:35 +00:00
return null
}
const content = await fs.promises.readFile(filePath, {
encoding: 'utf-8'
})
2023-09-18 10:23:35 +00:00
const sanitized = await sanitizeChannelConfigurationOptions(options, channelId, JSON.parse(content))
return sanitized
}
function getDefaultChannelConfigurationOptions (_options: RegisterServerOptions): ChannelConfigurationOptions {
2023-08-09 14:16:02 +00:00
return {
2023-09-21 17:32:47 +00:00
bot: {
enabled: false,
nickname: 'Sepia',
// Note: we are instanciating several data for forbiddenWords, quotes and commands.
// This will be used by the frontend to instanciates requires fields
forbiddenWords: [
{
entries: []
},
{
entries: []
},
{
entries: []
}
],
quotes: [
{
messages: [],
delay: 5 * 60 // seconds to minutes
}
],
commands: [
{
command: '',
message: ''
},
{
command: '',
message: ''
},
{
command: '',
message: ''
}
]
}
2023-08-09 14:16:02 +00:00
}
}
/**
* Save channel configuration options.
* Can throw an exception.
* @param options Peertube server options
2023-09-18 10:23:35 +00:00
* @param ChannelConfigurationOptions data to save
*/
async function storeChannelConfigurationOptions (
options: RegisterServerOptions,
2023-09-18 10:23:35 +00:00
channelId: number | string,
channelConfigurationOptions: ChannelConfigurationOptions
2023-08-09 14:16:02 +00:00
): Promise<void> {
2023-09-18 10:23:35 +00:00
const filePath = _getFilePath(options, channelId)
if (!fs.existsSync(filePath)) {
const dir = path.dirname(filePath)
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true })
}
}
2023-09-18 10:23:35 +00:00
const jsonContent = JSON.stringify(channelConfigurationOptions)
await fs.promises.writeFile(filePath, jsonContent, {
encoding: 'utf-8'
})
2023-09-15 15:55:07 +00:00
2023-09-18 10:23:35 +00:00
RoomChannel.singleton().refreshChannelConfigurationOptions(channelId)
}
/**
* Converts the channel configuration to the bot room configuration object (minus the room JID and domain)
* @param options server options
* @param channelConfigurationOptions The channel configuration
* @returns Partial bot room configuration
*/
function channelConfigurationOptionsToBotRoomConf (
options: RegisterServerOptions,
channelConfigurationOptions: ChannelConfigurationOptions
): ChannelCommonRoomConf {
2023-09-19 18:49:14 +00:00
// Note concerning handlers:
// If we want the bot to correctly enable/disable the handlers,
// we must always define all handlers, even if not used.
const handlers: ConfigHandlers = []
2023-09-21 17:32:47 +00:00
channelConfigurationOptions.bot.forbiddenWords.forEach((v, i) => {
handlers.push(_getForbiddenWordsHandler(
'forbidden_words_' + i.toString(),
channelConfigurationOptions.bot.forbiddenWords[i]
))
})
2023-09-19 18:49:14 +00:00
2023-09-19 16:56:39 +00:00
const roomConf: ChannelCommonRoomConf = {
2023-09-21 17:32:47 +00:00
enabled: channelConfigurationOptions.bot.enabled,
2023-09-19 18:49:14 +00:00
handlers
2023-09-15 15:55:07 +00:00
}
2023-09-21 17:32:47 +00:00
if (channelConfigurationOptions.bot.nickname && channelConfigurationOptions.bot.nickname !== '') {
roomConf.nick = channelConfigurationOptions.bot.nickname
2023-09-19 16:56:39 +00:00
}
2023-09-18 10:23:35 +00:00
return roomConf
}
2023-09-19 18:49:14 +00:00
function _getForbiddenWordsHandler (
id: string,
2023-09-21 17:32:47 +00:00
forbiddenWords: ChannelConfigurationOptions['bot']['forbiddenWords'][0]
2023-09-19 18:49:14 +00:00
): ConfigHandler {
const handler: ConfigHandler = {
type: 'moderate',
id,
enabled: false,
options: {
rules: []
}
}
2023-09-21 17:32:47 +00:00
if (forbiddenWords.entries.length === 0) {
2023-09-19 18:49:14 +00:00
return handler
}
handler.enabled = true
const rule: any = {
2023-09-21 17:32:47 +00:00
name: id
}
if (forbiddenWords.regexp) {
// Note: on the Peertube frontend, channelConfigurationOptions.forbiddenWords
// is an array of RegExp definition (strings).
// They are validated one by bone.
// To increase the bot performance, we will join them all (hopping the bot will optimize them).
rule.regexp = '(?:' + forbiddenWords.entries.join(')|(?:') + ')'
} else {
// Here we must add word-breaks and escape entries.
// We join all entries in one Regexp (for the same reason as above).
rule.regexp = '(?:' +
forbiddenWords.entries.map(s => {
s = _stringToWordRegexp(s)
// Must add the \b...
// ... but... won't work if the first (or last) char is an emoji.
// So, doing this trick:
if (/^\w/.test(s)) {
s = '\\b' + s
}
if (/\w$/.test(s)) {
s = s + '\\b'
}
// FIXME: this solution wont work for non-latin charsets.
return s
}).join(')|(?:') + ')'
2023-09-19 18:49:14 +00:00
}
2023-09-21 17:32:47 +00:00
if (forbiddenWords.reason) {
rule.reason = forbiddenWords.reason
2023-09-19 18:49:14 +00:00
}
handler.options.rules.push(rule)
2023-09-21 17:32:47 +00:00
handler.options.applyToModerators = !!forbiddenWords.applyToModerators
2023-09-19 18:49:14 +00:00
return handler
}
2023-09-21 17:32:47 +00:00
const stringToWordRegexpSpecials = [
// order matters for these
'-', '[', ']',
// order doesn't matter for any of these
'/', '{', '}', '(', ')', '*', '+', '?', '.', '\\', '^', '$', '|'
]
// I choose to escape every character with '\'
// even though only some strictly require it when inside of []
const stringToWordRegexp = RegExp('[' + stringToWordRegexpSpecials.join('\\') + ']', 'g')
function _stringToWordRegexp (s: string): string {
return s.replace(stringToWordRegexp, '\\$&')
}
function _getFilePath (
options: RegisterServerOptions,
2023-09-18 10:23:35 +00:00
channelId: number | string
): string {
// some sanitization, just in case...
2023-09-18 10:23:35 +00:00
channelId = parseInt(channelId.toString())
if (isNaN(channelId)) {
throw new Error(`Invalid channelId: ${channelId}`)
}
return path.resolve(
options.peertubeHelpers.plugin.getDataDirectoryPath(),
'channelConfigurationOptions',
channelId.toString() + '.json'
)
2023-08-09 14:16:02 +00:00
}
export {
getChannelConfigurationOptions,
2023-09-18 10:23:35 +00:00
getDefaultChannelConfigurationOptions,
channelConfigurationOptionsToBotRoomConf,
storeChannelConfigurationOptions
2023-08-09 14:16:02 +00:00
}