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: Require discord role option #440

Merged
merged 8 commits into from
Jun 8, 2022
Merged
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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- Option to turn on/off the Discord role requirement for Praise givers #419 #434 #440
- Support multiple wallets: WalletConneect, Trust, Rainbow etc #424

### Fixed
Expand All @@ -21,12 +22,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added

- Convert the Quantifier Pool screen to a User Admin screen #320 #395
- Option - Require discord role to allow praise giving #434

### Fixed

- Removed migration unable to fetch info from Discord threads #445
- Quantify slider displaying praise score #444 #443
- Clicking a link within a Praise reason should _only_ trigger opening the link #427 #437
- Whoami command says user does not have praise powers, when they do #419

## [0.7.0] - 2022-05-31

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { SettingGroup } from '@settings/types';
import { SettingsModel } from '../../settings/entities';

const newSetting = [
{
key: 'PRAISE_GIVER_ROLE_ID_REQUIRED',
value: false,
type: 'Boolean',
label: 'Praise Giver Discord Role Required',
description:
'Checking this box requires users to be assigned to the Discord Role ID specified in "Praise Giver Discord Role" in order to praise.',
group: SettingGroup.DISCORD,
},
];

const updatedSetting = [
{
key: 'PRAISE_GIVER_ROLE_ID',
description:
'Discord Role ID authorized to praise or forward praise. Used only if "Praise Giver Discord Role Required" is checked.',
},
];

const oldSetting = [
{
key: 'PRAISE_GIVER_ROLE_ID',
description: 'Discord Role ID authorized to dish praise',
},
];

const up = async (): Promise<void> => {
const ns = newSetting.map((s) => ({
updateOne: {
filter: { key: s.key },

// Insert setting if not found, otherwise continue
update: { $setOnInsert: { ...s } },
upsert: true,
},
}));
await SettingsModel.bulkWrite(ns);

const us = updatedSetting.map((s) => ({
updateOne: {
filter: { key: s.key },
update: { $set: { description: s.description } },
},
}));
await SettingsModel.bulkWrite(us);
};

const down = async (): Promise<void> => {
const allKeys = newSetting.map((s) => s.key);
await SettingsModel.deleteMany({ key: { $in: allKeys } });

const os = oldSetting.map((s) => ({
updateOne: {
filter: { key: s.key },
update: { $set: { description: s.description } },
},
}));
await SettingsModel.bulkWrite(os);
};

export { up, down };
31 changes: 10 additions & 21 deletions packages/discord-bot/src/handlers/forward.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { UserModel } from 'api/dist/user/entities';
import { EventLogTypeKey } from 'api/src/eventlog/types';
import { logEvent } from 'api/src/eventlog/utils';
import logger from 'jet-logger';
import { getSetting } from '../utils/getSettings';
import { getUserAccount } from '../utils/getUserAccount';
import { UserRole } from 'api/dist/user/types';
import {
Expand All @@ -16,21 +15,20 @@ import {
praiseSuccessDM,
roleMentionWarning,
undefinedReceiverWarning,
giverRoleError,
forwardSuccess,
giverNotActivatedError,
} from '../utils/praiseEmbeds';
import { assertPraiseGiver } from '../utils/assertPraiseGiver';

import { CommandHandler } from 'src/interfaces/CommandHandler';

export const forwardHandler: CommandHandler = async (
interaction,
responseUrl
) => {
const { guild, channel, member } = interaction;

if (!responseUrl) return;

const { guild, channel, member } = interaction;
if (!guild || !member || !channel) {
await interaction.editReply(await dmError());
return;
Expand All @@ -41,35 +39,24 @@ export const forwardHandler: CommandHandler = async (
await interaction.editReply(await notActivatedError());
return;
}

const forwarderUser = await UserModel.findOne({ _id: forwarderAccount.user });
if (!forwarderUser?.roles.includes(UserRole.FORWARDER)) {
await interaction.editReply(
"You don't have the permission to use this command."
"**❌ You don't have the permission to use this command.**"
);
return;
}

const praiseGiver = interaction.options.getMember('giver') as GuildMember;

const praiseGiverRoleID = await getSetting('PRAISE_GIVER_ROLE_ID');
const praiseGiverRole = guild.roles.cache.find(
(r) => r.id === praiseGiverRoleID
);

if (
praiseGiverRole &&
!praiseGiver?.roles.cache.find((r) => r.id === praiseGiverRole?.id)
) {
await interaction.editReply({
embeds: [await giverRoleError(praiseGiverRole, praiseGiver.user)],
});
if (!praiseGiver) {
await interaction.editReply('**❌ No Praise giver specified**');
return;
}
const giverAccount = await getUserAccount(praiseGiver);

const receivers = interaction.options.getString('receivers');
const reason = interaction.options.getString('reason');
if (!(await assertPraiseGiver(praiseGiver, interaction, true))) return;

const receivers = interaction.options.getString('receivers');
const receiverData = {
validReceiverIds: receivers?.match(/<@!([0-9]+)>/g),
undefinedReceivers: receivers?.match(/@([a-z0-9]+)/gi),
Expand All @@ -86,11 +73,13 @@ export const forwardHandler: CommandHandler = async (
return;
}

const reason = interaction.options.getString('reason');
if (!reason || reason.length === 0) {
await interaction.editReply(await missingReasonError());
return;
}

const giverAccount = await getUserAccount(praiseGiver);
if (!giverAccount.user) {
await interaction.editReply(await giverNotActivatedError(praiseGiver.user));
return;
Expand Down
43 changes: 12 additions & 31 deletions packages/discord-bot/src/handlers/praise.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ import { UserAccount } from 'api/src/useraccount/types';
import { EventLogTypeKey } from 'api/src/eventlog/types';
import { logEvent } from 'api/src/eventlog/utils';
import logger from 'jet-logger';
import { Message, Util } from 'discord.js';
import { getSetting } from '../utils/getSettings';
import { GuildMember, Message, User, Util } from 'discord.js';
import {
dmError,
invalidReceiverError,
Expand All @@ -14,42 +13,26 @@ import {
notActivatedError,
praiseSuccess,
praiseSuccessDM,
roleError,
roleMentionWarning,
undefinedReceiverWarning,
} from '../utils/praiseEmbeds';

import { assertPraiseGiver } from '../utils/assertPraiseGiver';
import { CommandHandler } from 'src/interfaces/CommandHandler';

export const praiseHandler: CommandHandler = async (
interaction,
responseUrl
) => {
const { guild, channel, member } = interaction;

if (!responseUrl) return;

const { guild, channel, member } = interaction;
if (!guild || !member || !channel) {
await interaction.editReply(await dmError());
return;
}

const praiseGiverRoleID = await getSetting('PRAISE_GIVER_ROLE_ID');
const praiseGiverRole = guild.roles.cache.find(
(r) => r.id === praiseGiverRoleID
);
const praiseGiver = await guild.members.fetch(member.user.id);

if (
praiseGiverRole &&
!praiseGiver.roles.cache.find((r) => r.id === praiseGiverRole?.id)
) {
await interaction.editReply({
embeds: [await roleError(praiseGiverRole, praiseGiver.user)],
});

if (!(await assertPraiseGiver(member as GuildMember, interaction, true)))
return;
}

const ua = {
accountId: member.user.id,
Expand All @@ -58,15 +41,7 @@ export const praiseHandler: CommandHandler = async (
platform: 'DISCORD',
} as UserAccount;

const userAccount = await UserAccountModel.findOneAndUpdate(
{ accountId: ua.accountId },
ua,
{ upsert: true, new: true }
);

const receivers = interaction.options.getString('receivers');
const reason = interaction.options.getString('reason');

const receiverData = {
validReceiverIds: receivers?.match(/<@!?([0-9]+)>/g),
undefinedReceivers: receivers?.match(/[^<]@([a-z0-9]+)/gi),
Expand All @@ -82,11 +57,17 @@ export const praiseHandler: CommandHandler = async (
return;
}

const reason = interaction.options.getString('reason');
if (!reason || reason.length === 0) {
await interaction.editReply(await missingReasonError());
return;
}

const userAccount = await UserAccountModel.findOneAndUpdate(
{ accountId: ua.accountId },
ua,
{ upsert: true, new: true }
);
if (!userAccount.user) {
await interaction.editReply(await notActivatedError());
return;
Expand Down Expand Up @@ -171,15 +152,15 @@ export const praiseHandler: CommandHandler = async (
receiverData.undefinedReceivers
.map((id) => id.replace(/[<>]/, ''))
.join(', '),
praiseGiver.user
member.user as User
)
);
}
if (receiverData.roleMentions) {
await msg.reply(
await roleMentionWarning(
receiverData.roleMentions.join(', '),
praiseGiver.user
member.user as User
)
);
}
Expand Down
23 changes: 10 additions & 13 deletions packages/discord-bot/src/handlers/whoami.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,19 @@ import { UserModel } from 'api/dist/user/entities';
import { UserAccountModel } from 'api/dist/useraccount/entities';
import { CommandInteraction, GuildMember } from 'discord.js';
import { UserState } from '../interfaces/UserState';
import { getSetting } from '../utils/getSettings';
import { getUserAccount } from '../utils/getUserAccount';
import { getStateEmbed } from '../utils/stateEmbed';
import { assertPraiseGiver } from '../utils/assertPraiseGiver';
import { dmError } from '../utils/praiseEmbeds';

export const whoamiHandler = async (
interaction: CommandInteraction
): Promise<void> => {
const { member, guild } = interaction;
if (!member || !guild) return;
if (!guild || !member) {
await interaction.editReply(await dmError());
return;
}

const ua = await getUserAccount(member as GuildMember);

Expand All @@ -21,18 +25,11 @@ export const whoamiHandler = async (
activated: !ua.user ? false : true,
};

const praiseGiverRoleID = await getSetting('PRAISE_GIVER_ROLE_ID');
const praiseGiverRole = guild.roles.cache.find(
(r) => r.id === praiseGiverRoleID
state.hasPraiseGiverRole = await assertPraiseGiver(
member as GuildMember,
interaction,
false
);
const updatedMember = await guild.members.fetch(member.user.id);

if (
praiseGiverRole &&
updatedMember.roles.cache.find((r) => r.id === praiseGiverRole?.id)
) {
state.hasPraiseGiverRole = true;
}

const User = await UserModel.findOne({ _id: ua.user });
state.praiseRoles = User?.roles || [];
Expand Down
55 changes: 55 additions & 0 deletions packages/discord-bot/src/utils/assertPraiseGiver.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { CacheType, CommandInteraction, GuildMember } from 'discord.js';
import { dmError, praiseRoleError } from '../utils/praiseEmbeds';
import { settingValue } from 'api/dist/shared/settings';

export const assertPraiseGiver = async (
praiseGiver: GuildMember,
interaction: CommandInteraction<CacheType>,
sendReply: boolean
): Promise<boolean> => {
const praiseGiverRoleIDRequired = (await settingValue(
'PRAISE_GIVER_ROLE_ID_REQUIRED'
)) as boolean;
const praiseGiverRoleID = (await settingValue(
'PRAISE_GIVER_ROLE_ID'
)) as string;

if (!praiseGiverRoleIDRequired) {
return true;
}

if (praiseGiverRoleID === '0') {
sendReply &&
(await interaction.editReply(
'**❌ No Praise Giver Discord Role ID specified.**'
));
return false;
}

const { guild } = interaction;
if (!guild) {
sendReply && (await interaction.editReply(await dmError()));
return false;
}

const praiseGiverRole = guild.roles.cache.find(
(r) => r.id === praiseGiverRoleID
);
if (!praiseGiverRole) {
sendReply &&
(await interaction.editReply(
'**❌ Unknown Praise Giver Discord Role ID.**'
));
return false;
}

if (!praiseGiver.roles.cache.find((r) => r.id === praiseGiverRole.id)) {
sendReply &&
(await interaction.editReply({
embeds: [await praiseRoleError(praiseGiverRole, praiseGiver.user)],
}));
return false;
}

return true;
};
Loading