mirror of
https://github.com/toast-ts/Daggerbot-TS.git
synced 2024-11-17 20:30:58 -05:00
Compare commits
4 Commits
d0d65c2986
...
137a0ae7b8
Author | SHA1 | Date | |
---|---|---|---|
|
137a0ae7b8 | ||
|
ab6c6de9c1 | ||
|
2a7c243c16 | ||
|
f72c17746a |
@ -19,7 +19,7 @@ export default class Case {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
static run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){
|
static run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){
|
||||||
if (!MessageTool.isStaff(interaction.member)) return MessageTool.youNeedRole(interaction, 'dcmod');
|
if (!MessageTool.isModerator(interaction.member)) return MessageTool.youNeedRole(interaction, 'dcmod');
|
||||||
const caseId = interaction.options.getInteger('id');
|
const caseId = interaction.options.getInteger('id');
|
||||||
({
|
({
|
||||||
update: async()=>{
|
update: async()=>{
|
||||||
@ -49,14 +49,14 @@ export default class Case {
|
|||||||
const user = (interaction.options.getUser('user') as Discord.User);
|
const user = (interaction.options.getUser('user') as Discord.User);
|
||||||
if (user.bot) return interaction.reply(`**${user.username}**'s punishment history cannot be viewed as they are a bot.`)
|
if (user.bot) return interaction.reply(`**${user.username}**'s punishment history cannot be viewed as they are a bot.`)
|
||||||
const punishments = await client.punishments.getAllCases();
|
const punishments = await client.punishments.getAllCases();
|
||||||
const userPunishmentData = punishments.filter(x=>x.dataValues.member === user.id);
|
const userPunishmentData = punishments.filter(x=>x.dataValues.member === user.id).sort((a,b)=>b.dataValues.time-a.dataValues.time);
|
||||||
const userPunishment = userPunishmentData.sort((a,b)=>a.dataValues.time-b.dataValues.time).map(punishment=>{
|
const userPunishment = userPunishmentData.map(punishment=>{
|
||||||
return {
|
return {
|
||||||
name: `${punishment.dataValues.type[0].toUpperCase()+punishment.dataValues.type.slice(1)} | Case #${punishment.dataValues.case_id}`,
|
name: `${punishment.dataValues.type[0].toUpperCase()+punishment.dataValues.type.slice(1)} | Case #${punishment.dataValues.case_id}`,
|
||||||
value: `Reason: \`${punishment.dataValues.reason}\`\n${punishment.dataValues.duration ? `Duration: ${Formatters.timeFormat(punishment.dataValues.duration, 3)}\n` : ''}Moderator: ${MessageTool.formatMention(punishment.dataValues.moderator, 'user')}${punishment.dataValues.expired ? `\nOverwritten by Case #${punishments.find(x=>x.dataValues.cancels===punishment.dataValues.case_id)?.case_id}` : ''}${punishment.dataValues.cancels ? `\nOverwrites Case #${punishment.dataValues.cancels}` : ''}`
|
value: `Reason: \`${punishment.dataValues.reason}\`\n${punishment.dataValues.duration ? `Duration: ${Formatters.timeFormat(punishment.dataValues.duration, 3)}\n` : ''}Moderator: ${MessageTool.formatMention(punishment.dataValues.moderator, 'user')}${punishment.dataValues.expired ? `\nOverwritten by Case #${punishments.find(x=>x.dataValues.cancels===punishment.dataValues.case_id)?.case_id}` : ''}${punishment.dataValues.cancels ? `\nOverwrites Case #${punishment.dataValues.cancels}` : ''}`
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (!punishments || !userPunishment) return interaction.reply(`**${user.username}** has a clean record.`)
|
if (!userPunishment.length) return interaction.reply(`**${user.username}** has a clean record.`)
|
||||||
const pageNum = interaction.options.getInteger('page') ?? 1;
|
const pageNum = interaction.options.getInteger('page') ?? 1;
|
||||||
return interaction.reply({embeds: [new client.embed().setColor(client.config.embedColor).setTitle(`${user.username}'s punishment history`).setDescription(`**ID:** \`${user.id}\``).setFooter({text: `${userPunishment.length} total punishments. Viewing page ${pageNum} out of ${Math.ceil(userPunishment.length/6)}.`}).addFields(userPunishment.slice((pageNum - 1) * 6, pageNum * 6))]});
|
return interaction.reply({embeds: [new client.embed().setColor(client.config.embedColor).setTitle(`${user.username}'s punishment history`).setDescription(`**ID:** \`${user.id}\``).setFooter({text: `${userPunishment.length} total punishments. Viewing page ${pageNum} out of ${Math.ceil(userPunishment.length/6)}.`}).addFields(userPunishment.slice((pageNum - 1) * 6, pageNum * 6))]});
|
||||||
}
|
}
|
||||||
|
@ -76,7 +76,7 @@ export default class Developer {
|
|||||||
else if (stdout.includes('Already up to date')) hammondYouIdiot.edit('Repository is currently up to date.');
|
else if (stdout.includes('Already up to date')) hammondYouIdiot.edit('Repository is currently up to date.');
|
||||||
else hammondYouIdiot.edit('Running `yarn tsc`...').then(()=>exec('yarn tsc', {windowsHide:true}, (err:Error)=>{
|
else hammondYouIdiot.edit('Running `yarn tsc`...').then(()=>exec('yarn tsc', {windowsHide:true}, (err:Error)=>{
|
||||||
if (err) hammondYouIdiot.edit(`\`\`\`${UsernameHelper(err.message)}\`\`\``);
|
if (err) hammondYouIdiot.edit(`\`\`\`${UsernameHelper(err.message)}\`\`\``);
|
||||||
else if (interaction.options.getBoolean('restart')) hammondYouIdiot.edit(msgBody + `\nUptime: ${Formatters.timeFormat(process.uptime()*1000, 4, {longNames:true, commas:true})}`).then(()=>process.exit(0));
|
else if (interaction.options.getBoolean('restart')) hammondYouIdiot.edit(msgBody + `\nUptime: **${Formatters.timeFormat(process.uptime()*1000, 4, {longNames:true, commas:true})}**`).then(()=>process.exit(0));
|
||||||
else hammondYouIdiot.edit(msgBody);
|
else hammondYouIdiot.edit(msgBody);
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
@ -4,7 +4,7 @@ import HookMgr from '../components/HookManager.js';
|
|||||||
import MessageTool from '../helpers/MessageTool.js';
|
import MessageTool from '../helpers/MessageTool.js';
|
||||||
export default class ProhibitedWords {
|
export default class ProhibitedWords {
|
||||||
static async run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){
|
static async run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){
|
||||||
if (!MessageTool.isStaff(interaction.member) && !client.config.whitelist.includes(interaction.member.id)) return MessageTool.youNeedRole(interaction, 'admin');
|
if (!MessageTool.isModerator(interaction.member) && !client.config.whitelist.includes(interaction.member.id)) return MessageTool.youNeedRole(interaction, 'admin');
|
||||||
const word = interaction.options.getString('word');
|
const word = interaction.options.getString('word');
|
||||||
const wordExists = await client.prohibitedWords.findWord(word);
|
const wordExists = await client.prohibitedWords.findWord(word);
|
||||||
({
|
({
|
||||||
|
@ -3,7 +3,7 @@ import TClient from '../client.js';
|
|||||||
import MessageTool from '../helpers/MessageTool.js';
|
import MessageTool from '../helpers/MessageTool.js';
|
||||||
export default class Purge {
|
export default class Purge {
|
||||||
static async run(_client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){
|
static async run(_client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){
|
||||||
if (!MessageTool.isStaff(interaction.member)) return MessageTool.youNeedRole(interaction, 'dcmod');
|
if (!MessageTool.isModerator(interaction.member)) return MessageTool.youNeedRole(interaction, 'dcmod');
|
||||||
const amount = interaction.options.getInteger('amount') as number;
|
const amount = interaction.options.getInteger('amount') as number;
|
||||||
if (amount > 100) return interaction.reply({content: 'Discord API limits purging up to 100 messages.', ephemeral: true})
|
if (amount > 100) return interaction.reply({content: 'Discord API limits purging up to 100 messages.', ephemeral: true})
|
||||||
const user = interaction.options.getUser('user');
|
const user = interaction.options.getUser('user');
|
||||||
|
@ -4,7 +4,7 @@ import Logger from '../helpers/Logger.js';
|
|||||||
import MessageTool from '../helpers/MessageTool.js';
|
import MessageTool from '../helpers/MessageTool.js';
|
||||||
export default class Unpunish {
|
export default class Unpunish {
|
||||||
static async run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){
|
static async run(client: TClient, interaction: Discord.ChatInputCommandInteraction<'cached'>){
|
||||||
if (!MessageTool.isStaff(interaction.member as Discord.GuildMember)) return MessageTool.youNeedRole(interaction, 'dcmod');
|
if (!MessageTool.isModerator(interaction.member as Discord.GuildMember)) return MessageTool.youNeedRole(interaction, 'dcmod');
|
||||||
const punishment = await client.punishments.findCase(interaction.options.getInteger('case_id', true));
|
const punishment = await client.punishments.findCase(interaction.options.getInteger('case_id', true));
|
||||||
if (!punishment) return interaction.reply({content: 'Case ID is not found in database.', ephemeral: true});
|
if (!punishment) return interaction.reply({content: 'Case ID is not found in database.', ephemeral: true});
|
||||||
if (['unban', 'unmute', 'punishmentOverride'].includes(punishment.dataValues.type)) return interaction.reply({content: 'This case ID is immutable. (Informative case)', ephemeral: true});
|
if (['unban', 'unmute', 'punishmentOverride'].includes(punishment.dataValues.type)) return interaction.reply({content: 'This case ID is immutable. (Informative case)', ephemeral: true});
|
||||||
|
@ -3,9 +3,7 @@ import TClient from '../client.js';
|
|||||||
import Logger from '../helpers/Logger.js';
|
import Logger from '../helpers/Logger.js';
|
||||||
export default class Automoderator {
|
export default class Automoderator {
|
||||||
private static lockQuery:Set<Discord.Snowflake> = new Set();
|
private static lockQuery:Set<Discord.Snowflake> = new Set();
|
||||||
static scanMsg(message:Discord.Message) {
|
static scanMsg =(message:Discord.Message)=>message.content.toLowerCase().replaceAll(/[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?\n?0-9]|[]|ing\b/g, '').split(' ').join('');
|
||||||
return message.content.toLowerCase().replaceAll(/[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?\n?0-9]|[]|ing\b/g, '').split(' ').join('');
|
|
||||||
}
|
|
||||||
static async repeatedMessages(client:TClient, message:Discord.Message, thresholdTime:number, thresholdAmount:number, type:string, duration:string, reason:string) {
|
static async repeatedMessages(client:TClient, message:Discord.Message, thresholdTime:number, thresholdAmount:number, type:string, duration:string, reason:string) {
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
|
|
||||||
|
@ -29,12 +29,8 @@ export default class CacheServer {
|
|||||||
if (jsonMode) return await RedisClient.json.set(key, '.', value);
|
if (jsonMode) return await RedisClient.json.set(key, '.', value);
|
||||||
else return await RedisClient.set(key, JSON.stringify(value));
|
else return await RedisClient.set(key, JSON.stringify(value));
|
||||||
}
|
}
|
||||||
public static async expiry(key:any, time:number) {
|
public static expiry = async(key:any, time:number)=>await RedisClient.expire(key, time); // NOTE: time is in seconds, not milliseconds -- you know what you did wrong
|
||||||
return await RedisClient.expire(key, time); // NOTE: time is in seconds, not milliseconds -- you know what you did wrong
|
public static delete = async(key:any)=>await RedisClient.del(key);
|
||||||
}
|
|
||||||
public static async delete(key:any) {
|
|
||||||
return await RedisClient.del(key);
|
|
||||||
}
|
|
||||||
public static init() {
|
public static init() {
|
||||||
try {
|
try {
|
||||||
RedisClient.connect();
|
RedisClient.connect();
|
||||||
|
@ -14,9 +14,7 @@ export default class HookMgr {
|
|||||||
this.webhookId = webhookId;
|
this.webhookId = webhookId;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async channelFetch(client:TClient, channel:ChannelList) {
|
protected channelFetch = async(client:TClient, channel:ChannelList)=>await client.channels.fetch(config.dcServer.channels[channel]) as Discord.TextChannel;
|
||||||
return await client.channels.fetch(config.dcServer.channels[channel]) as Discord.TextChannel;
|
|
||||||
}
|
|
||||||
protected async fetch(client:TClient, channel:ChannelList, webhookId:Discord.Snowflake) {
|
protected async fetch(client:TClient, channel:ChannelList, webhookId:Discord.Snowflake) {
|
||||||
const hookInstance = await (await this.channelFetch(client, channel)).fetchWebhooks().then(x=>x.find(y=>y.id===webhookId));
|
const hookInstance = await (await this.channelFetch(client, channel)).fetchWebhooks().then(x=>x.find(y=>y.id===webhookId));
|
||||||
if (!hookInstance) throw new Error('[HookManager] Webhook not found.');
|
if (!hookInstance) throw new Error('[HookManager] Webhook not found.');
|
||||||
|
@ -30,21 +30,13 @@ export class DailyMsgsSvc {
|
|||||||
})
|
})
|
||||||
this.model.sync();
|
this.model.sync();
|
||||||
}
|
}
|
||||||
async nukeDays() {
|
nukeDays = async()=>await this.model.destroy({truncate: true});
|
||||||
return await this.model.destroy({truncate: true})
|
fetchDays = async()=>await this.model.findAll();
|
||||||
// Drop a nuclear bomb on the table.
|
|
||||||
}
|
|
||||||
async fetchDays() {
|
|
||||||
return await this.model.findAll();
|
|
||||||
// Fetch every rows from database.
|
|
||||||
}
|
|
||||||
async newDay(formattedDate:number, total:number) {
|
async newDay(formattedDate:number, total:number) {
|
||||||
if (await this.model.findOne({where: {day: formattedDate}})) return console.log('This day already exists!')
|
if (await this.model.findOne({where: {day: formattedDate}})) return console.log('This day already exists!')
|
||||||
return await this.model.create({day: formattedDate, total: total});
|
return await this.model.create({day: formattedDate, total: total});
|
||||||
// Save previous day's total messages into database when a new day starts.
|
// Save previous day's total messages into database when a new day starts.
|
||||||
}
|
}
|
||||||
async updateDay(formattedDate:number, total:number) {
|
updateDay = async(formattedDate:number, total:number)=>await this.model.update({total: total}, {where: {day: formattedDate}});
|
||||||
return await this.model.update({total: total}, {where: {day: formattedDate}});
|
|
||||||
// THIS IS FOR DEVELOPMENT PURPOSES ONLY, NOT TO BE USED IN LIVE ENVIRONMENT!
|
// THIS IS FOR DEVELOPMENT PURPOSES ONLY, NOT TO BE USED IN LIVE ENVIRONMENT!
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -26,9 +26,7 @@ export class ProhibitedWordsSvc {
|
|||||||
})
|
})
|
||||||
this.model.sync();
|
this.model.sync();
|
||||||
}
|
}
|
||||||
async findWord(word:string) {
|
findWord = async(word:string)=>await this.model.findByPk(word);
|
||||||
return await this.model.findByPk(word);
|
|
||||||
}
|
|
||||||
async importWords(file:string) {
|
async importWords(file:string) {
|
||||||
const jsonData = await new Promise<string>((resolve, reject)=>{
|
const jsonData = await new Promise<string>((resolve, reject)=>{
|
||||||
get(file, res=>{
|
get(file, res=>{
|
||||||
@ -48,13 +46,7 @@ export class ProhibitedWordsSvc {
|
|||||||
throw new Error(`Failed to insert words into Postgres database: ${err.message}`)
|
throw new Error(`Failed to insert words into Postgres database: ${err.message}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async getAllWords() {
|
getAllWords = async()=>await this.model.findAll();
|
||||||
return await this.model.findAll();
|
insertWord = async(word:string)=>await this.model.create({word: word});
|
||||||
}
|
removeWord = async(word:string)=>await this.model.destroy({where: {word: word}})
|
||||||
async insertWord(word:string) {
|
|
||||||
return await this.model.create({word: word})
|
|
||||||
}
|
|
||||||
async removeWord(word:string) {
|
|
||||||
return await this.model.destroy({where: {word: word}})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -82,15 +82,9 @@ export class PunishmentsSvc {
|
|||||||
const findCase = this.findCase(caseId);
|
const findCase = this.findCase(caseId);
|
||||||
if (findCase) return this.model.update({reason: reason}, {where: {case_id: caseId}});
|
if (findCase) return this.model.update({reason: reason}, {where: {case_id: caseId}});
|
||||||
}
|
}
|
||||||
async findCase(caseId:number) {
|
findCase =(caseId:number)=>this.model.findOne({where: {case_id: caseId}});
|
||||||
return this.model.findOne({where: {case_id: caseId}});
|
findByCancels =(caseId:number)=>this.model.findOne({where: {cancels: caseId}})
|
||||||
}
|
getAllCases =()=>this.model.findAll();
|
||||||
async findByCancels(caseId:number) {
|
|
||||||
return this.model.findOne({where: {cancels: caseId}})
|
|
||||||
}
|
|
||||||
async getAllCases() {
|
|
||||||
return this.model.findAll();
|
|
||||||
}
|
|
||||||
async generateCaseId() {
|
async generateCaseId() {
|
||||||
const result = await this.model.findAll();
|
const result = await this.model.findAll();
|
||||||
return Math.max(...result.map((x:Punishment)=>x.case_id), 0) + 1;
|
return Math.max(...result.map((x:Punishment)=>x.case_id), 0) + 1;
|
||||||
@ -107,7 +101,7 @@ export class PunishmentsSvc {
|
|||||||
async findInCache():Promise<any> {
|
async findInCache():Promise<any> {
|
||||||
const cacheKey = 'punishments';
|
const cacheKey = 'punishments';
|
||||||
const cachedResult = await CacheServer.get(cacheKey, true);
|
const cachedResult = await CacheServer.get(cacheKey, true);
|
||||||
let result;
|
let result:any;
|
||||||
if (cachedResult) result = cachedResult;
|
if (cachedResult) result = cachedResult;
|
||||||
else {
|
else {
|
||||||
result = await this.model.findAll();
|
result = await this.model.findAll();
|
||||||
@ -164,13 +158,7 @@ export class PunishmentsSvc {
|
|||||||
const durText = millisecondTime ? ` for ${Formatters.timeFormat(millisecondTime, 4, {longNames: true, commas: true})}` : '';
|
const durText = millisecondTime ? ` for ${Formatters.timeFormat(millisecondTime, 4, {longNames: true, commas: true})}` : '';
|
||||||
if (time) embed.addFields({name: 'Duration', value: durText});
|
if (time) embed.addFields({name: 'Duration', value: durText});
|
||||||
|
|
||||||
if (guildUser) {
|
if (guildUser) await guildUser.send(`You've been ${this.getPastTense(type)} ${inOrFromBoolean} **${guild.name}**${durText}\n\`${reason}\` (Case #${punishment.case_id})`).catch(()=>embed.setFooter({text: 'Unable to DM a member'}));
|
||||||
try {
|
|
||||||
await guildUser.send(`You've been ${this.getPastTense(type)} ${inOrFromBoolean} **${guild.name}**${durText}\n\`${reason}\` (Case #${punishment.case_id})`)
|
|
||||||
} catch {
|
|
||||||
embed.setFooter({text: 'Unable to DM a member'})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (['ban', 'softban'].includes(type)) {
|
if (['ban', 'softban'].includes(type)) {
|
||||||
const alreadyBanned = await guild.bans.fetch(user.id).catch(()=>null); // 172800 seconds is 48 hours, just for future reference
|
const alreadyBanned = await guild.bans.fetch(user.id).catch(()=>null); // 172800 seconds is 48 hours, just for future reference
|
||||||
|
@ -40,16 +40,8 @@ export class SuggestionsSvc {
|
|||||||
})
|
})
|
||||||
this.model.sync();
|
this.model.sync();
|
||||||
}
|
}
|
||||||
async fetchById(id:number) {
|
fetchById = async(id:number)=>await this.model.findByPk(id);
|
||||||
return await this.model.findByPk(id);
|
updateStatus = async(id:number, status:string)=>await this.model.update({status: status}, {where: {id: id}});
|
||||||
}
|
create =(userid:string, description:string)=>this.model.create({userid: userid, suggestion: description, status: 'Pending'})
|
||||||
async updateStatus(id:number, status:string) {
|
delete =(id:number)=>this.model.destroy({where: {id: id}});
|
||||||
return await this.model.update({status: status}, {where: {id: id}})
|
|
||||||
}
|
|
||||||
async create(userid:string, description:string) {
|
|
||||||
return this.model.create({userid: userid, suggestion: description, status: 'Pending'})
|
|
||||||
}
|
|
||||||
async delete(id:number) {
|
|
||||||
return this.model.destroy({where: {id: id}});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -47,15 +47,9 @@ export class UserLevelsSvc {
|
|||||||
});
|
});
|
||||||
this.model.sync();
|
this.model.sync();
|
||||||
}
|
}
|
||||||
async fetchEveryone() {
|
fetchEveryone = async()=>await this.model.findAll();
|
||||||
return await this.model.findAll();
|
fetchUser = async(userId:string)=>await this.model.findByPk(userId);
|
||||||
}
|
deleteUser = async(userId:string)=>await this.model.destroy({where: {id: userId}});
|
||||||
async fetchUser(userId:string) {
|
|
||||||
return await this.model.findByPk(userId);
|
|
||||||
}
|
|
||||||
async deleteUser(userId:string) {
|
|
||||||
return await this.model.destroy({where: {id: userId}});
|
|
||||||
}
|
|
||||||
async modifyUser(userId:string, updatedMessages:number) {
|
async modifyUser(userId:string, updatedMessages:number) {
|
||||||
await this.model.update({messages: updatedMessages}, {where: {id: userId}});
|
await this.model.update({messages: updatedMessages}, {where: {id: userId}});
|
||||||
return (await this.model.findByPk(userId)).dataValues;
|
return (await this.model.findByPk(userId)).dataValues;
|
||||||
|
@ -34,15 +34,11 @@ export class YouTubeChannelsSvc {
|
|||||||
})
|
})
|
||||||
this.model.sync();
|
this.model.sync();
|
||||||
}
|
}
|
||||||
async getChannels() {
|
|
||||||
return await this.model.findAll();
|
|
||||||
}
|
|
||||||
async addChannel(YTChannelID:string, DCChannelID:string, DCRole:string) {
|
async addChannel(YTChannelID:string, DCChannelID:string, DCRole:string) {
|
||||||
if (await this.model.findOne({where: {ytchannel: YTChannelID}})) return false;
|
if (await this.model.findOne({where: {ytchannel: YTChannelID}})) return false;
|
||||||
await this.model.create({ytchannel: YTChannelID, dcchannel: DCChannelID, dcrole: DCRole});
|
await this.model.create({ytchannel: YTChannelID, dcchannel: DCChannelID, dcrole: DCRole});
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
async delChannel(YTChannelID:string) {
|
delChannel = async(YTChannelID:string)=>await this.model.destroy({where: {ytchannel: YTChannelID}});
|
||||||
return await this.model.destroy({where: {ytchannel: YTChannelID}});
|
getChannels = async()=>await this.model.findAll();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user