import { ActionRowBuilder, ButtonBuilder, ButtonStyle, CacheType, ChatInputCommandInteraction, Events, Interaction, InteractionReplyOptions, MessageFlags, SlashCommandBuilder, User } from "discord.js"; import { db } from "../db/db"; import { b64decode, b64encode } from "../lib/b64"; import { createEmbed } from "../lib/embed"; import { formatSize } from "../lib/files"; import { StereoFile } from "../types/files"; export const data = new SlashCommandBuilder() .setName("list") .setDescription("list all of your files") .addIntegerOption(option => option.setName("page") .setDescription("page number to view") .setRequired(false) .setMinValue(1) ); export async function renderList(user: User, userId: string, page: number): Promise { const amount = 2; const { count } = await db.get<{ count: number }>` SELECT COUNT(*) as count FROM files WHERE owner = ${userId} `; const totalPages = Math.max(1, Math.ceil(count / amount)); const pages = Math.max(1, Math.min(page, totalPages)); const files = await db.all` SELECT * FROM files WHERE owner = ${userId} ORDER BY created_at DESC LIMIT ${amount} OFFSET ${(pages - 1) * amount} `; if (files.length === 0) { return { content: "you havn't uploaded any files yet, visit the website to get started!", flags: MessageFlags.Ephemeral }; } const embed = createEmbed(user) .setTitle("your files") .addFields(...files.map(file => ({ name: file.name, value: `${formatSize(file.size)}\n${new Date(file.created_at).toLocaleString()}\n[open in browser](${process.env.API}/${file.id})`, inline: true }))) .setFooter({ text: `page ${pages} of ${totalPages}` }); const row = new ActionRowBuilder() .addComponents( new ButtonBuilder() .setCustomId(b64encode({ direction: -1, user: userId, pages: pages })) .setLabel('<') .setStyle(ButtonStyle.Danger) .setDisabled(pages === 1), new ButtonBuilder() .setCustomId(b64encode({ direction: 1, user: userId, pages: pages })) .setLabel('>') .setStyle(ButtonStyle.Danger) .setDisabled(pages === totalPages) ); return { embeds: [embed], components: [row] }; } export const execute = async (interaction: ChatInputCommandInteraction) => { const page = interaction.options.getInteger("page") || 1; const userId = interaction.user.id; const reply = await renderList(interaction.user, userId, page); await interaction.reply(reply); }; export const on: { [event in Events]?: (interaction: Interaction) => Promise; } = { [Events.InteractionCreate]: async (interaction: Interaction) => { if (!interaction.isButton()) return; // old, cursed code :vomit: // const match = interaction.customId.match(/^s\.list\.(prev|next)\.(\d+)$/); // if (!match) return; // let page = parseInt(match[2] || "1", 10); // page = match[1] === "next" ? page + 1 : page - 1; // const dirtyReply = await renderList(interaction.user, interaction.user.id, page); // const { flags, ...reply } = dirtyReply; // await interaction.update({ ...reply }); // new, clean code :sunglasses: (minified since discord limits ids to 100 chars) const data = b64decode<{ direction: -1 | 1; user: string; pages: number; }>(interaction.customId); if (interaction.user.id !== data.user) { await interaction.reply({ content: "hey, stop that!!!", flags: MessageFlags.Ephemeral }); return; } const page = data.pages + data.direction; const dirty = await renderList(interaction.user, interaction.user.id, page); const { flags, ...r } = dirty; await interaction.update({ ...r }); } } export default [data, execute, on] as const;