colors & pronouns

This commit is contained in:
Jill 2022-07-16 22:24:13 +03:00
parent c34497c61a
commit 4cdd583eea
9 changed files with 237 additions and 33 deletions

View File

@ -22,6 +22,7 @@
"@types/parse-color": "^1.0.1",
"@typescript-eslint/eslint-plugin": "^5.27.1",
"@typescript-eslint/parser": "^5.27.1",
"discord-api-types": "^0.36.2",
"eslint": "^8.17.0",
"typescript": "4.8.0-dev.20220606"
}

View File

@ -6,6 +6,7 @@ specifiers:
'@types/parse-color': ^1.0.1
'@typescript-eslint/eslint-plugin': ^5.27.1
'@typescript-eslint/parser': ^5.27.1
discord-api-types: ^0.36.2
discord.js: ^13.8.0
eslint: ^8.17.0
parse-color: ^1.0.0
@ -23,6 +24,7 @@ devDependencies:
'@types/parse-color': 1.0.1
'@typescript-eslint/eslint-plugin': 5.27.1_555veff7pbs3zkovuk7bd6t3v4
'@typescript-eslint/parser': 5.27.1_tk6tc2atvak5hdwmpl7y3anzei
discord-api-types: 0.36.2
eslint: 8.17.0
typescript: 4.8.0-dev.20220606
@ -461,6 +463,10 @@ packages:
resolution: {integrity: sha512-Y6RMvXsHKiBgQhm/q5MgRieXc4Tzh5p/JuDyqreI48lmy+AQfO+g9Xhz0tuGBaN1FtsrLT7mD+lbFONPo5vdwA==}
dev: false
/discord-api-types/0.36.2:
resolution: {integrity: sha512-TunPAvzwneK/m5fr4hxH3bMsrtI22nr9yjfHyo5NBGMjpsAauGNiGCmwoFf0oO3jSd2mZiKUvZwCKDaB166u2Q==}
dev: true
/discord.js/13.8.0:
resolution: {integrity: sha512-EPAA/2VLycYN5wSzavqa4iJ6qj3UtQFtHw5TH/60Fj29ymfEsCQVn//o1mTpwDxzwb+rPIrWhkxKIGGnjfv0Iw==}
engines: {node: '>=16.6.0', npm: '>=7.0.0'}

View File

@ -42,7 +42,12 @@ module.exports = {
data: new SlashCommandBuilder()
.setName('change')
.setDescription('Change')
.addStringOption((option) => option.setName('what').setDescription('Change what? Examples include: "environment", "perspective", etc...').setRequired(true)),
.addStringOption((option) =>
option
.setName('what')
.setDescription('Change what? Examples include: "environment", "perspective", etc...')
.setRequired(true)
),
execute: async (interaction: CommandInteraction, member: GuildMember) => {
const what = interaction.options.getString('what')!;

View File

@ -1,10 +1,9 @@
import { SlashCommandBuilder } from '@discordjs/builders';
import { CommandInteraction, Guild, GuildMember } from 'discord.js';
import { default as parseColor } from 'parse-color';
import { CommandInteraction, CreateRoleOptions, GuildMember, Message, MessageActionRow, MessageButton, MessageEmbed, TextChannel } from 'discord.js';
import { default as parseColor, Color } from 'parse-color';
import { isColorRole, COLOR_ROLE_SEPERATOR } from '../lib/assignableRoles';
function isColorRole(name: string) {
return name.startsWith('#') && name.length === 7;
}
const PREVIEW_DURATION = 1000 * 60;
function removeAllColorRoles(member: GuildMember) {
return Promise.all(member.roles.cache.map(role => {
@ -14,50 +13,109 @@ function removeAllColorRoles(member: GuildMember) {
}));
}
function garbageCollectRoles(guild: Guild) {
return Promise.all(guild.roles.cache.map(role => {
if (isColorRole(role.name)) {
const members = role.members;
if (members.size === 0) {
return role.delete();
}
}
}));
async function applyColor(member: GuildMember, color: Color) {
await removeAllColorRoles(member);
const colorRoleSeperator = await member.guild.roles.fetch(COLOR_ROLE_SEPERATOR);
if (!colorRoleSeperator) console.error('no color role seperator found?!?!');
const roleSettings: CreateRoleOptions = {
name: color.hex,
color: color.rgb,
hoist: false,
mentionable: false,
permissions: 0n,
};
if (colorRoleSeperator) {
roleSettings.position = colorRoleSeperator.position;
}
const role = member.guild.roles.cache.find(role => role.name === color.hex) || await member.guild.roles.create(roleSettings);
await member.roles.add(role);
}
module.exports = {
data: new SlashCommandBuilder()
.setName('color')
.setDescription('Change your role color.')
.addStringOption((option) => option.setName('color').setDescription('Color to set. Must be a parsable HTML color. Examples include: #ff0000, rgb(50, 0, 0), red, etc.')),
.addStringOption((option) =>
option.setName('color')
.setDescription('Color to set. Must be a parsable HTML color. Examples include: #ff0000, rgb(50, 0, 0), red, etc.')
.setRequired(true)
)
.addBooleanOption((option) =>
option.setName('preview').setDescription('Preview the color instead of applying it')
),
execute: async (interaction: CommandInteraction, member: GuildMember) => {
const color = interaction.options.getString('color');
const preview = interaction.options.getBoolean('preview');
if (color) {
const parsed = parseColor(color);
const parsed = parseColor(color.trim());
if (!parsed.hex) {
interaction.reply({
content: 'Invalid color. Try an HTML color like `#ff0000`, `rgb(50, 0, 0)`, `red`, etc.',
ephemeral: true,
ephemeral: true
});
return;
}
await removeAllColorRoles(member);
const role = member.guild.roles.cache.find(role => role.name === parsed.hex) || await member.guild.roles.create({
name: parsed.hex,
color: parsed.rgb,
hoist: false,
mentionable: false,
permissions: 0n,
});
await member.roles.add(role);
//await garbageCollectRoles(member.guild);
interaction.reply({
content: 'Your color has been set.',
ephemeral: true
});
if (preview) {
const embed = new MessageEmbed();
embed.setImage(`https://dummyimage.com/200x200/${parsed.hex.slice(1)}/${parsed.hex.slice(1)}.png`);
embed.setTitle(`Previewing ${parsed.hex}`);
const row = new MessageActionRow().addComponents(
new MessageButton()
.setLabel('Apply')
.setStyle('PRIMARY')
.setCustomId(`apply-color-${parsed.hex}`)
);
const message = (await interaction.reply({
embeds: [embed],
components: [row],
fetchReply: true
})) as Message;
const collector = (interaction.channel as TextChannel).createMessageComponentCollector({
filter: (i) => i.message.id === message.id,
time: PREVIEW_DURATION,
});
collector.on('collect', async (i) => {
if (i.customId !== `apply-color-${parsed.hex}`) return;
if (i.member === member) {
collector.stop();
await i.deferReply({ephemeral: false});
row.components[0].setDisabled(true);
await message.edit({
embeds: [embed],
components: [row]
});
await applyColor(member, parsed);
await i.followUp({
content: 'Color applied.'
});
} else {
i.reply({
content: 'You didn\'t run the command!',
ephemeral: true
});
}
});
} else {
await interaction.deferReply({
ephemeral: true
});
await applyColor(member, parsed);
interaction.followUp({
content: 'Your color has been set.'
});
}
} else {
await removeAllColorRoles(member);
interaction.reply({

View File

@ -0,0 +1,51 @@
import { SlashCommandBuilder } from '@discordjs/builders';
import { CommandInteraction, Guild, GuildMember, Role } from 'discord.js';
import { isColorRole, isPronounRole } from '../lib/assignableRoles';
async function fetchRoleMembers(role: Role) {
const members = await role.guild.members.fetch();
return members.filter(m => m.roles.cache.has(role.id));
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
async function garbageCollectRoles(guild: Guild, dryRun: boolean): Promise<Role[]> {
const roles = guild.roles.cache;
const garbageRoles = roles.filter((r: Role) => isColorRole(r.name) || isPronounRole(r.name));
const deletedRoles = garbageRoles.map(async (role: Role) => {
const members = await fetchRoleMembers(role);
if (members.size === 0) {
if (dryRun) return role;
return await role.delete('garbage collection');
}
});
return (await Promise.all(deletedRoles)).filter(r => r) as Role[];
}
module.exports = {
data: new SlashCommandBuilder()
.setName('garbage-collect-roles')
.setDescription('Garbage collect unused roles for colors and pronouns.')
.addBooleanOption((option) => option.setName('dry-run').setDescription('Only show roles that would be deleted.'))
.setDefaultPermission(false),
execute: async (interaction: CommandInteraction, member: GuildMember) => {
await interaction.deferReply({
ephemeral: false
});
const dryrun = interaction.options.getBoolean('dry-run');
const colorRoles = await garbageCollectRoles(member.guild, dryrun || false);
if (dryrun) {
interaction.followUp({
content: `${colorRoles.length} role${colorRoles.length === 1 ? '' : 's'} would be deleted${colorRoles.length === 0 ? '.' : ':\n'}${colorRoles.map(r => `\`${r.name}\``).join('\n')}`,
ephemeral: true,
});
} else {
interaction.followUp({
content: `${colorRoles.length} role${colorRoles.length === 1 ? ' was' : 's were'} deleted${colorRoles.length === 0 ? '.' : ':\n'}${colorRoles.map(r => `\`${r.name}\``).join('\n')}`,
ephemeral: true,
});
}
}
};

View File

@ -26,7 +26,12 @@ module.exports = {
data: new SlashCommandBuilder()
.setName('monitor')
.setDescription('Monitor')
.addStringOption((option) => option.setName('what').setDescription('Monitor what? Examples include: "lobby", "bedroom", "park", "playground", etc...').setRequired(true)),
.addStringOption((option) =>
option
.setName('what')
.setDescription('Monitor what? Examples include: "lobby", "bedroom", "park", "playground", etc...')
.setRequired(true)
),
execute: async (interaction: CommandInteraction) => {
let img;

55
src/commands/pronouns.ts Normal file
View File

@ -0,0 +1,55 @@
import { SlashCommandBuilder } from '@discordjs/builders';
import { CommandInteraction, CreateRoleOptions, GuildMember } from 'discord.js';
import { pronouns, PRONOUN_ROLE_SEPERATOR } from '../lib/assignableRoles';
function extendOption(t: string) {
return {name: t, value: t};
}
module.exports = {
data: new SlashCommandBuilder()
.setName('pronoun')
.setDescription('Give yourself a pronoun or two.')
.addStringOption((option) =>
option
.setName('pronoun')
.setDescription('Pronoun to add. If you already have a pronoun, you can specify it to remove it.')
.setRequired(true)
.setChoices(...Array.from(pronouns.values()).map(extendOption))
),
execute: async (interaction: CommandInteraction, member: GuildMember) => {
await interaction.deferReply({
ephemeral: true
});
const pronoun = interaction.options.getString('pronoun') as string;
const pronounRoleSeperator = await member.guild.roles.fetch(PRONOUN_ROLE_SEPERATOR);
if (!pronounRoleSeperator) console.error('no pronoun role seperator found?!?!');
const roleSettings: CreateRoleOptions = {
name: pronoun,
hoist: false,
mentionable: false,
permissions: 0n
};
if (pronounRoleSeperator) {
roleSettings.position = pronounRoleSeperator.position;
}
const pronounRole = member.guild.roles.cache.find(role => role.name === pronoun) || await member.guild.roles.create(roleSettings);
if (member.roles.cache.has(pronounRole.id)) {
await member.roles.remove(pronounRole);
interaction.followUp({
content: `Removed \`${pronoun}\` from your roles.`
});
} else {
await member.roles.add(pronounRole);
interaction.followUp({
content: `Added \`${pronoun}\` to your roles.`
});
}
}
};

View File

@ -62,6 +62,7 @@ const bot = new Discord.Client({
Discord.Intents.FLAGS.GUILD_MESSAGES,
Discord.Intents.FLAGS.GUILD_VOICE_STATES,
Discord.Intents.FLAGS.GUILD_MESSAGE_REACTIONS,
Discord.Intents.FLAGS.GUILD_MEMBERS,
],
});

View File

@ -0,0 +1,22 @@
export const COLOR_ROLE_SEPERATOR = '997893394850381834'; // put color roles below this role
export const PRONOUN_ROLE_SEPERATOR = '997893842961440779'; // put pronoun roles below this role
export const pronouns = new Set([
'he/him',
'she/her',
'they/them',
'it/its',
'fae/faer',
'no pronouns',
'any pronouns',
'was/were',
'will/would',
]);
export function isColorRole(name: string) {
return name.startsWith('#') && name.length === 7;
}
export function isPronounRole(name: string) {
return pronouns.has(name);
}