Refactor SelfGrantableRoles to use config instead of command management, add max_roles option
This commit is contained in:
parent
6a5e71d7c1
commit
b403db5188
3 changed files with 203 additions and 291 deletions
|
@ -1,38 +0,0 @@
|
|||
import { BaseGuildRepository } from "./BaseGuildRepository";
|
||||
import { getRepository, Repository } from "typeorm";
|
||||
import { SelfGrantableRole } from "./entities/SelfGrantableRole";
|
||||
|
||||
export class GuildSelfGrantableRoles extends BaseGuildRepository {
|
||||
private selfGrantableRoles: Repository<SelfGrantableRole>;
|
||||
|
||||
constructor(guildId) {
|
||||
super(guildId);
|
||||
this.selfGrantableRoles = getRepository(SelfGrantableRole);
|
||||
}
|
||||
|
||||
async getForChannel(channelId: string): Promise<SelfGrantableRole[]> {
|
||||
return this.selfGrantableRoles.find({
|
||||
where: {
|
||||
guild_id: this.guildId,
|
||||
channel_id: channelId,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async delete(channelId: string, roleId: string) {
|
||||
await this.selfGrantableRoles.delete({
|
||||
guild_id: this.guildId,
|
||||
channel_id: channelId,
|
||||
role_id: roleId,
|
||||
});
|
||||
}
|
||||
|
||||
async add(channelId: string, roleId: string, aliases: string[]) {
|
||||
await this.selfGrantableRoles.insert({
|
||||
guild_id: this.guildId,
|
||||
channel_id: channelId,
|
||||
role_id: roleId,
|
||||
aliases,
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
import { Entity, Column, PrimaryColumn } from "typeorm";
|
||||
|
||||
@Entity("self_grantable_roles")
|
||||
export class SelfGrantableRole {
|
||||
@Column()
|
||||
@PrimaryColumn()
|
||||
id: number;
|
||||
|
||||
@Column() guild_id: string;
|
||||
|
||||
@Column() channel_id: string;
|
||||
|
||||
@Column() role_id: string;
|
||||
|
||||
@Column("simple-array") aliases: string[];
|
||||
}
|
|
@ -1,103 +1,161 @@
|
|||
import { decorators as d, IPluginOptions } from "knub";
|
||||
import { GuildSelfGrantableRoles } from "../data/GuildSelfGrantableRoles";
|
||||
import { GuildChannel, Message, Role, TextChannel } from "eris";
|
||||
import { asSingleLine, chunkArray, errorMessage, sorter, successMessage, trimLines } from "../utils";
|
||||
import { asSingleLine, chunkArray, errorMessage, sorter, successMessage, tDeepPartial, trimLines } from "../utils";
|
||||
import { ZeppelinPlugin } from "./ZeppelinPlugin";
|
||||
import * as t from "io-ts";
|
||||
|
||||
const ConfigSchema = t.type({
|
||||
can_manage: t.boolean,
|
||||
const RoleMap = t.record(t.string, t.array(t.string));
|
||||
|
||||
const SelfGrantableRoleEntry = t.type({
|
||||
roles: RoleMap,
|
||||
can_use: t.boolean,
|
||||
can_ignore_cooldown: t.boolean,
|
||||
max_roles: t.number,
|
||||
});
|
||||
const PartialRoleEntry = t.partial(SelfGrantableRoleEntry.props);
|
||||
type TSelfGrantableRoleEntry = t.TypeOf<typeof SelfGrantableRoleEntry>;
|
||||
|
||||
const ConfigSchema = t.type({
|
||||
entries: t.record(t.string, SelfGrantableRoleEntry),
|
||||
});
|
||||
type TConfigSchema = t.TypeOf<typeof ConfigSchema>;
|
||||
|
||||
const PartialConfigSchema = tDeepPartial(ConfigSchema);
|
||||
|
||||
const defaultSelfGrantableRoleEntry: t.TypeOf<typeof PartialRoleEntry> = {
|
||||
can_use: false,
|
||||
can_ignore_cooldown: false,
|
||||
max_roles: 0,
|
||||
};
|
||||
|
||||
export class SelfGrantableRolesPlugin extends ZeppelinPlugin<TConfigSchema> {
|
||||
public static pluginName = "self_grantable_roles";
|
||||
public static showInDocs = false;
|
||||
public static configSchema = ConfigSchema;
|
||||
|
||||
protected selfGrantableRoles: GuildSelfGrantableRoles;
|
||||
|
||||
public static getStaticDefaultOptions(): IPluginOptions<TConfigSchema> {
|
||||
return {
|
||||
config: {
|
||||
can_manage: false,
|
||||
can_use: false,
|
||||
can_ignore_cooldown: false,
|
||||
entries: {},
|
||||
},
|
||||
|
||||
overrides: [
|
||||
{
|
||||
level: ">=50",
|
||||
config: {
|
||||
can_ignore_cooldown: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
level: ">=100",
|
||||
config: {
|
||||
can_manage: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
onLoad() {
|
||||
this.selfGrantableRoles = GuildSelfGrantableRoles.getGuildInstance(this.guildId);
|
||||
protected static preprocessStaticConfig(config: t.TypeOf<typeof PartialConfigSchema>) {
|
||||
for (const [key, entry] of Object.entries(config.entries)) {
|
||||
// Apply default entry config
|
||||
config.entries[key] = { ...defaultSelfGrantableRoleEntry, ...entry };
|
||||
|
||||
// Normalize alias names
|
||||
if (entry.roles) {
|
||||
for (const [roleId, aliases] of Object.entries(entry.roles)) {
|
||||
entry.roles[roleId] = aliases.map(a => a.toLowerCase());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
protected splitRoleNames(roleNames: string[]) {
|
||||
return roleNames
|
||||
.map(v => v.split(/[\s,]+/))
|
||||
.flat()
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
protected normalizeRoleNames(roleNames: string[]) {
|
||||
return roleNames.map(v => v.toLowerCase());
|
||||
}
|
||||
|
||||
protected getApplyingEntries(msg): TSelfGrantableRoleEntry[] {
|
||||
const config = this.getConfigForMsg(msg);
|
||||
return Object.entries(config.entries)
|
||||
.filter(
|
||||
([k, e]) => e.can_use && !(!e.can_ignore_cooldown && this.cooldowns.isOnCooldown(`${k}:${msg.author.id}`)),
|
||||
)
|
||||
.map(pair => pair[1]);
|
||||
}
|
||||
|
||||
protected findMatchingRoles(roleNames, entries: TSelfGrantableRoleEntry[]): string[] {
|
||||
const aliasToRoleId = entries.reduce((map, entry) => {
|
||||
for (const [roleId, aliases] of Object.entries(entry.roles)) {
|
||||
for (const alias of aliases) {
|
||||
map.set(alias, roleId);
|
||||
}
|
||||
}
|
||||
|
||||
return map;
|
||||
}, new Map());
|
||||
|
||||
return roleNames.map(roleName => aliasToRoleId.get(roleName)).filter(Boolean);
|
||||
}
|
||||
|
||||
@d.command("role help", [], {
|
||||
aliases: ["role"],
|
||||
})
|
||||
async roleHelpCmd(msg: Message) {
|
||||
const applyingEntries = this.getApplyingEntries(msg);
|
||||
if (applyingEntries.length === 0) return;
|
||||
|
||||
const allPrimaryAliases = [];
|
||||
for (const entry of applyingEntries) {
|
||||
for (const aliases of Object.values(entry.roles)) {
|
||||
if (aliases[0]) {
|
||||
allPrimaryAliases.push(aliases[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const prefix = this.guildConfig.prefix;
|
||||
const [firstRole, secondRole] = allPrimaryAliases;
|
||||
|
||||
const help1 = asSingleLine(`
|
||||
To give yourself a role, type e.g. \`${prefix}role ${firstRole}\` where **${firstRole}** is the role you want.
|
||||
${secondRole ? `You can also add multiple roles at once, e.g. \`${prefix}role ${firstRole} ${secondRole}\`` : ""}
|
||||
`);
|
||||
|
||||
const help2 = asSingleLine(`
|
||||
To remove a role, type \`!role remove ${firstRole}\`,
|
||||
again replacing **${firstRole}** with the role you want to remove.
|
||||
`);
|
||||
|
||||
const helpMessage = trimLines(`
|
||||
${help1}
|
||||
|
||||
${help2}
|
||||
|
||||
**Roles available to you:**
|
||||
${allPrimaryAliases.join(", ")}
|
||||
`);
|
||||
|
||||
const helpEmbed = {
|
||||
title: "How to get roles",
|
||||
description: helpMessage,
|
||||
color: parseInt("42bff4", 16),
|
||||
};
|
||||
|
||||
msg.channel.createMessage({ embed: helpEmbed });
|
||||
}
|
||||
|
||||
@d.command("role remove", "<roleNames:string...>")
|
||||
@d.permission("can_use")
|
||||
@d.cooldown(2500, "can_ignore_cooldown")
|
||||
async roleRemoveCmd(msg: Message, args: { roleNames: string[] }) {
|
||||
const lock = await this.locks.acquire(`grantableRoles:${msg.author.id}`);
|
||||
|
||||
const channelGrantableRoles = await this.selfGrantableRoles.getForChannel(msg.channel.id);
|
||||
if (channelGrantableRoles.length === 0) {
|
||||
const applyingEntries = this.getApplyingEntries(msg);
|
||||
if (applyingEntries.length === 0) {
|
||||
lock.unlock();
|
||||
return;
|
||||
}
|
||||
|
||||
const nonMatchingRoleNames: string[] = [];
|
||||
const rolesToRemove: Set<Role> = new Set();
|
||||
const roleNames = this.normalizeRoleNames(this.splitRoleNames(args.roleNames));
|
||||
const matchedRoleIds = this.findMatchingRoles(roleNames, applyingEntries);
|
||||
|
||||
// Match given role names with actual grantable roles
|
||||
const roleNames = new Set(
|
||||
args.roleNames
|
||||
.map(n => n.split(/[\s,]+/))
|
||||
.flat()
|
||||
.map(v => v.toLowerCase()),
|
||||
);
|
||||
for (const roleName of roleNames) {
|
||||
let matched = false;
|
||||
|
||||
for (const grantableRole of channelGrantableRoles) {
|
||||
let matchedAlias = false;
|
||||
|
||||
for (const alias of grantableRole.aliases) {
|
||||
const normalizedAlias = alias.toLowerCase();
|
||||
if (roleName === normalizedAlias && this.guild.roles.has(grantableRole.role_id)) {
|
||||
rolesToRemove.add(this.guild.roles.get(grantableRole.role_id));
|
||||
matched = true;
|
||||
matchedAlias = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (matchedAlias) break;
|
||||
}
|
||||
|
||||
if (!matched) {
|
||||
nonMatchingRoleNames.push(roleName);
|
||||
}
|
||||
}
|
||||
const rolesToRemove = Array.from(matchedRoleIds.values()).map(id => this.guild.roles.get(id));
|
||||
const roleIdsToRemove = rolesToRemove.map(r => r.id);
|
||||
|
||||
// Remove the roles
|
||||
if (rolesToRemove.size) {
|
||||
const rolesToRemoveArr = Array.from(rolesToRemove.values());
|
||||
const roleIdsToRemove = rolesToRemoveArr.map(r => r.id);
|
||||
if (rolesToRemove.length) {
|
||||
const newRoleIds = msg.member.roles.filter(roleId => !roleIdsToRemove.includes(roleId));
|
||||
|
||||
try {
|
||||
|
@ -105,10 +163,10 @@ export class SelfGrantableRolesPlugin extends ZeppelinPlugin<TConfigSchema> {
|
|||
roles: newRoleIds,
|
||||
});
|
||||
|
||||
const removedRolesStr = rolesToRemoveArr.map(r => `**${r.name}**`);
|
||||
const removedRolesWord = rolesToRemoveArr.length === 1 ? "role" : "roles";
|
||||
const removedRolesStr = rolesToRemove.map(r => `**${r.name}**`);
|
||||
const removedRolesWord = rolesToRemove.length === 1 ? "role" : "roles";
|
||||
|
||||
if (nonMatchingRoleNames.length) {
|
||||
if (rolesToRemove.length !== roleNames.length) {
|
||||
this.sendSuccessMessage(
|
||||
msg.channel,
|
||||
`<@!${msg.author.id}> Removed ${removedRolesStr.join(", ")} ${removedRolesWord};` +
|
||||
|
@ -133,194 +191,102 @@ export class SelfGrantableRolesPlugin extends ZeppelinPlugin<TConfigSchema> {
|
|||
}
|
||||
|
||||
@d.command("role", "<roleNames:string...>")
|
||||
@d.permission("can_use")
|
||||
@d.cooldown(1500, "can_ignore_cooldown")
|
||||
async roleCmd(msg: Message, args: { roleNames: string[] }) {
|
||||
const lock = await this.locks.acquire(`grantableRoles:${msg.author.id}`);
|
||||
|
||||
const channelGrantableRoles = await this.selfGrantableRoles.getForChannel(msg.channel.id);
|
||||
if (channelGrantableRoles.length === 0) {
|
||||
const applyingEntries = this.getApplyingEntries(msg);
|
||||
if (applyingEntries.length === 0) {
|
||||
lock.unlock();
|
||||
return;
|
||||
}
|
||||
|
||||
const nonMatchingRoleNames: string[] = [];
|
||||
const rolesToGrant: Set<Role> = new Set();
|
||||
const roleNames = this.normalizeRoleNames(this.splitRoleNames(args.roleNames));
|
||||
const matchedRoleIds = this.findMatchingRoles(roleNames, applyingEntries);
|
||||
|
||||
// Match given role names with actual grantable roles
|
||||
const roleNames = new Set(
|
||||
args.roleNames
|
||||
.map(n => n.split(/[\s,]+/))
|
||||
.flat()
|
||||
.map(v => v.toLowerCase()),
|
||||
);
|
||||
const hasUnknownRoles = matchedRoleIds.length !== roleNames.length;
|
||||
|
||||
for (const roleName of roleNames) {
|
||||
let matched = false;
|
||||
const rolesToAdd: Map<string, Role> = Array.from(matchedRoleIds.values())
|
||||
.map(id => this.guild.roles.get(id))
|
||||
.filter(Boolean)
|
||||
.reduce((map, role) => {
|
||||
map.set(role.id, role);
|
||||
return map;
|
||||
}, new Map());
|
||||
|
||||
for (const grantableRole of channelGrantableRoles) {
|
||||
let matchedAlias = false;
|
||||
|
||||
for (const alias of grantableRole.aliases) {
|
||||
const normalizedAlias = alias.toLowerCase();
|
||||
if (roleName === normalizedAlias && this.guild.roles.has(grantableRole.role_id)) {
|
||||
rolesToGrant.add(this.guild.roles.get(grantableRole.role_id));
|
||||
matched = true;
|
||||
matchedAlias = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (matchedAlias) break;
|
||||
}
|
||||
|
||||
if (!matched) {
|
||||
nonMatchingRoleNames.push(roleName);
|
||||
}
|
||||
if (!rolesToAdd.size) {
|
||||
this.sendErrorMessage(
|
||||
msg.channel,
|
||||
`<@!${msg.author.id}> Unknown ${args.roleNames.length === 1 ? "role" : "roles"}`,
|
||||
);
|
||||
lock.unlock();
|
||||
return;
|
||||
}
|
||||
|
||||
// Grant the roles
|
||||
if (rolesToGrant.size) {
|
||||
const rolesToGrantArr = Array.from(rolesToGrant.values());
|
||||
const roleIdsToGrant = rolesToGrantArr.map(r => r.id);
|
||||
const newRoleIds = Array.from(new Set(msg.member.roles.concat(roleIdsToGrant)).values());
|
||||
try {
|
||||
await msg.member.edit({
|
||||
roles: newRoleIds,
|
||||
});
|
||||
const newRoleIds = new Set([...rolesToAdd.keys(), ...msg.member.roles]);
|
||||
|
||||
const grantedRolesStr = rolesToGrantArr.map(r => `**${r.name}**`);
|
||||
const grantedRolesWord = rolesToGrantArr.length === 1 ? "role" : "roles";
|
||||
// Remove extra roles (max_roles) for each entry
|
||||
const skipped: Set<Role> = new Set();
|
||||
const removed: Set<Role> = new Set();
|
||||
|
||||
if (nonMatchingRoleNames.length) {
|
||||
this.sendSuccessMessage(
|
||||
msg.channel,
|
||||
`<@!${msg.author.id}> Granted you the ${grantedRolesStr.join(", ")} ${grantedRolesWord};` +
|
||||
` couldn't recognize the other roles you mentioned`,
|
||||
);
|
||||
} else {
|
||||
this.sendSuccessMessage(
|
||||
msg.channel,
|
||||
`<@!${msg.author.id}> Granted you the ${grantedRolesStr.join(", ")} ${grantedRolesWord}`,
|
||||
);
|
||||
for (const entry of applyingEntries) {
|
||||
if (entry.max_roles === 0) continue;
|
||||
|
||||
let foundRoles = 0;
|
||||
|
||||
for (const roleId of newRoleIds) {
|
||||
if (entry.roles[roleId]) {
|
||||
if (foundRoles < entry.max_roles) {
|
||||
foundRoles++;
|
||||
} else {
|
||||
newRoleIds.delete(roleId);
|
||||
rolesToAdd.delete(roleId);
|
||||
|
||||
if (msg.member.roles.includes(roleId)) {
|
||||
removed.add(this.guild.roles.get(roleId));
|
||||
} else {
|
||||
skipped.add(this.guild.roles.get(roleId));
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
msg.channel.createMessage(
|
||||
errorMessage(`<@!${msg.author.id}> Got an error while trying to grant you the roles`),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
msg.channel.createMessage(
|
||||
errorMessage(`<@!${msg.author.id}> Unknown ${args.roleNames.length === 1 ? "role" : "roles"}`),
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
await msg.member.edit({
|
||||
roles: Array.from(newRoleIds),
|
||||
});
|
||||
} catch (e) {
|
||||
this.sendErrorMessage(msg.channel, `<@!${msg.author.id}> Got an error while trying to grant you the roles`);
|
||||
return;
|
||||
}
|
||||
|
||||
const addedRolesStr = Array.from(rolesToAdd.values()).map(r => `**${r.name}**`);
|
||||
const addedRolesWord = rolesToAdd.size === 1 ? "role" : "roles";
|
||||
|
||||
const messageParts = [];
|
||||
messageParts.push(`Granted you the ${addedRolesStr.join(", ")} ${addedRolesWord}`);
|
||||
|
||||
if (skipped.size || removed.size) {
|
||||
const skippedRolesStr = skipped.size
|
||||
? "skipped " +
|
||||
Array.from(skipped.values())
|
||||
.map(r => `**${r.name}**`)
|
||||
.join(",")
|
||||
: null;
|
||||
const removedRolesStr = removed.size ? "removed " + Array.from(removed.values()).map(r => `**${r.name}**`) : null;
|
||||
|
||||
const skippedRemovedStr = [skippedRolesStr, removedRolesStr].filter(Boolean).join(" and ");
|
||||
|
||||
messageParts.push(`${skippedRemovedStr} due to role limits`);
|
||||
}
|
||||
|
||||
if (hasUnknownRoles) {
|
||||
messageParts.push("couldn't recognize some of the roles");
|
||||
}
|
||||
|
||||
this.sendSuccessMessage(msg.channel, `<@!${msg.author.id}> ${messageParts.join("; ")}`);
|
||||
|
||||
lock.unlock();
|
||||
}
|
||||
|
||||
@d.command("role help", [], {
|
||||
aliases: ["role"],
|
||||
})
|
||||
@d.permission("can_use")
|
||||
@d.cooldown(5000, "can_ignore_cooldown")
|
||||
async roleHelpCmd(msg: Message) {
|
||||
const channelGrantableRoles = await this.selfGrantableRoles.getForChannel(msg.channel.id);
|
||||
if (channelGrantableRoles.length === 0) return;
|
||||
|
||||
const prefix = this.guildConfig.prefix;
|
||||
const firstRole = channelGrantableRoles[0].aliases[0];
|
||||
const secondRole = channelGrantableRoles[1] ? channelGrantableRoles[1].aliases[0] : null;
|
||||
|
||||
const help1 = asSingleLine(`
|
||||
To give yourself a role, type e.g. \`${prefix}role ${firstRole}\` where **${firstRole}** is the role you want.
|
||||
${secondRole ? `You can also add multiple roles at once, e.g. \`${prefix}role ${firstRole} ${secondRole}\`` : ""}
|
||||
`);
|
||||
|
||||
const help2 = asSingleLine(`
|
||||
To remove a role, type \`!role remove ${firstRole}\`,
|
||||
again replacing **${firstRole}** with the role you want to remove.
|
||||
`);
|
||||
|
||||
const helpMessage = trimLines(`
|
||||
${help1}
|
||||
|
||||
${help2}
|
||||
|
||||
**Available roles:**
|
||||
${channelGrantableRoles.map(r => r.aliases[0]).join(", ")}
|
||||
`);
|
||||
|
||||
const helpEmbed = {
|
||||
title: "How to get roles",
|
||||
description: helpMessage,
|
||||
color: parseInt("42bff4", 16),
|
||||
};
|
||||
|
||||
msg.channel.createMessage({ embed: helpEmbed });
|
||||
}
|
||||
|
||||
@d.command("self_grantable_roles add", "<channel:channel> <roleId:string> [aliases:string...]")
|
||||
@d.permission("can_manage")
|
||||
async addSelfGrantableRoleCmd(msg: Message, args: { channel: GuildChannel; roleId: string; aliases?: string[] }) {
|
||||
if (!(args.channel instanceof TextChannel)) {
|
||||
msg.channel.createMessage(errorMessage("Invalid channel (must be a text channel)"));
|
||||
return;
|
||||
}
|
||||
|
||||
const role = this.guild.roles.get(args.roleId);
|
||||
if (!role) {
|
||||
msg.channel.createMessage(errorMessage("Unknown role"));
|
||||
return;
|
||||
}
|
||||
|
||||
const aliases = (args.aliases || []).map(n => n.split(/[\s,]+/)).flat();
|
||||
aliases.push(role.name.replace(/\s+/g, ""));
|
||||
const uniqueAliases = Array.from(new Set(aliases).values());
|
||||
|
||||
// Remove existing self grantable role on that channel, if one exists
|
||||
await this.selfGrantableRoles.delete(args.channel.id, role.id);
|
||||
|
||||
// Add new one
|
||||
await this.selfGrantableRoles.add(args.channel.id, role.id, uniqueAliases);
|
||||
|
||||
this.sendSuccessMessage(msg.channel, `Self-grantable role **${role.name}** added to **#${args.channel.name}**`);
|
||||
}
|
||||
|
||||
@d.command("self_grantable_roles delete", "<channel:channel> <roleId:string>")
|
||||
@d.permission("can_manage")
|
||||
async deleteSelfGrantableRoleCmd(msg: Message, args: { channel: GuildChannel; roleId: string }) {
|
||||
await this.selfGrantableRoles.delete(args.channel.id, args.roleId);
|
||||
|
||||
const roleName = this.guild.roles.has(args.roleId) ? this.guild.roles.get(args.roleId).name : args.roleId;
|
||||
|
||||
this.sendSuccessMessage(msg.channel, `Self-grantable role **${roleName}** removed from **#${args.channel.name}**`);
|
||||
}
|
||||
|
||||
@d.command("self_grantable_roles", "<channel:channel>")
|
||||
@d.permission("can_manage")
|
||||
async selfGrantableRolesCmd(msg: Message, args: { channel: GuildChannel }) {
|
||||
if (!(args.channel instanceof TextChannel)) {
|
||||
msg.channel.createMessage(errorMessage("Invalid channel (must be a text channel)"));
|
||||
return;
|
||||
}
|
||||
|
||||
const channelGrantableRoles = await this.selfGrantableRoles.getForChannel(args.channel.id);
|
||||
if (channelGrantableRoles.length === 0) {
|
||||
msg.channel.createMessage(errorMessage(`No self-grantable roles on **#${args.channel.name}**`));
|
||||
return;
|
||||
}
|
||||
|
||||
channelGrantableRoles.sort(sorter(gr => gr.aliases.join(", ")));
|
||||
|
||||
const longestId = channelGrantableRoles.reduce((longest, gr) => Math.max(longest, gr.role_id.length), 0);
|
||||
const lines = channelGrantableRoles.map(gr => {
|
||||
const paddedId = gr.role_id.padEnd(longestId, " ");
|
||||
return `${paddedId} ${gr.aliases.join(", ")}`;
|
||||
});
|
||||
|
||||
const batches = chunkArray(lines, 20);
|
||||
for (const batch of batches) {
|
||||
await msg.channel.createMessage(`\`\`\`js\n${batch.join("\n")}\n\`\`\``);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue