Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/317/verbose /admin announce summary #441

Merged
merged 10 commits into from
Jun 15, 2022
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- Option to turn on/off the Discord role requirement for Praise givers #419 #434 #440
- Support multiple wallets: WalletConneect, Trust, Rainbow etc #424
- More verbose output from the `/admin announce` command #317 #441

### Fixed

Expand Down
1 change: 0 additions & 1 deletion packages/discord-bot/src/commands/forward.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { SlashCommandBuilder } from '@discordjs/builders';
import { APIMessage } from 'discord-api-types/v9';
import logger from 'jet-logger';
import { forwardHandler } from '../handlers/forward';
import { Command } from '../interfaces/Command';
Expand Down
1 change: 0 additions & 1 deletion packages/discord-bot/src/commands/praise.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { SlashCommandBuilder } from '@discordjs/builders';
import { APIMessage } from 'discord-api-types/v9';
import logger from 'jet-logger';
import { praiseHandler } from '../handlers/praise';
import { Command } from '../interfaces/Command';
Expand Down
4 changes: 4 additions & 0 deletions packages/discord-bot/src/handlers/announce.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,10 @@ export const announcementHandler: CommandHandler = async (interaction) => {
break;
}
case 'continue': {
await interaction.editReply({
content: 'Sending…',
components: [],
});
await selectTargets(
interaction,
selectedUserType,
Expand Down
6 changes: 6 additions & 0 deletions packages/discord-bot/src/interfaces/FailedToDmUsersList.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export interface FailedToDmUsersList {
invalidUsers: string[];
notFoundUsers: string[];
closedDmUsers: string[];
unknownErrorUsers: string[];
}
104 changes: 96 additions & 8 deletions packages/discord-bot/src/utils/dmTargets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,24 @@ import {
PeriodDocument,
PeriodDetailsQuantifierDto,
} from 'api/dist/period/types';
import { FailedToDmUsersList } from 'src/interfaces/FailedToDmUsersList';

import { CommandInteraction } from 'discord.js';
import { CommandInteraction, DiscordAPIError } from 'discord.js';
import { PraiseModel } from 'api/dist/praise/entities';
import { Buffer } from 'node:buffer';

const sendDMs = async (
interaction: CommandInteraction,
users: UserDocument[] | PeriodDetailsQuantifierDto[],
message: string
): Promise<void> => {
const successful = [];
const failed = [];
const failed: FailedToDmUsersList = {
invalidUsers: <string[]>[],
notFoundUsers: <string[]>[],
closedDmUsers: <string[]>[],
unknownErrorUsers: <string[]>[],
};
if (!users || users.length === 0) {
await interaction.editReply(
'Message not sent. No recipients matched filter.'
Expand All @@ -28,30 +35,111 @@ const sendDMs = async (
const userAccount = await UserAccountModel.findOne({
user: user._id,
});
const userId: string = userAccount?.accountId || 'oops';
const userId: string = userAccount?.accountId || 'Unknown user';
const userName: string = userAccount?.name || userId;
try {
const discordUser = await interaction.guild?.members.fetch(userId);
if (!discordUser) {
failed.push(`<@!${userId}`);
failed.notFoundUsers.push(userAccount?.name || userId);
continue;
}
await discordUser.send(message);
successful.push(`<@!${userId}>`);
successful.push(`${discordUser.user.tag}`);
} catch (err) {
failed.push(`<@!${userId}>`);
const error = err as Error;
if (error instanceof DiscordAPIError) {
/* The numbers used below are status codes from discord's API.
* (ref - https://discord.com/developers/docs/topics/opcodes-and-status-codes#json)
*/
const discordErrorCode = (err as DiscordAPIError).code;
switch (discordErrorCode) {
case 50035:
failed.invalidUsers.push(userName);
break;
case 50007:
failed.closedDmUsers.push(userName);
break;
default:
failed.unknownErrorUsers.push(userAccount?.name || userId);
break;
}
} else {
failed.unknownErrorUsers.push(userAccount?.name || userId);
}
}
}
const failedMsg = `Announcement could not be delivered to ${failed.length} users.`;

const failedCount =
failed.invalidUsers.length +
failed.notFoundUsers.length +
failed.closedDmUsers.length +
failed.unknownErrorUsers.length;
const failedMsg = `Announcement could not be delivered to ${failedCount} users.`;
const successMsg = `Announcement successfully delivered to ${successful.length} recipients.`;
const content =
successful.length === 0
? failedMsg
: failed.length === 0
: failedCount === 0
? successMsg
: successMsg + '\n' + failedMsg;

// TODO - Create a utility function to tabularise this data neatly
let summary = 'User\t\t\tStatus\t\tReason\n';
successful.forEach((username: string) => {
summary += `${
username.length <= 24
? username + new String(' ').repeat(24 - username.length)
: username.slice(0, 21) + ' '
}Delivered\t-\n${
username.length > 24 ? username.slice(21, username.length) + '\n' : ''
}`;
});
failed.invalidUsers.forEach((username: string) => {
summary += `${
username.length <= 24
? username + new String(' ').repeat(24 - username.length)
: username.slice(0, 21) + ' '
}Not Delivered\tInvalid User\n${
username.length > 24 ? username.slice(21, username.length) + '\n' : ''
}`;
});
failed.notFoundUsers.forEach((username: string) => {
summary += `${
username.length <= 24
? username + new String(' ').repeat(24 - username.length)
: username.slice(0, 21) + ' '
}Not Delivered\tUser Not Found In Discord\n${
username.length > 24 ? username.slice(21, username.length) + '\n' : ''
}`;
});
failed.closedDmUsers.forEach((username: string) => {
summary += `${
username.length <= 24
? username + new String(' ').repeat(24 - username.length)
: username.slice(0, 21) + ' '
}Not Delivered\tDMs closed\n${
username.length > 24 ? username.slice(21, username.length) + '\n' : ''
}`;
});
failed.unknownErrorUsers.forEach((username: string) => {
summary += `${
username.length <= 24
? username + new String(' ').repeat(24 - username.length)
: username.slice(0, 21) + ' '
}Not Delivered\tUnknown Error\n${
username.length > 24 ? username.slice(21, username.length) + '\n' : ''
}`;
});

await interaction.editReply({
content: content,
components: [],
files: [
{
attachment: Buffer.from(summary, 'utf8'),
name: 'announcement_summary.txt',
},
],
});
};

Expand Down