feat: add admin command with stats subcommand and integrate dsxjs dependency
This commit is contained in:
parent
9583267f64
commit
7089d8ad28
7 changed files with 90 additions and 7 deletions
3
bun.lock
3
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=="],
|
||||
|
|
67
cmd/admin.ts
Normal file
67
cmd/admin.ts
Normal file
|
@ -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<void> => {
|
||||
const subcommand = interaction.options.getSubcommand();
|
||||
|
||||
if (subcommand === "stats") {
|
||||
const files = await db.all<StereoFile[]>`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<string, number> = {};
|
||||
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;
|
9
cmd/example.ts
Normal file
9
cmd/example.ts
Normal file
|
@ -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<void> => {}
|
||||
export const on: {
|
||||
[event in Events]?: (interaction: Interaction<CacheType>) => Promise<void>;
|
||||
} = {}
|
||||
|
||||
export default [data, execute, on] as const;
|
|
@ -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
|
||||
]
|
12
cmd/user.ts
12
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] });
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"discord.js": "^14.21.0",
|
||||
"dsxjs": "0.0.3",
|
||||
"ky": "^1.8.2",
|
||||
"msgpack-lite": "^0.1.26"
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
"module": "Preserve",
|
||||
"moduleDetection": "force",
|
||||
"jsx": "react-jsx",
|
||||
"jsxImportSource": "dsxjs",
|
||||
"allowJs": true,
|
||||
|
||||
// Best practices
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue