Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions core/modules/ConfigStore/schema/discordBot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,15 @@ const guild = typeNullableConfig({
fixer: SYM_FIXER_DEFAULT,
});

const warningsChannel = typeNullableConfig({
name: 'Warnings Channel ID',
const punishmentsChannel = typeNullableConfig({
name: 'Punishments Channel ID',
default: null,
validator: discordSnowflakeSchema.nullable(),
fixer: SYM_FIXER_DEFAULT,
});

const announcementsChannel = typeNullableConfig({
name: 'Announcements Channel ID',
default: null,
validator: discordSnowflakeSchema.nullable(),
fixer: SYM_FIXER_DEFAULT,
Expand Down Expand Up @@ -63,7 +70,8 @@ export default {
enabled,
token,
guild,
warningsChannel,
punishmentsChannel,
announcementsChannel,
embedJson,
embedConfigJson,
} as const;
3 changes: 2 additions & 1 deletion core/modules/ConfigStore/schema/oldConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ const restructureOldConfig = (old: any) => {
enabled: old?.discordBot?.enabled,
token: old?.discordBot?.token,
guild: old?.discordBot?.guild,
warningsChannel: old?.discordBot?.announceChannel, //NOTE:renamed
punishmentsChannel: old?.discordBot?.punishmentsChannel,
announcementsChannel: old?.discordBot?.announceChannel, //NOTE:renamed
embedJson: old?.discordBot?.embedJson,
embedConfigJson: old?.discordBot?.embedConfigJson,
},
Expand Down
76 changes: 69 additions & 7 deletions core/modules/DiscordBot/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import consoleFactory from '@lib/console';
import { embedColors } from './discordHelpers';
import { DiscordBotStatus } from '@shared/enums';
import { UpdateConfigKeySet } from '@modules/ConfigStore/utils';
import { AuthedAdmin } from '@modules/WebServer/authLogic';
const console = consoleFactory(modulename);


Expand All @@ -20,10 +21,16 @@ type AnnouncementType = {
description: string | MessageTranslationType;
type: keyof typeof embedColors;
}
type PunishmentType = {
admin: AuthedAdmin;
title?: string | MessageTranslationType;
description: string | MessageTranslationType;
type: keyof typeof embedColors;
}

type SpawnConfig = Pick<
TxConfigs['discordBot'],
'enabled' | 'token' | 'guild' | 'warningsChannel'
'enabled' | 'token' | 'guild' | 'punishmentsChannel' | 'announcementsChannel'
>;


Expand Down Expand Up @@ -59,6 +66,7 @@ export default class DiscordBot {
guild: Discord.Guild | undefined;
guildName: string | undefined;
announceChannel: Discord.TextBasedChannel | undefined;
punishmentsChannel: Discord.TextBasedChannel | undefined;
#lastDisallowedIntentsError: number = 0; //ms
#lastGuildMembersCacheRefresh: number = 0; //ms
#lastStatus = DiscordBotStatus.Disabled;
Expand Down Expand Up @@ -161,7 +169,7 @@ export default class DiscordBot {
async sendAnnouncement(content: AnnouncementType) {
if (!txConfig.discordBot.enabled) return;
if (
!txConfig.discordBot.warningsChannel
!txConfig.discordBot.punishmentsChannel
|| !this.#client?.isReady()
|| !this.announceChannel
) {
Expand Down Expand Up @@ -191,6 +199,47 @@ export default class DiscordBot {
}


/**
* Send an announcement to the configured channel
*/
async sendPunishment(content: PunishmentType) {
if (!txConfig.discordBot.enabled) return;
if (
!txConfig.discordBot.punishmentsChannel
|| !this.#client?.isReady()
|| !this.punishmentsChannel
) {
console.verbose.warn('not ready yet to send announcement');
return false;
}

try {
let title;
if (content.title) {
title = (typeof content.title === 'string')
? content.title
: txCore.translator.t(content.title.key, content.title.data);
}
let description;
if (content.description) {
description = (typeof content.description === 'string')
? content.description
: txCore.translator.t(content.description.key, content.description.data);
}

const embed = new EmbedBuilder({ title, description })
.setColor(embedColors[content.type])
.setAuthor({
name: content.admin.name,
iconURL: content.admin.profilePicture,
});
await this.punishmentsChannel.send({ embeds: [embed] });
} catch (error) {
console.error(`Error sending Discord punishment: ${(error as Error).message}`);
}
}


/**
* Update persistent status and activity
*/
Expand Down Expand Up @@ -239,7 +288,8 @@ export default class DiscordBot {
enabled: txConfig.discordBot.enabled,
token: txConfig.discordBot.token,
guild: txConfig.discordBot.guild,
warningsChannel: txConfig.discordBot.warningsChannel,
punishmentsChannel: txConfig.discordBot.punishmentsChannel,
announcementsChannel: txConfig.discordBot.announcementsChannel,
}
if (!botCfg.enabled) return;

Expand Down Expand Up @@ -354,13 +404,25 @@ export default class DiscordBot {
}
}

//Fetching warnings channel
if (botCfg.punishmentsChannel) {
const fetchedChannel = this.#client.channels.cache.find((x) => x.id === botCfg.punishmentsChannel);
if (!fetchedChannel) {
return sendError(`Channel ${botCfg.punishmentsChannel} not found.`);
} else if (fetchedChannel.type !== ChannelType.GuildText && fetchedChannel.type !== ChannelType.GuildAnnouncement) {
return sendError(`Channel ${botCfg.punishmentsChannel} - ${(fetchedChannel as any)?.name} is not a text or announcement channel.`);
} else {
this.punishmentsChannel = fetchedChannel;
}
}

//Fetching announcements channel
if (botCfg.warningsChannel) {
const fetchedChannel = this.#client.channels.cache.find((x) => x.id === botCfg.warningsChannel);
if (botCfg.announcementsChannel) {
const fetchedChannel = this.#client.channels.cache.find((x) => x.id === botCfg.announcementsChannel);
if (!fetchedChannel) {
return sendError(`Channel ${botCfg.warningsChannel} not found.`);
return sendError(`Channel ${botCfg.announcementsChannel} not found.`);
} else if (fetchedChannel.type !== ChannelType.GuildText && fetchedChannel.type !== ChannelType.GuildAnnouncement) {
return sendError(`Channel ${botCfg.warningsChannel} - ${(fetchedChannel as any)?.name} is not a text or announcement channel.`);
return sendError(`Channel ${botCfg.announcementsChannel} - ${(fetchedChannel as any)?.name} is not a text or announcement channel.`);
} else {
this.announceChannel = fetchedChannel;
}
Expand Down
36 changes: 36 additions & 0 deletions core/routes/history/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,22 @@ async function handleBandIds(ctx: AuthedCtx): Promise<GenericApiOkResp> {
expiration,
false
);

txCore.discordBot.sendPunishment({
admin: ctx.admin,
type: 'danger',
title: {
key: 'ban_messages.embed.title',
},
description: {
key: 'ban_messages.embed.idban_description',
data: {
expiration: expiration ? `<t:${expiration}>` : 'X',
reason,
identifiers: identifiers.join('\n'),
}
}
});
} catch (error) {
return { error: `Failed to ban identifiers: ${(error as Error).message}` };
}
Expand Down Expand Up @@ -162,6 +178,26 @@ async function handleRevokeAction(ctx: AuthedCtx): Promise<GenericApiOkResp> {
try {
action = txCore.database.actions.revoke(actionId, ctx.admin.name, perms) as DatabaseActionType;
ctx.admin.logAction(`Revoked ${action.type} id ${actionId} from ${action.playerName ?? 'identifiers'}`);

txCore.discordBot.sendPunishment({
admin: ctx.admin,
type: 'info',
title: {
key: 'revocation_messages.embed.title',
data: {
action: action.type,
},
},
description: {
key: 'revocation_messages.embed.description',
data: {
actionId,
action: action.type,
target: action.playerName ?? 'identifiers',
identifiers: action.ids.join('\n'),
},
},
});
} catch (error) {
return { error: `Failed to revoke action: ${(error as Error).message}` };
}
Expand Down
64 changes: 64 additions & 0 deletions core/routes/player/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,22 @@ async function handleWarning(ctx: AuthedCtx, player: PlayerClass): Promise<Gener
reason,
player.displayName,
);

txCore.discordBot.sendPunishment({
admin: ctx.admin,
type: 'warning',
title: {
key: 'warning_messages.embed.title',
},
description: {
key: 'warning_messages.embed.description',
data: {
player: player.displayName,
reason,
identifiers: `${player.allIdentifiers.join('\n')}`,
},
},
});
} catch (error) {
return { error: `Failed to warn player: ${(error as Error).message}` };
}
Expand Down Expand Up @@ -189,6 +205,23 @@ async function handleBan(ctx: AuthedCtx, player: PlayerClass): Promise<GenericAp
player.displayName,
allHwids
);

txCore.discordBot.sendPunishment({
admin: ctx.admin,
type: 'danger',
title: {
key: 'ban_messages.embed.title',
},
description: {
key: 'ban_messages.embed.playerban_description',
data: {
player: player.displayName,
reason,
expiration: expiration ? `<t:${expiration}>` : 'X',
identifiers: player.allIdentifiers.join('\n'),
}
}
});
} catch (error) {
return { error: `Failed to ban player: ${(error as Error).message}` };
}
Expand Down Expand Up @@ -359,6 +392,21 @@ async function handleDirectMessage(ctx: AuthedCtx, player: PlayerClass): Promise

try {
ctx.admin.logAction(`DM to "${player.displayName}": ${message}`);

txCore.discordBot.sendPunishment({
admin: ctx.admin,
type: 'danger',
title: {
key: 'dm_messages.embed.title',
},
description: {
key: 'dm_messages.embed.description',
data: {
player: player.displayName,
message,
}
}
});

// Dispatch `txAdmin:events:playerDirectMessage`
txCore.fxRunner.sendEvent('playerDirectMessage', {
Expand Down Expand Up @@ -406,6 +454,22 @@ async function handleKick(ctx: AuthedCtx, player: PlayerClass): Promise<GenericA
'kick_messages.player',
{ reason: kickReason }
);

txCore.discordBot.sendPunishment({
admin: ctx.admin,
type: 'danger',
title: {
key: 'kick_messages.embed.title',
},
description: {
key: 'kick_messages.embed.description',
data: {
player: player.displayName,
reason: kickReason,
identifiers: player.allIdentifiers.join('\n'),
}
}
});

// Dispatch `txAdmin:events:playerKicked`
txCore.fxRunner.sendEvent('playerKicked', {
Expand Down
5 changes: 3 additions & 2 deletions core/routes/settings/saveConfigs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,8 @@ const handleDiscordCard: CardHandler = async (inputConfig, sendTypedResp) => {
[schemas.enabled, inputConfig.discordBot.enabled],
[schemas.token, inputConfig.discordBot.token],
[schemas.guild, inputConfig.discordBot.guild],
[schemas.warningsChannel, inputConfig.discordBot.warningsChannel],
[schemas.announcementsChannel, inputConfig.discordBot.announcementsChannel],
[schemas.punishmentsChannel, inputConfig.discordBot.punishmentsChannel],
]);
if (validationError) {
return sendTypedResp({
Expand Down Expand Up @@ -360,7 +361,7 @@ const handleDiscordCard: CardHandler = async (inputConfig, sendTypedResp) => {
//They have been validated, so this is fine
token: inputConfig.discordBot.token as any,
guild: inputConfig.discordBot.guild as any,
warningsChannel: inputConfig.discordBot.warningsChannel as any,
punishmentsChannel: inputConfig.discordBot.punishmentsChannel as any,
});
} catch (error) {
const errorCode = (error as any).code;
Expand Down
29 changes: 28 additions & 1 deletion locale/ar.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@
"kick_messages": {
"everyone": "تم طرد جميع اللاعبين: %{reason}.",
"player": "تم طردك: %{reason}.",
"unknown_reason": "لسبب مجهول"
"unknown_reason": "لسبب مجهول",
"embed": {
"title": "Player Kicked",
"playerban_description": "**Player:** %{player}\n**Reason:** %{reason}\n\n**Identifiers:**\n```%{identifiers}```"
}
},
"ban_messages": {
"kick_temporary": ".%{expiration} :ستنتهي صلاحية حظرك في .\"%{reason}\" لقد تم حظرك من هذا الخادم لسبب (%{author})",
Expand All @@ -29,6 +33,11 @@
"label_id": "معرف الحظر",
"note_multiple_bans": ".ملاحظة: لديك أكثر من حظر نشط على المعرفات الخاصة بك",
"note_diff_license": ".تتطابق مع تلك المرتبطة بهذا الحظر HWID مما يعني أن بعض معرفاتك <code>license</code> ملاحظة: تم تطبيق الحظر أعلاه على شخص آخر"
},
"embed": {
"title": "Player Banned",
"idban_description": "**Reason:** %{reason}\n**Expires:** %{expiration}\n\n**Identifiers:**\n```%{identifiers}```",
"playerban_description": "**Player:** %{player}\n**Reason:** %{reason}\n**Expires:** %{expiration}\n\n**Identifiers:**\n```%{identifiers}```"
}
},
"whitelist_messages": {
Expand Down Expand Up @@ -363,5 +372,23 @@
"all_hwids": "كافة معرفات الأجهزة"
}
}
},
"warning_messages": {
"embed": {
"title": "Player Warned",
"description": "**Player:** %{player}\n**Reason:** %{reason}\n\n**Identifiers:**\n```%{identifiers}```"
}
},
"revocation_messages": {
"embed": {
"title": "Action Revoked",
"description": "**Action Type:** %{action} (`#%{actionId}`)\n**Target:** %{target}\n\n**Identifiers:**\n```%{identifiers}```"
}
},
"dm_messages": {
"embed": {
"title": "Admin DM Sent",
"description": "**Recipient:** %{player}\n**Message:**\n%{message}"
}
}
}
Loading
Loading