diff --git a/src/graphql/queries.rs b/src/graphql/queries.rs index 7d118c6..4a737fc 100644 --- a/src/graphql/queries.rs +++ b/src/graphql/queries.rs @@ -146,4 +146,83 @@ impl GraphQLClient { ); Ok(attendance) } + + pub async fn save_member_roles( + &self, + discord_id: String, + roles: Vec, + ) -> anyhow::Result<()> { + let query = r#" + mutation($discordId: String!, $roles: [String!]!) { + saveMemberRoles(discordId: $discordId, roles: $roles) + }"#; + + let variables = serde_json::json!({ + "discordId": discord_id, + "roles": roles + }); + + let res = self + .http() + .post(self.root_url()) + .bearer_auth(self.api_key()) + .json(&serde_json::json!({ + "query": query, + "variables": variables + })) + .send() + .await? + .error_for_status()?; + + let response: serde_json::Value = res.json().await?; + + if response.get("errors").is_some() { + anyhow::bail!("GraphQL error: {:?}", response["errors"]); + } + Ok(()) + } + + pub async fn get_member_roles( + &self, + discord_id: String, + ) -> anyhow::Result<(bool, Vec)> { + let query = r#" + query($discordId: String!) { + memberRoles(discordId: $discordId){ + exists + roles + } + }"#; + + let variables = serde_json::json!({ + "discordId": discord_id + }); + + let response = self + .http() + .post(self.root_url()) + .bearer_auth(self.api_key()) + .json(&serde_json::json!({ + "query": query, + "variables": variables + })) + .send() + .await? + .json::() + .await?; + + // Collect roles returned by the API as strings; any filtering (e.g. removing @everyone) is handled by the caller. + let data = &response["data"]["memberRoles"]; + + let exists = data["exists"].as_bool().unwrap_or(false); + + let roles = data["roles"] + .as_array() + .unwrap_or(&vec![]) + .iter() + .filter_map(|v| v.as_str()) + .map(|s| s.to_string()) + .collect(); + Ok((exists, roles)) + } } diff --git a/src/main.rs b/src/main.rs index 2e583d0..ca94b3a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -107,7 +107,9 @@ fn prepare_data(config: &Config, reload_handle: ReloadHandle) -> Data { async fn build_client(config: &Config, data: Data) -> Result { ClientBuilder::new( config.discord_token.clone(), - GatewayIntents::non_privileged() | GatewayIntents::MESSAGE_CONTENT, + GatewayIntents::non_privileged() + | GatewayIntents::MESSAGE_CONTENT + | GatewayIntents::GUILD_MEMBERS, ) .framework(build_framework( config.owner_id, @@ -160,6 +162,53 @@ async fn event_handler( FullEvent::ReactionRemove { removed_reaction } => { handle_reaction(ctx, removed_reaction, data, false).await?; } + + FullEvent::GuildMemberRemoval { + guild_id: _, + user, + member_data_if_available: Some(member), + } => { + let roles: Vec = member + .roles + .iter() + .filter(|r| r.get() != member.guild_id.get()) + .map(|r| r.get().to_string()) + .collect(); + + if let Err(e) = data + .graphql_client + .save_member_roles(user.id.to_string(), roles) + .await + { + println!("Failed to save roles: {:?}", e); + } + } + FullEvent::GuildMemberAddition { new_member } => { + let (exists, roles) = match data + .graphql_client + .get_member_roles(new_member.user.id.to_string()) + .await + { + Ok(r) => r, + Err(e) => { + println!("Failed to fetch roles: {:?}", e); + (false, vec![]) + } + }; + + if let Ok(member) = new_member.guild_id.member(ctx, new_member.user.id).await { + // restore roles + for role in roles { + if let Ok(role_id) = role.parse::() { + let _ = member.add_role(ctx, RoleId::new(role_id)).await; + } + } + // probated role + if exists { + let _ = member.add_role(ctx, RoleId::new(1484798446228475905)).await; + } + } + } _ => {} }