Skip to content

Commit

Permalink
Merge branch 'Rdeisenroth:main' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
niklhut authored Dec 8, 2023
2 parents aac9b55 + 63194d3 commit 1812a09
Show file tree
Hide file tree
Showing 12 changed files with 83 additions and 48 deletions.
4 changes: 2 additions & 2 deletions Setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@
5. Call the command `/admin genverifyroles` to generate the roles in the database.
6. Create a new category, call it for example `Tutoring` and make it private but allow the `Tutor` role to see it.
7. Create one voice channel in the category, for example `tutoring-waiting` and allow the `Verified` role to see it. The `Tutor` role should be able to see it already because it is in the same category.
8. Call the command `/create-queue` and pass it the name for the queue and a description, for example `tutoring`. This will create a new queue in the database.
9. Call the command `/setqueue` and pass it the name of the waiting channel, for example `tutoring-waiting` and the name of the queue, for example `tutoring` as well as the supervisor role, for example `Tutor`. This will set the waiting channel for the queue and the supervisor role.
8. Call the command `/config queue create` and pass it the name for the queue and a description, for example `tutoring` and `the queue used to wait for tutoring`. This will create a new queue in the database.
9. Call the command `/config queue set_waiting_room` and pass it the name of the waiting channel, for example `tutoring-waiting` and the name of the queue, for example `tutoring` as well as the supervisor role, for example `Tutor`. This will set the waiting channel for the queue and the supervisor role.
10. Optionally rename the `coach` commands on the server to `tutor` by calling the command `/config commands rename` and passing it the old and new name of the command.

## Testing the Bot
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"start": "tsc --build --clean tsconfig.json; tsc --build tsconfig.json; node dist/index.js",
"nodemon": "nodemon --watch 'src/**/*.ts' src/index.ts",
"build": "tsc --build --clean tsconfig.json; tsc --build tsconfig.json",
"lint": "eslint --ext .ts src/",
"lint": "eslint --ext .ts src/ --fix",
"watch": "tsc -p tsconfig.json -w",
"test": "jest --detectOpenHandles"
},
Expand Down
11 changes: 8 additions & 3 deletions src/commands/coach/queue/list.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ApplicationCommandOptionType, EmbedField, Message } from "discord.js";
import { ApplicationCommandOptionType, EmbedField, GuildMember, Message } from "discord.js";
import { Command } from "../../../../typings";
import { GuildModel } from "../../../models/guilds";
import { UserModel } from "../../../models/users";
Expand Down Expand Up @@ -54,9 +54,14 @@ const command: Command = {
const position = queueData.getPosition(e.discord_id) + 1;
const joined_at = `<t:${Math.round((+e.joinedAt) / 1000)}:f>`;
const intent = e.intent;
const member = await g.members.fetch(e.discord_id);
let member: GuildMember | null;
try {
member = await g.members.fetch(e.discord_id);
} catch (error) {
member = null;
}
fields.push({
name: member.displayName, value:
name: member?.displayName ?? "unknown", value:
`-Position: ${position}`
+ `\n-joined at: ${joined_at}`
+ (intent ? `\n-intent: ${intent}` : ""),
Expand Down
14 changes: 10 additions & 4 deletions src/commands/coach/queue/next.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,11 @@ const command: Command = {
if (!queueData) {
return await client.utils.embeds.SimpleEmbed(interaction, { title: "Coaching System", text: "Queue Could not be Found.", empheral: true });
}
const members = await g.members.fetch();

// check if user is still on the server
await queueData.kickNonServerMembers(g, members);

if (queueData.isEmpty()) {
return await client.utils.embeds.SimpleEmbed(interaction, { title: "Coaching System", text: "The Queue is Empty", empheral: true });
}
Expand All @@ -66,10 +71,11 @@ const command: Command = {
return await client.utils.embeds.SimpleEmbed(interaction, { title: "Coaching System Error", text: `There are less participants in the queue than requested.\n\\> Requested: ${interaction.options.getInteger("amount") ?? 1}\n\\> Available: ${entries.length}`, empheral: true });
}


// Get Room Spawner

let spawner: VoiceChannelSpawner | undefined = queueData.room_spawner?.toObject();
const queue_channel_data = guildData.voice_channels.find(x => x.queue && x.queue == queue);
const queue_channel_data = guildData.voice_channels.find(x => x.queue && x.queue._id.equals(queue));
const queue_channel = g.channels.cache.get(queue_channel_data?._id ?? "");
const member = client.utils.general.getMember(interaction)!;
if (!spawner) {
Expand Down Expand Up @@ -106,7 +112,7 @@ const command: Command = {
},
);
} else {
spawner.name = spawner.name ?? `${member.displayName}' ${queueData.name} Room ${coachingSession.getRoomAmount() + 1}`;
spawner.name = spawner.name ?? `${member.displayName}${member.displayName.endsWith("s") ? "'" : "s'"} ${queueData.name} Room ${coachingSession.getRoomAmount() + 1}`;
}
spawner.permission_overwrites = new mongoose.Types.DocumentArray(
entries.map(x => {
Expand Down Expand Up @@ -172,7 +178,7 @@ const command: Command = {
// Try to move
try {
const member = g.members.resolve(user)!;
roomData.events.push({ emitted_by: "me", type: eventType.move_member, timestamp: Date.now().toString(), reason: `Queue System: '${queueData.name}' Queue automated member Move: ${member.id}`, target:member.id } as EVT);
roomData.events.push({ emitted_by: "me", type: eventType.move_member, timestamp: Date.now().toString(), reason: `Queue System: '${queueData.name}' Queue automated member Move: ${member.id}`, target: member.id } as EVT);
await member.voice.setChannel(room);
} catch (error) {
// Ignore Errors
Expand All @@ -188,7 +194,7 @@ const command: Command = {
// Try to move Coach
try {
await member.voice.setChannel(room);
roomData.events.push({ emitted_by: "me", type: eventType.move_member, timestamp: Date.now().toString(), reason: `Queue System: '${queueData.name}' Queue automated member Move: ${member.id} (coach)`, target:member.id } as EVT);
roomData.events.push({ emitted_by: "me", type: eventType.move_member, timestamp: Date.now().toString(), reason: `Queue System: '${queueData.name}' Queue automated member Move: ${member.id} (coach)`, target: member.id } as EVT);
} catch (error) {
// Ignore Errors
}
Expand Down
4 changes: 3 additions & 1 deletion src/commands/coach/queue/pick.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ const command: Command = {
if (!queueData) {
return await client.utils.embeds.SimpleEmbed(interaction, { title: "Coaching System", text: "Queue Could not be Found.", empheral: true });
}

await queueData.kickNonServerMembers(g);
if (queueData.isEmpty()) {
return await client.utils.embeds.SimpleEmbed(interaction, { title: "Coaching System", text: "The Queue is Empty", empheral: true });
}
Expand Down Expand Up @@ -121,7 +123,7 @@ const command: Command = {
},
);
} else {
spawner.name = spawner.name ?? `${member.displayName}' ${queueData.name} Room ${coachingSession.getRoomAmount() + 1}`;
spawner.name = spawner.name ?? `${member.displayName}${member.displayName.endsWith("s") ? "'" : "s'"} ${queueData.name} Room ${coachingSession.getRoomAmount() + 1}`;
}
spawner.permission_overwrites = new mongoose.Types.DocumentArray([
{
Expand Down
4 changes: 2 additions & 2 deletions src/commands/coach/session/quit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Command } from "../../../../typings";
import { GuildModel } from "../../../models/guilds";
import { UserModel } from "../../../models/users";
import moment from "moment";
import {removeRoleFromUser} from "../../../utils/general";
import { removeRoleFromUser } from "../../../utils/general";

const command: Command = {
name: "quit",
Expand Down Expand Up @@ -34,7 +34,7 @@ const command: Command = {
return await client.utils.embeds.SimpleEmbed(interaction, { title: "Coaching System", text: "You Have no Active Coaching Session.", empheral: true });
}

await removeRoleFromUser(g, user, 'active_session')
await removeRoleFromUser(g, user, "active_session");

//TODO: Terminate Rooms

Expand Down
4 changes: 2 additions & 2 deletions src/commands/coach/session/start.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { UserModel } from "../../../models/users";
import { SessionModel, sessionRole } from "../../../models/sessions";
import { Queue } from "../../../models/queues";
import { DocumentType } from "@typegoose/typegoose";
import {assignRoleToUser} from "../../../utils/general";
import { assignRoleToUser } from "../../../utils/general";

const command: Command = {
name: "start",
Expand Down Expand Up @@ -60,7 +60,7 @@ const command: Command = {
userEntry.sessions.push(session._id);
await userEntry.save();

await assignRoleToUser(g, user, 'active_session')
await assignRoleToUser(g, user, "active_session");

client.utils.embeds.SimpleEmbed(interaction, { title: "Coaching System", text: "The Session was started.", empheral: true });
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { ApplicationCommandOptionType, Message } from "discord.js";
import { Command } from "../../typings";
import { GuildModel } from "../models/guilds";
import { Queue } from "../models/queues";
import { Command } from "../../../../typings";
import { GuildModel } from "../../../models/guilds";
import { Queue } from "../../../models/queues";
import { mongoose } from "@typegoose/typegoose";
import { FilterOutFunctionKeys } from "@typegoose/typegoose/lib/types";

const command: Command = {
name: "create-queue",
description: "creates a queue",
name: "create",
description: "Creates a queue",
aliases: ["cq"],
usage: "[channel resolvable]",
cooldown: 5,
Expand All @@ -23,7 +23,7 @@ const command: Command = {
name: "description",
description: "The Queue Description",
type: ApplicationCommandOptionType.String,
required: false,
required: true,
},
],
guildOnly: true,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { VoiceChannelModel } from "./../models/voice_channels";
import { VoiceChannelModel } from "../../../models/voice_channels";
import { ApplicationCommandOptionType, GuildChannel, Message } from "discord.js";
import { Command } from "../../typings";
import { GuildModel } from "../models/guilds";
import { VoiceChannel } from "../models/voice_channels";
import { Command } from "../../../../typings";
import { GuildModel } from "../../../models/guilds";
import { VoiceChannel } from "../../../models/voice_channels";
import { mongoose } from "@typegoose/typegoose";
import { FilterOutFunctionKeys } from "@typegoose/typegoose/lib/types";

const command: Command = {
name: "setqueue",
description: "sets the given channel as join to create",
name: "set_waiting_room",
description: "Sets the given channel as join to create",
aliases: ["setwaitingroom", "swr", "setwr", "setqueue", "sq"],
usage: "[channel resolvable]",
cooldown: 5,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { ApplicationCommandOptionType, CategoryChannel, GuildChannel, Message, VoiceChannel as dvc } from "discord.js";
import { Command } from "../../typings";
import { GuildModel } from "../models/guilds";
import { VoiceChannel } from "../models/voice_channels";
import { Command } from "../../../typings";
import { GuildModel } from "../../models/guilds";
import { VoiceChannel } from "../../models/voice_channels";
import { FilterOutFunctionKeys } from "@typegoose/typegoose/lib/types";
import { mongoose } from "@typegoose/typegoose";

Expand Down
52 changes: 37 additions & 15 deletions src/models/queues.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import * as moment from "moment";
import { QueueSpan } from "./queue_span";
import { Guild } from "./guilds";
import { VoiceChannelSpawner } from "./voice_channel_spawner";
import * as djs from "discord.js";

/**
* A Queue from the Database
Expand Down Expand Up @@ -107,7 +108,7 @@ export class Queue {
* Put an Entry into the Queue
* @param entry The Queue Entry
*/
public async join(this: DocumentType<Queue>, entry: QueueEntry): Promise<QueueEntry>{
public async join(this: DocumentType<Queue>, entry: QueueEntry): Promise<QueueEntry> {
if (this.entries.find(x => x.discord_id === entry.discord_id)) {
throw new Error("Dublicate Entry");
}
Expand All @@ -120,7 +121,7 @@ export class Queue {
* Leaves the queue
* @param discord_id The Discord ID of the entry
*/
public async leave(this: DocumentType<Queue>, discord_id: string): Promise<QueueEntry>{
public async leave(this: DocumentType<Queue>, discord_id: string): Promise<QueueEntry> {
const entry = this.entries.find(x => x.discord_id === discord_id);
if (!entry) {
throw new Error("Not Found");
Expand All @@ -133,7 +134,7 @@ export class Queue {
* Gets the Sorted Entries with the First ones being the ones with the highest Importance
* @param limit How many entries should we get at most?
*/
public getSortedEntries(this: DocumentType<Queue>, limit?: number | undefined): DocumentType<QueueEntry>[]{
public getSortedEntries(this: DocumentType<Queue>, limit?: number | undefined): DocumentType<QueueEntry>[] {
const entries = this.entries.sort((x, y) => {
const x_importance = (Date.now() - (+x.joinedAt)) * (x.importance || 1);
const y_importance = (Date.now() - (+y.joinedAt)) * (y.importance || 1);
Expand All @@ -145,7 +146,7 @@ export class Queue {
* Returns true if the ID is contained in the queue
* @param discord_id the Discord ID to check if it's contained
*/
public contains(this:DocumentType<Queue>, discord_id: string): boolean {
public contains(this: DocumentType<Queue>, discord_id: string): boolean {
return this.entries.some(x => x.discord_id === discord_id);
}
/**
Expand All @@ -159,7 +160,7 @@ export class Queue {
* Gets the Position in the Current Queue
* @param discord_id the Discord ID of the entry
*/
public getPosition(this: DocumentType<Queue>, discord_id: string): number{
public getPosition(this: DocumentType<Queue>, discord_id: string): number {
return this.getSortedEntries().findIndex(x => x.discord_id === discord_id);
}
/**
Expand Down Expand Up @@ -190,7 +191,7 @@ export class Queue {
* @param string The String to Interpolate
* @param entry_resolvable The Entry Resolvable
*/
public interpolateQueueString(this: DocumentType<Queue>, string:string, entry_resolvable?: string | QueueEntry | undefined): string | null{
public interpolateQueueString(this: DocumentType<Queue>, string: string, entry_resolvable?: string | QueueEntry | undefined): string | null {
try {
const replacements: StringReplacements = {
"limit": this.limit,
Expand All @@ -213,7 +214,8 @@ export class Queue {
"member_id": entry.discord_id,
"user": `<@${entry.discord_id}>`,
"pos": this.getPosition(entry.discord_id) + 1,
"time_spent": moment.duration(Date.now() - (+entry.joinedAt)).format("d[d ]h[h ]m[m ]s.S[s]"),
"time_spent": (moment.duration(Date.now() - (+entry.joinedAt)) as unknown as { format: (arg0: string) => string; })
.format("d[d ]h[h ]m[m ]s.S[s]"),
};
for (const [key, value] of Object.entries(entryReplacements)) {
replacements[key] = value;
Expand Down Expand Up @@ -245,7 +247,7 @@ export class Queue {
* Gets the leave Message of the Queue
* @param entry_resolvable The Entry Resolvable
*/
public getLeaveMessage(this: DocumentType<Queue>, entry_resolvable?: string | QueueEntry | undefined): string{
public getLeaveMessage(this: DocumentType<Queue>, entry_resolvable?: string | QueueEntry | undefined): string {
const default_leave_message = "You left the Queue.";
if (this.leave_message) {
const leave_msg = this.interpolateQueueString(this.leave_message!, entry_resolvable);
Expand Down Expand Up @@ -273,7 +275,7 @@ export class Queue {
* Gets the leave Room Message of the Queue
* @param entry_resolvable The Entry Resolvable
*/
public getLeaveRoomMessage(this: DocumentType<Queue>, entry_resolvable?: string | QueueEntry | undefined): string{
public getLeaveRoomMessage(this: DocumentType<Queue>, entry_resolvable?: string | QueueEntry | undefined): string {
const default_leave_message = `You left the Room. Please confirm your stay or you will be removed from the queue after the Timeout of ${(this.disconnect_timeout ?? 0) / 1000}s.`;
if (this.leave_room_message) {
const leave_msg = this.interpolateQueueString(this.leave_room_message, entry_resolvable);
Expand Down Expand Up @@ -301,7 +303,7 @@ export class Queue {
* Gets the leave Message of the Queue
* @param entry_resolvable The Entry Resolvable
*/
public getJoinMessage(this: DocumentType<Queue>, entry_resolvable?: string | QueueEntry | undefined): string{
public getJoinMessage(this: DocumentType<Queue>, entry_resolvable?: string | QueueEntry | undefined): string {
const default_join_message = "You left the Queue.";
if (this.join_message) {
const join_msg = this.interpolateQueueString(this.join_message, entry_resolvable);
Expand All @@ -314,36 +316,56 @@ export class Queue {
/**
* Returns `true` if the Queue is Empty
*/
public isEmpty(this: DocumentType<Queue>): boolean{
public isEmpty(this: DocumentType<Queue>): boolean {
return this.entries.length < 1;
}
/**
* Locks the queue. This removes the voice Channel Permissions and disallows the queue from the /queue join command
*/
public async lock(this: DocumentType<Queue>): Promise<void>{
public async lock(this: DocumentType<Queue>): Promise<void> {
this.locked = true;
await this.$parent()?.save();
}
/**
* Unlocks the queue. This restores the voice Channel Permissions and allows the queue from the /queue join command
*/
public async unlock(this: DocumentType<Queue>): Promise<void>{
public async unlock(this: DocumentType<Queue>): Promise<void> {
this.locked = false;
await this.$parent()?.save();
}
/**
* Locks or Unlocks the queue (opposite State).
*/
public async toggleLock(this: DocumentType<Queue>): Promise<void>{
public async toggleLock(this: DocumentType<Queue>): Promise<void> {
this.locked = !this.locked;
await this.$parent()?.save();
}
/**
* Resolves all Waiting rooms for the current Queue
*/
public getWaitingRooms(this: DocumentType<Queue>, guild: Guild): DocumentType<VoiceChannel>[]{
public getWaitingRooms(this: DocumentType<Queue>, guild: Guild): DocumentType<VoiceChannel>[] {
return guild.voice_channels?.filter(x => x.queue?._id.equals(this._id!)) ?? [];
}

/**
* Kicks all queue entries that are not in the server the queue is in
* @param this the Queue instance
* @param guild (optionally) the corresponding guild
*/
public async kickNonServerMembers(this: DocumentType<Queue>, guild: djs.Guild, members?: Map<string, djs.GuildMember>) {
if (!members) {
members = await guild.members.fetch();
}
const entries = this.entries.filter(x => !members!.has(x.discord_id));
if (!entries.length) {
return [];
}
console.log(`Missing: ${entries.map(x => x.discord_id).join(", ")} queue entries on Guild. Removing from Queue.`);
for (const entry of entries) {
await this.leave(entry.discord_id);
}
return entries;
}
}

export const QueueModel = getModelForClass(Queue, {
Expand Down
Loading

0 comments on commit 1812a09

Please sign in to comment.