Skip to content

Commit

Permalink
Merge pull request #97 from prsm/develop
Browse files Browse the repository at this point in the history
  • Loading branch information
jonasschultheiss authored Sep 6, 2022
2 parents 98c5432 + dbd4a11 commit c22dbc8
Show file tree
Hide file tree
Showing 18 changed files with 202 additions and 281 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,8 @@ Deploy image with a postgres db and see the slash commands.

- [x] Init
- [x] Events
- [ ] Roles
- [ ] Channels
- [x] Roles
- [x] Channels
- [ ] Jira integration

See the [open issues](https://github.com/prsm/crystal-nest/issues) for a full list of proposed features (and known issues).
Expand Down
1 change: 1 addition & 0 deletions renovate.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"enabled": true,
"recreateClosed": true,
"rebaseStalePrs": true,
"automerge": true,
"branchTopic": "lock-file-maintenance",
"commitMessageAction": "Lock file maintenance",
"schedule": ["after 10pm and before 5:00am every day"],
Expand Down
4 changes: 2 additions & 2 deletions src/bot/dynamic-roles/commands/dynamic-role.command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import { UpdateDynamicRoleSubCommand } from './sub-commands/update.sub-command';
description: 'Dynamic roles. Used for accessing hidden channels',
include: [
CreateDynamicRoleSubCommand,
// UpdateDynamicRoleSubCommand,
// DeleteDynamicRoleSubCommand,
UpdateDynamicRoleSubCommand,
DeleteDynamicRoleSubCommand,
SelectDynamicRolesSubCommand
]
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,13 @@ import {
SubCommand,
TransformedCommandExecutionContext,
UseFilters,
UseGuards,
UsePipes
} from '@discord-nestjs/core';
import { Logger } from '@nestjs/common';
import { DynamicRole } from '@prisma/client';
import { roleMention } from 'discord.js';
import { InteractionFromPermittedUserGuard } from 'src/bot/role.guard';
import { CommandValidationFilter } from '../../../filter/command-validation.filter';
import { PrismaExceptionFilter } from '../../../filter/prisma-exception.filter';
import { CreateDynamicRoleDto } from '../../dto/create-dynamic-role.dto';
Expand All @@ -19,6 +23,7 @@ import { DynamicRolesService } from '../../dynamic-roles.service';
})
@UsePipes(TransformPipe, ValidationPipe)
@UseFilters(CommandValidationFilter, PrismaExceptionFilter)
@UseGuards(InteractionFromPermittedUserGuard)
export class CreateDynamicRoleSubCommand implements DiscordTransformedCommand<CreateDynamicRoleDto> {
private readonly logger: Logger;

Expand All @@ -30,7 +35,7 @@ export class CreateDynamicRoleSubCommand implements DiscordTransformedCommand<Cr
@Payload() createDynamicRoleDto: CreateDynamicRoleDto,
{ interaction }: TransformedCommandExecutionContext
): Promise<void> {
let dynamicRole;
let dynamicRole: DynamicRole;
const createdBy = interaction.member.user.id;
try {
dynamicRole = await this.dynamicRolesService.create(createdBy, createDynamicRoleDto);
Expand All @@ -41,7 +46,7 @@ export class CreateDynamicRoleSubCommand implements DiscordTransformedCommand<Cr
return;
}

const loggingString = `Successfully created dynamic role with name ${dynamicRole.name}`;
const loggingString = `Successfully created dynamic role ${roleMention(dynamicRole.roleId)}`;
this.logger.log(loggingString);
await interaction.reply({ content: loggingString, ephemeral: true });
}
Expand Down
20 changes: 15 additions & 5 deletions src/bot/dynamic-roles/commands/sub-commands/delete.sub-command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@ import {
SubCommand,
TransformedCommandExecutionContext,
UseFilters,
UseGuards,
UsePipes
} from '@discord-nestjs/core';
import { Logger } from '@nestjs/common';
import { PrismaNotFoundExceptionFilter } from 'src/bot/filter/prisma-not-found.filter';
import { InteractionFromPermittedUserGuard } from 'src/bot/role.guard';
import { CommandValidationFilter } from '../../../filter/command-validation.filter';
import { PrismaExceptionFilter } from '../../../filter/prisma-exception.filter';
import { DeleteDynamicRoleDto } from '../../dto/delete-dynamic-role.dto';
Expand All @@ -18,7 +21,8 @@ import { DynamicRolesService } from '../../dynamic-roles.service';
description: 'Deletes a dynamic role'
})
@UsePipes(TransformPipe, ValidationPipe)
@UseFilters(CommandValidationFilter, PrismaExceptionFilter)
@UseFilters(CommandValidationFilter, PrismaExceptionFilter, PrismaNotFoundExceptionFilter)
@UseGuards(InteractionFromPermittedUserGuard)
export class DeleteDynamicRoleSubCommand implements DiscordTransformedCommand<DeleteDynamicRoleDto> {
private readonly logger: Logger;

Expand All @@ -30,9 +34,15 @@ export class DeleteDynamicRoleSubCommand implements DiscordTransformedCommand<De
@Payload() { name }: DeleteDynamicRoleDto,
{ interaction }: TransformedCommandExecutionContext
): Promise<void> {
const dynamicRole = await this.dynamicRolesService.delete(name);
const loggingString = `Successfully deleted dynamic role with name ${dynamicRole.name}`;
this.logger.log(loggingString);
await interaction.reply({ content: loggingString, ephemeral: true });
try {
const dynamicRole = await this.dynamicRolesService.delete(name);
const loggingString = `Successfully deleted dynamic role with name ${dynamicRole.name}`;
this.logger.log(loggingString);
await interaction.reply({ content: loggingString, ephemeral: true });
} catch (error) {
const loggingString = `Failed to delete dynamic role`;
this.logger.log(loggingString);
await interaction.reply({ content: loggingString, ephemeral: true });
}
}
}
24 changes: 9 additions & 15 deletions src/bot/dynamic-roles/commands/sub-commands/select.sub-command.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { CommandExecutionContext, DiscordCommand, SubCommand, UseCollectors, UseFilters } from '@discord-nestjs/core';
import { DiscordCommand, SubCommand, UseCollectors, UseFilters } from '@discord-nestjs/core';
import { Logger } from '@nestjs/common';
import { ActionRowBuilder, ButtonBuilder, ButtonStyle, CommandInteraction, GuildMemberRoleManager } from 'discord.js';
import { CommandInteraction, GuildMemberRoleManager } from 'discord.js';
import { PrismaExceptionFilter } from '../../../filter/prisma-exception.filter';
import { DynamicRolesCollector } from '../../dynamic-roles.collector';
import { DynamicRolesService } from '../../dynamic-roles.service';
Expand All @@ -18,19 +18,13 @@ export class SelectDynamicRolesSubCommand implements DiscordCommand {
this.logger = new Logger(SelectDynamicRolesSubCommand.name);
}

async handler(interaction: CommandInteraction, executionContext: CommandExecutionContext): Promise<void> {
const roles = await this.dynamicRolesService.findAll();
const components = new ActionRowBuilder<ButtonBuilder>();
async handler(interaction: CommandInteraction): Promise<void> {
const roleManager = interaction.member.roles as GuildMemberRoleManager;
for (const { name, emoji, roleId } of roles) {
const button = new ButtonBuilder()
.setLabel(name)
.setEmoji(emoji)
.setCustomId(name)
.setStyle(roleManager.cache.some(role => role.id === roleId) ? ButtonStyle.Primary : ButtonStyle.Secondary);
components.addComponents(button);
}

await interaction.reply({ content: 'AMk, select role', ephemeral: true, components: [components] });
const components = await this.dynamicRolesService.createButtonComponents(roleManager);
await interaction.reply({
content: 'Bitte wähle aus, welche Rollen du haben möchtest\nPlease select which roles you would like to have',
ephemeral: true,
components: [components]
});
}
}
21 changes: 16 additions & 5 deletions src/bot/dynamic-roles/commands/sub-commands/update.sub-command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,13 @@ import {
SubCommand,
TransformedCommandExecutionContext,
UseFilters,
UseGuards,
UsePipes
} from '@discord-nestjs/core';
import { Logger } from '@nestjs/common';
import { roleMention } from 'discord.js';
import { PrismaNotFoundExceptionFilter } from 'src/bot/filter/prisma-not-found.filter';
import { InteractionFromPermittedUserGuard } from 'src/bot/role.guard';
import { CommandValidationFilter } from '../../../filter/command-validation.filter';
import { PrismaExceptionFilter } from '../../../filter/prisma-exception.filter';
import { UpdateDynamicRoleDto } from '../../dto/update-dynamic-role.dto';
Expand All @@ -18,7 +22,8 @@ import { DynamicRolesService } from '../../dynamic-roles.service';
description: 'Updates an existing dynamic role'
})
@UsePipes(TransformPipe, ValidationPipe)
@UseFilters(CommandValidationFilter, PrismaExceptionFilter)
@UseFilters(CommandValidationFilter, PrismaExceptionFilter, PrismaNotFoundExceptionFilter)
@UseGuards(InteractionFromPermittedUserGuard)
export class UpdateDynamicRoleSubCommand implements DiscordTransformedCommand<UpdateDynamicRoleDto> {
private readonly logger: Logger;

Expand All @@ -30,9 +35,15 @@ export class UpdateDynamicRoleSubCommand implements DiscordTransformedCommand<Up
@Payload() updateDynamicRoleDto: UpdateDynamicRoleDto,
{ interaction }: TransformedCommandExecutionContext
): Promise<void> {
const dynamicRole = await this.dynamicRolesService.update(updateDynamicRoleDto);
const loggingString = `Successfully updated dynamic role with name ${dynamicRole.name}`;
this.logger.log(loggingString);
await interaction.reply({ content: loggingString, ephemeral: true });
try {
const dynamicRole = await this.dynamicRolesService.update(updateDynamicRoleDto);
const loggingString = `Successfully updated dynamic role ${roleMention(dynamicRole.roleId)}`;
this.logger.log(loggingString);
await interaction.reply({ content: loggingString, ephemeral: true });
} catch (error) {
const loggingString = `Failed to update dynamic role`;
this.logger.log(loggingString);
await interaction.reply({ content: loggingString, ephemeral: true });
}
}
}
2 changes: 1 addition & 1 deletion src/bot/dynamic-roles/dto/create-dynamic-role.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { HexColorString } from 'discord.js';
export class CreateDynamicRoleDto {
@MinLength(3)
@MaxLength(18)
@Transform(({ value }) => value.toLowerCase())
@Transform(({ value }) => (value ? value.toLowerCase() : value))
@Param({
name: 'name',
description: 'The name of the role',
Expand Down
4 changes: 2 additions & 2 deletions src/bot/dynamic-roles/dto/update-dynamic-role.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export class UpdateDynamicRoleDto {
@IsNotEmpty()
@MinLength(3)
@MaxLength(18)
@Transform(({ value }) => value.toLowerCase())
@Transform(({ value }) => (value ? value.toLowerCase() : value))
@Param({
name: 'name',
description: 'The name of the role',
Expand All @@ -19,7 +19,7 @@ export class UpdateDynamicRoleDto {
@IsOptional()
@MinLength(3)
@MaxLength(18)
@Transform(({ value }) => value.toLowerCase())
@Transform(({ value }) => (value ? value.toLowerCase() : value))
@Param({
name: 'new-name',
description: 'A new name for the role',
Expand Down
8 changes: 5 additions & 3 deletions src/bot/dynamic-roles/dynamic-roles.collector.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { InteractionEventCollector, On } from '@discord-nestjs/core';
import { Logger } from '@nestjs/common';
import { ButtonInteraction, ComponentType } from 'discord.js';
import { ButtonInteraction, ComponentType, GuildMemberRoleManager } from 'discord.js';
import { DynamicRolesService } from './dynamic-roles.service';

@InteractionEventCollector({ time: 3 * 1000, componentType: ComponentType.Button })
@InteractionEventCollector({ time: 60 * 1000, componentType: ComponentType.Button })
export class DynamicRolesCollector {
private readonly logger: Logger;
constructor(private readonly dynamicRolesService: DynamicRolesService) {
Expand All @@ -12,8 +12,10 @@ export class DynamicRolesCollector {

@On('collect')
async onCollect(interaction: ButtonInteraction): Promise<void> {
const roleManager = interaction.member.roles as GuildMemberRoleManager;
const loggingString = await this.dynamicRolesService.handleRoleChange(interaction);
const updatedComponents = await this.dynamicRolesService.createButtonComponents(roleManager);
this.logger.log(loggingString);
interaction.update({ content: loggingString, components: [] });
interaction.update({ content: loggingString, components: [updatedComponents] });
}
}
82 changes: 67 additions & 15 deletions src/bot/dynamic-roles/dynamic-roles.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,22 @@ import { Injectable, Logger } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { DynamicRole } from '@prisma/client';
import {
ActionRowBuilder,
ButtonBuilder,
ButtonInteraction,
ButtonStyle,
ChannelType,
Client,
GuildChannel,
GuildMember,
GuildMemberRoleManager,
GuildPremiumTier,
HexColorString,
PermissionsBitField,
Role,
TextChannel
roleMention,
TextChannel,
userMention
} from 'discord.js';

import { GuildService } from '../guild.service';
Expand All @@ -34,17 +40,32 @@ export class DynamicRolesService extends GuildService {
this.logger = new Logger(DynamicRolesService.name);
}

async createButtonComponents(roleManager: GuildMemberRoleManager): Promise<ActionRowBuilder<ButtonBuilder>> {
const components = new ActionRowBuilder<ButtonBuilder>();
const roles = await this.findAll();
for (const { name, emoji, roleId } of roles) {
const button = new ButtonBuilder()
.setLabel(name)
.setEmoji(emoji)
.setCustomId(name)
.setStyle(roleManager.cache.some(role => role.id === roleId) ? ButtonStyle.Primary : ButtonStyle.Secondary);
components.addComponents(button);
}

return components;
}

async handleRoleChange(interaction: ButtonInteraction): Promise<string> {
const { roleId, name } = await this.dynamicRolesRepository.findOneByName(interaction.customId);
const member = interaction.member as GuildMember;
if (member.roles.cache.some(role => role.id === roleId)) {
await member.roles.remove(roleId);
this.dynamicRolesRepository.removeSubscriber(name);
return `Removed role ${name} from member ${member.id}`;
return `Removed role ${roleMention(roleId)} from member ${userMention(member.id)}`;
} else {
await member.roles.add(roleId);
this.dynamicRolesRepository.addSubscriber(name);
return `Added the role ${name} to member ${member.id}`;
return `Added the role ${roleMention(roleId)} to member ${userMention(member.id)}`;
}
}

Expand Down Expand Up @@ -89,24 +110,30 @@ export class DynamicRolesService extends GuildService {
}

async update(updateDynamicRoleDto: UpdateDynamicRoleDto): Promise<DynamicRole> {
const guild = await this.getGuild();
const { name, newName, emoji, shortDescription } = updateDynamicRoleDto;
const { name, newName, emoji, shortDescription, color } = updateDynamicRoleDto;
const currentDynamicRole = await this.dynamicRolesRepository.findOneByName(name);
const currentChannel = await guild.channels.fetch(currentDynamicRole.channelId);
const currentRole = await guild.roles.fetch(currentDynamicRole.roleId);
try {
await this.updateChannel(
currentChannel.id,
newName || currentDynamicRole.name,
emoji || currentDynamicRole.emoji,
shortDescription
);
} catch (error) {}
await this.updateChannel(
currentDynamicRole.channelId,
newName || currentDynamicRole.name,
emoji || currentDynamicRole.emoji,
shortDescription
);

await this.updateRole(
currentDynamicRole.roleId,
newName || currentDynamicRole.name,
color || (currentDynamicRole.color as HexColorString),
emoji || currentDynamicRole.emoji
);

return this.dynamicRolesRepository.update(updateDynamicRoleDto);
}

async delete(name: string): Promise<DynamicRole> {
const { name: confirmedName, channelId, roleId } = await this.dynamicRolesRepository.findOneByName(name);
const guild = await this.getGuild();
await guild.roles.delete(roleId);
await this.retireChannel(channelId, confirmedName);
return this.dynamicRolesRepository.remove(name);
}

Expand Down Expand Up @@ -199,4 +226,29 @@ export class DynamicRolesService extends GuildService {
position
});
}

private async retireChannel(channelId: string, name: string): Promise<GuildChannel> {
const guild = await this.getGuild();
const parent = this.getRetiredDynamicRolesCategoryId();
return guild.channels.edit(channelId, {
name,
parent,
permissionOverwrites: [
{
id: guild.id,
deny: [PermissionsBitField.Flags.ViewChannel, PermissionsBitField.Flags.SendMessages]
},
{
id: this.getPlayerRoleId(),
allow: [PermissionsBitField.Flags.ViewChannel],
deny: [PermissionsBitField.Flags.SendMessages]
},
{
id: this.getMemberRoleId(),
allow: [PermissionsBitField.Flags.ViewChannel],
deny: [PermissionsBitField.Flags.SendMessages]
}
]
});
}
}
Loading

0 comments on commit c22dbc8

Please sign in to comment.