- Introduced new models in the Prisma schema for statistics, social feeds, scheduled messages, and guild backups, enhancing the bot's functionality. - Implemented command modules for managing stats channels, feeds, scheduling messages, and creating/restoring backups, improving user engagement and server management. - Updated job handling to include dedicated workers for stats updates, feed polling, scheduling, and backup restoration, enhancing automation. - Enhanced command localization for new features in both German and English. - Documented the new features and their setup processes in PHASE-TRACKING.md for clarity on implementation steps.
142 lines
4.3 KiB
TypeScript
142 lines
4.3 KiB
TypeScript
import { PermissionFlagsBits } from 'discord.js';
|
|
import { SocialFeedTypeSchema, t, tf } from '@nexumi/shared';
|
|
import type { SlashCommand } from '../../types.js';
|
|
import { getGuildLocale } from '../../i18n.js';
|
|
import { requirePermission } from '../../permissions.js';
|
|
import { feedsCommandData } from './command-definitions.js';
|
|
import {
|
|
addFeed,
|
|
FeedAddSchema,
|
|
FeedError,
|
|
getFeedById,
|
|
listFeeds,
|
|
removeFeed,
|
|
testFeed
|
|
} from './service.js';
|
|
|
|
const feedsCommand: SlashCommand = {
|
|
data: feedsCommandData,
|
|
async execute(interaction, context) {
|
|
const locale = await getGuildLocale(context.prisma, interaction.guildId);
|
|
if (!interaction.inGuild()) {
|
|
await interaction.reply({ content: t(locale, 'generic.guildOnly'), ephemeral: true });
|
|
return;
|
|
}
|
|
|
|
const sub = interaction.options.getSubcommand(true);
|
|
const guildId = interaction.guildId!;
|
|
|
|
if (!(await requirePermission(interaction, PermissionFlagsBits.ManageGuild, locale))) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
if (sub === 'add') {
|
|
const input = FeedAddSchema.parse({
|
|
type: SocialFeedTypeSchema.parse(interaction.options.getString('type', true)),
|
|
source: interaction.options.getString('source', true),
|
|
channelId: interaction.options.getChannel('channel', true).id,
|
|
rolePingId: interaction.options.getRole('role')?.id,
|
|
template: interaction.options.getString('template') ?? undefined
|
|
});
|
|
|
|
const feed = await addFeed(context.prisma, guildId, input);
|
|
await interaction.reply({
|
|
content: tf(locale, 'feeds.added', { id: feed.id, type: feed.type }),
|
|
ephemeral: true
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (sub === 'remove') {
|
|
const feedId = interaction.options.getString('id', true);
|
|
const removed = await removeFeed(context.prisma, guildId, feedId);
|
|
if (!removed) {
|
|
await interaction.reply({ content: t(locale, 'feeds.error.not_found'), ephemeral: true });
|
|
return;
|
|
}
|
|
|
|
await interaction.reply({
|
|
content: tf(locale, 'feeds.removed', { id: removed.id }),
|
|
ephemeral: true
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (sub === 'list') {
|
|
const feeds = await listFeeds(context.prisma, guildId);
|
|
if (feeds.length === 0) {
|
|
await interaction.reply({ content: t(locale, 'feeds.list.empty'), ephemeral: true });
|
|
return;
|
|
}
|
|
|
|
const lines = feeds.map((feed) =>
|
|
tf(locale, 'feeds.list.line', {
|
|
id: feed.id,
|
|
type: feed.type,
|
|
source: feed.sourceId,
|
|
channel: `<#${feed.channelId}>`
|
|
})
|
|
);
|
|
|
|
await interaction.reply({
|
|
content: `${t(locale, 'feeds.list.header')}\n${lines.join('\n')}`,
|
|
ephemeral: true
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (sub === 'test') {
|
|
const feedId = interaction.options.getString('id', true);
|
|
const feed = await getFeedById(context.prisma, guildId, feedId);
|
|
if (!feed) {
|
|
await interaction.reply({ content: t(locale, 'feeds.error.not_found'), ephemeral: true });
|
|
return;
|
|
}
|
|
|
|
const result = await testFeed(context, feed);
|
|
if (result.skipped) {
|
|
const skipKey =
|
|
result.message === 'twitch_credentials_missing'
|
|
? 'feeds.test.twitchSkipped'
|
|
: 'feeds.test.skipped';
|
|
await interaction.reply({
|
|
content: t(locale, skipKey),
|
|
ephemeral: true
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (result.message && result.posted === 0) {
|
|
await interaction.reply({
|
|
content: tf(locale, 'feeds.test.failed', { reason: result.message }),
|
|
ephemeral: true
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (result.posted === 0) {
|
|
await interaction.reply({ content: t(locale, 'feeds.test.noItems'), ephemeral: true });
|
|
return;
|
|
}
|
|
|
|
await interaction.reply({
|
|
content: tf(locale, 'feeds.test.posted', { count: result.posted }),
|
|
ephemeral: true
|
|
});
|
|
}
|
|
} catch (error) {
|
|
if (error instanceof FeedError) {
|
|
await interaction.reply({
|
|
content: t(locale, `feeds.error.${error.code}`),
|
|
ephemeral: true
|
|
});
|
|
return;
|
|
}
|
|
throw error;
|
|
}
|
|
}
|
|
};
|
|
|
|
export const feedsCommands: SlashCommand[] = [feedsCommand];
|