From 7089d8ad28966f9788caa95e40c1d4086faaf80a Mon Sep 17 00:00:00 2001 From: grngxd <36968271+grngxd@users.noreply.github.com> Date: Sun, 3 Aug 2025 22:13:02 +0100 Subject: [PATCH] feat: add admin command with stats subcommand and integrate dsxjs dependency --- bun.lock | 3 +++ cmd/admin.ts | 67 ++++++++++++++++++++++++++++++++++++++++++++++++++ cmd/example.ts | 9 +++++++ cmd/index.ts | 4 ++- cmd/user.ts | 12 ++++----- package.json | 1 + tsconfig.json | 1 + 7 files changed, 90 insertions(+), 7 deletions(-) create mode 100644 cmd/admin.ts create mode 100644 cmd/example.ts diff --git a/bun.lock b/bun.lock index e4c11a1..651526f 100644 --- a/bun.lock +++ b/bun.lock @@ -5,6 +5,7 @@ "name": "bot", "dependencies": { "discord.js": "^14.21.0", + "dsxjs": "0.0.3", "ky": "^1.8.2", "msgpack-lite": "^0.1.26", }, @@ -56,6 +57,8 @@ "discord.js": ["discord.js@14.21.0", "", { "dependencies": { "@discordjs/builders": "^1.11.2", "@discordjs/collection": "1.5.3", "@discordjs/formatters": "^0.6.1", "@discordjs/rest": "^2.5.1", "@discordjs/util": "^1.1.1", "@discordjs/ws": "^1.2.3", "@sapphire/snowflake": "3.5.3", "discord-api-types": "^0.38.1", "fast-deep-equal": "3.1.3", "lodash.snakecase": "4.1.1", "magic-bytes.js": "^1.10.0", "tslib": "^2.6.3", "undici": "6.21.3" } }, "sha512-U5w41cEmcnSfwKYlLv5RJjB8Joa+QJyRwIJz5i/eg+v2Qvv6EYpCRhN9I2Rlf0900LuqSDg8edakUATrDZQncQ=="], + "dsxjs": ["dsxjs@0.0.3", "", { "peerDependencies": { "discord.js": "^14.21.0", "typescript": "^5" } }, "sha512-7HZdhxIGOUPaRtvr6JkaRE9de9srE6M/0KPNNbsbk7v/j6oYCTMvStVhX5jZc8WNA53lrQTFs+FFoQGmJYJJNg=="], + "event-lite": ["event-lite@0.1.3", "", {}, "sha512-8qz9nOz5VeD2z96elrEKD2U433+L3DWdUdDkOINLGOJvx1GsMBbMn0aCeu28y8/e85A6mCigBiFlYMnTBEGlSw=="], "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], diff --git a/cmd/admin.ts b/cmd/admin.ts new file mode 100644 index 0000000..de8b246 --- /dev/null +++ b/cmd/admin.ts @@ -0,0 +1,67 @@ +import { ChatInputCommandInteraction, SlashCommandBuilder } from "discord.js"; +import { db } from "../db/db"; +import { createEmbed } from "../lib/embed"; +import { formatSize } from "../lib/files"; +import { StereoFile } from "../types/files"; + +export const data = new SlashCommandBuilder() + .setName("admin") + .setDescription("admin commands") + .addSubcommand(subcommand => + subcommand + .setName("stats") + .setDescription("view misc stats") + ); + +export const execute = async (interaction: ChatInputCommandInteraction): Promise => { + const subcommand = interaction.options.getSubcommand(); + + if (subcommand === "stats") { + const files = await db.all`SELECT * FROM files`; + + const totalSize = files.reduce((a, b) => a + b.size, 0); + const totalFiles = files.length; + + const mimes: string[] = files.map(file => file.mime); + const sortedMimes = mimes.sort((a, b) => + mimes.filter(v => v === a).length - mimes.filter(v => v === b).length + ); + const mostCommonMime = sortedMimes.pop() || "unknown"; + const mostCommonType = ("." + mostCommonMime.split("/")[1]) || "unknown"; + const mostCommonCount = mimes.filter(v => v === mostCommonMime).length; + + const uniqueUsers = new Set(files.map(file => file.owner)).size; + const uploaderCounts: Record = {}; + files.forEach(file => { + uploaderCounts[file.owner] = (uploaderCounts[file.owner] || 0) + 1; + }); + + const topUploader = (Object.entries(uploaderCounts).sort((a, b) => b[1] - a[1])[0]) || ["none", 0]; + + const oldestFile = files.reduce((a, b) => new Date(a.created_at) < new Date(b.created_at) ? a : b); + const newestFile = files.reduce((a, b) => new Date(a.created_at) > new Date(b.created_at) ? a : b); + + const oldestDate = new Date(oldestFile.created_at); + const newestDate = new Date(newestFile.created_at); + + const daysSpan = Math.max(1, Math.ceil((newestDate.getTime() - oldestDate.getTime()) / (1000 * 60 * 60 * 24))); + const filesPerDay = (totalFiles / daysSpan).toFixed(2); + + const embed = createEmbed(interaction.user) + .setTitle("stats") + .addFields([ + { name: "total files", value: `${totalFiles}` }, + { name: "total size", value: `${formatSize(totalSize)}` }, + { name: "average size", value: `${formatSize(totalSize / totalFiles)}` }, + { name: "most common type", value: `${mostCommonType} (${mostCommonCount} files)` }, + { name: "files per day", value: `${Math.round(Number(filesPerDay) * 100) / 100} files/day` }, + { name: "total users", value: `${uniqueUsers}` }, + { name: "top uploader", value: `<@${topUploader[0]}> (${topUploader[1]} files)` } + ].map(field => ({ ...field, inline: true }))) + .setDescription("here's the global stats for stereo:") + + await interaction.reply({ embeds: [embed] }); + } +}; + +export default [data, execute] as const; \ No newline at end of file diff --git a/cmd/example.ts b/cmd/example.ts new file mode 100644 index 0000000..d77497f --- /dev/null +++ b/cmd/example.ts @@ -0,0 +1,9 @@ +import { CacheType, CommandInteraction, Events, Interaction, SlashCommandBuilder } from "discord.js"; + +export const data = new SlashCommandBuilder() +export const execute = async (interaction: CommandInteraction): Promise => {} +export const on: { + [event in Events]?: (interaction: Interaction) => Promise; +} = {} + +export default [data, execute, on] as const; \ No newline at end of file diff --git a/cmd/index.ts b/cmd/index.ts index 553901a..d78e65d 100644 --- a/cmd/index.ts +++ b/cmd/index.ts @@ -1,3 +1,4 @@ +import admin from "./admin"; import files from "./files"; import register from "./register"; import user from "./user"; @@ -5,5 +6,6 @@ import user from "./user"; export default [ user, register, - files + files, + admin ] \ No newline at end of file diff --git a/cmd/user.ts b/cmd/user.ts index 4c01057..b91403a 100644 --- a/cmd/user.ts +++ b/cmd/user.ts @@ -42,12 +42,12 @@ export const execute = async (interaction: ChatInputCommandInteraction) => { const totalSize = files.reduce((a, b) => a + b.size, 0); const embed = createEmbed(user) - .setDescription("Here's your overview:") - .addFields( - { name: "Uploads", value: `${files.length} files`, inline: true }, - { name: "Uploaded", value: `${formatSize(totalSize)} / 15 GB (${(totalSize / (15 * 1024 * 1024 * 1024)).toFixed(2)}%)`, inline: true }, - { name: "Plan", value: `Free`, inline: true } - ) + .setDescription("here's your account info:") + .addFields([ + { name: "uploads", value: `${files.length} files` }, + { name: "uploaded", value: `${formatSize(totalSize)} / 15 GB (${(totalSize / (15 * 1024 * 1024 * 1024)).toFixed(2)}%)` }, + { name: "plan", value: `free` } + ].map(field => ({ ...field, inline: true }))) await interaction.reply({ embeds: [embed] }); } diff --git a/package.json b/package.json index 76dd15c..47f268e 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "discord.js": "^14.21.0", + "dsxjs": "0.0.3", "ky": "^1.8.2", "msgpack-lite": "^0.1.26" } diff --git a/tsconfig.json b/tsconfig.json index d802d23..09c7aee 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,6 +6,7 @@ "module": "Preserve", "moduleDetection": "force", "jsx": "react-jsx", + "jsxImportSource": "dsxjs", "allowJs": true, // Best practices