diff --git a/src/commands/General/info.ts b/src/commands/General/info.ts new file mode 100644 index 00000000..0cdb3609 --- /dev/null +++ b/src/commands/General/info.ts @@ -0,0 +1,140 @@ +import { injectable } from "tsyringe" +import { Client } from "discordx" +import { Category } from "@discordx/utilities" +import { ActionRowBuilder, ButtonBuilder, ButtonStyle, CommandInteraction, EmbedBuilder, EmbedField } from "discord.js" +import relativeTime from 'dayjs/plugin/relativeTime' +import dayjs from 'dayjs' +dayjs.extend(relativeTime) + +import { Discord, Slash } from "@decorators" +import { Guard } from "@guards" +import { formatDate, getColor, isValidUrl, timeAgo } from "@utils/functions" +import { generalConfig } from "@config" +import { Stats } from "@services" + +import packageJSON from '../../../package.json' + +const links = [ + { label: 'Invite me!', url: generalConfig.links.invite }, + { label: 'Support server', url: generalConfig.links.supportServer }, + { label: 'Github', url: generalConfig.links.gitRemoteRepo } +] + +@Discord() +@injectable() +@Category('General') +export default class InfoCommand { + + constructor( + private stats: Stats + ) {} + + @Slash({ + name: 'info', + }) + @Guard() + async info( + interaction: CommandInteraction, + client: Client, + { localize }: InteractionData + ) { + + const embed = new EmbedBuilder() + .setAuthor({ + name: interaction.user.username, + iconURL: interaction.user.displayAvatarURL(), + }) + .setTitle(client.user!.tag) + .setThumbnail(client.user!.displayAvatarURL()) + .setColor(getColor('primary')) + .setDescription(packageJSON.description) + + const fields: EmbedField[] = [] + + /** + * Owner field + */ + const owner = await client.users.fetch(generalConfig.ownerId) + if (owner) { + fields.push({ + name: 'Owner', + value: `\`${owner.tag}\``, + inline: true, + }) + } + + /** + * Uptime field + */ + const uptime = timeAgo(new Date(Date.now() - client.uptime!)) + fields.push({ + name: 'Uptime', + value: uptime, + inline: true, + }) + + /** + * Totals field + */ + const totalStats = await this.stats.getTotalStats() + fields.push({ + name: 'Totals', + value: `**${totalStats.TOTAL_GUILDS}** guilds\n**${totalStats.TOTAL_USERS}** users\n**${totalStats.TOTAL_COMMANDS}** commands`, + inline: true, + }) + + /** + * Bot version field + */ + fields.push({ + name: 'Bot version', + value: `v${packageJSON.version}`, + inline: true, + }) + + /** + * Framework/template field + */ + fields.push({ + name: 'Framework/template', + value: `[TSCord](https://github.com/barthofu/tscord) (*v${generalConfig.__templateVersion}*)`, + inline: true, + }) + + /** + * Libraries field + */ + fields.push({ + name: 'Libraries', + value: `[discord.js](https://discord.js.org/) (*v${packageJSON.dependencies['discord.js'].replace('^', '')}*)\n[discordx](https://discordx.js.org/) (*v${packageJSON.dependencies['discordx'].replace('^', '')}*)`, + inline: true, + }) + + // add the fields to the embed + embed.addFields(fields) + + /** + * Define links buttons + */ + const buttons = links + .map(link => { + const url = link.url.split('_').join('') + if (isValidUrl(url)) { + return new ButtonBuilder() + .setLabel(link.label) + .setURL(url) + .setStyle(ButtonStyle.Link) + } else return null + }) + .filter(link => link) as ButtonBuilder[] + const row = new ActionRowBuilder() + .addComponents(...buttons) + + // finaly send the embed + interaction.followUp({ + embeds: [embed], + components: [row], + }) + + } +} \ No newline at end of file diff --git a/src/commands/General/invite.ts b/src/commands/General/invite.ts index f690c8be..75eb9743 100644 --- a/src/commands/General/invite.ts +++ b/src/commands/General/invite.ts @@ -24,7 +24,7 @@ export default class InviteCommand { const embed = new EmbedBuilder() .setTitle(localize.COMMANDS.INVITE.EMBED.TITLE()) - .setDescription(localize.COMMANDS.INVITE.EMBED.DESCRIPTION({link: generalConfig.inviteLink})) + .setDescription(localize.COMMANDS.INVITE.EMBED.DESCRIPTION({link: generalConfig.links.invite})) .setColor(getColor('primary')) .setFooter({ text : 'Powered by DiscBot Team ❤'}) diff --git a/src/config/general.ts b/src/config/general.ts index 50d52d01..b892f0c6 100644 --- a/src/config/general.ts +++ b/src/config/general.ts @@ -8,7 +8,12 @@ export const generalConfig: GeneralConfigType = { simpleCommandsPrefix: '!', ownerId: '260908777446965248', timezone: 'Europe/Paris', - inviteLink: 'https://www.change_invite_link_here.com', + + links: { + invite: 'https://www.change_invite_link_here.com', + supportServer: 'https://discord.com/your_invitation_link', + gitRemoteRepo: 'https://github.com/barthofu/tscord', + }, automaticUploadImagesToImgur: false, diff --git a/src/utils/functions/date.ts b/src/utils/functions/date.ts index ae85fc2b..38365c88 100644 --- a/src/utils/functions/date.ts +++ b/src/utils/functions/date.ts @@ -26,4 +26,9 @@ const dateMasks = { export const formatDate = (date: Date, mask: keyof typeof dateMasks = 'default') => { return datejs(date).format(dateMasks[mask]) +} + +export const timeAgo = (date: Date) => { + + return dayjs(date).fromNow() } \ No newline at end of file diff --git a/src/utils/functions/string.ts b/src/utils/functions/string.ts index 407a6e24..725e0e82 100644 --- a/src/utils/functions/string.ts +++ b/src/utils/functions/string.ts @@ -41,4 +41,14 @@ export const constantPreserveDots = (string: string) => { .split('.') .map(word => constant(word)) .join('.') +} + +export const isValidUrl = (url: string) => { + + try { + new URL(url) + return true + } catch { + return false + } } \ No newline at end of file diff --git a/src/utils/types/configs.d.ts b/src/utils/types/configs.d.ts index cc7a088c..08f36a0d 100644 --- a/src/utils/types/configs.d.ts +++ b/src/utils/types/configs.d.ts @@ -1,27 +1,32 @@ type GeneralConfigType = { - __templateVersion: string, + __templateVersion: string - name: string, - description: string, - defaultLocale: import('@i18n').Locales, - simpleCommandsPrefix: string, - ownerId: string, - timezone: string, - automaticUploadImagesToImgur: boolean, - inviteLink: string, + name: string + description: string + defaultLocale: import('@i18n').Locales + simpleCommandsPrefix: string + ownerId: string + timezone: string + automaticUploadImagesToImgur: boolean - devs: string[], + links: { + invite: string + supportServer: string + gitRemoteRepo: string + } + + devs: string[] eval: { - name: string, + name: string onlyOwner: boolean - }, + } activities: { - text: string, + text: string type: "PLAYING" | "STREAMING" | "LISTENING" | "WATCHING" | "CUSTOM" | "COMPETING" - }[], + }[] } @@ -30,44 +35,44 @@ type DatabaseConfigType = { path: `${string}/` backup: { - enabled: boolean, + enabled: boolean path: `${string}/` } } type LogsConfigType = { - debug: boolean, + debug: boolean interaction: { - file: boolean, - console: boolean, - channel: string | null, + file: boolean + console: boolean + channel: string | null exclude: InteractionsConstants[] } simpleCommand: { - file: boolean, - console: boolean, + file: boolean + console: boolean channel: string | null } newUser: { - file: boolean, - console: boolean, + file: boolean + console: boolean channel: string | null } guild: { - file: boolean, - console: boolean, + file: boolean + console: boolean channel: string | null } error: { - file: boolean, - console: boolean, + file: boolean + console: boolean channel: string | null } } @@ -82,5 +87,5 @@ type StatsConfigType = { type APIConfigType = { - port: number, + port: number } \ No newline at end of file