3
0
Fork 0
mirror of https://github.com/ZeppelinBot/Zeppelin.git synced 2025-03-15 05:41:51 +00:00

feat: add 'exclusive' option for role buttons; add documentation for role buttons; mark reaction roles as legacy

This commit is contained in:
Dragory 2022-04-23 17:30:37 +03:00
parent 05334a772f
commit 784c54b22a
No known key found for this signature in database
GPG key ID: 5F387BA66DF8AAC1
8 changed files with 144 additions and 20 deletions

View file

@ -36,6 +36,7 @@ export const ReactionRolesPlugin = zeppelinGuildPlugin<ReactionRolesPluginType>(
showInDocs: true,
info: {
prettyName: "Reaction roles",
legacy: "Consider using the [Role buttons](/docs/plugins/role_buttons) plugin instead.",
},
dependencies: () => [LogsPlugin],

View file

@ -7,10 +7,13 @@ import { GuildRoleButtons } from "../../data/GuildRoleButtons";
import { RoleManagerPlugin } from "../RoleManager/RoleManagerPlugin";
import { StrictValidationError } from "../../validatorUtils";
import { onButtonInteraction } from "./events/buttonInteraction";
import { pluginInfo } from "./info";
export const RoleButtonsPlugin = zeppelinGuildPlugin<RoleButtonsPluginType>()({
name: "role_buttons",
configSchema: ConfigSchema,
info: pluginInfo,
showInDocs: true,
configPreprocessor(options) {
// Auto-fill "name" property for buttons based on the object key

View file

@ -1,6 +1,8 @@
import { typedGuildEventListener } from "knub";
import { RoleButtonsPluginType, TRoleButtonOption } from "../types";
import { RoleManagerPlugin } from "../../RoleManager/RoleManagerPlugin";
import { GuildMember } from "discord.js";
import { getAllRolesInButtons } from "../functions/getAllRolesInButtons";
export const onButtonInteraction = typedGuildEventListener<RoleButtonsPluginType>()({
event: "interactionCreate",
@ -12,8 +14,9 @@ export const onButtonInteraction = typedGuildEventListener<RoleButtonsPluginType
const config = pluginData.config.get();
const [, name, optionIndex] = args.interaction.customId.split(":");
// For some reason TS's type inference fails here so using a type annotation
const option: TRoleButtonOption | undefined = config.buttons[name]?.options[optionIndex];
if (!option) {
const buttons = config.buttons[name];
const option: TRoleButtonOption | undefined = buttons?.options[optionIndex];
if (!buttons || !option) {
args.interaction.reply({
ephemeral: true,
content: "Invalid option selected",
@ -21,30 +24,41 @@ export const onButtonInteraction = typedGuildEventListener<RoleButtonsPluginType
return;
}
const member = args.interaction.member || (await pluginData.guild.members.fetch(args.interaction.user.id));
if (!member) {
args.interaction.reply({
ephemeral: true,
content: "Error while fetching member to apply roles for",
});
return;
}
const member = args.interaction.member as GuildMember;
const role = pluginData.guild.roles.cache.get(option.role_id);
const roleName = role?.name || option.role_id;
const hasRole = Array.isArray(member.roles)
? member.roles.includes(option.role_id)
: member.roles.cache.has(option.role_id);
if (hasRole) {
pluginData.getPlugin(RoleManagerPlugin).removeRole(member.user.id, option.role_id);
const rolesToRemove: string[] = [];
const rolesToAdd: string[] = [];
if (member.roles.cache.has(option.role_id)) {
rolesToRemove.push(option.role_id);
args.interaction.reply({
ephemeral: true,
content: "The selected role will be removed shortly!",
content: `The role **${roleName}** will be removed shortly!`,
});
} else {
pluginData.getPlugin(RoleManagerPlugin).addRole(member.user.id, option.role_id);
rolesToAdd.push(option.role_id);
if (buttons.exclusive) {
for (const roleId of getAllRolesInButtons(buttons)) {
if (member.roles.cache.has(roleId)) {
rolesToRemove.push(roleId);
}
}
}
args.interaction.reply({
ephemeral: true,
content: "You will receive the selected role shortly!",
content: `You will receive the **${roleName}** role shortly!`,
});
}
for (const roleId of rolesToAdd) {
pluginData.getPlugin(RoleManagerPlugin).addRole(member.user.id, roleId);
}
for (const roleId of rolesToRemove) {
pluginData.getPlugin(RoleManagerPlugin).removeRole(member.user.id, roleId);
}
},
});

View file

@ -0,0 +1,10 @@
import { TRoleButtonsConfigItem } from "../types";
// This function will be more complex in the future when the plugin supports select menus + sub-menus
export function getAllRolesInButtons(buttons: TRoleButtonsConfigItem): string[] {
const roles = new Set<string>();
for (const option of buttons.options) {
roles.add(option.role_id);
}
return Array.from(roles);
}

View file

@ -0,0 +1,80 @@
import { trimPluginDescription } from "../../utils";
import { ZeppelinGuildPluginBlueprint } from "../ZeppelinPluginBlueprint";
export const pluginInfo: ZeppelinGuildPluginBlueprint["info"] = {
prettyName: "Role buttons",
description: trimPluginDescription(`
Allow users to pick roles by clicking on buttons
`),
configurationGuide: trimPluginDescription(`
Button roles are entirely config-based; this is in contrast to the old reaction roles. They can either be added to an existing message posted by Zeppelin or posted as a new message.
## Basic role buttons
~~~yml
role_buttons:
config:
buttons:
my_roles: # You can use any name you want here, but make sure not to change it afterwards
messages:
channel_id: "967407495544983552"
content: "Click the reactions below to get roles! Click again to remove the role."
options:
- role_id: "878339100015489044"
label: "Role 1"
- role_id: "967410091571703808"
emoji: "😁" # Default emoji as a unicode emoji
label: "Role 2"
- role_id: "967410091571703234"
emoji: "967412591683047445" # Custom emoji ID
- role_id: "967410091571703567"
label: "Role 4"
style: DANGER # Button style (in all caps), see https://discord.com/developers/docs/interactions/message-components#button-object-button-styles
~~~
### Or with an embed:
~~~yml
role_buttons:
config:
buttons:
my_roles:
messages:
channel_id: "967407495544983552"
content:
embeds:
- title: "Pick your role below!"
color: 0x0088FF
description: "You can pick any role you want by clicking the buttons below."
options:
... # See above for examples for options
~~~
## Role buttons for an existing message
This message must be posted by Zeppelin.
~~~yml
role_buttons:
config:
buttons:
my_roles:
messages:
channel_id: "967407495544983552"
message_id: "967407554412040193"
options:
... # See above for examples for options
~~~
## Limiting to one role ("exclusive" roles)
When the \`exclusive\` option is enabled, only one role can be selected at a time.
~~~yml
role_buttons:
config:
buttons:
my_roles:
messages:
channel_id: "967407495544983552"
message_id: "967407554412040193"
exclusive: true # With this option set, only one role can be selected at a time
options:
... # See above for examples for options
~~~
`),
};

View file

@ -32,6 +32,7 @@ const RoleButtonsConfigItem = t.type({
}),
]),
options: t.array(RoleButtonOption),
exclusive: tNullable(t.boolean),
});
export type TRoleButtonsConfigItem = t.TypeOf<typeof RoleButtonsConfigItem>;

View file

@ -27,7 +27,7 @@ export interface ZeppelinGuildPluginBlueprint<TPluginData extends GuildPluginDat
description?: TMarkdown;
usageGuide?: TMarkdown;
configurationGuide?: TMarkdown;
legacy?: boolean;
legacy?: boolean | string;
};
configPreprocessor?: (

View file

@ -8,6 +8,20 @@
<!-- Description -->
<MarkdownBlock :content="data.info.description" class="content"></MarkdownBlock>
<div v-if="data.info.legacy">
<div class="px-3 py-2 mb-4 rounded bg-gray-800 shadow-md inline-block flex">
<div class="flex-none mr-2">
<alert class="inline-icon mr-1 text-yellow-300" />
</div>
<div class="flex-auto">
<strong>Note!</strong> This is a legacy plugin which is no longer actively maintained and may be removed in a future update.
<div v-if="typeof data.info.legacy === 'string'" class="mt-4">
<MarkdownBlock v-if="typeof data.info.legacy === 'string'" :content="data.info.legacy"></MarkdownBlock>
</div>
</div>
</div>
</div>
<Tabs>
<Tab :active="tab === 'usage'">
<router-link class="unstyled" v-bind:to="'/docs/plugins/' + pluginName + '/usage'">Usage</router-link>
@ -172,12 +186,13 @@
import Tab from "../Tab.vue";
import Expandable from "../Expandable.vue";
import { DocsState } from "../../store/types";
import Alert from 'vue-material-design-icons/Alert.vue';
const validTabs = ['usage', 'configuration'];
const defaultTab = 'usage';
export default {
components: { CodeBlock, MarkdownBlock, Tabs, Tab, Expandable },
components: { CodeBlock, MarkdownBlock, Tabs, Tab, Expandable, Alert },
async mounted() {
this.loading = true;