super cool refactor

This commit is contained in:
rainydevzz 2025-03-26 15:21:40 -07:00
parent 7d1cf86a07
commit 0d02981330
22 changed files with 1140 additions and 471 deletions

8
biome.json Normal file
View File

@ -0,0 +1,8 @@
{
"$schema": "./node_modules/@biomejs/biome/configuration_schema.json",
"javascript": {
"parser": {
"unsafeParameterDecoratorsEnabled": true
}
}
}

View File

@ -1,241 +1,284 @@
import { Discord, Slash, SlashGroup, SlashOption } from "discordx"; import { Discord, Slash, SlashGroup, SlashOption } from "discordx";
import { ApplicationCommandOptionType, CommandInteraction, EmbedBuilder, GuildMember, MessageFlags } from "discord.js"; import {
ApplicationCommandOptionType,
CommandInteraction,
EmbedBuilder,
GuildMember,
MessageFlags,
} from "discord.js";
import db from "../db"; import db from "../db";
import { colonTable, type ColonThreeType } from "../db/schema"; import { colonTable } from "../db/schema";
import { eq, desc } from "drizzle-orm"; import { eq, desc } from "drizzle-orm";
@Discord() @Discord()
export class ColonThreeInit { export class ColonThreeInit {
@Slash({ description: "Init all users for :3 Leaderboard", name: "init_colonthree" }) @Slash({
description: "Init all users for :3 Leaderboard",
name: "init_colonthree",
})
async init_colon(inter: CommandInteraction) { async init_colon(inter: CommandInteraction) {
if (Bun.env.OWNER != inter.user.id) { if (Bun.env.OWNER != inter.user.id) {
await inter.reply({ content: "You're not allowed to run this.", flags: MessageFlags.Ephemeral }) await inter.reply({
return; content: "You're not allowed to run this.",
} flags: MessageFlags.Ephemeral,
});
return;
}
for (const userObject of await inter.guild!.members.cache) { for (const userObject of await inter.guild!.members.cache) {
const user = userObject[1]; const user = userObject[1];
if (user.user.bot) { if (user.user.bot) {
continue; continue;
} }
const check = await db.select().from(colonTable).where(eq(colonTable.user, user.id)); const check = await db
.select()
.from(colonTable)
.where(eq(colonTable.user, user.id));
if (check.length >= 1) { if (check.length >= 1) {
continue; continue;
} }
await db.insert(colonTable).values({ await db.insert(colonTable).values({
user: user.id, user: user.id,
amount: 0, amount: 0,
messages_count: 0 messages_count: 0,
}); });
} }
await inter.reply({ content: "All users have been initalized", flags: MessageFlags.Ephemeral }); await inter.reply({
content: "All users have been initalized",
flags: MessageFlags.Ephemeral,
});
} }
} }
@Discord() @Discord()
@SlashGroup({ @SlashGroup({
description: "The :3 Commands", description: "The :3 Commands",
name: "colonthree" name: "colonthree",
}) })
@SlashGroup("colonthree") @SlashGroup("colonthree")
export class ColonThree { export class ColonThree {
@Slash({ description: "Stats" }) @Slash({ description: "Stats" })
async stats( async stats(
@SlashOption({ @SlashOption({
description: "Get stats from user", description: "Get stats from user",
name: "user", name: "user",
required: false, required: false,
type: ApplicationCommandOptionType.User, type: ApplicationCommandOptionType.User,
}) })
user: GuildMember, user: GuildMember,
inter: CommandInteraction inter: CommandInteraction,
) { ) {
const statsUser = ( const statsUser = (
await db await db
.select() .select()
.from(colonTable) .from(colonTable)
.where(eq(colonTable.user, (user?.id || inter.user.id))) .where(eq(colonTable.user, user?.id || inter.user.id))
)[0]; )[0];
if (!statsUser) { if (!statsUser) {
await inter.reply({ content: `Failed to get <@${user?.id || inter.user.id}>'s stats.`, flags: MessageFlags.Ephemeral }); await inter.reply({
return; content: `Failed to get <@${user?.id || inter.user.id}>'s stats.`,
} flags: MessageFlags.Ephemeral,
});
return;
}
const userObject = inter.client.users.cache.get(statsUser.user); const userObject = inter.client.users.cache.get(statsUser.user);
if (!userObject) { if (!userObject) {
await inter.reply({ content: "Something went wrong...", flags: MessageFlags.Ephemeral }); await inter.reply({
return; content: "Something went wrong...",
} flags: MessageFlags.Ephemeral,
});
return;
}
const embed = new EmbedBuilder() const embed = new EmbedBuilder()
.setTitle(`${userObject.username}'s Stats`) .setTitle(`${userObject.username}'s Stats`)
.setAuthor({ name: userObject.username, iconURL: userObject.avatarURL()! }) .setAuthor({
.addFields( name: userObject.username,
{ iconURL: userObject.avatarURL()!,
name: '**Total :3 Sent**', })
value: `► **${statsUser.amount}** times`, .addFields(
inline: true {
}, name: "**Total :3 Sent**",
{ value: `► **${statsUser.amount}** times`,
name: '**Average :3 per Message**', inline: true,
value: `► **${(statsUser.amount / statsUser.messages_count).toFixed(2)}**`, },
inline: true {
}, name: "**Average :3 per Message**",
{ value: `► **${(statsUser.amount / statsUser.messages_count).toFixed(2)}**`,
name: '**Messages Count**', inline: true,
value: `► **${statsUser.messages_count}**`, },
inline: true {
} name: "**Messages Count**",
); value: `► **${statsUser.messages_count}**`,
inline: true,
await inter.reply( { embeds: [embed], flags: MessageFlags.Ephemeral }); },
} );
@Slash({ description: "Compare" }) await inter.reply({ embeds: [embed], flags: MessageFlags.Ephemeral });
async compare(
@SlashOption({
description: "Get stats from user",
name: "x",
required: true,
type: ApplicationCommandOptionType.User,
})
x: GuildMember,
@SlashOption({
description: "Get stats from user",
name: "y",
required: true,
type: ApplicationCommandOptionType.User,
})
y: GuildMember,
inter: CommandInteraction
) {
const xStats = (
await db
.select()
.from(colonTable)
.where(eq(colonTable.user, x.id))
)[0];
if (!xStats) {
await inter.reply({ content: `Failed to get <@${x.id}>'s stats.`, flags: MessageFlags.Ephemeral });
return;
}
const yStats = (
await db
.select()
.from(colonTable)
.where(eq(colonTable.user, y.id))
)[0];
if (!yStats) {
await inter.reply({ content: `Failed to get <@${y.id}>'s stats.`, flags: MessageFlags.Ephemeral });
return;
}
const winner = xStats.amount > yStats.amount ? x : y;
const embed = new EmbedBuilder()
.setTitle(`🎉 ${winner.user.username} is using :3 more!`)
.addFields(
{
name: `📊 ${x.user.username}'s Stats`,
value: `► Sent **${xStats.amount}** :3\n► Avg: **${(xStats.amount / xStats.messages_count).toFixed(2)}** :3 per message`,
inline: true
},
{
name: `📊 ${y.user.username}'s Stats`,
value: `► Sent **${yStats.amount}** :3\n► Avg: **${(yStats.amount / yStats.messages_count).toFixed(2)}** :3 per message`,
inline: true
}
)
await inter.reply({ embeds: [embed] });
}
@Slash({ description: "Leaderboard" })
async board(inter: CommandInteraction) {
const theColonThreeLeaders = await db.select()
.from(colonTable)
.orderBy(desc(colonTable.amount));
const topTen = theColonThreeLeaders
.slice(0, 10)
.filter(user => user.amount >= 1);
const leaderboardText = topTen.map((user, index) => {
const rank = index + 1;
const avg = (user.amount / user.messages_count).toFixed(2);
const medal = rank === 1 ? "🥇" : rank === 2 ? "🥈" : rank === 3 ? "🥉" : `**${rank}.**`;
return `${medal} <@${user.user}> » **${user.amount}** :3 (avg: **${avg}**)`;
});
const embed = new EmbedBuilder()
.setTitle(`🏆 :3 Leaderboard`)
.setDescription(leaderboardText.join("\n") || "*No data yet!*")
.addFields({
name: 'Stats',
value: `► **Tracking users:** ${theColonThreeLeaders.length}`,
inline: false
})
.setFooter({ text: 'Keep using :3!!!!!!' })
.setTimestamp();
await inter.reply({ embeds: [embed] });
} }
@Slash({ description: "Stop tracking and delete my data" }) @Slash({ description: "Compare" })
async compare(
@SlashOption({
description: "Get stats from user",
name: "x",
required: true,
type: ApplicationCommandOptionType.User,
})
x: GuildMember,
@SlashOption({
description: "Get stats from user",
name: "y",
required: true,
type: ApplicationCommandOptionType.User,
})
y: GuildMember,
inter: CommandInteraction,
) {
const xStats = (
await db.select().from(colonTable).where(eq(colonTable.user, x.id))
)[0];
if (!xStats) {
await inter.reply({
content: `Failed to get <@${x.id}>'s stats.`,
flags: MessageFlags.Ephemeral,
});
return;
}
const yStats = (
await db.select().from(colonTable).where(eq(colonTable.user, y.id))
)[0];
if (!yStats) {
await inter.reply({
content: `Failed to get <@${y.id}>'s stats.`,
flags: MessageFlags.Ephemeral,
});
return;
}
const winner = xStats.amount > yStats.amount ? x : y;
const embed = new EmbedBuilder()
.setTitle(`🎉 ${winner.user.username} is using :3 more!`)
.addFields(
{
name: `📊 ${x.user.username}'s Stats`,
value: `► Sent **${xStats.amount}** :3\n► Avg: **${(xStats.amount / xStats.messages_count).toFixed(2)}** :3 per message`,
inline: true,
},
{
name: `📊 ${y.user.username}'s Stats`,
value: `► Sent **${yStats.amount}** :3\n► Avg: **${(yStats.amount / yStats.messages_count).toFixed(2)}** :3 per message`,
inline: true,
},
);
await inter.reply({ embeds: [embed] });
}
@Slash({ description: "Leaderboard" })
async board(inter: CommandInteraction) {
const theColonThreeLeaders = await db
.select()
.from(colonTable)
.orderBy(desc(colonTable.amount));
const topTen = theColonThreeLeaders
.slice(0, 10)
.filter((user) => user.amount >= 1);
const leaderboardText = topTen.map((user, index) => {
const rank = index + 1;
const avg = (user.amount / user.messages_count).toFixed(2);
const medal =
rank === 1
? "🥇"
: rank === 2
? "🥈"
: rank === 3
? "🥉"
: `**${rank}.**`;
return `${medal} <@${user.user}> » **${user.amount}** :3 (avg: **${avg}**)`;
});
const embed = new EmbedBuilder()
.setTitle(`🏆 :3 Leaderboard`)
.setDescription(leaderboardText.join("\n") || "*No data yet!*")
.addFields({
name: "Stats",
value: `► **Tracking users:** ${theColonThreeLeaders.length}`,
inline: false,
})
.setFooter({ text: "Keep using :3!!!!!!" })
.setTimestamp();
await inter.reply({ embeds: [embed] });
}
@Slash({ description: "Stop tracking and delete my data" })
async delete( async delete(
@SlashOption({ @SlashOption({
description: "Type confirm to delete", description: "Type confirm to delete",
name: "confirmation", name: "confirmation",
required: true, required: true,
type: ApplicationCommandOptionType.String, type: ApplicationCommandOptionType.String,
}) })
confirmation: string, confirmation: string,
inter: CommandInteraction inter: CommandInteraction,
) { ) {
if (confirmation === "confirm") { if (confirmation === "confirm") {
await db.delete(colonTable).where(eq(colonTable.user, inter.user.id)); await db.delete(colonTable).where(eq(colonTable.user, inter.user.id));
await inter.reply({ content: "All of data was deleted, you will not be tracked again.", flags: MessageFlags.Ephemeral }); await inter.reply({
return; content: "All of data was deleted, you will not be tracked again.",
} flags: MessageFlags.Ephemeral,
});
return;
}
await inter.reply({ content: 'You need to type "confirm" to delete.', flags: MessageFlags.Ephemeral }); await inter.reply({
} content: 'You need to type "confirm" to delete.',
flags: MessageFlags.Ephemeral,
});
}
@Slash({ description: "Start tracking" }) @Slash({ description: "Start tracking" })
async start( async start(inter: CommandInteraction) {
inter: CommandInteraction const check = (
) { await db
const check = ( .select()
await db .from(colonTable)
.select() .where(eq(colonTable.user, inter.user.id))
.from(colonTable) )[0];
.where(eq(colonTable.user, inter.user.id)
)
)[0];
if (check) { if (check) {
await inter.reply({ content: "Scythe already tracks your :3 data.", flags: MessageFlags.Ephemeral }); await inter.reply({
return; content: "Scythe already tracks your :3 data.",
} flags: MessageFlags.Ephemeral,
});
return;
}
await db.insert(colonTable).values({ await db.insert(colonTable).values({
user: inter.user.id, user: inter.user.id,
amount: 0, amount: 0,
messages_count: 0 messages_count: 0,
}); });
await inter.reply({ content: "Scythe starts tracking your :3 data again.", flags: MessageFlags.Ephemeral }); await inter.reply({
} content: "Scythe starts tracking your :3 data again.",
flags: MessageFlags.Ephemeral,
});
}
} }

103
src/commands/greets.ts Normal file
View File

@ -0,0 +1,103 @@
import {
ApplicationCommandOptionType,
MessageFlags,
TextChannel,
type CommandInteraction,
} from "discord.js";
import { Discord, Slash, SlashOption } from "discordx";
import db from "../db";
import { byeTable, greetsTable } from "../db/schema";
import { sql } from "drizzle-orm";
@Discord()
export class GreetCmds {
@Slash({ name: "hello-setup", description: "set up the hello message :3" })
async helloSetup(
@SlashOption({
name: "channel",
description: "channel to send in",
required: true,
type: ApplicationCommandOptionType.Channel,
})
channel: TextChannel,
@SlashOption({
name: "message",
description: "message to send",
required: true,
type: ApplicationCommandOptionType.String,
})
message: string,
inter: CommandInteraction,
) {
if (inter.user.id != inter.guild?.ownerId) {
return await inter.reply({
content: "you cannot use this command u goober!",
flags: MessageFlags.Ephemeral,
});
}
if (!inter.guildId) {
return await inter.reply("you can't use this in DMs silly!");
}
await db
.insert(greetsTable)
.values({
guild: inter.guildId,
channel: channel.id,
message,
})
.onConflictDoUpdate({
target: greetsTable.guild,
set: { message, channel: channel.id },
setWhere: sql`guild = ${inter.guildId}`,
});
await inter.reply({
content: "setup done! :3",
flags: MessageFlags.Ephemeral,
});
}
@Slash({ name: "bye-setup", description: "set up the bye message :3" })
async byeSetup(
@SlashOption({
name: "channel",
description: "channel to send in",
required: true,
type: ApplicationCommandOptionType.Channel,
})
channel: TextChannel,
@SlashOption({
name: "message",
description: "message to send",
required: true,
type: ApplicationCommandOptionType.String,
})
message: string,
inter: CommandInteraction,
) {
if (inter.user.id != inter.guild?.ownerId) {
return await inter.reply({
content: "you cannot use this command u goober!",
flags: MessageFlags.Ephemeral,
});
}
if (!inter.guildId) {
return await inter.reply("you can't use this in DMs silly!");
}
await db
.insert(byeTable)
.values({
guild: inter.guildId,
channel: channel.id,
message,
})
.onConflictDoUpdate({
target: byeTable.guild,
set: { message, channel: channel.id },
setWhere: sql`guild = ${inter.guildId}`,
});
await inter.reply({
content: "setup done! :3",
flags: MessageFlags.Ephemeral,
});
}
}

View File

@ -0,0 +1,38 @@
import {
ApplicationCommandOptionType,
CommandInteraction,
MessageFlags,
type TextChannel,
} from "discord.js";
import { Discord, Slash, SlashOption } from "discordx";
import db from "../db";
import { introTable } from "../db/schema";
import { sql } from "drizzle-orm";
@Discord()
export class Intro {
@Slash({ name: "intro-purge", description: "sets up intro purge" })
async introPurge(
@SlashOption({
name: "channel",
description: "channel to check",
required: true,
type: ApplicationCommandOptionType.Channel,
})
channel: TextChannel,
inter: CommandInteraction,
) {
await db
.insert(introTable)
.values({
guild: inter.guildId!,
channel: channel.id,
})
.onConflictDoUpdate({
target: introTable.guild,
set: { channel: channel.id },
setWhere: sql`guild = ${inter.guildId}`,
});
await inter.reply({content: "intro setup done!", flags: MessageFlags.Ephemeral});
}
}

View File

@ -1,37 +1,48 @@
import { ActionRowBuilder, ButtonBuilder, ButtonStyle, EmbedBuilder, Message, MessageFlags, type CommandInteraction, type TextChannel } from "discord.js"; import {
ActionRowBuilder,
ButtonBuilder,
ButtonStyle,
EmbedBuilder,
MessageFlags,
type CommandInteraction,
type TextChannel,
} from "discord.js";
import { Discord, Slash } from "discordx"; import { Discord, Slash } from "discordx";
@Discord() @Discord()
export class TicketCmd { export class TicketCmd {
@Slash({ name: "setup-ticket", description: "Setup the tickets" }) @Slash({ name: "setup-ticket", description: "Setup the tickets" })
async snipe(inter: CommandInteraction) { async snipe(inter: CommandInteraction) {
if (inter.user.id != Bun.env.OWNER) { if (inter.user.id != Bun.env.OWNER) {
await inter.reply({ content: "You're not the owner", flags: MessageFlags.Ephemeral }); await inter.reply({
return; content: "You're not the owner",
} flags: MessageFlags.Ephemeral,
});
return;
}
const channel = inter.client.channels.cache.get(Bun.env.TICKET_CHANNEL) as TextChannel; const channel = inter.client.channels.cache.get(
Bun.env.TICKET_CHANNEL,
) as TextChannel;
const embed = new EmbedBuilder() const embed = new EmbedBuilder()
.setTitle(`Tickets`) .setTitle(`Tickets`)
.setDescription( .setDescription(`Click the button to make a ticket! :3`);
`Click the button to make a ticket! :3`,
); const createButton = new ButtonBuilder()
.setCustomId("createTicket")
const createButton = new ButtonBuilder() .setLabel("Create Ticket")
.setCustomId('createTicket')
.setLabel('Create Ticket')
.setStyle(ButtonStyle.Primary); .setStyle(ButtonStyle.Primary);
const row = new ActionRowBuilder<ButtonBuilder>()
.addComponents(createButton);
await channel.send({ const row = new ActionRowBuilder<ButtonBuilder>().addComponents(
embeds: [embed], createButton,
components: [row] );
});
await inter.reply({ content: "Done", flags: MessageFlags.Ephemeral }); await channel.send({
} embeds: [embed],
components: [row],
});
await inter.reply({ content: "Done", flags: MessageFlags.Ephemeral });
}
} }

View File

@ -1,19 +1,37 @@
import type { CommandInteraction, TextChannel } from "discord.js"; import {
import { Discord, Slash } from "discordx"; ApplicationCommandOptionType,
type CommandInteraction,
type TextChannel,
} from "discord.js";
import { Discord, Slash, SlashOption } from "discordx";
import db from "../db";
import { uptimeTable } from "../db/schema";
@Discord() @Discord()
export class UptimeCmd { export class UptimeCmd {
@Slash({ name: "uptime-setup", description: "set up uptime cmd" }) @Slash({ name: "uptime-setup", description: "set up uptime cmd" })
async uptimeCmd(inter: CommandInteraction) { async uptimeCmd(
if (inter.user.id != Bun.env.OWNER) { @SlashOption({
await inter.reply("you cannot run this command :p"); name: "channel",
return; description: "channel to send in",
required: true,
type: ApplicationCommandOptionType.Channel,
})
channel: TextChannel,
inter: CommandInteraction,
) {
if (inter.user.id != inter.guild?.ownerId) {
return await inter.reply("you cannot run this command :p");
} }
const channel = inter.client.channels.cache.get(
Bun.env.SCYTHE_CHANNEL, const msg = await channel.send(
) as TextChannel; `bot is up, last ping: <t:${Math.floor(Date.now() / 1000)}:f> | if the ping was more than 1 minute ago, cosmic needs to check the bot lol :3`,
await channel.send(
`bot is up, last ping: <t:${Math.floor(Date.now()/1000)}:f> | if the ping was more than 1 minute ago, cosmic needs to check the bot lol :3`,
); );
await db.insert(uptimeTable).values({
guild: inter.guildId!,
channel: channel.id,
message: msg.id,
});
} }
} }

View File

@ -1,13 +1,13 @@
import { import {
ActionRowBuilder, ActionRowBuilder,
ButtonBuilder, ButtonBuilder,
ButtonInteraction, ButtonInteraction,
ButtonStyle, ButtonStyle,
CategoryChannel, CategoryChannel,
ChannelType, ChannelType,
EmbedBuilder, EmbedBuilder,
MessageFlags, MessageFlags,
PermissionsBitField PermissionsBitField,
} from "discord.js"; } from "discord.js";
import { ButtonComponent, Discord } from "discordx"; import { ButtonComponent, Discord } from "discordx";
@ -20,14 +20,17 @@ import { eq } from "drizzle-orm";
export class TicketComponenets { export class TicketComponenets {
@ButtonComponent({ id: "createTicket" }) @ButtonComponent({ id: "createTicket" })
async createHandler(inter: ButtonInteraction): Promise<void> { async createHandler(inter: ButtonInteraction): Promise<void> {
const check = await db.select().from(ticketsTable).where(eq(ticketsTable.user, inter.user.id)); const check = await db
if (check.length >= 1) { .select()
await inter.reply({ .from(ticketsTable)
content: "You already have a ticket open.", .where(eq(ticketsTable.user, inter.user.id));
flags: MessageFlags.Ephemeral if (check.length >= 1) {
}); await inter.reply({
return; content: "You already have a ticket open.",
} flags: MessageFlags.Ephemeral,
});
return;
}
let channel = await inter.guild!.channels.create({ let channel = await inter.guild!.channels.create({
type: ChannelType.GuildText, type: ChannelType.GuildText,
@ -39,51 +42,58 @@ export class TicketComponenets {
}, },
{ {
id: inter.user.id, id: inter.user.id,
allow: [PermissionsBitField.Flags.ViewChannel, PermissionsBitField.Flags.SendMessages], allow: [
PermissionsBitField.Flags.ViewChannel,
PermissionsBitField.Flags.SendMessages,
],
}, },
{ {
id: Bun.env.MOD_ROLE, id: Bun.env.MOD_ROLE,
allow: [PermissionsBitField.Flags.ViewChannel, PermissionsBitField.Flags.SendMessages], allow: [
} PermissionsBitField.Flags.ViewChannel,
] PermissionsBitField.Flags.SendMessages,
],
},
],
}); });
const category = inter.guild?.channels.cache.get(Bun.env.TICKET_CATEGORY) as CategoryChannel; const category = inter.guild?.channels.cache.get(
channel = await channel.setParent(category); Bun.env.TICKET_CATEGORY,
) as CategoryChannel;
channel = await channel.setParent(category);
const embed = new EmbedBuilder() const embed = new EmbedBuilder()
.setTitle(`Tickets`) .setTitle(`Tickets`)
.setDescription( .setDescription(`<@${inter.user.id}> here's your ticket!`);
`<@${inter.user.id}> here's your ticket!`,
); const deleteButton = new ButtonBuilder()
.setCustomId("deleteTicket")
const deleteButton = new ButtonBuilder() .setLabel("Delete Ticket")
.setCustomId('deleteTicket') .setStyle(ButtonStyle.Primary);
.setLabel('Delete Ticket')
.setStyle(ButtonStyle.Primary); const row = new ActionRowBuilder<ButtonBuilder>().addComponents(
deleteButton,
const row = new ActionRowBuilder<ButtonBuilder>() );
.addComponents(deleteButton);
await channel.send({ await channel.send({
embeds: [embed], embeds: [embed],
components: [row] components: [row],
}); });
await inter.reply({ await inter.reply({
content: `<#${channel.id}>`, content: `<#${channel.id}>`,
flags: MessageFlags.Ephemeral flags: MessageFlags.Ephemeral,
}) });
await db.insert(ticketsTable).values({ await db.insert(ticketsTable).values({
user: inter.user.id, user: inter.user.id,
channel: channel.id channel: channel.id,
}); });
} }
@ButtonComponent({ id: "deleteTicket" }) @ButtonComponent({ id: "deleteTicket" })
async deleteHandler(inter: ButtonInteraction): Promise<void> { async deleteHandler(inter: ButtonInteraction): Promise<void> {
await inter.channel?.delete(`<@${inter.user.id}> has deleted the ticket.`); await inter.channel?.delete(`<@${inter.user.id}> has deleted the ticket.`);
await db.delete(ticketsTable).where(eq(ticketsTable.user, inter.user.id)); await db.delete(ticketsTable).where(eq(ticketsTable.user, inter.user.id));
} }
} }

View File

@ -1,3 +1,3 @@
import { drizzle } from 'drizzle-orm/libsql'; import { drizzle } from "drizzle-orm/libsql";
export default drizzle(Bun.env.DB); export default drizzle(Bun.env.DB);

View File

@ -0,0 +1,11 @@
CREATE TABLE `bye` (
`guild` text PRIMARY KEY NOT NULL,
`channel` text NOT NULL,
`message` text NOT NULL
);
--> statement-breakpoint
CREATE TABLE `greets` (
`guild` text PRIMARY KEY NOT NULL,
`channel` text NOT NULL,
`message` text NOT NULL
);

View File

@ -0,0 +1,10 @@
CREATE TABLE `intro` (
`guild` text PRIMARY KEY NOT NULL,
`channel` text NOT NULL
);
--> statement-breakpoint
CREATE TABLE `uptime` (
`guild` text PRIMARY KEY NOT NULL,
`channel` text NOT NULL,
`message` text NOT NULL
);

View File

@ -1,42 +1,42 @@
{ {
"version": "6", "version": "6",
"dialect": "sqlite", "dialect": "sqlite",
"id": "fa98c7d2-c794-4f03-887b-f6f5c1b3891d", "id": "fa98c7d2-c794-4f03-887b-f6f5c1b3891d",
"prevId": "00000000-0000-0000-0000-000000000000", "prevId": "00000000-0000-0000-0000-000000000000",
"tables": { "tables": {
"tickets": { "tickets": {
"name": "tickets", "name": "tickets",
"columns": { "columns": {
"user": { "user": {
"name": "user", "name": "user",
"type": "text", "type": "text",
"primaryKey": true, "primaryKey": true,
"notNull": true, "notNull": true,
"autoincrement": false "autoincrement": false
}, },
"channel": { "channel": {
"name": "channel", "name": "channel",
"type": "text", "type": "text",
"primaryKey": false, "primaryKey": false,
"notNull": true, "notNull": true,
"autoincrement": false "autoincrement": false
} }
}, },
"indexes": {}, "indexes": {},
"foreignKeys": {}, "foreignKeys": {},
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": {}, "uniqueConstraints": {},
"checkConstraints": {} "checkConstraints": {}
} }
}, },
"views": {}, "views": {},
"enums": {}, "enums": {},
"_meta": { "_meta": {
"schemas": {}, "schemas": {},
"tables": {}, "tables": {},
"columns": {} "columns": {}
}, },
"internal": { "internal": {
"indexes": {} "indexes": {}
} }
} }

View File

@ -1,73 +1,73 @@
{ {
"version": "6", "version": "6",
"dialect": "sqlite", "dialect": "sqlite",
"id": "a3e18d09-b7d4-48e3-8920-1fa55c43ee70", "id": "a3e18d09-b7d4-48e3-8920-1fa55c43ee70",
"prevId": "fa98c7d2-c794-4f03-887b-f6f5c1b3891d", "prevId": "fa98c7d2-c794-4f03-887b-f6f5c1b3891d",
"tables": { "tables": {
"colonthree": { "colonthree": {
"name": "colonthree", "name": "colonthree",
"columns": { "columns": {
"user": { "user": {
"name": "user", "name": "user",
"type": "text", "type": "text",
"primaryKey": true, "primaryKey": true,
"notNull": true, "notNull": true,
"autoincrement": false "autoincrement": false
}, },
"amount": { "amount": {
"name": "amount", "name": "amount",
"type": "integer", "type": "integer",
"primaryKey": false, "primaryKey": false,
"notNull": true, "notNull": true,
"autoincrement": false "autoincrement": false
}, },
"messages_count": { "messages_count": {
"name": "messages_count", "name": "messages_count",
"type": "integer", "type": "integer",
"primaryKey": false, "primaryKey": false,
"notNull": true, "notNull": true,
"autoincrement": false "autoincrement": false
} }
}, },
"indexes": {}, "indexes": {},
"foreignKeys": {}, "foreignKeys": {},
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": {}, "uniqueConstraints": {},
"checkConstraints": {} "checkConstraints": {}
}, },
"tickets": { "tickets": {
"name": "tickets", "name": "tickets",
"columns": { "columns": {
"user": { "user": {
"name": "user", "name": "user",
"type": "text", "type": "text",
"primaryKey": true, "primaryKey": true,
"notNull": true, "notNull": true,
"autoincrement": false "autoincrement": false
}, },
"channel": { "channel": {
"name": "channel", "name": "channel",
"type": "text", "type": "text",
"primaryKey": false, "primaryKey": false,
"notNull": true, "notNull": true,
"autoincrement": false "autoincrement": false
} }
}, },
"indexes": {}, "indexes": {},
"foreignKeys": {}, "foreignKeys": {},
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": {}, "uniqueConstraints": {},
"checkConstraints": {} "checkConstraints": {}
} }
}, },
"views": {}, "views": {},
"enums": {}, "enums": {},
"_meta": { "_meta": {
"schemas": {}, "schemas": {},
"tables": {}, "tables": {},
"columns": {} "columns": {}
}, },
"internal": { "internal": {
"indexes": {} "indexes": {}
} }
} }

View File

@ -0,0 +1,135 @@
{
"version": "6",
"dialect": "sqlite",
"id": "2218a8ed-3583-479c-b142-997455b0c7dc",
"prevId": "a3e18d09-b7d4-48e3-8920-1fa55c43ee70",
"tables": {
"bye": {
"name": "bye",
"columns": {
"guild": {
"name": "guild",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"channel": {
"name": "channel",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"message": {
"name": "message",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"colonthree": {
"name": "colonthree",
"columns": {
"user": {
"name": "user",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"amount": {
"name": "amount",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"messages_count": {
"name": "messages_count",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"greets": {
"name": "greets",
"columns": {
"guild": {
"name": "guild",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"channel": {
"name": "channel",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"message": {
"name": "message",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"tickets": {
"name": "tickets",
"columns": {
"user": {
"name": "user",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"channel": {
"name": "channel",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
}
},
"views": {},
"enums": {},
"_meta": {
"schemas": {},
"tables": {},
"columns": {}
},
"internal": {
"indexes": {}
}
}

View File

@ -0,0 +1,190 @@
{
"version": "6",
"dialect": "sqlite",
"id": "fb3b9c6d-7d3b-4651-b230-e163bf08b586",
"prevId": "2218a8ed-3583-479c-b142-997455b0c7dc",
"tables": {
"bye": {
"name": "bye",
"columns": {
"guild": {
"name": "guild",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"channel": {
"name": "channel",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"message": {
"name": "message",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"colonthree": {
"name": "colonthree",
"columns": {
"user": {
"name": "user",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"amount": {
"name": "amount",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"messages_count": {
"name": "messages_count",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"greets": {
"name": "greets",
"columns": {
"guild": {
"name": "guild",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"channel": {
"name": "channel",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"message": {
"name": "message",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"intro": {
"name": "intro",
"columns": {
"guild": {
"name": "guild",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"channel": {
"name": "channel",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"tickets": {
"name": "tickets",
"columns": {
"user": {
"name": "user",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"channel": {
"name": "channel",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"uptime": {
"name": "uptime",
"columns": {
"guild": {
"name": "guild",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"channel": {
"name": "channel",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"message": {
"name": "message",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
}
},
"views": {},
"enums": {},
"_meta": {
"schemas": {},
"tables": {},
"columns": {}
},
"internal": {
"indexes": {}
}
}

View File

@ -1,20 +1,34 @@
{ {
"version": "7", "version": "7",
"dialect": "sqlite", "dialect": "sqlite",
"entries": [ "entries": [
{ {
"idx": 0, "idx": 0,
"version": "6", "version": "6",
"when": 1742570954158, "when": 1742570954158,
"tag": "0000_nervous_komodo", "tag": "0000_nervous_komodo",
"breakpoints": true "breakpoints": true
}, },
{ {
"idx": 1, "idx": 1,
"version": "6", "version": "6",
"when": 1742844574438, "when": 1742844574438,
"tag": "0001_warm_ego", "tag": "0001_warm_ego",
"breakpoints": true "breakpoints": true
} },
] {
} "idx": 2,
"version": "6",
"when": 1742864217240,
"tag": "0002_colorful_colleen_wing",
"breakpoints": true
},
{
"idx": 3,
"version": "6",
"when": 1742927150677,
"tag": "0003_clumsy_mephistopheles",
"breakpoints": true
}
]
}

View File

@ -3,14 +3,37 @@ import { text, sqliteTable, int } from "drizzle-orm/sqlite-core";
import type { InferSelectModel } from "drizzle-orm"; import type { InferSelectModel } from "drizzle-orm";
export const ticketsTable = sqliteTable("tickets", { export const ticketsTable = sqliteTable("tickets", {
user: text().primaryKey().notNull(), user: text().primaryKey().notNull(),
channel: text().notNull() channel: text().notNull(),
}) });
export const colonTable = sqliteTable("colonthree", { export const colonTable = sqliteTable("colonthree", {
user: text().primaryKey().notNull(), user: text().primaryKey().notNull(),
amount: int().notNull(), amount: int().notNull(),
messages_count: int().notNull() messages_count: int().notNull(),
}) });
export type ColonThreeType = InferSelectModel<typeof colonTable>; export const greetsTable = sqliteTable("greets", {
guild: text().primaryKey().notNull(),
channel: text().notNull(),
message: text().notNull(),
});
export const byeTable = sqliteTable("bye", {
guild: text().primaryKey().notNull(),
channel: text().notNull(),
message: text().notNull(),
});
export const uptimeTable = sqliteTable("uptime", {
guild: text().primaryKey(),
channel: text().notNull(),
message: text().notNull(),
});
export const introTable = sqliteTable("intro", {
guild: text().primaryKey(),
channel: text().notNull(),
});
export type ColonThreeType = InferSelectModel<typeof colonTable>;

View File

@ -2,39 +2,63 @@ import type { TextChannel } from "discord.js";
import { Client, Discord, On, type ArgsOf } from "discordx"; import { Client, Discord, On, type ArgsOf } from "discordx";
import db from "../db"; import db from "../db";
import { colonTable } from "../db/schema"; import { byeTable, colonTable, greetsTable } from "../db/schema";
import { eq } from "drizzle-orm"; import { eq } from "drizzle-orm";
@Discord() @Discord()
export class MemberEvents { export class MemberEvents {
@On({ event: "guildMemberAdd" }) @On({ event: "guildMemberAdd" })
async memberAdd([member]: ArgsOf<"guildMemberAdd">, client: Client) { async memberAdd([member]: ArgsOf<"guildMemberAdd">, client: Client) {
const channel = client.channels.cache.get(Bun.env.WELCOME!) as TextChannel; const greetRes = await db
await channel.send( .select()
`Welcome to **${member.guild.name}** <@${member.user.id}> ! You are member #${member.guild.memberCount}! Get a color role and Operating System role(s) in the Channels & Roles section if you want and enjoy your stay <3`, .from(greetsTable)
); .where(eq(greetsTable.guild, member.guild.id));
if (greetRes.length > 0) {
const channel = client.channels.cache.get(
greetRes[0].channel,
) as TextChannel;
await channel.send(
greetRes[0].message.replace("{user}", `<@${member.user.id}>`),
);
}
if (member.user.bot) { if (member.user.bot) {
const botRole = member.guild.roles.cache.get(Bun.env.BOT_ROLE!); const botRole = member.guild.roles.cache.get(Bun.env.BOT_ROLE!);
if (botRole) { if (botRole) {
await member.roles.add(botRole); await member.roles.add(botRole);
} }
} else { } else {
if(Date.now() - member.user.createdAt.getTime() < 1000 * 60 * 60 * 24 * 7) {
try {
await member.send("to protect against raids, bots, and other disturbances, accounts under a week old are kicked upon joining. please wait for your account to mature before rejoining.")
} catch(_) {}
await member.kick("account less than week old")
return;
}
await db.insert(colonTable).values({ await db.insert(colonTable).values({
user: member.id, user: member.id,
amount: 0, amount: 0,
messages_count: 0 messages_count: 0,
}); });
} }
} }
@On({ event: "guildMemberRemove" }) @On({ event: "guildMemberRemove" })
async memberRemove([member]: ArgsOf<"guildMemberRemove">, client: Client) { async memberRemove([member]: ArgsOf<"guildMemberRemove">, client: Client) {
const channel = client.channels.cache.get(Bun.env.GOODBYE!) as TextChannel; const byeRes = await db
await channel.send(`We're sad to see you go, ${member.user.username}.`); .select()
.from(byeTable)
.where(eq(byeTable.guild, member.guild.id));
if (byeRes.length > 0) {
const channel = client.channels.cache.get(
byeRes[0].channel,
) as TextChannel;
await channel.send(
byeRes[0].message.replace("{user}", `${member.user.username}`),
);
}
await db await db.delete(colonTable).where(eq(colonTable.user, member.id));
.delete(colonTable)
.where(eq(colonTable.user, member.id));
} }
@On({ event: "guildMemberUpdate" }) @On({ event: "guildMemberUpdate" })

View File

@ -46,14 +46,20 @@ export class MessageEvents {
if (cThreeRegex.test(msg.content)) { if (cThreeRegex.test(msg.content)) {
let colonThrees = msg.content.match(cThreeRegex); let colonThrees = msg.content.match(cThreeRegex);
await db.update(colonTable).set({ await db
amount: sql`${colonTable.amount} + ${colonThrees?.length}`, .update(colonTable)
messages_count: sql`${colonTable.messages_count} + 1`, .set({
}).where(eq(colonTable.user, msg.author.id)); amount: sql`${colonTable.amount} + ${colonThrees?.length}`,
messages_count: sql`${colonTable.messages_count} + 1`,
})
.where(eq(colonTable.user, msg.author.id));
} else { } else {
await db.update(colonTable).set({ await db
messages_count: sql`${colonTable.messages_count} + 1`, .update(colonTable)
}).where(eq(colonTable.user, msg.author.id)); .set({
messages_count: sql`${colonTable.messages_count} + 1`,
})
.where(eq(colonTable.user, msg.author.id));
} }
} }
} }

View File

@ -1,8 +1,8 @@
import { Client, Discord, On, type ArgsOf } from "discordx"; import { Client, Discord, On, type ArgsOf } from "discordx";
import { underageCheck } from "../utils/underage"; import { underageCheck } from "../utils/underage";
import { bumpRemind } from "../utils/bump"; import { bumpRemind } from "../utils/bump";
import {stat, mkdir} from "fs/promises";
import { uptimeLoop } from "../utils/uptime-loop"; import { uptimeLoop } from "../utils/uptime-loop";
import { introCheck } from "../utils/intro-check";
@Discord() @Discord()
export class Ready { export class Ready {
@ -12,9 +12,6 @@ export class Ready {
await client.initApplicationCommands(); await client.initApplicationCommands();
await client.guilds.fetch(); await client.guilds.fetch();
const members = client.guilds.cache.get(Bun.env.GUILD!)?.members; const members = client.guilds.cache.get(Bun.env.GUILD!)?.members;
if(!((await stat("data")).isDirectory())) {
await mkdir("data")
}
if (!(await Bun.file("bump.json").exists())) { if (!(await Bun.file("bump.json").exists())) {
await Bun.write("bump.json", "{}"); await Bun.write("bump.json", "{}");
} }
@ -23,5 +20,6 @@ export class Ready {
} }
bumpRemind(client); bumpRemind(client);
uptimeLoop(client); uptimeLoop(client);
introCheck(client);
} }
} }

View File

@ -22,7 +22,9 @@ const client = new Client({
client.on("error", console.error); client.on("error", console.error);
const run = async () => { const run = async () => {
await importx(`${dirname(import.meta.url)}/{events,commands,components}/**/*.ts`); await importx(
`${dirname(import.meta.url)}/{events,commands,components}/**/*.ts`,
);
client.login(Bun.env.TOKEN!); client.login(Bun.env.TOKEN!);
}; };

22
src/utils/intro-check.ts Normal file
View File

@ -0,0 +1,22 @@
import type { Client } from "discordx";
import db from "../db";
import { introTable } from "../db/schema";
import type { TextChannel } from "discord.js";
import { sleep } from "./underage";
export const introCheck = async (client: Client) => {
while (true) {
const channelRes = await db.select().from(introTable);
for (const c of channelRes) {
const channel = client.channels.cache.get(c.channel) as TextChannel;
const guild = client.guilds.cache.get(c.guild);
const msgs = await channel.messages.fetch({ limit: 10 });
for (const m of msgs) {
if (!guild?.members.cache.has(m[1].author.id)) {
await m[1].delete();
}
}
}
await sleep(1000 * 60 * 20);
}
};

View File

@ -1,16 +1,19 @@
import type { Client } from "discordx"; import type { Client } from "discordx";
import { sleep } from "./underage"; import { sleep } from "./underage";
import type { TextChannel } from "discord.js"; import type { TextChannel } from "discord.js";
import db from "../db";
import { uptimeTable } from "../db/schema";
export const uptimeLoop = async (client: Client) => { export const uptimeLoop = async (client: Client) => {
while (true) { while (true) {
await sleep(30 * 1000); await sleep(30 * 1000);
const channel = client.channels.cache.get( const uptimeRes = await db.select().from(uptimeTable);
Bun.env.SCYTHE_CHANNEL, for (const i of uptimeRes) {
) as TextChannel; const channel = client.channels.cache.get(i.channel) as TextChannel;
const msg = await channel.messages.fetch(Bun.env.UPTIME_MESSAGE); const msg = await channel.messages.fetch(i.message);
await msg.edit( await msg.edit(
`bot is up, last ping: <t:${Math.floor(Date.now()/1000)}:f> | if the ping was more than 1 minute ago, cosmic needs to check the bot lol :3`, `bot is up, last ping: <t:${Math.floor(Date.now() / 1000)}:f> | if the ping was more than 1 minute ago, cosmic needs to check the bot lol :3`,
); );
}
} }
}; };