import discord, { CommandInteraction, CommandInteractionOptionResolver, SlashCommandBuilder, SlashCommandStringOption, SlashCommandAttachmentOption, SlashCommandSubcommandBuilder, EmbedBuilder, InteractionResponse, SlashCommandSubcommandsOnlyBuilder, Attachment } from 'discord.js'; import { HydratedDocument } from 'mongoose'; import fs from 'fs'; import path from 'path'; import axios from 'axios'; import { Client } from 'minio'; import { Command } from '../../classes/command'; import { logger } from '../../logger'; import { config } from '../../config'; import { reactPreprocess } from '../../functions/react-preprocess'; import { Image, imageModel } from '../../models/Image'; import { Alias, aliasModel } from '../../models/Alias'; import { Guild, guildModel } from '../../models/Guild'; import { Token, tokenModel } from '../../models/Token'; type CIOR = CommandInteractionOptionResolver; const client = new Client(config.minio.client); async function download(url: string, file: string): Promise{ const saveFile = await axios({ url: url, method: 'GET', responseType: 'arraybuffer' }); if(!await client.bucketExists(config.minio.bucket)) await client.makeBucket(config.minio.bucket); await client.putObject(config.minio.bucket, file, saveFile.data); } async function upload(interaction: CommandInteraction): Promise{ if(!interaction.guildId) throw Error('guildId not exist'); const file: discord.Attachment | null = (interaction.options as CIOR).getAttachment('file'); if(!file) throw Error('file not exist'); const fileExt: string = path.extname(file.name); const image: HydratedDocument | null = await (new imageModel({extension: fileExt})).save(); if(!image) throw Error('image not exist'); await download(file.url, `${String(image._id)}${fileExt}`); const publicUrl: string = `${config.httpServer.external.url}/${String(image._id)}${fileExt}`; await interaction.reply({ content: logger.log(`Image saved with id: ${image._id}.`), }); if(interaction.channel) await interaction.channel.send({content: publicUrl}); } async function link(interaction: CommandInteraction): Promise{ if(!interaction.guildId) throw Error('guildId not exist'); let aliasText: string | null = (interaction.options as CIOR).getString('alias'); if(!aliasText) throw Error('aliasText not exist'); const imageId: string | null = (interaction.options as CIOR).getString('image'); if(!imageId) throw Error('imageId not exist'); aliasText = reactPreprocess(aliasText); if(!interaction.guild || !interaction.guild.id || !interaction.guild.name) throw Error('guild not exist'); const guild: HydratedDocument | null = await guildModel.findOneAndUpdate( {id: interaction.guild.id}, {$set: {name: interaction.guild.name}}, {new: true, upsert: true} ); if(!guild) throw Error('guild not exist'); const image: HydratedDocument | null = await imageModel.findById(imageId); if(!image) throw Error('image not exist'); const alias: HydratedDocument | null = await aliasModel.findOneAndUpdate( {guild: guild, text: aliasText, images: {$nin: image}}, {$push: {images: image}}, {new: true, upsert: true} ); if(!alias) throw Error('alias update failed'); await interaction.reply({ content: logger.log(`Alias ${aliasText} has linked to image id: ${imageId}.`) }); } async function unlink(interaction: CommandInteraction): Promise{ if(!interaction.guildId) throw Error('guildId not exist'); let aliasText: string | null = (interaction.options as CIOR).getString('alias'); if(!aliasText) throw Error('aliasText not exist'); aliasText = reactPreprocess(aliasText); if(!interaction.guild || !interaction.guild.id) throw Error('guild not exist'); const guild: HydratedDocument | null = await guildModel.findOne({id: interaction.guild.id}); if(!guild) throw Error('guild not exist'); const res = await aliasModel.deleteOne( {guild: guild, text: aliasText} ); if(!res) logger.warning('alias not exist'); await interaction.reply({ content: logger.log(`All the image have been unlinked to alias ${aliasText}.`) }); } async function list(interaction: CommandInteraction): Promise{ const token: string = String(Math.floor(Math.random() * 100000000)); if(!interaction.guild || !interaction.guild.id) throw Error('guild not exist'); await tokenModel.create({ token: token, guildId: interaction.guild.id, exp: Date.now() + config.tokenTimeLimit }); const embed = new EmbedBuilder() .setColor(0x1f1e33) .setTitle('Autoreact Image List') .setDescription('experimental function') .addFields({ name: 'One Time Link:', value: `[open in browser](https://amane.konchin.com/auth?token=${token})` }); await interaction.reply({embeds: [embed], ephemeral: true}); } class ImageCmd extends Command{ get name(){return "image";} get description(){return "Modify the reacting image database.";} async execute(interaction: CommandInteraction): Promise{ try{ const subcommand: string | null = (interaction.options as CIOR).getSubcommand(); switch(subcommand){ case 'upload': await upload(interaction); break; case 'link': await link(interaction); break; case 'unlink': await unlink(interaction); break; case 'list': await list(interaction); break; default: throw Error('subcommand not exist'); } }catch(err: unknown){ let message; if(err instanceof Error) message = err.message; else message = String(message); logger.error(`While executing "/image", ${message}`); await interaction.reply({content: `While executing "/image", ${message}`}); } } override build(): SlashCommandSubcommandsOnlyBuilder{ return new SlashCommandBuilder() .setName(this.name) .setDescription(this.description) .addSubcommand((subcommand: SlashCommandSubcommandBuilder) => subcommand .setName('upload') .setDescription('Upload an image.') .addAttachmentOption((option: SlashCommandAttachmentOption) => option .setName('file') .setDescription('The image file that you want to upload.') .setRequired(true) ) ) .addSubcommand((subcommand: SlashCommandSubcommandBuilder) => subcommand .setName('link') .setDescription('Link an alias to an image') .addStringOption((option: SlashCommandStringOption) => option .setName('alias') .setDescription('The alias to be linked.') .setRequired(true) ) .addStringOption((option: SlashCommandStringOption) => option .setName('image') .setDescription('The image to be linked, as id.') .setRequired(true) ) ) .addSubcommand((subcommand: SlashCommandSubcommandBuilder) => subcommand .setName('unlink') .setDescription('Unlink an alias to an image') .addStringOption((option: SlashCommandStringOption) => option .setName('alias') .setDescription('The alias to be unlinked.') .setRequired(true) ) ) .addSubcommand((subcommand: SlashCommandSubcommandBuilder) => subcommand .setName('list') .setDescription('Show the image list') ) } }; export const command = new ImageCmd();