Skip to content

Commit a3cf443

Browse files
committed
Add config command for viewing and adjusting bot parameters on the fly (#47)
1 parent d1d2656 commit a3cf443

File tree

3 files changed

+185
-3
lines changed

3 files changed

+185
-3
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ docker compose -f docker-compose-warp.yml up -d
122122
| `list` | Show local video library | |
123123
| `status` | Show playback status | |
124124
| `preview <video>` | Generate video thumbnails | |
125+
| `config [parameter] [value]` | View or adjust bot configuration parameters (Admin only) | `cfg`, `set` |
125126
| `ping` | Check bot latency | |
126127
| `help` | Show available commands | |
127128

src/commands/config.ts

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
import { BaseCommand } from "./base.js";
2+
import { CommandContext } from "../types/index.js";
3+
import config, { parseBoolean, parseVideoCodec, parsePreset } from "../config.js";
4+
import logger from "../utils/logger.js";
5+
6+
export default class ConfigCommand extends BaseCommand {
7+
name = "config";
8+
description = "View or adjust bot configuration parameters (Admin only)";
9+
usage = "config [parameter] [value]";
10+
aliases = ["cfg", "set"];
11+
12+
async execute(context: CommandContext): Promise<void> {
13+
// Check if user is an admin
14+
if (!this.isAdmin(context.message.author.id)) {
15+
await this.sendError(context.message, "You don't have permission to use this command. Admin access required.");
16+
logger.warn(`Unauthorized config command attempt by user ${context.message.author.id}`);
17+
return;
18+
}
19+
20+
const args = context.args;
21+
22+
// If no arguments, show current config
23+
if (args.length === 0) {
24+
await this.showConfig(context);
25+
return;
26+
}
27+
28+
// If one argument, show specific parameter
29+
if (args.length === 1) {
30+
await this.showParameter(context, args[0]);
31+
return;
32+
}
33+
34+
// If two or more arguments, set parameter
35+
const parameter = args[0].toLowerCase();
36+
const value = args.slice(1).join(' ');
37+
await this.setParameter(context, parameter, value);
38+
}
39+
40+
private async showConfig(context: CommandContext): Promise<void> {
41+
const configInfo = [
42+
"**Stream Options:**",
43+
`• respect_video_params: ${config.respect_video_params}`,
44+
`• width: ${config.width}`,
45+
`• height: ${config.height}`,
46+
`• fps: ${config.fps}`,
47+
`• bitrateKbps: ${config.bitrateKbps}`,
48+
`• maxBitrateKbps: ${config.maxBitrateKbps}`,
49+
`• hardwareAcceleratedDecoding: ${config.hardwareAcceleratedDecoding}`,
50+
`• h26xPreset: ${config.h26xPreset}`,
51+
`• videoCodec: ${config.videoCodec}`,
52+
"",
53+
"**General Options:**",
54+
`• videosDir: ${config.videosDir}`,
55+
`• previewCacheDir: ${config.previewCacheDir}`,
56+
"",
57+
"Use `config <parameter>` to view a specific parameter",
58+
"Use `config <parameter> <value>` to change a parameter"
59+
].join('\n');
60+
61+
await this.sendInfo(context.message, 'Bot Configuration', configInfo);
62+
}
63+
64+
private async showParameter(context: CommandContext, parameter: string): Promise<void> {
65+
const param = parameter.toLowerCase();
66+
const configKeys = this.getConfigKeys();
67+
68+
if (!configKeys.includes(param)) {
69+
await this.sendError(context.message, `Unknown parameter: ${parameter}`);
70+
return;
71+
}
72+
73+
const value = (config as any)[param];
74+
await this.sendInfo(context.message, `Config: ${parameter}`, `Current value: \`${value}\``);
75+
}
76+
77+
private async setParameter(context: CommandContext, parameter: string, value: string): Promise<void> {
78+
const param = parameter.toLowerCase();
79+
80+
try {
81+
switch (param) {
82+
// Boolean parameters
83+
case 'respect_video_params':
84+
case 'hardwareacceleration':
85+
case 'hardwareaccelerateddecoding':
86+
const boolValue = parseBoolean(value);
87+
if (param === 'hardwareacceleration' || param === 'hardwareaccelerateddecoding') {
88+
config.hardwareAcceleratedDecoding = boolValue;
89+
await this.sendSuccess(context.message, `Set hardwareAcceleratedDecoding to \`${boolValue}\``);
90+
} else {
91+
(config as any)[param] = boolValue;
92+
await this.sendSuccess(context.message, `Set ${parameter} to \`${boolValue}\``);
93+
}
94+
logger.info(`Config updated: ${parameter} = ${boolValue}`);
95+
break;
96+
97+
// Number parameters
98+
case 'width':
99+
case 'height':
100+
case 'fps':
101+
case 'bitratekbps':
102+
case 'maxbitratekbps':
103+
const numValue = parseInt(value);
104+
if (isNaN(numValue) || numValue <= 0) {
105+
await this.sendError(context.message, `Invalid number value: ${value}`);
106+
return;
107+
}
108+
(config as any)[param] = numValue;
109+
await this.sendSuccess(context.message, `Set ${parameter} to \`${numValue}\``);
110+
logger.info(`Config updated: ${parameter} = ${numValue}`);
111+
break;
112+
113+
// Video codec
114+
case 'videocodec':
115+
const codec = parseVideoCodec(value);
116+
if (!codec) {
117+
await this.sendError(context.message, `Invalid video codec. Valid options: VP8, H264, H265`);
118+
return;
119+
}
120+
config.videoCodec = codec;
121+
await this.sendSuccess(context.message, `Set videoCodec to \`${codec}\``);
122+
logger.info(`Config updated: videoCodec = ${codec}`);
123+
break;
124+
125+
// H26x preset
126+
case 'h26xpreset':
127+
case 'preset':
128+
const preset = parsePreset(value);
129+
if (!preset) {
130+
await this.sendError(context.message, `Invalid preset. Valid options: ultrafast, superfast, veryfast, faster, fast, medium, slow, slower, veryslow`);
131+
return;
132+
}
133+
config.h26xPreset = preset;
134+
await this.sendSuccess(context.message, `Set h26xPreset to \`${preset}\``);
135+
logger.info(`Config updated: h26xPreset = ${preset}`);
136+
break;
137+
138+
// String parameters
139+
case 'videosdir':
140+
case 'previewcachedir':
141+
(config as any)[param] = value;
142+
await this.sendSuccess(context.message, `Set ${parameter} to \`${value}\``);
143+
logger.info(`Config updated: ${parameter} = ${value}`);
144+
break;
145+
146+
default:
147+
await this.sendError(context.message, `Cannot modify parameter: ${parameter}`);
148+
return;
149+
}
150+
} catch (error) {
151+
logger.error(`Error setting config parameter ${parameter}:`, error);
152+
await this.sendError(context.message, `Failed to set ${parameter}: ${error}`);
153+
}
154+
}
155+
156+
private getConfigKeys(): string[] {
157+
return [
158+
'respect_video_params',
159+
'width',
160+
'height',
161+
'fps',
162+
'bitratekbps',
163+
'maxbitratekbps',
164+
'hardwareaccelerateddecoding',
165+
'hardwareacceleration',
166+
'h26xpreset',
167+
'preset',
168+
'videocodec',
169+
'videosdir',
170+
'previewcachedir'
171+
];
172+
}
173+
174+
private isAdmin(userId: string): boolean {
175+
// If no admins configured, allow all users (backwards compatibility)
176+
if (!config.adminIds || config.adminIds.length === 0) {
177+
return true;
178+
}
179+
return config.adminIds.includes(userId);
180+
}
181+
}

src/config.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ dotenv.config({ quiet: true });
55

66
const VALID_VIDEO_CODECS = ['VP8', 'H264', 'H265', 'VP9', 'AV1'];
77

8-
function parseVideoCodec(value: string): "VP8" | "H264" | "H265" {
8+
export function parseVideoCodec(value: string): "VP8" | "H264" | "H265" {
99
if (typeof value === "string") {
1010
value = value.trim().toUpperCase();
1111
}
@@ -15,7 +15,7 @@ function parseVideoCodec(value: string): "VP8" | "H264" | "H265" {
1515
return "H264";
1616
}
1717

18-
function parsePreset(value: string): "ultrafast" | "superfast" | "veryfast" | "faster" | "fast" | "medium" | "slow" | "slower" | "veryslow" {
18+
export function parsePreset(value: string): "ultrafast" | "superfast" | "veryfast" | "faster" | "fast" | "medium" | "slow" | "slower" | "veryslow" {
1919
if (typeof value === "string") {
2020
value = value.trim().toLowerCase();
2121
}
@@ -35,7 +35,7 @@ function parsePreset(value: string): "ultrafast" | "superfast" | "veryfast" | "f
3535
}
3636
}
3737

38-
function parseBoolean(value: string | undefined): boolean {
38+
export function parseBoolean(value: string | undefined): boolean {
3939
if (typeof value === "string") {
4040
value = value.trim().toLowerCase();
4141
}

0 commit comments

Comments
 (0)