better commanding and bridging slopping

This commit is contained in:
legop3
2026-06-20 00:21:55 -04:00
parent 9cf44b316d
commit e6c16c3af9
5 changed files with 100 additions and 50 deletions
@@ -194,8 +194,8 @@ const integrationHandlers = integrations.register();
client.on('messageCreate', async (message) => {
try {
await commands.handleCommand(message);
await integrationHandlers.handleBridgeInbound(message);
await commands.handleCommand(message);
} catch (err) {
logger.warn('Error handling Discord message', err.message);
}
@@ -19,6 +19,45 @@ function formatToolCallsCodeBlock(toolCalls = []) {
return `\`\`\`txt\nTool calls:\n${rows.join('\n')}\n\`\`\``;
}
function discordPayloadToBridgeText(payload = {}) {
if (typeof payload === 'string') return payload;
if (!payload || typeof payload !== 'object') return '';
const parts = [];
if (payload.content) parts.push(String(payload.content));
(Array.isArray(payload.embeds) ? payload.embeds : []).forEach((embed) => {
const data = embed?.data || embed || {};
const rows = [];
if (data.title) rows.push(String(data.title));
if (data.description) rows.push(String(data.description));
(Array.isArray(data.fields) ? data.fields : []).forEach((field) => {
if (!field) return;
// The bridge stays straight-through for plain Discord content. Embeds are
// the only Discord-specific shape that web chat cannot show directly, so
// flatten only the readable embed text here at the bridge boundary.
rows.push(`${field.name || 'Field'}\n${field.value || ''}`.trim());
});
if (data.footer?.text) rows.push(String(data.footer.text));
if (rows.length) parts.push(rows.join('\n\n'));
});
(Array.isArray(payload.attachments) ? payload.attachments : []).forEach((attachment) => {
const name = attachment?.name || attachment?.filename || 'attachment';
const url = attachment?.url || attachment?.proxyURL || attachment?.proxyUrl || '';
parts.push(url ? `${name}: ${url}` : String(name));
});
return parts.join('\n\n').trim();
}
function discordMessageToBridgeText(message) {
const attachments = message?.attachments?.values
? Array.from(message.attachments.values())
: [];
return discordPayloadToBridgeText({
content: message?.content || '',
embeds: message?.embeds || [],
attachments,
});
}
function createChatBridgeHandlers(deps) {
const {
logger,
@@ -26,6 +65,7 @@ function createChatBridgeHandlers(deps) {
roverManager,
getGuildConfig,
listGuildConfigs,
sendToChannel,
sendExternalMessage,
sendExternalTyping,
isAdminUser,
@@ -40,23 +80,37 @@ function createChatBridgeHandlers(deps) {
const guildConfig = getGuildConfig(message.guild.id);
if (!guildConfig?.channelId) return;
if (String(message.channelId) !== String(guildConfig.channelId)) return;
if (message.author.bot) return;
if (message.webhookId) return;
const isRoverBotMessage = message.author?.bot && client.user?.id && String(message.author.id) === String(client.user.id);
if (message.author.bot && !isRoverBotMessage) return;
const content = (message.content || '').trim();
const lower = content.toLowerCase();
if (lower.startsWith('rs') || lower === 'ts' || lower.startsWith('ts ')) return;
const bridgedText = isRoverBotMessage ? discordMessageToBridgeText(message) : content;
if (!bridgedText.trim()) return;
const nickname = message.member?.nickname || message.author?.globalName || message.author?.username || 'Discord';
const nickname = isRoverBotMessage
? client.user?.username || 'Rover bot'
: message.member?.nickname || message.author?.globalName || message.author?.username || 'Discord';
const role = isAdminUser(message.author.id) ? 'admin' : 'user';
const guildIconUrl = message.guild.iconURL?.({ extension: 'png', size: 64 }) || null;
const userAvatarUrl = message.author.displayAvatarURL?.({ extension: 'png', size: 64 }) || null;
try {
sendExternalMessage({ text: content, nickname, role, roverId: null, discordGuildId: message.guild.id, discordGuildName: message.guild.name, discordGuildIconUrl: guildIconUrl, discordChannelId: message.channelId, discordUserId: message.author?.id || null, discordUserName: message.author?.globalName || message.author?.username || null, discordUserAvatarUrl: userAvatarUrl });
sendExternalMessage({ text: bridgedText, nickname, role, roverId: null, discordGuildId: message.guild.id, discordGuildName: message.guild.name, discordGuildIconUrl: guildIconUrl, discordChannelId: message.channelId, discordUserId: message.author?.id || null, discordUserName: message.author?.globalName || message.author?.username || null, discordUserAvatarUrl: userAvatarUrl, bot: isRoverBotMessage, profileImage: isRoverBotMessage ? userAvatarUrl : null });
} catch (err) {
logger.warn('Failed to bridge inbound Discord chat', err.message);
}
}
async function handleBridgeSendRequest(event) {
const payload = event?.payload || {};
const guildConfigs = listGuildConfigs();
if (!guildConfigs.length) return;
await Promise.all(guildConfigs.map(async (entry) => {
if (!entry?.channelId) return;
await sendToChannel(entry.channelId, payload.content || '', payload.options || {}, { parse: [] }, false);
}));
}
function handleChatBridgeOutbound(event) {
const payload = event?.payload;
if (!payload) return;
@@ -130,7 +184,11 @@ function createChatBridgeHandlers(deps) {
handleChatBridgeOutbound,
handleChatTypingOutbound,
handleDiscordTypingStart,
handleBridgeSendRequest,
};
}
module.exports = { createChatBridgeHandlers };
module.exports = {
createChatBridgeHandlers,
discordPayloadToBridgeText,
};
@@ -39,6 +39,7 @@ function createIntegrations(deps) {
subscribe('privateRoverAccess.requested', dm.sendPrivateRoverAccessRequestDms);
subscribe('chat:message', chat.handleChatBridgeOutbound);
subscribe('chat:typing', chat.handleChatTypingOutbound);
subscribe('discord.bridgeSend', chat.handleBridgeSendRequest);
return {
handleBridgeInbound: chat.handleBridgeInbound,