mirror of
https://github.com/Hessenuk/DiscordTickets.git
synced 2024-11-17 09:23:07 +02:00
Add message and ticket referencing, fixes
This commit is contained in:
parent
f6666b103e
commit
c64b18a397
@ -8,5 +8,37 @@ module.exports = class ReferencesCompleter extends Autocompleter {
|
||||
});
|
||||
}
|
||||
|
||||
async run(value, comamnd, interaction) { }
|
||||
/**
|
||||
* @param {string} value
|
||||
* @param {*} comamnd
|
||||
* @param {import("discord.js").AutocompleteInteraction} interaction
|
||||
*/
|
||||
async run(value, comamnd, interaction) {
|
||||
/** @type {import("client")} */
|
||||
const client = this.client;
|
||||
const settings = await client.prisma.guild.findUnique({ where: { id: interaction.guild.id } });
|
||||
const tickets = await client.prisma.ticket.findMany({
|
||||
where: {
|
||||
createdById: interaction.user.id,
|
||||
guildId: interaction.guild.id,
|
||||
open: false,
|
||||
},
|
||||
});
|
||||
const options = value ? tickets.filter(t =>
|
||||
String(t.number).match(new RegExp(value, 'i')) ||
|
||||
t.topic?.match(new RegExp(value, 'i')) ||
|
||||
new Date(t.createdAt).toLocaleString(settings.locale, { dateStyle: 'short' })?.match(new RegExp(value, 'i')),
|
||||
) : tickets;
|
||||
await interaction.respond(
|
||||
options
|
||||
.slice(0, 25)
|
||||
.map(t => {
|
||||
const date = new Date(t.createdAt).toLocaleString(settings.locale, { dateStyle: 'short' });
|
||||
return {
|
||||
name: `#${t.number} - ${date} ${t.topic ? '| ' + t.topic.substring(0, 50) : ''}`,
|
||||
value: t.id,
|
||||
};
|
||||
}),
|
||||
);
|
||||
}
|
||||
};
|
@ -1,4 +1,5 @@
|
||||
const { MessageCommand } = require('@eartharoid/dbf');
|
||||
const { useGuild } = require('../../lib/tickets/utils');
|
||||
|
||||
module.exports = class CreateMessageCommand extends MessageCommand {
|
||||
constructor(client, options) {
|
||||
@ -13,7 +14,11 @@ module.exports = class CreateMessageCommand extends MessageCommand {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import("discord.js").MessageContextMenuCommandInteraction} interaction
|
||||
*/
|
||||
async run(interaction) {
|
||||
// TODO: archive message
|
||||
await useGuild(this.client, interaction, { referencesMessage: interaction.targetMessage.channelId + '/' + interaction.targetId });
|
||||
}
|
||||
};
|
@ -1,5 +1,6 @@
|
||||
const { SlashCommand } = require('@eartharoid/dbf');
|
||||
const { ApplicationCommandOptionType } = require('discord.js');
|
||||
const { useGuild } = require('../../lib/tickets/utils');
|
||||
|
||||
module.exports = class NewSlashCommand extends SlashCommand {
|
||||
constructor(client, options) {
|
||||
@ -14,7 +15,7 @@ module.exports = class NewSlashCommand extends SlashCommand {
|
||||
autocomplete: true,
|
||||
name: 'references',
|
||||
required: false,
|
||||
type: ApplicationCommandOptionType.Integer,
|
||||
type: ApplicationCommandOptionType.String,
|
||||
},
|
||||
];
|
||||
opts = opts.map(o => {
|
||||
@ -43,5 +44,12 @@ module.exports = class NewSlashCommand extends SlashCommand {
|
||||
});
|
||||
}
|
||||
|
||||
async run(interaction) { }
|
||||
/**
|
||||
*
|
||||
* @param {import("discord.js").ChatInputCommandInteraction} interaction
|
||||
*/
|
||||
async run(interaction) {
|
||||
await useGuild(this.client, interaction, { referencesTicketId: interaction.options.getString('references', false) });
|
||||
|
||||
}
|
||||
};
|
@ -194,7 +194,9 @@ misc:
|
||||
member_limit:
|
||||
description:
|
||||
- Please use your existing ticket or close it before creating another.
|
||||
- Please close a ticket before creating another.
|
||||
- |
|
||||
Please close a ticket before creating another.
|
||||
Use `/tickets` to view your existing tickets.
|
||||
title:
|
||||
- ❌ You already have a ticket
|
||||
- ❌ You already have %d open tickets
|
||||
@ -228,3 +230,13 @@ ticket:
|
||||
{creator} has created a new ticket
|
||||
fields:
|
||||
topic: Topic
|
||||
references_message:
|
||||
description: 'References [a message]({url}) sent {timestamp} by {author}.'
|
||||
title: ℹ️ Reference
|
||||
references_ticket:
|
||||
description: 'This ticket is related to a previous ticket:'
|
||||
fields:
|
||||
date: Created at
|
||||
number: Number
|
||||
topic: Topic
|
||||
title: ℹ️ Reference
|
||||
|
11
src/lib/tickets/archiver.js
Normal file
11
src/lib/tickets/archiver.js
Normal file
@ -0,0 +1,11 @@
|
||||
const Cryptr = require('cryptr');
|
||||
const cryptr = new Cryptr(process.env.ENCRYPTION_KEY);
|
||||
|
||||
module.exports = class TicketArchiver {
|
||||
constructor(client) {
|
||||
/** @type {import("client")} */
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
async addMessage() {}
|
||||
};
|
@ -1,4 +1,5 @@
|
||||
/* eslint-disable max-lines */
|
||||
const TicketArchiver = require('./archiver');
|
||||
const {
|
||||
ActionRowBuilder,
|
||||
ButtonBuilder,
|
||||
@ -24,7 +25,7 @@ module.exports = class TicketManager {
|
||||
constructor(client) {
|
||||
/** @type {import("client")} */
|
||||
this.client = client;
|
||||
|
||||
this.archiver = new TicketArchiver(client);
|
||||
this.$ = { categories: {} };
|
||||
}
|
||||
|
||||
@ -92,7 +93,7 @@ module.exports = class TicketManager {
|
||||
* @param {string?} [data.topic]
|
||||
*/
|
||||
async create({
|
||||
categoryId, interaction, topic, referencesMessage, referencesTicket,
|
||||
categoryId, interaction, topic, referencesMessage, referencesTicketId,
|
||||
}) {
|
||||
categoryId = Number(categoryId);
|
||||
const category = await this.getCategory(categoryId);
|
||||
@ -122,6 +123,9 @@ module.exports = class TicketManager {
|
||||
});
|
||||
}
|
||||
|
||||
/** @type {import("discord.js").Guild} */
|
||||
const guild = this.client.guilds.cache.get(category.guild.id);
|
||||
const member = interaction.member ?? await guild.members.fetch(interaction.user.id);
|
||||
const getMessage = this.client.i18n.getLocale(category.guild.locale);
|
||||
|
||||
const rlKey = `ratelimits/guild-user:${category.guildId}-${interaction.user.id}`;
|
||||
@ -130,7 +134,7 @@ module.exports = class TicketManager {
|
||||
return await interaction.reply({
|
||||
embeds: [
|
||||
new ExtendedEmbedBuilder({
|
||||
iconURL: interaction.guild.iconURL(),
|
||||
iconURL: guild.iconURL(),
|
||||
text: category.guild.footer,
|
||||
})
|
||||
.setColor(category.guild.errorColour)
|
||||
@ -146,7 +150,7 @@ module.exports = class TicketManager {
|
||||
const sendError = name => interaction.reply({
|
||||
embeds: [
|
||||
new ExtendedEmbedBuilder({
|
||||
iconURL: interaction.guild.iconURL(),
|
||||
iconURL: guild.iconURL(),
|
||||
text: category.guild.footer,
|
||||
})
|
||||
.setColor(category.guild.errorColour)
|
||||
@ -156,10 +160,6 @@ module.exports = class TicketManager {
|
||||
ephemeral: true,
|
||||
});
|
||||
|
||||
/** @type {import("discord.js").Guild} */
|
||||
const guild = this.client.guilds.cache.get(category.guild.id);
|
||||
const member = interaction.member ?? await guild.members.fetch(interaction.user.id);
|
||||
|
||||
if (category.guild.blocklist.length !== 0) {
|
||||
const blocked = category.guild.blocklist.some(r => member.roles.cache.has(r));
|
||||
if (blocked) return await sendError('blocked');
|
||||
@ -181,7 +181,7 @@ module.exports = class TicketManager {
|
||||
return await interaction.reply({
|
||||
embeds: [
|
||||
new ExtendedEmbedBuilder({
|
||||
iconURL: interaction.guild.iconURL(),
|
||||
iconURL: guild.iconURL(),
|
||||
text: category.guild.footer,
|
||||
})
|
||||
.setColor(category.guild.errorColour)
|
||||
@ -206,7 +206,7 @@ module.exports = class TicketManager {
|
||||
// return await interaction.reply({
|
||||
// embeds: [
|
||||
// new ExtendedEmbedBuilder({
|
||||
// iconURL: interaction.guild.iconURL(),
|
||||
// iconURL: guild.iconURL(),
|
||||
// text: category.guild.footer,
|
||||
// })
|
||||
// .setColor(category.guild.errorColour)
|
||||
@ -222,7 +222,7 @@ module.exports = class TicketManager {
|
||||
return await interaction.reply({
|
||||
embeds: [
|
||||
new ExtendedEmbedBuilder({
|
||||
iconURL: interaction.guild.iconURL(),
|
||||
iconURL: guild.iconURL(),
|
||||
text: category.guild.footer,
|
||||
})
|
||||
.setColor(category.guild.errorColour)
|
||||
@ -240,7 +240,7 @@ module.exports = class TicketManager {
|
||||
action: 'questions',
|
||||
categoryId,
|
||||
referencesMessage,
|
||||
referencesTicket,
|
||||
referencesTicketId,
|
||||
}))
|
||||
.setTitle(category.name)
|
||||
.setComponents(
|
||||
@ -290,7 +290,7 @@ module.exports = class TicketManager {
|
||||
action: 'topic',
|
||||
categoryId,
|
||||
referencesMessage,
|
||||
referencesTicket,
|
||||
referencesTicketId,
|
||||
}))
|
||||
.setTitle(category.name)
|
||||
.setComponents(
|
||||
@ -311,6 +311,8 @@ module.exports = class TicketManager {
|
||||
await this.postQuestions({
|
||||
categoryId,
|
||||
interaction,
|
||||
referencesMessage,
|
||||
referencesTicketId,
|
||||
topic,
|
||||
});
|
||||
}
|
||||
@ -323,7 +325,7 @@ module.exports = class TicketManager {
|
||||
* @param {string?} [data.topic]
|
||||
*/
|
||||
async postQuestions({
|
||||
action, categoryId, interaction, topic, referencesMessage, referencesTicket,
|
||||
action, categoryId, interaction, topic, referencesMessage, referencesTicketId,
|
||||
}) {
|
||||
await interaction.deferReply({ ephemeral: true });
|
||||
|
||||
@ -480,6 +482,69 @@ module.exports = class TicketManager {
|
||||
|
||||
// TODO: referenced msg or ticket
|
||||
|
||||
if (referencesMessage) {
|
||||
referencesMessage = referencesMessage.split('/');
|
||||
/** @type {import("discord.js").Message} */
|
||||
const message = await (await this.client.channels.fetch(referencesMessage[0]))?.messages.fetch(referencesMessage[1]);
|
||||
if (message) {
|
||||
await channel.send({
|
||||
embeds: [
|
||||
new ExtendedEmbedBuilder()
|
||||
.setColor(category.guild.primaryColour)
|
||||
.setTitle(getMessage('ticket.references_message.title'))
|
||||
.setDescription(
|
||||
getMessage('ticket.references_message.description', {
|
||||
author: message.author.toString(),
|
||||
timestamp: `<t:${Math.ceil(message.createdTimestamp / 1000)}:R>`,
|
||||
url: message.url,
|
||||
})),
|
||||
new ExtendedEmbedBuilder({
|
||||
iconURL: guild.iconURL(),
|
||||
text: category.guild.footer,
|
||||
})
|
||||
.setColor(category.guild.primaryColour)
|
||||
.setAuthor({
|
||||
iconURL: message.member?.displayAvatarURL(),
|
||||
name: message.member?.displayName || 'Unknown',
|
||||
})
|
||||
.setDescription(message.content.substring(0, 1000) + message.content.length > 1000 ? '...' : ''),
|
||||
],
|
||||
});
|
||||
}
|
||||
} else if (referencesTicketId) {
|
||||
// TODO: add portal url
|
||||
const ticket = await this.client.prisma.ticket.findUnique({ where: { id: referencesTicketId } });
|
||||
if (ticket) {
|
||||
const embed = new ExtendedEmbedBuilder({
|
||||
iconURL: guild.iconURL(),
|
||||
text: category.guild.footer,
|
||||
})
|
||||
.setColor(category.guild.primaryColour)
|
||||
.setTitle(getMessage('ticket.references_ticket.title'))
|
||||
.setDescription(getMessage('ticket.references_ticket.description'))
|
||||
.setFields([
|
||||
{
|
||||
inline: true,
|
||||
name: getMessage('ticket.references_ticket.fields.number'),
|
||||
value: inlineCode(ticket.number),
|
||||
},
|
||||
{
|
||||
inline: true,
|
||||
name: getMessage('ticket.references_ticket.fields.date'),
|
||||
value: `<t:${Math.ceil(ticket.createdAt / 1000)}:f>`,
|
||||
},
|
||||
]);
|
||||
if (ticket.topic) {
|
||||
embed.addFields({
|
||||
inline: false,
|
||||
name: getMessage('ticket.references_ticket.fields.topic'),
|
||||
value: ticket.topic,
|
||||
});
|
||||
}
|
||||
await channel.send({ embeds: [embed] });
|
||||
}
|
||||
}
|
||||
|
||||
const data = {
|
||||
category: { connect: { id: categoryId } },
|
||||
createdBy: {
|
||||
@ -494,10 +559,10 @@ module.exports = class TicketManager {
|
||||
openingMessageId: sent.id,
|
||||
topic,
|
||||
};
|
||||
if (referencesTicket) data.referencesTicket = { connect: { id: referencesTicket } };
|
||||
if (referencesTicketId) data.referencesTicket = { connect: { id: referencesTicketId } };
|
||||
let message;
|
||||
if (referencesMessage) message = this.client.prisma.archivedMessage.findUnique({ where: { id: referencesMessage } });
|
||||
if (message) data.referencesMessage = { connect: { id: referencesMessage } }; // only add if the message has been archived ^^
|
||||
if (referencesMessage) message = await this.client.prisma.archivedMessage.findUnique({ where: { id: referencesMessage[1] } });
|
||||
if (message) data.referencesMessage = { connect: { id: referencesMessage[0] } }; // only add if the message has been archived ^^
|
||||
if (answers) data.questionAnswers = { createMany: { data: answers } };
|
||||
await interaction.editReply({
|
||||
components: [],
|
||||
|
77
src/lib/tickets/utils.js
Normal file
77
src/lib/tickets/utils.js
Normal file
@ -0,0 +1,77 @@
|
||||
const {
|
||||
ActionRowBuilder,
|
||||
EmbedBuilder,
|
||||
SelectMenuBuilder,
|
||||
SelectMenuOptionBuilder,
|
||||
} = require('discord.js');
|
||||
const emoji = require('node-emoji');
|
||||
|
||||
module.exports = {
|
||||
/**
|
||||
* @param {import("client")} client
|
||||
* @param {import("discord.js").ButtonInteraction|import("discord.js").SelectMenuInteraction} interaction
|
||||
*/
|
||||
async useGuild(client, interaction, {
|
||||
referencesMessage,
|
||||
referencesTicketId,
|
||||
topic,
|
||||
}) {
|
||||
const settings = await client.prisma.guild.findUnique({
|
||||
select: {
|
||||
categories: true,
|
||||
errorColour: true,
|
||||
locale: true,
|
||||
primaryColour: true,
|
||||
},
|
||||
where: { id: interaction.guild.id },
|
||||
});
|
||||
const getMessage = client.i18n.getLocale(settings.locale);
|
||||
if (settings.categories.length === 0) {
|
||||
interaction.reply({
|
||||
components: [],
|
||||
embeds: [
|
||||
new EmbedBuilder()
|
||||
.setColor(settings.errorColour)
|
||||
.setTitle(getMessage('misc.no_categories.title'))
|
||||
.setDescription(getMessage('misc.no_categories.description')),
|
||||
],
|
||||
ephemeral: true,
|
||||
});
|
||||
} else if (settings.categories.length === 1) {
|
||||
await client.tickets.create({
|
||||
categoryId: settings.categories[0].id,
|
||||
interaction,
|
||||
referencesMessage,
|
||||
referencesTicketId,
|
||||
topic,
|
||||
});
|
||||
} else {
|
||||
await interaction.reply({
|
||||
components: [
|
||||
new ActionRowBuilder()
|
||||
.setComponents(
|
||||
new SelectMenuBuilder()
|
||||
.setCustomId(JSON.stringify({
|
||||
action: 'create',
|
||||
referencesMessage,
|
||||
referencesTicketId,
|
||||
topic,
|
||||
}))
|
||||
.setPlaceholder(getMessage('menus.category.placeholder'))
|
||||
.setOptions(
|
||||
settings.categories.map(category =>
|
||||
new SelectMenuOptionBuilder()
|
||||
.setValue(String(category.id))
|
||||
.setLabel(category.name)
|
||||
.setDescription(category.description)
|
||||
.setEmoji(emoji.hasEmoji(category.emoji) ? emoji.get(category.emoji) : { id: category.emoji }),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
ephemeral: true,
|
||||
});
|
||||
}
|
||||
|
||||
},
|
||||
};
|
@ -23,13 +23,13 @@ module.exports = class extends Listener {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} guildId
|
||||
* @param {import('@prisma/client').Guild} settings
|
||||
* @param {import("discord.js").ButtonInteraction|import("discord.js").SelectMenuInteraction} interaction
|
||||
*/
|
||||
async useGuild(settings, interaction, topic) {
|
||||
const getMessage = this.client.i18n.getLocale(settings.locale);
|
||||
if (settings.categories.length === 0) {
|
||||
interaction.editReply({
|
||||
interaction.update({
|
||||
components: [],
|
||||
embeds: [
|
||||
new EmbedBuilder()
|
||||
@ -45,7 +45,7 @@ module.exports = class extends Listener {
|
||||
topic,
|
||||
});
|
||||
} else {
|
||||
const sent = await interaction.editReply({
|
||||
await interaction.update({
|
||||
components: [
|
||||
new ActionRowBuilder()
|
||||
.setComponents(
|
||||
@ -67,17 +67,17 @@ module.exports = class extends Listener {
|
||||
),
|
||||
],
|
||||
});
|
||||
sent.awaitMessageComponent({
|
||||
interaction.message.awaitMessageComponent({
|
||||
componentType: ComponentType.SelectMenu,
|
||||
filter: () => true,
|
||||
time: ms('30s'),
|
||||
})
|
||||
.then(async () => {
|
||||
await sent.delete();
|
||||
interaction.message.delete();
|
||||
})
|
||||
.catch(error => {
|
||||
if (error) this.client.log.error(error);
|
||||
sent.delete();
|
||||
interaction.message.delete();
|
||||
});
|
||||
}
|
||||
|
||||
@ -92,7 +92,7 @@ module.exports = class extends Listener {
|
||||
|
||||
if (message.channel.type === ChannelType.DM) {
|
||||
if (message.author.bot) return false;
|
||||
const commonGuilds = await getCommonGuilds(this.client, message.author.id);
|
||||
const commonGuilds = await getCommonGuilds(client, message.author.id);
|
||||
if (commonGuilds.size === 0) {
|
||||
return false;
|
||||
} else if (commonGuilds.size === 1) {
|
||||
@ -105,7 +105,7 @@ module.exports = class extends Listener {
|
||||
},
|
||||
where: { id: commonGuilds.at(0).id },
|
||||
});
|
||||
const getMessage = this.client.i18n.getLocale(settings.locale);
|
||||
const getMessage = client.i18n.getLocale(settings.locale);
|
||||
const sent = await message.reply({
|
||||
components: [
|
||||
new ActionRowBuilder()
|
||||
@ -126,16 +126,16 @@ module.exports = class extends Listener {
|
||||
});
|
||||
sent.awaitMessageComponent({
|
||||
componentType: ComponentType.Button,
|
||||
filter: interaction => interaction.deferUpdate(),
|
||||
filter: () => true,
|
||||
time: ms('30s'),
|
||||
})
|
||||
.then(async interaction => await this.useGuild(settings, interaction, message.content))
|
||||
.catch(error => {
|
||||
if (error) this.client.log.error(error);
|
||||
if (error) client.log.error(error);
|
||||
sent.delete();
|
||||
});
|
||||
} else {
|
||||
const getMessage = this.client.i18n.getLocale();
|
||||
const getMessage = client.i18n.getLocale();
|
||||
const sent = await message.reply({
|
||||
components: [
|
||||
new ActionRowBuilder()
|
||||
@ -156,7 +156,7 @@ module.exports = class extends Listener {
|
||||
});
|
||||
sent.awaitMessageComponent({
|
||||
componentType: ComponentType.SelectMenu,
|
||||
filter: interaction => interaction.deferUpdate(),
|
||||
filter: () => true,
|
||||
time: ms('30s'),
|
||||
})
|
||||
.then(async interaction => {
|
||||
@ -172,7 +172,7 @@ module.exports = class extends Listener {
|
||||
await this.useGuild(settings, interaction, message.content);
|
||||
})
|
||||
.catch(error => {
|
||||
if (error) this.client.log.error(error);
|
||||
if (error) client.log.error(error);
|
||||
sent.delete();
|
||||
});
|
||||
}
|
||||
|
@ -25,11 +25,16 @@ module.exports = class extends Listener {
|
||||
cooldown: true,
|
||||
id: true,
|
||||
tickets: {
|
||||
select: { createdById: true },
|
||||
select: {
|
||||
createdById: true,
|
||||
guildId: true,
|
||||
id: true,
|
||||
},
|
||||
where: { open: true },
|
||||
},
|
||||
},
|
||||
});
|
||||
let deleted = 0;
|
||||
let ticketCount = 0;
|
||||
let cooldowns = 0;
|
||||
for (const category of categories) {
|
||||
@ -38,6 +43,13 @@ module.exports = class extends Listener {
|
||||
for (const ticket of category.tickets) {
|
||||
if (client.tickets.$.categories[category.id][ticket.createdById]) client.tickets.$.categories[category.id][ticket.createdById]++;
|
||||
else client.tickets.$.categories[category.id][ticket.createdById] = 1;
|
||||
/** @type {import("discord.js").Guild} */
|
||||
const guild = client.guilds.cache.get(ticket.guildId);
|
||||
if (guild && guild.available && !client.channels.cache.has(ticket.id)) {
|
||||
deleted += 0;
|
||||
await client.tickets.close(ticket.id);
|
||||
}
|
||||
|
||||
}
|
||||
if (category.cooldown) {
|
||||
const recent = await client.prisma.ticket.findMany({
|
||||
@ -63,6 +75,7 @@ module.exports = class extends Listener {
|
||||
// const ticketCount = categories.reduce((total, category) => total + category.tickets.length, 0);
|
||||
client.log.info(`Cached ticket count of ${categories.length} categories (${ticketCount} open tickets)`);
|
||||
client.log.info(`Loaded ${cooldowns} active cooldowns`);
|
||||
client.log.info(`Closed ${deleted} deleted tickets`);
|
||||
|
||||
// presence/activity
|
||||
let next = 0;
|
||||
|
@ -1,4 +1,5 @@
|
||||
const { Menu } = require('@eartharoid/dbf');
|
||||
const { MessageFlags } = require('discord.js');
|
||||
|
||||
module.exports = class CreateMenu extends Menu {
|
||||
constructor(client, options) {
|
||||
@ -13,11 +14,11 @@ module.exports = class CreateMenu extends Menu {
|
||||
* @param {import("discord.js").SelectMenuInteraction} interaction
|
||||
*/
|
||||
async run(id, interaction) {
|
||||
interaction.message.edit({ components: interaction.message.components }); // reset the select menu (minor client-side UI issue)
|
||||
if (!interaction.message.flags.has(MessageFlags.Ephemeral)) interaction.message.edit({ components: interaction.message.components }); // reset the select menu (minor client-side UI issue)
|
||||
await this.client.tickets.create({
|
||||
...id,
|
||||
categoryId: interaction.values[0],
|
||||
interaction,
|
||||
topic: id.topic,
|
||||
});
|
||||
}
|
||||
};
|
Loading…
Reference in New Issue
Block a user