Merge branch 'dev' into 'main'

Dev

See merge request illegitimate/illegitimate-bot!334
This commit is contained in:
2025-03-08 19:06:26 +01:00
21 changed files with 16 additions and 2004 deletions

View File

@@ -36,7 +36,6 @@
"author": "Taken", "author": "Taken",
"license": "GPL-3.0-only", "license": "GPL-3.0-only",
"dependencies": { "dependencies": {
"@discord-player/extractor": "^7.1.0",
"@swc/cli": "^0.6.0", "@swc/cli": "^0.6.0",
"@swc/core": "^1.11.5", "@swc/core": "^1.11.5",
"@t3-oss/env-core": "^0.12.0", "@t3-oss/env-core": "^0.12.0",
@@ -44,8 +43,6 @@
"axios": "^1.8.1", "axios": "^1.8.1",
"chalk": "^5.4.1", "chalk": "^5.4.1",
"cron": "^4.1.0", "cron": "^4.1.0",
"discord-player": "^7.1.0",
"discord-player-youtubei": "^1.4.2",
"discord.js": "^14.18.0", "discord.js": "^14.18.0",
"drizzle-orm": "^0.40.0", "drizzle-orm": "^0.40.0",
"ioredis": "^5.5.0", "ioredis": "^5.5.0",
@@ -72,11 +69,5 @@
"tsx": "^4.19.3", "tsx": "^4.19.3",
"typescript": "^5.8.2" "typescript": "^5.8.2"
}, },
"packageManager": "pnpm@9.15.6", "packageManager": "pnpm@9.15.6"
"pnpm": {
"patchedDependencies": {
"whatwg-url@5.0.0": "patches/whatwg-url@5.0.0.patch",
"tr46@0.0.3": "patches/tr46@0.0.3.patch"
}
}
} }

View File

@@ -1,15 +0,0 @@
diff --git a/.npmignore b/.npmignore
deleted file mode 100644
index 96e9161fde31e9906718f689d5cc135e507a51e1..0000000000000000000000000000000000000000
diff --git a/index.js b/index.js
index 9ce12ca2d026fa202f7a0d32e0a7c8526660ed78..7c3b5d7ff1624d2bfbb5b83f69a9b460e38dbeab 100644
--- a/index.js
+++ b/index.js
@@ -1,6 +1,6 @@
"use strict";
-var punycode = require("punycode");
+var punycode = require("punycode/");
var mappingTable = require("./lib/mappingTable.json");
var PROCESSING_OPTIONS = {

View File

@@ -1,11 +0,0 @@
diff --git a/lib/url-state-machine.js b/lib/url-state-machine.js
index c25dbc2c486289fbd7446baed24dc6343f0226f6..e1681d27501013c5e6aa9b720b32338c177b692c 100644
--- a/lib/url-state-machine.js
+++ b/lib/url-state-machine.js
@@ -1,5 +1,5 @@
"use strict";
-const punycode = require("punycode");
+const punycode = require("punycode/");
const tr46 = require("tr46");
const specialSchemes = {

1233
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,66 +0,0 @@
import { InteractionContextType, PermissionFlagsBits, SlashCommandBuilder } from "discord.js"
import { devMessage, embedColor } from "~/config/options"
import { ICommand } from "~/typings"
import ban from "./counting/ban"
import unban from "./counting/unban"
export default {
name: "counting",
description: "counting subcommands",
dev: false,
public: true,
subcommands: true,
data: new SlashCommandBuilder()
.setName("counting")
.setDescription("counting subcommands")
.addSubcommand(subcommand =>
subcommand
.setName("ban")
.setDescription("Ban a user from counting")
.addUserOption(option =>
option
.setName("user")
.setDescription("The user to ban")
.setRequired(true)
)
)
.addSubcommand(subcommand =>
subcommand
.setName("unban")
.setDescription("Unban a user from counting")
.addUserOption(option =>
option
.setName("user")
.setDescription("The user to ban")
.setRequired(true)
)
)
.setDefaultMemberPermissions(PermissionFlagsBits.Administrator)
.setContexts(InteractionContextType.Guild),
async execute({ interaction }) {
const subcommand = interaction.options.getSubcommand()
if (subcommand === "ban") {
ban(interaction)
return
}
if (subcommand === "unban") {
unban(interaction)
return
}
await interaction.reply({
embeds: [{
description: "This command is currently under development",
color: embedColor,
footer: {
text: interaction.guild!.name + " | " + devMessage,
icon_url: interaction.guild!.iconURL() || undefined
}
}]
})
}
} as ICommand

View File

@@ -1,36 +0,0 @@
import { GuildMember, userMention } from "discord.js"
import { devMessage, embedColor } from "~/config/options"
import { countingBanned } from "~/config/roles"
import { SubCommand } from "~/typings"
const cmd: SubCommand = async (interaction) => {
const member = interaction.options.getMember("user")! as GuildMember
if (member.roles.cache.has(countingBanned)) {
await interaction.reply({
embeds: [{
description: userMention(member.user.id) + " is currently banned from counting",
color: embedColor,
footer: {
icon_url: interaction.guild!.iconURL() || undefined,
text: interaction.guild!.name + " | " + devMessage
}
}]
})
} else {
await member.roles.add(countingBanned)
await interaction.reply({
embeds: [{
description: userMention(member.user.id) + " has been banned from counting",
color: embedColor,
footer: {
icon_url: interaction.guild!.iconURL() || undefined,
text: interaction.guild!.name + " | " + devMessage
}
}]
})
}
}
export default cmd

View File

@@ -1,36 +0,0 @@
import { GuildMember, userMention } from "discord.js"
import { devMessage, embedColor } from "~/config/options"
import { countingBanned } from "~/config/roles"
import { SubCommand } from "~/typings"
const cmd: SubCommand = async (interaction) => {
const member = interaction.options.getMember("user")! as GuildMember
if (!member.roles.cache.has(countingBanned)) {
await interaction.reply({
embeds: [{
description: userMention(member.user.id) + " is currently not banned from counting",
color: embedColor,
footer: {
icon_url: interaction.guild!.iconURL() || undefined,
text: interaction.guild!.name + " | " + devMessage
}
}]
})
} else {
await member.roles.remove(countingBanned)
await interaction.reply({
embeds: [{
description: userMention(member.user.id) + " has been unbanned from counting",
color: embedColor,
footer: {
icon_url: interaction.guild!.iconURL() || undefined,
text: interaction.guild!.name + " | " + devMessage
}
}]
})
}
}
export default cmd

View File

@@ -1,43 +0,0 @@
import { InteractionContextType, PermissionFlagsBits, SlashCommandBuilder } from "discord.js"
import { devMessage, embedColor, instructionsgif } from "~/config/options"
import { ICommand } from "~/typings"
export default {
name: "instructions",
description: "Instructions for verification",
dev: false,
public: false,
data: new SlashCommandBuilder()
.setName("instructions")
.setDescription("Instructions for verification")
.setDefaultMemberPermissions(PermissionFlagsBits.Administrator)
.setContexts(InteractionContextType.Guild),
async execute({ interaction }) {
await interaction.reply({
embeds: [{
title: "Verification",
description: `
- Log onto hypixel.
- Right click with the head in your hotbar.
- Click on the social media icon.
- Click on the discord icon.
- Type your username in the chat and press enter.
- Run the \`/verify\` command in this channel.
`.removeIndents(),
thumbnail: {
url: interaction.guild?.iconURL() || ""
},
color: embedColor,
footer: {
text: interaction.guild!.name + " | " + devMessage,
icon_url: interaction.guild!.iconURL() || undefined
},
image: {
url: instructionsgif
}
}]
})
}
} as ICommand

View File

@@ -1,150 +0,0 @@
import { InteractionContextType, PermissionFlagsBits, SlashCommandBuilder } from "discord.js"
import { ICommand } from "~/typings"
import leave from "./music/leave"
import nowplaying from "./music/nowplaying"
import pause from "./music/pause"
import play from "./music/play"
import queue from "./music/queue"
import repeat from "./music/repeat"
import skip from "./music/skip"
import unpause from "./music/unpause"
import volume from "./music/volume"
export default {
name: "music",
description: "Subcommands for music commands",
dev: true,
public: false,
subcommands: true,
data: new SlashCommandBuilder()
.setName("music")
.setDescription("Subcommands for music commands")
.addSubcommand(subcommand =>
subcommand
.setName("play")
.setDescription("Play a song")
.addStringOption(option =>
option
.setName("query")
.setDescription("The song to play")
.setAutocomplete(true)
.setRequired(true)
)
)
.addSubcommand(subcommand =>
subcommand
.setName("volume")
.setDescription("Change the volume of the music")
.addNumberOption(option =>
option
.setName("volume")
.setDescription("The volume to set")
.setMinValue(1)
.setMaxValue(100)
.setRequired(true)
)
)
.addSubcommand(subcommand =>
subcommand
.setName("skip")
.setDescription("Skip the current song")
.addNumberOption(option =>
option
.setName("amount")
.setDescription("The amount of songs to skip")
)
)
.addSubcommand(subcommand =>
subcommand
.setName("repeat")
.setDescription("Set repeat mode")
.addStringOption(option =>
option
.setName("mode")
.setDescription("The repeat mode")
.addChoices(
{ name: "Off", value: "off" },
{ name: "Track", value: "track" },
{ name: "Queue", value: "queue" }
)
.setRequired(true)
)
)
.addSubcommand(subcommand =>
subcommand
.setName("queue")
.setDescription("Show the queue")
)
.addSubcommand(subcommand =>
subcommand
.setName("nowplaying")
.setDescription("Show the currently playing song")
)
.addSubcommand(subcommand =>
subcommand
.setName("pause")
.setDescription("Pause the music")
)
.addSubcommand(subcommand =>
subcommand
.setName("unpause")
.setDescription("Unpause the music")
)
.addSubcommand(subcommand =>
subcommand
.setName("leave")
.setDescription("Leave the voice channel")
)
.setDefaultMemberPermissions(PermissionFlagsBits.Administrator)
.setContexts(InteractionContextType.Guild),
async execute({ interaction }) {
const subcommand = interaction.options.getSubcommand()
if (subcommand === "play") {
play(interaction)
return
}
if (subcommand === "volume") {
volume(interaction)
return
}
if (subcommand === "skip") {
skip(interaction)
return
}
if (subcommand === "repeat") {
repeat(interaction)
return
}
if (subcommand === "queue") {
queue(interaction)
return
}
if (subcommand === "nowplaying") {
nowplaying(interaction)
return
}
if (subcommand === "pause") {
pause(interaction)
return
}
if (subcommand === "unpause") {
unpause(interaction)
return
}
if (subcommand === "leave") {
leave(interaction)
return
}
}
} as ICommand

View File

@@ -1,27 +0,0 @@
import { useMainPlayer } from "discord-player"
import { embedColor } from "~/config/options"
import { SubCommand } from "~/typings"
const cmd: SubCommand = async (interaction) => {
const player = useMainPlayer()
const queue = player.queues.get(interaction.guildId!)
if (!queue) {
await interaction.reply({
embeds: [{
description: "There is no music playing",
color: embedColor
}]
})
return
}
queue.delete()
await interaction.reply({
embeds: [{
description: "Left the voice channel",
color: embedColor
}]
})
}
export default cmd

View File

@@ -1,60 +0,0 @@
import { useMainPlayer } from "discord-player"
import { embedColor } from "~/config/options"
import { SubCommand } from "~/typings"
const cmd: SubCommand = async (interaction) => {
await interaction.deferReply()
const player = useMainPlayer()
const queue = player.queues.get(interaction.guildId!)
if (!queue) {
await interaction.editReply({
embeds: [{
description: "There is no music playing",
color: embedColor
}]
})
return
}
const current = queue.currentTrack
if (!current) {
await interaction.editReply({
embeds: [{
description: "There is no music playing",
color: embedColor
}]
})
return
}
const progressBar = queue.node.createProgressBar({
leftChar: "▬",
rightChar: "▬",
separator: "|",
indicator: "🔘",
timecodes: true,
length: 15
})
await interaction.editReply({
embeds: [{
title: "Now Playing",
description: `
[${current.title}](${current.url})
${progressBar}
`.removeIndents(),
color: embedColor,
thumbnail: {
url: current.thumbnail
},
footer: {
text: `Requested by ${current.requestedBy!.username} | ${current.duration}`
}
}]
})
}
export default cmd

View File

@@ -1,40 +0,0 @@
import { useMainPlayer } from "discord-player"
import { embedColor } from "~/config/options"
import { SubCommand } from "~/typings"
const cmd: SubCommand = async (interaction) => {
await interaction.deferReply()
const player = useMainPlayer()
const queue = player.queues.get(interaction.guildId!)
if (!queue) {
await interaction.editReply({
embeds: [{
description: "There is no music playing",
color: embedColor
}]
})
return
}
if (queue.node.isPaused()) {
await interaction.editReply({
embeds: [{
description: "The music is already paused",
color: embedColor
}]
})
return
}
queue.node.setPaused(true)
await interaction.editReply({
embeds: [{
description: "Paused the music",
color: embedColor
}]
})
}
export default cmd

View File

@@ -1,53 +0,0 @@
import { useMainPlayer } from "discord-player"
import { GuildMember } from "discord.js"
import { embedColor } from "~/config/options"
import { SubCommand } from "~/typings"
const cmd: SubCommand = async (interaction) => {
await interaction.deferReply()
const query = interaction.options.getString("query")!
const channel = (interaction.member as GuildMember).voice.channel
const player = useMainPlayer()
if (!channel) {
await interaction.editReply({
embeds: [{
description: "You need to be in a voice channel to play music",
color: embedColor
}]
})
return
}
let replyMessage: string = ""
const queue = player.queues.get(interaction.guildId!)
const { track } = await player.play(channel, query, {
requestedBy: interaction.user,
nodeOptions: {
volume: 25
}
})
if (queue) {
replyMessage = `Added [${track.title}](${track.url}) to the queue`
} else {
replyMessage = `Playing [${track.title}](${track.url})`
}
await interaction.editReply({
embeds: [{
description: replyMessage,
thumbnail: {
url: track.thumbnail
},
color: embedColor,
footer: {
text: track.duration + " minutes",
icon_url: interaction.user.avatarURL()!
}
}]
})
}
export default cmd

View File

@@ -1,41 +0,0 @@
import { useMainPlayer } from "discord-player"
import { embedColor } from "~/config/options"
import { SubCommand } from "~/typings"
const cmd: SubCommand = async (interaction) => {
await interaction.deferReply()
const player = useMainPlayer()
const queue = player.queues.get(interaction.guildId!)
if (!queue) {
await interaction.editReply({
embeds: [{
description: "There is no queue",
color: embedColor
}]
})
return
}
const currentSong = queue.currentTrack
const nowPlaying = `Now playing: [${currentSong?.title}](${currentSong?.url})`
const tracks = queue.tracks.map((track, index) => {
return `${index + 1}. [${track.title}](${track.url})`
})
await interaction.editReply({
embeds: [{
title: "Queue",
description: nowPlaying + "\n\n" + tracks.join("\n"),
thumbnail: {
url: currentSong?.thumbnail || ""
},
color: embedColor,
footer: {
text: `Total tracks: ${queue.tracks.size}`
}
}]
})
}
export default cmd

View File

@@ -1,40 +0,0 @@
import { QueueRepeatMode, useMainPlayer } from "discord-player"
import { embedColor } from "~/config/options"
import { SubCommand } from "~/typings"
const QueueRepeatModes = {
"off": QueueRepeatMode.OFF,
"track": QueueRepeatMode.TRACK,
"queue": QueueRepeatMode.QUEUE
}
type RepeatMode = keyof typeof QueueRepeatModes
const cmd: SubCommand = async (interaction) => {
await interaction.deferReply()
const mode = interaction.options.getString("mode") as RepeatMode
const player = useMainPlayer()
const queue = player.queues.get(interaction.guildId!)
if (!queue) {
await interaction.editReply({
embeds: [{
description: "There is no queue",
color: embedColor
}]
})
return
}
queue.setRepeatMode(QueueRepeatModes[mode])
await interaction.editReply({
embeds: [{
description: `Repeat mode set to ${mode}`,
color: embedColor
}]
})
}
export default cmd

View File

@@ -1,46 +0,0 @@
import { useMainPlayer } from "discord-player"
import { embedColor } from "~/config/options"
import { SubCommand } from "~/typings"
const cmd: SubCommand = async (interaction) => {
await interaction.deferReply()
const amount = interaction.options.getNumber("amount") ?? 1
const player = useMainPlayer()
const queue = player.queues.get(interaction.guildId!)
if (!queue) {
await interaction.editReply({
embeds: [{
description: "There is no queue",
color: embedColor
}]
})
return
}
if (amount > queue.size) {
await interaction.editReply({
embeds: [{
description: `There are only ${queue.size} song${queue.size === 1 ? "" : "s"} in the queue`,
color: embedColor
}]
})
return
}
if (amount === 1) {
queue.node.skip()
} else {
queue.node.skipTo(amount)
}
await interaction.editReply({
embeds: [{
description: `Skipped ${amount === 1 ? "1 song" : `${amount} songs`}`,
color: embedColor
}]
})
}
export default cmd

View File

@@ -1,40 +0,0 @@
import { useMainPlayer } from "discord-player"
import { embedColor } from "~/config/options"
import { SubCommand } from "~/typings"
const cmd: SubCommand = async (interaction) => {
await interaction.deferReply()
const player = useMainPlayer()
const queue = player.queues.get(interaction.guildId!)
if (!queue) {
await interaction.editReply({
embeds: [{
description: "There is no music playing",
color: embedColor
}]
})
return
}
if (!queue.node.isPaused()) {
await interaction.editReply({
embeds: [{
description: "The music is not paused",
color: embedColor
}]
})
return
}
queue.node.setPaused(false)
await interaction.editReply({
embeds: [{
description: "Unpaused the music",
color: embedColor
}]
})
}
export default cmd

View File

@@ -1,31 +0,0 @@
import { useMainPlayer } from "discord-player"
import { embedColor } from "~/config/options"
import { SubCommand } from "~/typings"
const cmd: SubCommand = async (interaction) => {
await interaction.deferReply()
const volume = interaction.options.getNumber("volume")!
const player = useMainPlayer()
const queue = player.queues.get(interaction.guildId!)
if (!queue) {
await interaction.editReply({
embeds: [{
description: "There is no music playing",
color: embedColor
}]
})
return
}
queue.node.setVolume(volume)
await interaction.editReply({
embeds: [{
description: `Volume set to ${volume}`,
color: embedColor
}]
})
}
export default cmd

View File

@@ -1,32 +0,0 @@
import { QueryType, useMainPlayer } from "discord-player"
import { IAutocomplete } from "~/typings"
export default {
name: "music",
description: "Music",
async execute({ interaction }) {
const focusedOption = interaction.options.getFocused(true)
if (interaction.options.getSubcommand() !== "play" && focusedOption.name !== "query") return
if (focusedOption.value === "") {
await interaction.respond([{
name: "Please start typing a song to play",
value: "none"
}])
return
}
const player = useMainPlayer()
const { tracks } = await player.search(focusedOption.value, {
searchEngine: QueryType.YOUTUBE_SEARCH
})
const results = tracks.map(track => ({
name: `${track.title.slice(0, 20)} [${track.author}]`,
value: track.url
}))
await interaction.respond(results.slice(0, 6)).catch()
}
} as IAutocomplete

View File

@@ -14,8 +14,6 @@ const moderationLogChannel = "1193329771795447818"
const devLogChannel = "1193688673632391280" const devLogChannel = "1193688673632391280"
const waitingListChannel = "1145773618291298384" const waitingListChannel = "1145773618291298384"
const waitingListMessage = "1146027645415473193" const waitingListMessage = "1146027645415473193"
const instructionsgif =
"https =//cdn.discordapp.com/attachments/838716950723952640/1188211176300089384/4DMu513uNxbM.gif?ex=6599b2e4&is=65873de4&hm=e727c7a39aacbc47d6a5453f4b5f792a45679983c30d662cd258a311381b6df0&"
export { export {
applicationsChannel, applicationsChannel,
@@ -28,7 +26,6 @@ export {
guildLogChannel, guildLogChannel,
hypixelGuildID, hypixelGuildID,
inactivityLogChannel, inactivityLogChannel,
instructionsgif,
moderationLogChannel, moderationLogChannel,
onlineLogChannel, onlineLogChannel,
staffApplicationsChannel, staffApplicationsChannel,

View File

@@ -1,6 +1,3 @@
import { DefaultExtractors } from "@discord-player/extractor"
import { Player } from "discord-player"
import { YoutubeiExtractor } from "discord-player-youtubei"
import { Redis } from "ioredis" import { Redis } from "ioredis"
import { ExtendedClient as Client } from "~/utils/Client" import { ExtendedClient as Client } from "~/utils/Client"
import env from "~/utils/Env" import env from "~/utils/Env"
@@ -9,7 +6,6 @@ import { log } from "./Logger"
const client = new Client() const client = new Client()
const redis = new Redis(env.prod.REDISURI) const redis = new Redis(env.prod.REDISURI)
const player = new Player(client)
let ft: "js" | "ts" let ft: "js" | "ts"
if (process.env.NODE_ENV === "dev" && process.env.TYPESCRIPT === "true") { if (process.env.NODE_ENV === "dev" && process.env.TYPESCRIPT === "true") {
@@ -21,8 +17,6 @@ if (process.env.NODE_ENV === "dev" && process.env.TYPESCRIPT === "true") {
class Illegitimate { class Illegitimate {
async start() { async start() {
await loadAllEvents(client, ft) await loadAllEvents(client, ft)
await player.extractors.loadMulti(DefaultExtractors)
await player.extractors.register(YoutubeiExtractor, {})
await client.start() await client.start()
await this.databases() await this.databases()
this.loadMethods() this.loadMethods()