Skip to content

Commit

Permalink
Merge pull request #314 from Sayrix/feat
Browse files Browse the repository at this point in the history
Some Feature Updates
  • Loading branch information
zhiyan114 authored Feb 16, 2024
2 parents f71102c + 12b4675 commit a2712af
Show file tree
Hide file tree
Showing 14 changed files with 194 additions and 26 deletions.
9 changes: 6 additions & 3 deletions config/config.example.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@
"customDescription": "", // The custom description of the ticket type, here is all parameter: USERNAME, USERID, TICKETCOUNT, REASON1, 2, ect (set to blank to use the default description)
"cantAccess": ["1111111111111111111"], // The roles who can't access to this ticket type
"askQuestions": false, // If the bot should ask the reason of the ticket
"questions": [] // Leave blank if you don't want to ask questions
"questions": [], // Leave blank if you don't want to ask questions
"staffRoles": [] // Category specific staff role (instead of the default ones)
},
{
"codeName": "category-two", // The name need to be in lowercase
Expand All @@ -32,7 +33,8 @@
"customDescription": "Please explain your report in detail. If you have any images, please attach them to your message.", // The custom description of the ticket type, here is all parameter: USERNAME, USERID, TICKETCOUNT, REASON1, 2, ect (set to blank to use the default description)
"cantAccess": ["2222222222222222222"], // The roles who can't access to this ticket type
"askQuestions": false, // If the bot should ask the reason of the ticket
"questions": [] // Leave blank if you don't want to ask questions
"questions": [], // Leave blank if you don't want to ask questions
"staffRoles": [] // Category specific staff role (instead of the default ones)
},
{
"codeName": "other", // The name need to be in lowercase
Expand All @@ -45,6 +47,7 @@
"customDescription": "Thank you for your ticket, a staff will reply you as soon as possible\n\n__**What is the reason of the ticket?**__: REASON1", // The custom description of the ticket type, here is all parameter: USERNAME, USERID, TICKETCOUNT, REASON1, 2, ect (set to blank to use the default description)
"cantAccess": [], // The roles who can't access to this ticket type
"askQuestions": true, // If the bot should ask the reason of the ticket
"staffRoles": [], // Category specific staff role (instead of the default ones)
"questions": [
// Maximum of 5 questions can be set due to discord's limit
{
Expand All @@ -66,7 +69,7 @@
"categoryWhenClaimed": "" // The category the ticket is moved to when claimed
},

"rolesWhoHaveAccessToTheTickets": ["1111111111111111111", "2222222222222222222"], // Roles who can access to the tickets (Like the staff)
"rolesWhoHaveAccessToTheTickets": ["1111111111111111111", "2222222222222222222"], // Roles who can access to the tickets (Like the staff)/ Treat this as global admin role type of thing.

"rolesWhoCanNotCreateTickets": [], // Roles who can not create a tickets (Like a blacklist)

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
"dotenv": "^16.3.1",
"fs-extra": "^11.1.1",
"jsonc": "^2.0.0",
"mongoose": "^7.6.2",
"mongoose": "^8.0.3",
"readline": "^1.3.0",
"ticket-bot-transcript-uploader": "^1.3.0",
"websocket": "^1.0.34"
Expand Down
34 changes: 34 additions & 0 deletions src/commands/clearDM.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import {BaseCommand, ExtendedClient} from "../structure";
import {CommandInteraction, SlashCommandBuilder} from "discord.js";

/*
Copyright © 2024 小兽兽/zhiyan114 (github.com/zhiyan114)
File is licensed respectively under the terms of the Creative Commons Attribution 4.0 International
or whichever license the project is using at the time https://github.com/Sayrix/Ticket-Bot/blob/main/LICENSE.md
*/

export default class AddCommand extends BaseCommand {
public static data: SlashCommandBuilder = <SlashCommandBuilder>new SlashCommandBuilder()
.setName("cleardm")
.setDescription("Clear all of your ticket history in your DM");
constructor(client: ExtendedClient) {
super(client);
}

async execute(interaction: CommandInteraction) {
interaction.deferReply({ ephemeral: true });

const dm = await interaction.user.createDM();

let messages = (await dm.messages.fetch({ limit: 100 }))
.filter((message) => message.author.id === this.client.user?.id);
while(messages.size > 0) {
for(const message of messages)
await message[1].delete();
if(messages.size < 100)
break;
messages = await dm.messages.fetch({ limit: 100 });
}
await interaction.followUp({ content: "Cleared all of your DM history", ephemeral: true });
}
}
15 changes: 13 additions & 2 deletions src/commands/close.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { CommandInteraction, GuildMember, SlashCommandBuilder } from "discord.js";
import { closeAskReason } from "../utils/close_askReason";
import {close} from "../utils/close.js";
import {BaseCommand, ExtendedClient} from "../structure";
import {BaseCommand, ExtendedClient, TicketType} from "../structure";

/*
Copyright 2023 Sayrix (github.com/Sayrix)
Expand All @@ -18,9 +18,20 @@ export default class CloseCommand extends BaseCommand {
}

async execute(interaction: CommandInteraction) {

// @TODO: Breaking change refactor happens here as well..

const ticket = await this.client.prisma.tickets.findUnique({
where: {
channelid: interaction.channel?.id
}
});
const ticketType = ticket ? JSON.parse(ticket.category) as TicketType : undefined;

if (
this.client.config.closeOption.whoCanCloseTicket === "STAFFONLY" &&
!(interaction.member as GuildMember | null)?.roles.cache.some((r) => this.client.config.rolesWhoHaveAccessToTheTickets.includes(r.id))
!(interaction.member as GuildMember | null)?.roles.cache.some((r) => this.client.config.rolesWhoHaveAccessToTheTickets.includes(r.id) ||
ticketType?.staffRoles?.includes(r.id))
)
return interaction
.reply({
Expand Down
6 changes: 5 additions & 1 deletion src/commands/index.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
import AddCommand from "./add";
import MassAddCommand from "./massadd";
import ClaimCommand from "./claim";
import CloseCommand from "./close";
import RemoveCommand from "./remove";
import RenameCommand from "./rename";
import clearDM from "./clearDM";

export {
AddCommand,
MassAddCommand,
ClaimCommand,
CloseCommand,
RemoveCommand,
RenameCommand
RenameCommand,
clearDM
};
89 changes: 89 additions & 0 deletions src/commands/massadd.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import {BaseCommand, ExtendedClient} from "../structure";
import {CommandInteraction, SlashCommandBuilder, TextChannel} from "discord.js";
import {log} from "../utils/logs";

/*
Copyright © 2023 小兽兽/zhiyan114 (github.com/zhiyan114)
File is licensed respectively under the terms of the Apache License 2.0
or whichever license the project is using at the time https://github.com/Sayrix/Ticket-Bot/blob/main/LICENSE
*/


// Use add command if possible, otherwise you're missing out on proper user validation...
export default class MassAddCommand extends BaseCommand {
public static data: SlashCommandBuilder = <SlashCommandBuilder>new SlashCommandBuilder()
.setName("massadd")
.setDescription("Add multiple users to the ticket. It's recommended to use the regular add command when possible.")
.addStringOption((input) => input.setName("users").setDescription("Users to add. Use ',' as seperator.").setRequired(true));
constructor(client: ExtendedClient) {
super(client);
}

async execute(interaction: CommandInteraction) {

// In-case users will try things
const users = await Promise.all((interaction.options.get("users", true).value as string)
.replace(/\s/g, "") // Remove space incase user adds it as a seperator
.split(",") // Get a list from it
.filter((user) => user !== "") // anti seperator spams at the end lmao
.map(async user => await this.client.users.fetch(user))); // Convert it to discord users objects

// Additional checks
if(users.length == 0) return await interaction.reply({ content: "You need to specify at least one user", ephemeral: true });
if(users.length > 25) return await interaction.reply({ content: "You can't add more than 25 users", ephemeral: true });

const ticket = await this.client.prisma.tickets.findUnique({
select: {
id: true,
invited: true,
},
where: {
channelid: interaction.channel?.id
}
});

if (!ticket) return interaction.reply({ content: "Ticket not found", ephemeral: true }).catch((e) => console.log(e));

const invited = JSON.parse(ticket.invited) as string[];

for(const user of users) {
if (invited.includes(user.id))
continue;

if (invited.length >= 25)
break;

invited.push(user.id);

await (interaction.channel as TextChannel | null)?.permissionOverwrites
.edit(user, {
SendMessages: true,
AddReactions: true,
ReadMessageHistory: true,
AttachFiles: true,
ViewChannel: true,
});
log(
{
LogType: "userAdded",
user: interaction.user,
ticketId: ticket.id.toString(),
ticketChannelId: interaction.channel?.id,
target: user,
},
this.client
);
}

await this.client.prisma.tickets.update({
data: {
invited: JSON.stringify(invited)
},
where: {
channelid: interaction.channel?.id
}
});

await interaction.reply({ content: "> Mass User Add Completed! Do note that not all users may be added if internal checks failed. It's advise you use the regular add command to guarantee the add status." });
}
}
8 changes: 6 additions & 2 deletions src/commands/rename.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { CommandInteraction, GuildMember, SlashCommandBuilder, TextChannel } from "discord.js";
import {BaseCommand, ExtendedClient} from "../structure";
import {BaseCommand, ExtendedClient, TicketType} from "../structure";

/*
Copyright 2023 Sayrix (github.com/Sayrix)
Expand All @@ -23,8 +23,12 @@ export default class RenameCommand extends BaseCommand {
channelid: interaction.channel?.id
}
});
// @TODO: Breaking change refactor happens here as well..
const ticketType = ticket ? JSON.parse(ticket.category) as TicketType : undefined;

if (!ticket) return interaction.reply({ content: "Ticket not found", ephemeral: true }).catch((e) => console.log(e));
if (!(interaction.member as GuildMember | null)?.roles.cache.some((r) => this.client.config.rolesWhoHaveAccessToTheTickets.includes(r.id)))
if (!(interaction.member as GuildMember | null)?.roles.cache.some((r) => this.client.config.rolesWhoHaveAccessToTheTickets.includes(r.id) ||
ticketType?.staffRoles?.includes(r.id)))
return interaction
.reply({
content: this.client.locales.getValue("ticketOnlyRenamableByStaff"),
Expand Down
7 changes: 5 additions & 2 deletions src/structure/ExtendedClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {BaseCommand, ConfigType} from "./";
import {PrismaClient} from "@prisma/client";
import fs from "fs-extra";
import path from "node:path";
import {AddCommand, ClaimCommand, CloseCommand, RemoveCommand, RenameCommand} from "../commands";
import {AddCommand, MassAddCommand, ClaimCommand, CloseCommand, RemoveCommand, RenameCommand, clearDM} from "../commands";
import {InteractionCreateEvent, ReadyEvent} from "../events";
import {jsonc} from "jsonc";
import {REST} from "@discordjs/rest";
Expand All @@ -24,10 +24,12 @@ export default class ExtendedClient extends Client {
this.locales = new Translation(this.config.lang, path.join(__dirname, "../../locales/"));
this.commands = new Collection([
[AddCommand.data.name, new AddCommand(this)],
[MassAddCommand.data.name, new MassAddCommand(this)],
[ClaimCommand.data.name, new ClaimCommand(this)],
[CloseCommand.data.name, new CloseCommand(this)],
[RemoveCommand.data.name, new RemoveCommand(this)],
[RenameCommand.data.name, new RenameCommand(this)],
[clearDM.data.name, new clearDM(this)],
]);
this.loadEvents();

Expand Down Expand Up @@ -66,7 +68,8 @@ export default class ExtendedClient extends Client {
ClaimCommand.data.toJSON(),
CloseCommand.data.toJSON(),
RemoveCommand.data.toJSON(),
RenameCommand.data.toJSON()
RenameCommand.data.toJSON(),
clearDM.data.toJSON(),
];

const { guildId } = jsonc.parse(fs.readFileSync(path.join(__dirname, "../../config/config.jsonc"), "utf8"));
Expand Down
1 change: 1 addition & 0 deletions src/structure/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,4 +161,5 @@ export type TicketType = {
cantAccess: string[];
askQuestions: boolean;
questions: TicketQuestionType[];
staffRoles?: string[];
}
7 changes: 5 additions & 2 deletions src/utils/claim.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ please check https://creativecommons.org/licenses/by/4.0 for more informations.

import { APIButtonComponent, ActionRowBuilder, ButtonBuilder, ButtonInteraction, ChannelType, CommandInteraction, EmbedBuilder, GuildMember, TextChannel } from "discord.js";
import { log } from "./logs";
import {ExtendedClient} from "../structure";
import {ExtendedClient, TicketType} from "../structure";

export const claim = async(interaction: ButtonInteraction | CommandInteraction, client: ExtendedClient) => {
let ticket = await client.prisma.tickets.findUnique({
Expand All @@ -23,7 +23,10 @@ export const claim = async(interaction: ButtonInteraction | CommandInteraction,
ephemeral: true,
});

const canClaim = (interaction.member as GuildMember | null)?.roles.cache.some((r) => client.config.rolesWhoHaveAccessToTheTickets.includes(r.id));
// @TODO: Breaking change refactor happens here as well..
const ticketType = ticket ? JSON.parse(ticket.category) as TicketType : undefined;
const canClaim = (interaction.member as GuildMember | null)?.roles.cache.some((r) => client.config.rolesWhoHaveAccessToTheTickets.includes(r.id) ||
ticketType?.staffRoles?.includes(r.id));

if (!canClaim)
return interaction
Expand Down
8 changes: 6 additions & 2 deletions src/utils/close.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import zlib from "zlib";
import axios from "axios";
import { ActionRowBuilder, ButtonBuilder, ButtonInteraction, ButtonStyle, Collection, ColorResolvable, CommandInteraction, ComponentType, EmbedBuilder, GuildMember, Message, ModalSubmitInteraction, TextChannel } from "discord.js";
import { log } from "./logs";
import {ExtendedClient} from "../structure";
import {ExtendedClient, TicketType} from "../structure";
let domain = "https://ticket.pm/";

/*
Expand Down Expand Up @@ -41,9 +41,13 @@ export async function close(interaction: ButtonInteraction | CommandInteraction
const ticketClosed = ticket?.closedat && ticket.closedby;
if (!ticket) return interaction.editReply({ content: "Ticket not found" }).catch((e) => console.log(e));

// @TODO: Breaking change refactor happens here as well..
const ticketType = ticket ? JSON.parse(ticket.category) as TicketType : undefined;

if (
client.config.closeOption.whoCanCloseTicket === "STAFFONLY" &&
!(interaction.member as GuildMember | null)?.roles.cache.some((r) => client.config.rolesWhoHaveAccessToTheTickets.includes(r.id))
!(interaction.member as GuildMember | null)?.roles.cache.some((r) => client.config.rolesWhoHaveAccessToTheTickets.includes(r.id) ||
ticketType?.staffRoles?.includes(r.id))
)
return interaction
.editReply({
Expand Down
14 changes: 12 additions & 2 deletions src/utils/close_askReason.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,22 @@ please check https://creativecommons.org/licenses/by/4.0 for more informations.
*/

import { ActionRowBuilder, ButtonInteraction, CommandInteraction, GuildMember, ModalBuilder, TextInputBuilder, TextInputStyle } from "discord.js";
import {ExtendedClient} from "../structure";
import {ExtendedClient, TicketType} from "../structure";

export const closeAskReason = async(interaction: CommandInteraction | ButtonInteraction, client: ExtendedClient) => {

// @TODO: Breaking change refactor happens here as well..
const ticket = await client.prisma.tickets.findUnique({
where: {
channelid: interaction.channel?.id
}
});
const ticketType = ticket ? JSON.parse(ticket.category) as TicketType : undefined;

if (
client.config.closeOption.whoCanCloseTicket === "STAFFONLY" &&
!(interaction.member as GuildMember | null)?.roles.cache.some((r) => client.config.rolesWhoHaveAccessToTheTickets.includes(r.id))
!(interaction.member as GuildMember | null)?.roles.cache.some((r) => client.config.rolesWhoHaveAccessToTheTickets.includes(r.id) ||
ticketType?.staffRoles?.includes(r.id))
)
return interaction
.reply({
Expand Down
14 changes: 8 additions & 6 deletions src/utils/createTicket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,20 +84,21 @@ export const createTicket = async (interaction: StringSelectMenuInteraction | Mo
ViewChannel: true,
})
.catch((e) => console.log(e));

if (client.config.rolesWhoHaveAccessToTheTickets.length > 0) {
for (const role of client.config.rolesWhoHaveAccessToTheTickets) {

// Role Access Stuff
if (client.config.rolesWhoHaveAccessToTheTickets.length > 0 || (ticketType.staffRoles?.length ?? 0) > 0) {
for (const role of [...client.config.rolesWhoHaveAccessToTheTickets, ...(ticketType.staffRoles ?? [])])
await channel.permissionOverwrites
.edit(role, {
SendMessages: true,
AddReactions: true,
ReadMessageHistory: true,
AttachFiles: true,
ViewChannel: true,
})
.catch((e) => console.log(e));
}
});
}


const footer = locale.getSubValue("embeds", "ticketOpened", "footer", "text").replace("ticket.pm", "");
if(ticketType.color?.toString().trim() === "") ticketType.color = undefined;
const ticketOpenedEmbed = new EmbedBuilder({
Expand Down Expand Up @@ -190,6 +191,7 @@ export const createTicket = async (interaction: StringSelectMenuInteraction | Mo
.then((msg) => {
client.prisma.tickets.create({
data: {
// @TODO: When releasing a new breaking version, store only the codeName for the category...
category: JSON.stringify(ticketType),
reason: allReasons,
creator: interaction.user.id,
Expand Down
6 changes: 3 additions & 3 deletions src/utils/translation.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*
Copyright © 2023 小兽兽/zhiyan114 (github.com/zhiyan114)
File is licensed respectively under the terms of the Apache License 2.0
or whichever license the project is using at the time https://github.com/Sayrix/Ticket-Bot/blob/main/LICENSE
Copyright © 2024 小兽兽/zhiyan114 (github.com/zhiyan114)
File is licensed respectively under the terms of the Creative Commons Attribution 4.0 International
or whichever license the project is using at the time https://github.com/Sayrix/Ticket-Bot/blob/main/LICENSE.md
*/

import path from "node:path";
Expand Down

0 comments on commit a2712af

Please sign in to comment.