forked from cosmic/scythe
1
0
Fork 0

Compare commits

...

4 Commits

Author SHA1 Message Date
rainydevzz cfb7b31b36 fix docker 2025-03-26 15:58:04 -07:00
rainydevzz 3d1b5969e8 fix docker 2025-03-26 15:57:21 -07:00
rainydevzz 79150f3795 fix entrypoint? 2025-03-26 15:26:55 -07:00
rainydevzz 0d02981330 super cool refactor 2025-03-26 15:21:40 -07:00
24 changed files with 1146 additions and 473 deletions

View File

@ -2,6 +2,10 @@ FROM oven/bun:latest
WORKDIR /app WORKDIR /app
COPY package.json package.json
COPY . . COPY . .
RUN bun i
CMD [ "./entrypoint.sh" ] CMD [ "./entrypoint.sh" ]

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

@ -5,8 +5,8 @@
"private": true, "private": true,
"scripts": { "scripts": {
"start": "bun run src/index.ts", "start": "bun run src/index.ts",
"migrate": "bun drizzle-kit migrate", "migrate": "bunx drizzle-kit migrate",
"migrate:generate": "bun drizzle-kit generate" "migrate:generate": "bunx drizzle-kit generate"
}, },
"devDependencies": { "devDependencies": {
"@types/bun": "^1.2.5", "@types/bun": "^1.2.5",

View File

@ -1,17 +1,29 @@
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({
content: "You're not allowed to run this.",
flags: MessageFlags.Ephemeral,
});
return; return;
} }
@ -21,7 +33,10 @@ export class ColonThreeInit {
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;
@ -30,18 +45,21 @@ export class ColonThreeInit {
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 {
@ -54,45 +72,54 @@ export class ColonThree {
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({
content: `Failed to get <@${user?.id || inter.user.id}>'s stats.`,
flags: MessageFlags.Ephemeral,
});
return; 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({
content: "Something went wrong...",
flags: MessageFlags.Ephemeral,
});
return; 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({
name: userObject.username,
iconURL: userObject.avatarURL()!,
})
.addFields( .addFields(
{ {
name: '**Total :3 Sent**', name: "**Total :3 Sent**",
value: `► **${statsUser.amount}** times`, value: `► **${statsUser.amount}** times`,
inline: true inline: true,
}, },
{ {
name: '**Average :3 per Message**', name: "**Average :3 per Message**",
value: `► **${(statsUser.amount / statsUser.messages_count).toFixed(2)}**`, value: `► **${(statsUser.amount / statsUser.messages_count).toFixed(2)}**`,
inline: true inline: true,
}, },
{ {
name: '**Messages Count**', name: "**Messages Count**",
value: `► **${statsUser.messages_count}**`, value: `► **${statsUser.messages_count}**`,
inline: true inline: true,
} },
); );
await inter.reply({ embeds: [embed], flags: MessageFlags.Ephemeral }); await inter.reply({ embeds: [embed], flags: MessageFlags.Ephemeral });
@ -114,29 +141,29 @@ export class ColonThree {
type: ApplicationCommandOptionType.User, type: ApplicationCommandOptionType.User,
}) })
y: GuildMember, y: GuildMember,
inter: CommandInteraction inter: CommandInteraction,
) { ) {
const xStats = ( const xStats = (
await db await db.select().from(colonTable).where(eq(colonTable.user, x.id))
.select()
.from(colonTable)
.where(eq(colonTable.user, x.id))
)[0]; )[0];
if (!xStats) { if (!xStats) {
await inter.reply({ content: `Failed to get <@${x.id}>'s stats.`, flags: MessageFlags.Ephemeral }); await inter.reply({
content: `Failed to get <@${x.id}>'s stats.`,
flags: MessageFlags.Ephemeral,
});
return; return;
} }
const yStats = ( const yStats = (
await db await db.select().from(colonTable).where(eq(colonTable.user, y.id))
.select()
.from(colonTable)
.where(eq(colonTable.user, y.id))
)[0]; )[0];
if (!yStats) { if (!yStats) {
await inter.reply({ content: `Failed to get <@${y.id}>'s stats.`, flags: MessageFlags.Ephemeral }); await inter.reply({
content: `Failed to get <@${y.id}>'s stats.`,
flags: MessageFlags.Ephemeral,
});
return; return;
} }
@ -148,33 +175,41 @@ export class ColonThree {
{ {
name: `📊 ${x.user.username}'s Stats`, name: `📊 ${x.user.username}'s Stats`,
value: `► Sent **${xStats.amount}** :3\n► Avg: **${(xStats.amount / xStats.messages_count).toFixed(2)}** :3 per message`, value: `► Sent **${xStats.amount}** :3\n► Avg: **${(xStats.amount / xStats.messages_count).toFixed(2)}** :3 per message`,
inline: true inline: true,
}, },
{ {
name: `📊 ${y.user.username}'s Stats`, name: `📊 ${y.user.username}'s Stats`,
value: `► Sent **${yStats.amount}** :3\n► Avg: **${(yStats.amount / yStats.messages_count).toFixed(2)}** :3 per message`, value: `► Sent **${yStats.amount}** :3\n► Avg: **${(yStats.amount / yStats.messages_count).toFixed(2)}** :3 per message`,
inline: true inline: true,
} },
) );
await inter.reply({ embeds: [embed] }); await inter.reply({ embeds: [embed] });
} }
@Slash({ description: "Leaderboard" }) @Slash({ description: "Leaderboard" })
async board(inter: CommandInteraction) { async board(inter: CommandInteraction) {
const theColonThreeLeaders = await db.select() const theColonThreeLeaders = await db
.select()
.from(colonTable) .from(colonTable)
.orderBy(desc(colonTable.amount)); .orderBy(desc(colonTable.amount));
const topTen = theColonThreeLeaders const topTen = theColonThreeLeaders
.slice(0, 10) .slice(0, 10)
.filter(user => user.amount >= 1); .filter((user) => user.amount >= 1);
const leaderboardText = topTen.map((user, index) => { const leaderboardText = topTen.map((user, index) => {
const rank = index + 1; const rank = index + 1;
const avg = (user.amount / user.messages_count).toFixed(2); const avg = (user.amount / user.messages_count).toFixed(2);
const medal = rank === 1 ? "🥇" : rank === 2 ? "🥈" : rank === 3 ? "🥉" : `**${rank}.**`; const medal =
rank === 1
? "🥇"
: rank === 2
? "🥈"
: rank === 3
? "🥉"
: `**${rank}.**`;
return `${medal} <@${user.user}> » **${user.amount}** :3 (avg: **${avg}**)`; return `${medal} <@${user.user}> » **${user.amount}** :3 (avg: **${avg}**)`;
}); });
@ -183,11 +218,11 @@ export class ColonThree {
.setTitle(`🏆 :3 Leaderboard`) .setTitle(`🏆 :3 Leaderboard`)
.setDescription(leaderboardText.join("\n") || "*No data yet!*") .setDescription(leaderboardText.join("\n") || "*No data yet!*")
.addFields({ .addFields({
name: 'Stats', name: "Stats",
value: `► **Tracking users:** ${theColonThreeLeaders.length}`, value: `► **Tracking users:** ${theColonThreeLeaders.length}`,
inline: false inline: false,
}) })
.setFooter({ text: 'Keep using :3!!!!!!' }) .setFooter({ text: "Keep using :3!!!!!!" })
.setTimestamp(); .setTimestamp();
await inter.reply({ embeds: [embed] }); await inter.reply({ embeds: [embed] });
@ -202,40 +237,48 @@ export class ColonThree {
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({
content: "All of data was deleted, you will not be tracked again.",
flags: MessageFlags.Ephemeral,
});
return; 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 = ( const check = (
await db await db
.select() .select()
.from(colonTable) .from(colonTable)
.where(eq(colonTable.user, inter.user.id) .where(eq(colonTable.user, inter.user.id))
)
)[0]; )[0];
if (check) { if (check) {
await inter.reply({ content: "Scythe already tracks your :3 data.", flags: MessageFlags.Ephemeral }); await inter.reply({
content: "Scythe already tracks your :3 data.",
flags: MessageFlags.Ephemeral,
});
return; 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,4 +1,12 @@
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()
@ -6,30 +14,33 @@ 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({
content: "You're not the owner",
flags: MessageFlags.Ephemeral,
});
return; 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() const createButton = new ButtonBuilder()
.setCustomId('createTicket') .setCustomId("createTicket")
.setLabel('Create Ticket') .setLabel("Create Ticket")
.setStyle(ButtonStyle.Primary); .setStyle(ButtonStyle.Primary);
const row = new ActionRowBuilder<ButtonBuilder>().addComponents(
const row = new ActionRowBuilder<ButtonBuilder>() createButton,
.addComponents(createButton); );
await channel.send({ await channel.send({
embeds: [embed], embeds: [embed],
components: [row] components: [row],
}); });
await inter.reply({ content: "Done", flags: MessageFlags.Ephemeral }); 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;
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`, `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

@ -7,7 +7,7 @@ import {
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,11 +20,14 @@ 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
.select()
.from(ticketsTable)
.where(eq(ticketsTable.user, inter.user.id));
if (check.length >= 1) { if (check.length >= 1) {
await inter.reply({ await inter.reply({
content: "You already have a ticket open.", content: "You already have a ticket open.",
flags: MessageFlags.Ephemeral flags: MessageFlags.Ephemeral,
}); });
return; return;
} }
@ -39,45 +42,52 @@ 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(
Bun.env.TICKET_CATEGORY,
) as CategoryChannel;
channel = await channel.setParent(category); 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() const deleteButton = new ButtonBuilder()
.setCustomId('deleteTicket') .setCustomId("deleteTicket")
.setLabel('Delete Ticket') .setLabel("Delete Ticket")
.setStyle(ButtonStyle.Primary); .setStyle(ButtonStyle.Primary);
const row = new ActionRowBuilder<ButtonBuilder>() const row = new ActionRowBuilder<ButtonBuilder>().addComponents(
.addComponents(deleteButton); 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,
}); });
} }

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

@ -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

@ -15,6 +15,20 @@
"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

@ -4,13 +4,36 @@ 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 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>; 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
.select()
.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( await channel.send(
`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`, 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
.update(colonTable)
.set({
amount: sql`${colonTable.amount} + ${colonThrees?.length}`, amount: sql`${colonTable.amount} + ${colonThrees?.length}`,
messages_count: sql`${colonTable.messages_count} + 1`, messages_count: sql`${colonTable.messages_count} + 1`,
}).where(eq(colonTable.user, msg.author.id)); })
.where(eq(colonTable.user, msg.author.id));
} else { } else {
await db.update(colonTable).set({ await db
.update(colonTable)
.set({
messages_count: sql`${colonTable.messages_count} + 1`, messages_count: sql`${colonTable.messages_count} + 1`,
}).where(eq(colonTable.user, msg.author.id)); })
.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`,
); );
} }
}
}; };