Add text features: trusted triggers, context cache, response policy, MCP config tools
This commit is contained in:
@@ -36,6 +36,7 @@ const KNOWN_ACTIONS = new Set<string>([
|
||||
PuppetAction.StateGet,
|
||||
PuppetAction.MessageSend,
|
||||
PuppetAction.MessageRead,
|
||||
PuppetAction.MessageFetch,
|
||||
PuppetAction.DmSend
|
||||
]);
|
||||
|
||||
|
||||
@@ -8,12 +8,26 @@
|
||||
*/
|
||||
|
||||
import { openPrivateChannel, sendMessage } from "@vencord/types/utils";
|
||||
import { findByPropsLazy } from "@vencord/types/webpack";
|
||||
import { ChannelStore, GuildStore, MessageStore, UserStore } from "@vencord/types/webpack/common";
|
||||
import { isSnowflake, PUPPET_LIMITS, PuppetAction, PuppetErrorCode, type PuppetResult } from "shared/puppetProtocol";
|
||||
|
||||
/** Channel types that can receive text messages. */
|
||||
const SENDABLE_CHANNEL_TYPES = new Set([0, 1, 3, 5, 10, 11, 12]);
|
||||
|
||||
/** Discord's message fetch action, located lazily at runtime. */
|
||||
const MessageActions = findByPropsLazy("fetchMessages");
|
||||
|
||||
function compareIds(a: string, b: string): number {
|
||||
try {
|
||||
const left = BigInt(a);
|
||||
const right = BigInt(b);
|
||||
return left < right ? -1 : left > right ? 1 : 0;
|
||||
} catch {
|
||||
return a < b ? -1 : a > b ? 1 : 0;
|
||||
}
|
||||
}
|
||||
|
||||
class ActionError extends Error {
|
||||
constructor(
|
||||
public readonly code: PuppetErrorCode,
|
||||
@@ -175,6 +189,61 @@ export async function handlePuppetAction(
|
||||
});
|
||||
}
|
||||
|
||||
case PuppetAction.MessageFetch: {
|
||||
const { channelId, limit = 50 } = params;
|
||||
assert(isSnowflake(channelId), PuppetErrorCode.InvalidSnowflake, "channelId must be a snowflake");
|
||||
assert(
|
||||
Number.isInteger(limit) && Number(limit) >= 1 && Number(limit) <= PUPPET_LIMITS.maxFetchLimit,
|
||||
PuppetErrorCode.InvalidParam,
|
||||
`limit must be an integer between 1 and ${PUPPET_LIMITS.maxFetchLimit}`
|
||||
);
|
||||
if (params.before !== undefined) {
|
||||
assert(isSnowflake(params.before), PuppetErrorCode.InvalidSnowflake, "before must be a snowflake");
|
||||
}
|
||||
if (params.after !== undefined) {
|
||||
assert(isSnowflake(params.after), PuppetErrorCode.InvalidSnowflake, "after must be a snowflake");
|
||||
}
|
||||
assert(!(params.before && params.after), PuppetErrorCode.InvalidParam, "before and after are mutually exclusive");
|
||||
|
||||
assert(ChannelStore.getChannel(channelId), PuppetErrorCode.ChannelNotFound, `channel ${channelId} not found`);
|
||||
|
||||
const fetchMessages = (MessageActions as any)?.fetchMessages;
|
||||
assert(
|
||||
typeof fetchMessages === "function",
|
||||
PuppetErrorCode.NotSupported,
|
||||
"message fetch is not available in this client"
|
||||
);
|
||||
|
||||
const before = params.before as string | undefined;
|
||||
const after = params.after as string | undefined;
|
||||
|
||||
await fetchMessages({
|
||||
channelId,
|
||||
limit: Number(limit),
|
||||
...(before ? { before } : {}),
|
||||
...(after ? { after } : {})
|
||||
});
|
||||
|
||||
const all = MessageStore.getMessages(channelId)?._array ?? [];
|
||||
const filtered = all.filter(message => {
|
||||
if (after && compareIds(message.id, after) <= 0) return false;
|
||||
if (before && compareIds(message.id, before) >= 0) return false;
|
||||
return true;
|
||||
});
|
||||
|
||||
return ok({
|
||||
channelId,
|
||||
fetched: filtered.length,
|
||||
messages: filtered.slice(-Number(limit)).map(message => ({
|
||||
id: message.id,
|
||||
author: { id: message.author.id, username: message.author.username },
|
||||
content: message.content,
|
||||
timestamp: message.timestamp,
|
||||
attachments: (message.attachments ?? []).map(attachment => ({ filename: attachment.filename }))
|
||||
}))
|
||||
});
|
||||
}
|
||||
|
||||
case PuppetAction.DmSend: {
|
||||
const { userId } = params;
|
||||
assert(isSnowflake(userId), PuppetErrorCode.InvalidSnowflake, "userId must be a snowflake");
|
||||
|
||||
@@ -12,6 +12,7 @@ export const enum PuppetAction {
|
||||
StateGet = "state.get",
|
||||
MessageSend = "message.send",
|
||||
MessageRead = "message.read",
|
||||
MessageFetch = "message.fetch",
|
||||
DmSend = "dm.send"
|
||||
}
|
||||
|
||||
@@ -21,6 +22,7 @@ export const PuppetActions: readonly PuppetAction[] = [
|
||||
PuppetAction.StateGet,
|
||||
PuppetAction.MessageSend,
|
||||
PuppetAction.MessageRead,
|
||||
PuppetAction.MessageFetch,
|
||||
PuppetAction.DmSend
|
||||
];
|
||||
|
||||
@@ -38,6 +40,7 @@ export const enum PuppetErrorCode {
|
||||
NotSendable = "NOT_SENDABLE",
|
||||
SendFailed = "SEND_FAILED",
|
||||
DmUnavailable = "DM_UNAVAILABLE",
|
||||
NotSupported = "NOT_SUPPORTED",
|
||||
RateLimited = "RATE_LIMITED",
|
||||
Internal = "INTERNAL"
|
||||
}
|
||||
@@ -70,6 +73,7 @@ export interface PuppetEvent {
|
||||
export const PUPPET_LIMITS = {
|
||||
maxContentLength: 2000,
|
||||
maxReadLimit: 100,
|
||||
maxFetchLimit: 100,
|
||||
minActionIntervalMs: 500,
|
||||
maxActionsPerMinute: 30,
|
||||
maxRequestBytes: 64 * 1024,
|
||||
|
||||
Reference in New Issue
Block a user