Skip to content

Commit 9ff55e0

Browse files
committed
automatically grant user roles on join
1 parent f74cdf6 commit 9ff55e0

6 files changed

Lines changed: 128 additions & 4 deletions

File tree

.sqlx/query-47e50e24066c6d0ed4911b921f37766e6cb2508de9a0b9b8e91b5ded8e39e4f3.json

Lines changed: 26 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/faq.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44

55
### I've left and rejoined a server and lost my roles. Can I get them back?
66

7-
Yes, just re-register your license key again. As long as you're using the same Discord account that originally registered
8-
the license, Jinx will happily grant the roles again.
7+
Jinx should automatically restore your roles if you rejoin a server, but if for some reason this fails you can
8+
manually re-register your license key again using the register button.
99

1010
### I've lost my Discord account and had to make a new one. Can I move my Jinxxy license?
1111

docs/images/user_rejoin.png

22 KB
Loading

src/bot/event_handler.rs

Lines changed: 80 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use regex::Regex;
1515
use serenity::{
1616
Colour, Context, CreateEmbed, CreateInputText, CreateInteractionResponse, CreateInteractionResponseMessage,
1717
CreateLabel, CreateMessage, CreateModal, CreateModalComponent, CreateTextDisplay, EditInteractionResponse, Error,
18-
Event, EventHandler, FullEvent, GenericChannelId, GuildId, InputTextStyle, Interaction, LabelComponent,
18+
Event, EventHandler, FullEvent, GenericChannelId, GuildId, InputTextStyle, Interaction, LabelComponent, Member,
1919
ModalComponent, ModalInteraction, RatelimitInfo,
2020
};
2121
use std::borrow::Cow;
@@ -240,6 +240,24 @@ impl EventHandler for Data {
240240
command_interaction.user.id.get()
241241
);
242242
}
243+
FullEvent::GuildMemberAddition { new_member, .. } => {
244+
// If a banned user joins a guild, do nothing
245+
match self.db.get_user_ban(new_member.user.id).await {
246+
Ok(banned) => {
247+
if banned {
248+
info!("Ignored join event for banned user {}", new_member.user.id.get());
249+
return;
250+
}
251+
}
252+
Err(e) => {
253+
warn!("Could not check user ban: {e:?}");
254+
}
255+
}
256+
257+
if let Err(e) = grant_member_roles(self, context, new_member).await {
258+
error!("Error granting roles on join: {e:?}");
259+
}
260+
}
243261
FullEvent::GuildRoleDelete {
244262
guild_id,
245263
removed_role_id,
@@ -519,6 +537,67 @@ impl EventHandler for Data {
519537
}
520538
}
521539

540+
/// Grant any missing roles to a member
541+
async fn grant_member_roles(data: &Data, context: &Context, new_member: &Member) -> Result<(), JinxError> {
542+
let roles = data
543+
.db
544+
.get_roles_for_user(new_member.guild_id, new_member.user.id)
545+
.await?;
546+
let mut granted_roles = Vec::new();
547+
let mut errored_roles = Vec::new();
548+
for role in roles {
549+
if !new_member.roles.contains(&role) {
550+
let result = new_member.add_role(&context.http, role, Some("new user join")).await;
551+
if let Err(e) = result {
552+
warn!("Error adding role to new member. Skipping: {:?}", e);
553+
errored_roles.push(role);
554+
} else {
555+
granted_roles.push(role);
556+
}
557+
}
558+
}
559+
560+
if !granted_roles.is_empty() || !errored_roles.is_empty() {
561+
debug!(
562+
"Granted missing roles to {} in {}",
563+
new_member.user.id.get(),
564+
new_member.guild_id.get()
565+
);
566+
567+
// also send a notification to the guild owner bot log if it's set up for this guild
568+
if let Some(log_channel) = data.db.get_log_channel(new_member.guild_id).await? {
569+
let mut owner_message = format!(
570+
"<@{}> has rejoined the server and been granted the following roles:",
571+
new_member.user.id
572+
);
573+
for role in granted_roles {
574+
owner_message.push_str(format!("\n- <@&{}>", role.get()).as_str());
575+
}
576+
if !errored_roles.is_empty() {
577+
owner_message.push_str("\nCould not grant the following roles, likely due to missing permissions:");
578+
for role in errored_roles {
579+
owner_message.push_str(format!("\n- <@&{}>", role.get()).as_str());
580+
}
581+
}
582+
583+
let embed = CreateEmbed::default()
584+
.title("User Rejoin")
585+
.color(Colour::GOLD)
586+
.description(owner_message);
587+
let bot_log_message = CreateMessage::default().embed(embed);
588+
send_bot_log_message(
589+
data.db.clone(),
590+
context,
591+
new_member.guild_id,
592+
log_channel,
593+
bot_log_message,
594+
);
595+
}
596+
}
597+
598+
Ok(())
599+
}
600+
522601
async fn handle_license_registration(
523602
data: &Data,
524603
context: &Context,

src/bot/mod.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,8 @@ impl BotBuilder {
139139
let discord_token = Token::try_from(discord_token)?;
140140
let intents = GatewayIntents::GUILDS
141141
.union(GatewayIntents::GUILD_MESSAGES)
142-
.union(GatewayIntents::DIRECT_MESSAGES);
142+
.union(GatewayIntents::DIRECT_MESSAGES)
143+
.union(GatewayIntents::GUILD_MEMBERS);
143144
let commands = GLOBAL_COMMANDS
144145
.iter()
145146
.chain(CREATOR_COMMANDS.iter())

src/db/mod.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1057,6 +1057,7 @@ impl JinxDb {
10571057
Ok(result)
10581058
}
10591059

1060+
/// get all users in a guild that should have the given role
10601061
pub async fn get_users_for_role(&self, guild: GuildId, role: RoleId) -> JinxResult<Vec<UserId>> {
10611062
let guild_id = guild.get() as i64;
10621063
let role_id = role.get() as i64;
@@ -1073,6 +1074,23 @@ impl JinxDb {
10731074
Ok(result)
10741075
}
10751076

1077+
/// get all roles in a guild the given user should have
1078+
pub async fn get_roles_for_user(&self, guild: GuildId, user: UserId) -> JinxResult<Vec<RoleId>> {
1079+
let guild_id = guild.get() as i64;
1080+
let user_id = user.get() as i64;
1081+
let result = sqlx::query!(
1082+
r#"SELECT DISTINCT blanket_role_id as "role_id!" FROM license_activation INNER JOIN jinxxy_user_guild USING (jinxxy_user_id) INNER JOIN guild USING (guild_id) WHERE guild_id = ?1 AND activator_user_id = ?2 AND blanket_role_id IS NOT NULL
1083+
UNION SELECT DISTINCT role_id FROM license_activation INNER JOIN jinxxy_user_guild USING (jinxxy_user_id) INNER JOIN product_role USING (guild_id, jinxxy_user_id, product_id) WHERE guild_id = ?1 AND activator_user_id = ?2
1084+
UNION SELECT DISTINCT role_id FROM license_activation INNER JOIN jinxxy_user_guild USING (jinxxy_user_id) INNER JOIN product_version_role USING (guild_id, jinxxy_user_id, product_id, version_id) WHERE guild_id = ?1 AND activator_user_id = ?2"#,
1085+
guild_id,
1086+
user_id,
1087+
)
1088+
.map(|row| RoleId::new(row.role_id as u64))
1089+
.fetch_all(&self.read_pool)
1090+
.await?;
1091+
Ok(result)
1092+
}
1093+
10761094
/// get distinct roles from all links
10771095
pub async fn get_linked_roles(&self, guild: GuildId) -> JinxResult<Vec<RoleId>> {
10781096
let guild_id = guild.get() as i64;

0 commit comments

Comments
 (0)