Skip to content

Commit

Permalink
feat: update DJS and get ready for p4a 25
Browse files Browse the repository at this point in the history
  • Loading branch information
BenSegal855 committed Feb 12, 2025
1 parent a56f3e2 commit 16bdf2a
Show file tree
Hide file tree
Showing 12 changed files with 2,264 additions and 1,695 deletions.
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"faxable",
"ggsans",
"GREYPLE",
"ical",
"manageroll",
"managerolls",
"monoselect",
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,11 @@
"colorette": "2.0.20",
"common-tags": "1.8.2",
"date-and-time": "^3.1.1",
"discord.js": "14.14.1",
"discord.js": "^14.18.0",
"module-alias": "2.2.3",
"mongodb": "4.17.2",
"node-cron": "3.0.3",
"node-ical": "^0.20.1",
"parse-duration": "1.1.0",
"pdf2json": "^3.0.3",
"pretty-ms": "7.0.1",
Expand Down
4 changes: 2 additions & 2 deletions src/commands/Fax Center/setDesk.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ApplyOptions } from '@sapphire/decorators';
import type { CommandOptions } from '@sapphire/framework';
import { Message, ActionRowBuilder, StringSelectMenuBuilder, User, ChannelType, ComponentType } from 'discord.js';
import { Message, ActionRowBuilder, StringSelectMenuBuilder, User, ComponentType } from 'discord.js';
import { SteveCommand } from '@lib/extensions/SteveCommand';
import { getChannel, getGuild, sendLoadingMessage } from '@lib/utils';

Expand Down Expand Up @@ -37,7 +37,7 @@ export class UserCommand extends SteveCommand {

dbGuild.channels.fax.forEach(async (channelId) => {
const channel = await getChannel(channelId);
if (!channel?.isTextBased() || channel.type === ChannelType.DM || channel.partial) return;
if (!channel?.isSendable() || channel.isDMBased()) return;

dropdown.addOptions({
label: `#${channel.name}`,
Expand Down
73 changes: 48 additions & 25 deletions src/commands/Info/p4a.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,19 @@
import { ApplyOptions } from '@sapphire/decorators';
import type { Command, CommandOptions } from '@sapphire/framework';
import date from 'date-and-time';
import meridiem from 'date-and-time/plugin/meridiem';
import ical from 'node-ical';
import { EmbedBuilder, Message, TimestampStyles, time as discordTime } from 'discord.js';
import { SteveCommand } from '@lib/extensions/SteveCommand';
import { p4aSchedule } from '../../assets/P4A24Schedule.json';
import { send } from '@sapphire/plugin-editable-commands';

date.plugin(meridiem);
import { writeFileSync } from 'fs';

@ApplyOptions<CommandOptions>({
description: 'See who\'s live right now on the Project for Awesome',
preconditions: [['CommitteeOnly', 'DMOnly']]
})
export class UserCommand extends SteveCommand {

private icalURL25 = 'https://calendar.google.com/calendar/ical/c_b4abece77b5d42e59a82e68ef19a543c873b1177d53296529eb89cee0d179b5b%40group.calendar.google.com/public/basic.ics';

public override registerApplicationCommands(registry: Command.Registry) {
registry.registerChatInputCommand(builder => {
builder
Expand All @@ -24,45 +23,41 @@ export class UserCommand extends SteveCommand {
}

public async chatInputRun(interaction: Command.ChatInputCommandInteraction) {
interaction.reply({ embeds: [this.buildEmbed()] });
interaction.reply({ embeds: [await this.buildEmbed()] });
}

public async messageRun(msg: Message) {
return send(msg, { embeds: [this.buildEmbed()] });
return send(msg, { embeds: [await this.buildEmbed()] });
}

private buildEmbed(): EmbedBuilder {
private async buildEmbed(): Promise<EmbedBuilder> {
const embed = new EmbedBuilder()
.setThumbnail('https://projectforawesome.com/assets/2024/Social/p4a_2024_profile.png')
.setThumbnail('https://www.projectforawesome.com/assets/2025/Social/Profile_Lilac.png')
.setColor('#1B9C64');

const trueDateSchedule: timeslot[] = p4aSchedule.map(({ tag, hosts, time }) => ({
tag: tag === 'Live' || tag === 'Dark' || tag === 'Optional' ? tag : 'Unknown',
hosts,
time: date.parse(time, 'M/D/YYYY h:mma Z')
}));
const schedule = await this.getIcalData();
writeFileSync('schedule.json', JSON.stringify(schedule, null, '\t'));

const nextSlotIdx = trueDateSchedule.findIndex(timeslot => timeslot.time.getTime() > Date.now());
const now = new Date();
const currentSlot = schedule.find(slot => slot.end > now && slot.start < now);

if (nextSlotIdx < 0) {
return embed
.setTitle('The P4A is over. See you next year');
if (!currentSlot) {
return embed.setTitle('The Project for Awesome is almost here! See you soon!');
}

const currentSlot = trueDateSchedule[nextSlotIdx - 1];
const nextSlot = trueDateSchedule[nextSlotIdx];
const nextSlot = schedule.find(slot => slot.end > currentSlot?.end && slot.start < currentSlot?.end);

switch (currentSlot.tag) {
case 'Live':
embed.setTitle(`Live now: ${currentSlot.hosts}`)
.setDescription(`**${currentSlot.hosts}** Will be live until ${discordTime(nextSlot.time, TimestampStyles.ShortTime)}
Next up, its ${nextSlot.hosts}`)
.setDescription(`**${currentSlot.hosts}** Will be live until ${discordTime(currentSlot.end, TimestampStyles.ShortTime)}
${nextSlot ? `Next up, its ${nextSlot.hosts}` : ''}`)
.setURL('https://projectforawesome.com/live');
break;
case 'Optional':
embed.setTitle(`${currentSlot.hosts} might be live now, but they might not`)
.setDescription(`**${currentSlot.hosts}** Will be live until ${discordTime(nextSlot.time, TimestampStyles.ShortTime)}
Next up, its ${nextSlot.hosts}`)
.setDescription(`**${currentSlot.hosts}** Will be live until ${discordTime(currentSlot.end, TimestampStyles.ShortTime)}
${nextSlot ? `Next up, its ${nextSlot.hosts}` : ''}`)
.setURL('https://projectforawesome.com/live'); ;
break;
case 'Dark':
Expand All @@ -80,10 +75,38 @@ Next up, its ${nextSlot.hosts}`)
return embed;
}

private async getIcalData(): Promise<timeslot[]> {
const rawIcalData = await ical.fromURL(this.icalURL25);

const events = Object.values(rawIcalData).filter(event => event.type === 'VEVENT') as ical.VEvent[];

writeFileSync('events.json', JSON.stringify(events, null, '\t'));

return events.map(event => {
let tag: 'Live'|'Dark'|'Optional' = 'Live';

if (event.summary.toLowerCase().includes('optional')) {
tag = 'Optional';
}

if (event.summary.toLowerCase().includes('downtime')) {
tag = 'Dark';
}

return {
start: event.start,
end: event.end,
tag,
hosts: event.summary
};
});
}

}

type timeslot = {
time: Date,
start: Date,
end: Date,
tag: 'Live'|'Dark'|'Optional'|'Unknown',
hosts: string
};
69 changes: 69 additions & 0 deletions src/commands/Server Managment/condenseSummaries.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { ApplyOptions } from '@sapphire/decorators';
import { UserError, type Command, type CommandOptions } from '@sapphire/framework';
import { SteveCommand } from '@lib/extensions/SteveCommand';
import { ChannelType } from 'discord.js';

@ApplyOptions<CommandOptions>({
description: 'Condense a category into a summary channel',
requiredUserPermissions: ['ManageChannels']
})
export class UserCommand extends SteveCommand {

public override registerApplicationCommands(registry: Command.Registry) {
registry.registerChatInputCommand(builder => {
builder
.setName(this.name)
.setDescription(this.description)
.addChannelOption(option =>
option
.setName('category')
.setDescription('The category to condense')
.setRequired(true)
.addChannelTypes(ChannelType.GuildCategory)
);
}, { guildIds: ['989658500563095562', '723241105323327578'] });
}

public async chatInputRun(interaction: Command.ChatInputCommandInteraction) {
if (!interaction.guild) {
throw new UserError({
identifier: 'condenseSummariesMissingGuild',
message: 'This command must be run in a server.'
});
}
await interaction.deferReply({ ephemeral: true });

const category = interaction.options.getChannel<ChannelType.GuildCategory>('category', true);

const summaryChannelPromise = interaction.guild.channels.create({
name: category.name,
type: ChannelType.GuildText
});

const messages: string[] = [];

for (const channel of category.children.cache.sort((a, b) => a.position - b.position).values()) {
if (channel.type === ChannelType.GuildText) {
messages.push(`# ${channel.name
.replace(/[-_]/g, ' ')
.replace(/\w\S*/g, text => text.charAt(0).toUpperCase() + text.substring(1).toLowerCase())
}`);

await channel.messages.fetch().then(channelMessages => {
messages.push(...channelMessages.map(message => message.content));
});
}
};

const summaryChannel = await summaryChannelPromise;

for (const message of messages) {
await summaryChannel.send(message);
}

await interaction.editReply({
content: `Condensed ${category.name} into ${summaryChannel}`
});
}

}
2 changes: 1 addition & 1 deletion src/commands/User Managment/manageRolePlay.ts
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ export class UserCommand extends SteveSubcommand {

const pfpLog = await this.container.client.channels.fetch(process.env.RP_PFP_CHANNEL ?? '');

if (!pfpLog || !pfpLog.isTextBased()) return undefined;
if (!pfpLog || !pfpLog.isSendable()) return undefined;

ephemeralAttachment.name = ephemeralAttachment.name ?? `pfp.${ephemeralAttachment.contentType}`;

Expand Down
4 changes: 2 additions & 2 deletions src/commands/fun/roll.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import {
Guild,
Message,
EmbedBuilder,
type TextBasedChannel,
User
} from 'discord.js';
import { oneLine } from 'common-tags';
Expand Down Expand Up @@ -141,8 +140,9 @@ export class RollCommand extends SteveCommand {
private async logRolls(result: string, guild: Guild, author: User) {
const rollLogId = (await this.container.db.guilds.findOne({ id: guild.id }))?.channels?.rolls;
if (!rollLogId) return;
const rollLog = await guild.channels.fetch(rollLogId);
if (!rollLog?.isSendable()) return;

const rollLog = await guild.channels.fetch(rollLogId) as TextBasedChannel;
rollLog.send({ embeds: [new EmbedBuilder()
.setAuthor({ name: `${author.tag} rolled...`, iconURL: author.displayAvatarURL() })
.setDescription(result)
Expand Down
2 changes: 1 addition & 1 deletion src/lib/extensions/SteveBoi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export class SteveBoi extends SapphireClient {
= this.channels.cache.get(reminder.channel)
?? await this.channels.fetch(reminder.channel);

if (!channel?.isTextBased()) return;
if (!channel?.isSendable()) return;

await channel.send({
content: `<@${reminder.user}>, you asked me to remind you about this:\n${reminder.content}`,
Expand Down
4 changes: 2 additions & 2 deletions src/lib/resolvers/durationOrTimestamp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ import { resolveTimestamp } from './timestamp';
export async function resolveDurationOrTimestamp(parameter: string, user: User): Promise<Result<number|Date, 'InvalidDurationOrTimestamp'>> {
const timestampResult = await resolveTimestamp(parameter, user);
if (timestampResult.isOk()) {
return timestampResult;
return Result.ok(timestampResult.unwrap());
}

const durationResult = resolveDuration(parameter);
if (durationResult.isOk()) {
return durationResult;
return Result.ok(durationResult.unwrap());
}

return Result.err('InvalidDurationOrTimestamp');
Expand Down
1 change: 1 addition & 0 deletions src/listeners/mentionPrefixOnly.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export class UserEvent extends Listener<'mentionPrefixOnly'> {

public async run(message: Message) {
const prefix = this.container.client.options.defaultPrefix;
if (!message.channel.isSendable()) return;
return message.channel.send(
prefix
? `My prefix in this guild is: \`${prefix}\``
Expand Down
28 changes: 28 additions & 0 deletions src/preconditions/DepositsAndDeductionsOnly.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Precondition } from '@sapphire/framework';
import type { CommandInteraction, Message } from 'discord.js';

export class UserPrecondition extends Precondition {

public async messageRun(message: Message) {
return message.guildId === '989658500563095562'
? this.ok()
: this.error({
message: 'This command can only be run in the Deposits & Deductions Server'
});
}

public chatInputRun(interaction: CommandInteraction) {
return interaction.guildId === '989658500563095562'
? this.ok()
: this.error({
message: 'This command can only be run in the Deposits & Deductions Server'
});
}

}

declare module '@sapphire/framework' {
interface Preconditions {
DepositsAndDeductionsOnly: never;
}
}
Loading

0 comments on commit 16bdf2a

Please sign in to comment.