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: first praise -> activate #1070

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

### Changed
- **Discord bot**: Improved user onboarding - Activation flow when praising first-time #1070

### Deprecated

Expand Down
2 changes: 1 addition & 1 deletion packages/api/openapi.json

Large diffs are not rendered by default.

46 changes: 2 additions & 44 deletions packages/discord-bot/src/handlers/activate.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { CommandHandler } from '../interfaces/CommandHandler';
import { getUserAccount } from '../utils/getUserAccount';
import { GuildMember } from 'discord.js';
import { getActivateToken } from '../utils/getActivateToken';
import { renderMessage } from '../utils/renderMessage';
import { sendActivationMessage } from '../utils/sendActivationMessage';

/**
* Executes command /activate
Expand All @@ -21,45 +19,5 @@ export const activationHandler: CommandHandler = async (
return;
}

try {
const userAccount = await getUserAccount(
(member as GuildMember).user,
host
);
if (userAccount.user && userAccount.user !== null) {
await interaction.editReply({
content: await renderMessage(
'PRAISE_ACCOUNT_ALREADY_ACTIVATED_ERROR',
host
),
});
return;
}

const activateToken = await getActivateToken(userAccount, host);

if (!activateToken) {
await interaction.editReply({
content: 'Unable to activate user account.',
});
return;
}

const hostUrl =
process.env.NODE_ENV === 'development'
? process.env.FRONTEND_URL
: `https://${host}`;

const activationURL = `${hostUrl || 'undefined:/'}/activate?accountId=${
member.user.id
}&platform=DISCORD&token=${activateToken}`;

await interaction.editReply({
content: `To activate your account, follow this link and sign a message using your Ethereum wallet. [Activate my account!](${activationURL})`,
});
} catch (error) {
await interaction.editReply({
content: 'Unable to activate user account.',
});
}
await sendActivationMessage(interaction, host, member);
};
216 changes: 60 additions & 156 deletions packages/discord-bot/src/handlers/praise.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,15 @@
import { GuildMember, User } from 'discord.js';
import { ComponentType, GuildMember } from 'discord.js';
import { parseReceivers } from '../utils/parseReceivers';
import { sendReceiverDM } from '../utils/embeds/sendReceiverDM';
import { renderMessage, ephemeralWarning } from '../utils/renderMessage';

import { ephemeralWarning } from '../utils/renderMessage';
import { assertPraiseGiver } from '../utils/assertPraiseGiver';
import { assertPraiseAllowedInChannel } from '../utils/assertPraiseAllowedInChannel';
import { CommandHandler } from '../interfaces/CommandHandler';
import { getUserAccount } from '../utils/getUserAccount';
import { createPraise } from '../utils/createPraise';
import { praiseSuccessEmbed } from '../utils/embeds/praiseSuccessEmbed';
import { apiClient } from '../utils/api';
import {
PraisePaginatedResponseDto,
UserAccount,
Praise,
} from '../utils/api-schema';
import { getSetting } from '../utils/settingsUtil';

import { logger } from '../utils/logger';
import { sendActivationMessage } from '../utils/sendActivationMessage';
import { givePraise } from '../utils/givePraise';

/**
* Execute command /praise
Expand Down Expand Up @@ -77,160 +71,70 @@ export const praiseHandler: CommandHandler = async (
return;
}

const giverAccount = await getUserAccount(
(member as GuildMember).user,
host
);
let giverAccount = await getUserAccount((member as GuildMember).user, host);

if (!giverAccount || !giverAccount.user || giverAccount.user === null) {
await ephemeralWarning(
const response = await sendActivationMessage(
interaction,
'PRAISE_ACCOUNT_NOT_ACTIVATED_ERROR',
host
);
return;
}

const validReceiverIds: string[] = [
...new Set(
parsedReceivers.validReceiverIds.map((id: string) =>
id.replace(/\D/g, '')
)
),
];

const selfPraiseAllowed = (await getSetting(
'SELF_PRAISE_ALLOWED',
host
)) as boolean;

let warnSelfPraise = false;
if (
!selfPraiseAllowed &&
validReceiverIds.includes(giverAccount.accountId)
) {
warnSelfPraise = true;
validReceiverIds.splice(
validReceiverIds.indexOf(giverAccount.accountId),
1
);
}

const receivers: { guildMember: GuildMember; userAccount: UserAccount }[] =
await Promise.all(
(
await guild.members.fetch({ user: validReceiverIds })
).map(async (guildMember) => {
const userAccount = await getUserAccount(guildMember.user, host);
return {
guildMember,
userAccount,
};
})
host,
member,
true
);

let praiseItems: Praise[] = [];
if (receivers.length !== 0) {
await interaction.editReply({
embeds: [
await praiseSuccessEmbed(
interaction.user,
validReceiverIds.map((id) => `<@!${id}>`),
reason,
if (!response) return;

try {
const collector = response.createMessageComponentCollector({
filter: (i) =>
i.user.id === interaction.user.id && i.customId === 'retry',
componentType: ComponentType.Button,
time: 600000,
});

collector.on('collect', async (i) => {
giverAccount = await getUserAccount(
(member as GuildMember).user,
host
),
],
});
praiseItems = await createPraise(
);

if (giverAccount && giverAccount.user) {
collector.stop();
await givePraise(
interaction,
guild,
member as GuildMember,
giverAccount,
parsedReceivers,
receiverOptions,
reason,
host,
responseUrl
);
return;
}

await i.update({
content:
i.message.content +
'\nRetry failed... Retry praise after activating on the Praise dashboard',
});
});
} catch {
await interaction.editReply(
'Timed out... Please use /praise command again.'
);
}
} else {
await givePraise(
interaction,
guild,
member as GuildMember,
giverAccount,
receivers.map((receiver) => receiver.userAccount),
parsedReceivers,
receiverOptions,
reason,
host
);
} else if (warnSelfPraise) {
await ephemeralWarning(interaction, 'SELF_PRAISE_WARNING', host);
} else if (!receivers.length) {
await ephemeralWarning(
interaction,
'PRAISE_INVALID_RECEIVERS_ERROR',
host
);
} else {
await ephemeralWarning(interaction, 'PRAISE_FAILED', host);
}

const hostUrl =
process.env.NODE_ENV === 'development'
? process.env?.FRONTEND_URL || 'undefined:/'
: `https://${host}`;

await Promise.all(
praiseItems.map(async (praise) => {
console.log(praiseItems, receivers);
await sendReceiverDM(
praise._id,
receivers.filter(
(receiver) =>
receiver.userAccount.accountId === praise.receiver.accountId
)[0],
member as GuildMember,
reason,
responseUrl,
host,
hostUrl,
interaction.channelId
);
})
);

const warningMsgParts: string[] = [];

if (parsedReceivers.undefinedReceivers) {
const warning = await renderMessage(
'PRAISE_UNDEFINED_RECEIVERS_WARNING',
host,
{
receivers: parsedReceivers.undefinedReceivers.map((id) =>
id.replace(/[<>]/, '')
),
user: member.user as User,
}
responseUrl
);
warningMsgParts.push(warning);
}

if (parsedReceivers.roleMentions) {
const warning = await renderMessage('PRAISE_TO_ROLE_WARNING', host, {
user: member.user as User,
receivers: parsedReceivers.roleMentions,
});
warningMsgParts.push(warning);
}

if (receivers.length !== 0 && warnSelfPraise) {
const warning = await renderMessage('SELF_PRAISE_WARNING', host);
warningMsgParts.push(warning);
}

const warningMsg = warningMsgParts.join('\n');

if (warningMsg && warningMsg.length !== 0) {
await interaction.followUp({ content: warningMsg, ephemeral: true });
}

const praiseItemsCount = await apiClient
.get(`/praise?limit=1&giver=${giverAccount._id}`, {
headers: { host },
})
.then((res) => (res.data as PraisePaginatedResponseDto).totalPages)
.catch(() => 0);

if (receivers.length && receiverOptions.length && praiseItemsCount === 0) {
await interaction.followUp({
content: await renderMessage('FIRST_TIME_PRAISER', host),
ephemeral: true,
});
}
} catch (err) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Expand Down
4 changes: 2 additions & 2 deletions packages/discord-bot/src/utils/embeds/praiseEmbeds.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { User, EmbedBuilder, Role, Embed } from 'discord.js';
import { User, EmbedBuilder, Role } from 'discord.js';
import { renderMessage } from '../renderMessage';

export const praiseRoleError = async (
Expand Down Expand Up @@ -43,6 +43,6 @@ export const praiseWelcomeEmbed = (
return new EmbedBuilder()
.setTitle('Welcome to Praise!')
.setDescription(
`✅ Praise community created\n✅ Praise bot added to Discord\n✅ Praise bot linked to community\n`
'✅ Praise community created\n✅ Praise bot added to Discord\n✅ Praise bot linked to community\n'
);
};
Loading
Loading