Skip to content

Commit

Permalink
feat(reaction-roles): Add reaction roles
Browse files Browse the repository at this point in the history
  • Loading branch information
Marco (Valandur) committed Dec 1, 2019
1 parent 1b0cd72 commit be67b2e
Show file tree
Hide file tree
Showing 11 changed files with 262 additions and 22 deletions.
19 changes: 18 additions & 1 deletion i18n/bot/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -816,7 +816,8 @@
"description": "Purge messages in a channel up until a specified message."
},
"text": "Deleted {{ amount }} messages.\nThis message will be deleted in 5 seconds.",
"title": "Delete Messages"
"title": "Delete Messages",
"inProgress": "cmd.purgeUntil.inProgress"
},
"ranks": {
"entry": "{{{ role }}}: **{{ numInvites }} invites** {{ description }}",
Expand Down Expand Up @@ -1129,6 +1130,22 @@
"self": {
"description": "cmd.fixRanks.self.description"
}
},
"placeholder": {
"self": {
"flags": {
"edit": "cmd.placeholder.self.flags.edit"
},
"args": {
"message": "cmd.placeholder.self.args.message"
},
"description": "cmd.placeholder.self.description"
},
"noMessageFoundInDatabase": "cmd.placeholder.noMessageFoundInDatabase"
},
"reactionRole": {
"noMessageFoundInDatabase": "cmd.reactionRole.noMessageFoundInDatabase",
"unknownEmojiBotNeedsToBeInGuildWithEmoji": "cmd.reactionRole.unknownEmojiBotNeedsToBeInGuildWithEmoji"
}
},
"JOIN_LEAVE_EMBEDS_IS_PREMIUM": "Using an embed as your join/leave message is a premium feature. Premium does not seem to be active on this server. Please contact us to learn more about how to purchase premium.",
Expand Down
26 changes: 24 additions & 2 deletions scripts/db/setup_dbx.sql
Original file line number Diff line number Diff line change
Expand Up @@ -353,7 +353,24 @@ CREATE TABLE `messages` (
`guildId` varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL,
`createdAt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updatedAt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`content` text COLLATE utf8mb4_unicode_ci
`content` text COLLATE utf8mb4_unicode_ci,
`embeds` json DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- --------------------------------------------------------

--
-- Table structure for table `reactionRoles`
--

CREATE TABLE `reactionRoles` (
`channelId` varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL,
`guildId` varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL,
`messageId` varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL,
`roleId` varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL,
`emoji` varchar(255) DEFAULT NULL,
`createdAt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updatedAt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- --------------------------------------------------------
Expand Down Expand Up @@ -530,9 +547,14 @@ ALTER TABLE `strikes`
--
ALTER TABLE `messages`
ADD PRIMARY KEY (`guildId`, `channelId`, `id`),
ADD KEY `channelId` (`channelId`),
ADD KEY `id` (`id`);

--
-- Indexes for table `reactionRoles`
--
ALTER TABLE `reactionRoles`
ADD PRIMARY KEY (`guildId`, `channelId`, `messageId`, `emoji`);

/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
8 changes: 7 additions & 1 deletion src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import { RanksCache } from './invites/cache/RanksCache';
import { CaptchaService } from './invites/services/Captcha';
import { InvitesService } from './invites/services/Invites';
import { TrackingService } from './invites/services/Tracking';
import { ReactionRoleCache } from './management/cache/ReactionRoleCache';
import { ManagementService } from './management/services/ManagementService';
import { PunishmentCache } from './moderation/cache/PunishmentsCache';
import { StrikesCache } from './moderation/cache/StrikesCache';
import { ModerationService } from './moderation/services/Moderation';
Expand Down Expand Up @@ -69,6 +71,7 @@ export interface ClientCacheObject {
guilds: GuildSettingsCache;
strikes: StrikesCache;
music: MusicCache;
reactionRoles: ReactionRoleCache;
}

export class IMClient extends Client {
Expand Down Expand Up @@ -96,6 +99,7 @@ export class IMClient extends Client {
public music: MusicService;
public tracking: TrackingService;
public premium: PremiumService;
public management: ManagementService;

public startedAt: Moment;
public gatewayConnected: boolean;
Expand Down Expand Up @@ -158,7 +162,8 @@ export class IMClient extends Client {
punishments: new PunishmentCache(this),
guilds: new GuildSettingsCache(this),
strikes: new StrikesCache(this),
music: new MusicCache(this)
music: new MusicCache(this),
reactionRoles: new ReactionRoleCache(this)
};
this.rabbitmq = new RabbitMqService(this);
this.msg = new MessagingService(this);
Expand All @@ -170,6 +175,7 @@ export class IMClient extends Client {
this.tracking = new TrackingService(this);
this.music = new MusicService(this);
this.premium = new PremiumService(this);
this.management = new ManagementService(this);

// Services
this.cmds.init();
Expand Down
30 changes: 28 additions & 2 deletions src/framework/services/DatabaseService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { IMClient } from '../../client';
import { CustomInvite } from '../../invites/models/CustomInvite';
import { Rank } from '../../invites/models/Rank';
import { Message } from '../../management/models/Message';
import { ReactionRole } from '../../management/models/ReactionRole';
import { Punishment } from '../../moderation/models/Punishment';
import { PunishmentConfig, PunishmentType } from '../../moderation/models/PunishmentConfig';
import { Strike } from '../../moderation/models/Strike';
Expand Down Expand Up @@ -57,6 +58,7 @@ enum TABLE {
punishmentConfigs = '`punishmentConfigs`',
punishments = '`punishments`',
ranks = '`ranks`',
reactionRoles = '`reactionRoles`',
rolePermissions = '`rolePermissions`',
roles = '`roles`',
scheduledActions = '`scheduledActions`',
Expand Down Expand Up @@ -1015,13 +1017,37 @@ export class DatabaseService {
public async saveMessage(message: Partial<Message>) {
return this.insertOrUpdate(
TABLE.messages,
['guildId', 'channelId', 'id', 'content'],
['content'],
['guildId', 'channelId', 'id', 'content', 'embeds'],
['content', 'embeds'],
[message],
m => m.guildId
);
}

// ------------------
// Reaction roles
// ------------------
public async getReactionRolesForGuild(guildId: string) {
return this.findMany<ReactionRole>(guildId, TABLE.reactionRoles, '`guildId` = ?', [guildId]);
}
public async saveReactionRole(reactionRole: Partial<ReactionRole>) {
return this.insertOrUpdate(
TABLE.reactionRoles,
['guildId', 'channelId', 'messageId', 'emoji', 'roleId'],
['roleId'],
[reactionRole],
r => r.guildId
);
}
public async removeReactionRole(guildId: string, channelId: string, messageId: string, emoji: string) {
await this.delete(
guildId,
TABLE.reactionRoles,
'`guildId` = ? AND `channelId` = ? AND `messageId` = ? AND `emoji` = ?',
[guildId, channelId, messageId, emoji]
);
}

// ----------------------
// DB Sync
// ----------------------
Expand Down
12 changes: 12 additions & 0 deletions src/management/cache/ReactionRoleCache.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Cache } from '../../framework/cache/Cache';
import { ReactionRole } from '../models/ReactionRole';

export class ReactionRoleCache extends Cache<ReactionRole[]> {
public async init() {
// TODO
}

protected async _get(guildId: string): Promise<ReactionRole[]> {
return this.client.db.getReactionRolesForGuild(guildId);
}
}
38 changes: 22 additions & 16 deletions src/management/commands/messages/placeholder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,17 @@ import { Message } from 'eris';

import { IMClient } from '../../../client';
import { Command, Context } from '../../../framework/commands/Command';
import { EnumResolver, StringResolver } from '../../../framework/resolvers';
import { StringResolver } from '../../../framework/resolvers';
import { CommandGroup, GuildPermission, ManagementCommand } from '../../../types';

enum PlaceholderMode {
create = 'create',
edit = 'edit'
}
const THUMBS_UP = '👍';

export default class extends Command {
public constructor(client: IMClient) {
super(client, {
name: ManagementCommand.placeholder,
aliases: ['ph'],
args: [
{
name: 'mode',
resolver: new EnumResolver(client, Object.values(PlaceholderMode)),
required: true
},
{
name: 'message',
resolver: StringResolver,
Expand All @@ -45,8 +37,8 @@ export default class extends Command {
public async action(
message: Message,
[placeholder]: [string],
{ messageId }: { messageId: string },
{ t, me, guild }: Context
{ edit: messageId }: { edit: string },
{ t, guild }: Context
): Promise<any> {
if (!messageId) {
if (!placeholder) {
Expand All @@ -59,23 +51,37 @@ export default class extends Command {
guildId: guild.id,
channelId: newMessage.channel.id,
id: newMessage.id,
content: newMessage.content
content: newMessage.content,
embeds: newMessage.embeds
});

return;
}

const dbMessage = await this.client.db.getMessageById(guild.id, messageId);

if (!dbMessage) {
return this.sendReply(message, t('cmd.placeholder.noMessageFoundInDatabase'));
}

if (!placeholder) {
// Return current message
await this.sendReply(message, dbMessage.content);

const msg = dbMessage.content || dbMessage.embeds[0];
await this.sendReply(message, msg);
return;
}

// Edit message
const embed = this.createEmbed({ description: placeholder });
await this.client.editMessage(dbMessage.channelId, dbMessage.id, { embed });
const editMessage = await this.client.editMessage(dbMessage.channelId, dbMessage.id, { embed });
await this.client.db.saveMessage({
guildId: guild.id,
channelId: editMessage.channel.id,
id: editMessage.id,
content: editMessage.content,
embeds: editMessage.embeds
});

await this.client.addMessageReaction(message.channel.id, message.id, THUMBS_UP);
}
}
90 changes: 90 additions & 0 deletions src/management/commands/roles/reactionRoles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { Message, Role } from 'eris';

import { IMClient } from '../../../client';
import { Command, Context } from '../../../framework/commands/Command';
import { BooleanResolver, RoleResolver, StringResolver } from '../../../framework/resolvers';
import { CommandGroup, GuildPermission, ManagementCommand } from '../../../types';

const THUMBS_UP = '👍';
const CUSTOM_EMOJI_REGEX = /<:(\w+):(\d+)>/;

export default class extends Command {
public constructor(client: IMClient) {
super(client, {
name: ManagementCommand.reactionRole,
aliases: ['rr'],
args: [
{
name: 'messageId',
resolver: StringResolver,
required: true
},
{
name: 'emoji',
resolver: StringResolver,
required: true
},
{
name: 'role',
resolver: RoleResolver,
required: false
}
],
flags: [
{
name: 'remove',
resolver: BooleanResolver,
short: 'r'
}
],
group: CommandGroup.Other,
botPermissions: [GuildPermission.MANAGE_MESSAGES],
guildOnly: true,
defaultAdminOnly: true
});
}

public async action(
message: Message,
[messageId, emoji, role]: [string, string, Role],
{ remove }: { remove: boolean },
{ t, guild }: Context
): Promise<any> {
const dbMessage = await this.client.db.getMessageById(guild.id, messageId);

if (!dbMessage) {
return this.sendReply(message, t('cmd.reactionRole.noMessageFoundInDatabase'));
}

const matches = emoji.match(CUSTOM_EMOJI_REGEX);
const emojiId = matches ? `${matches[1]}:${matches[2]}` : emoji;

if (remove) {
await this.client.db.removeReactionRole(dbMessage.guildId, dbMessage.channelId, dbMessage.id, emojiId);
await this.client.removeMessageReaction(dbMessage.channelId, dbMessage.id, emojiId);
} else {
const reactionRole = {
guildId: dbMessage.guildId,
channelId: dbMessage.channelId,
messageId: dbMessage.id,
roleId: role.id,
emoji: emojiId
};

try {
await this.client.addMessageReaction(dbMessage.channelId, dbMessage.id, emojiId);
await this.client.db.saveReactionRole(reactionRole);
} catch (error) {
if (error.code === 10014) {
await this.sendReply(message, t('cmd.reactionRole.unknownEmoji'));
} else {
throw error;
}
}
}

this.client.cache.reactionRoles.flush(guild.id);

await this.client.addMessageReaction(message.channel.id, message.id, THUMBS_UP);
}
}
1 change: 1 addition & 0 deletions src/management/models/Message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ export class Message {
public createdAt: Date;
public updatedAt: Date;
public content: string;
public embeds: any;
}
10 changes: 10 additions & 0 deletions src/management/models/ReactionRole.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export class ReactionRole {
public id: number;
public messageId: string;
public channelId: string;
public guildId: string;
public createdAt: Date;
public updatedAt: Date;
public roleId: string;
public emoji: string;
}
Loading

0 comments on commit be67b2e

Please sign in to comment.