Add voice audio, capture and transcript, browser monitor, and DM UI
This commit is contained in:
+64
-4
@@ -99,6 +99,12 @@ export interface PuppetTransport {
|
||||
reactionAdd(channelId: string, messageId: string, emoji: string): Promise<unknown>;
|
||||
}
|
||||
|
||||
/** The audio actions a chat command can trigger. */
|
||||
export interface PuppetAudio {
|
||||
speak(text: string, options?: { provider?: "local" | "api"; voice?: string }): Promise<unknown>;
|
||||
stop(): void;
|
||||
}
|
||||
|
||||
interface PollState {
|
||||
channelId: string;
|
||||
authorId: string;
|
||||
@@ -142,7 +148,10 @@ export class Puppet {
|
||||
private selfId?: string;
|
||||
private queue: Promise<void> = Promise.resolve();
|
||||
|
||||
constructor(private readonly core: PuppetTransport) {
|
||||
constructor(
|
||||
private readonly core: PuppetTransport,
|
||||
private readonly audio?: PuppetAudio
|
||||
) {
|
||||
try {
|
||||
const state = JSON.parse(readFileSync(STATE_FILE, "utf8")) as PersistedState;
|
||||
if (state.ownerId) this.ownerId = state.ownerId;
|
||||
@@ -159,8 +168,7 @@ export class Puppet {
|
||||
|
||||
if (state.config) {
|
||||
this.config = {
|
||||
contextMessages: clamp(state.config.contextMessages ?? DEFAULT_CONFIG.contextMessages, 1, 50),
|
||||
contextChars: clamp(state.config.contextChars ?? DEFAULT_CONFIG.contextChars, 200, 20000),
|
||||
contextMessages: clamp(state.config.contextMessages ?? DEFAULT_CONFIG.contextMessages, 1, 50), contextChars: clamp(state.config.contextChars ?? DEFAULT_CONFIG.contextChars, 200, 20000),
|
||||
fetchCooldownSeconds: clamp(
|
||||
state.config.fetchCooldownSeconds ?? DEFAULT_CONFIG.fetchCooldownSeconds,
|
||||
0,
|
||||
@@ -181,6 +189,7 @@ export class Puppet {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
} catch {
|
||||
// first run: defaults apply
|
||||
}
|
||||
@@ -263,7 +272,11 @@ export class Puppet {
|
||||
writeFileSync(
|
||||
STATE_FILE,
|
||||
JSON.stringify(
|
||||
{ ownerId: this.ownerId, whitelist: this.whitelist, config: this.config },
|
||||
{
|
||||
ownerId: this.ownerId,
|
||||
whitelist: this.whitelist,
|
||||
config: this.config
|
||||
},
|
||||
null,
|
||||
4
|
||||
)
|
||||
@@ -446,6 +459,8 @@ export class Puppet {
|
||||
|
||||
if (command?.name === "whitelist") {
|
||||
await this.handleWhitelistCommand(command, author.id, tier, channelId);
|
||||
} else if (command?.name === "speak") {
|
||||
await this.handleSpeakCommand(command, author.id, channelId);
|
||||
} else if (POLL_ENABLED && command?.name === "poll") {
|
||||
await this.handlePollCommand(author.id, channelId, text);
|
||||
}
|
||||
@@ -541,6 +556,51 @@ export class Puppet {
|
||||
}
|
||||
}
|
||||
|
||||
/* Text to speech */
|
||||
|
||||
private async handleSpeakCommand(command: ParsedCommand, authorId: string, channelId: string) {
|
||||
if (!this.audio) {
|
||||
return this.respond(authorId, channelId, "speak", "Voice audio is not available.");
|
||||
}
|
||||
|
||||
const text = command.args.join(" ").trim();
|
||||
if (!text || text === "help") {
|
||||
return this.respond(
|
||||
authorId,
|
||||
channelId,
|
||||
"speak",
|
||||
["speak — text to speech.", "Usage:", " speak <text>", " speak stop"].join("\n")
|
||||
);
|
||||
}
|
||||
|
||||
if (text === "stop") {
|
||||
this.audio.stop();
|
||||
this.actionLog.append({ type: "command", command: "speak stop", requesterId: authorId, result: "ok" });
|
||||
return this.respond(authorId, channelId, "speak", "Stopped.");
|
||||
}
|
||||
|
||||
try {
|
||||
await this.audio.speak(text, {});
|
||||
this.actionLog.append({
|
||||
type: "command",
|
||||
command: "speak",
|
||||
requesterId: authorId,
|
||||
result: "queued",
|
||||
chars: text.length
|
||||
});
|
||||
return this.respond(authorId, channelId, "speak", "Speaking.");
|
||||
} catch (error) {
|
||||
this.actionLog.append({
|
||||
type: "command",
|
||||
command: "speak",
|
||||
requesterId: authorId,
|
||||
result: "error",
|
||||
error: String(error)
|
||||
});
|
||||
return this.respond(authorId, channelId, "speak", `Could not speak: ${(error as Error).message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/* Polls */
|
||||
|
||||
private async handlePollCommand(authorId: string, channelId: string, text: string) {
|
||||
|
||||
Reference in New Issue
Block a user