this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Api abstractions, fix gateway handler error handling, add dispatch event GuildAudtiLogCreate and update README.

For more information look at the examplebot for reference of the new abstractions until the guide in the README gets updated.

authored by

roufpup and committed by
roufpup
8e9f6fc9 35d7b0fb

+502 -402
+3 -15
Cargo.lock
··· 1053 1053 1054 1054 [[package]] 1055 1055 name = "fluxer-rs" 1056 - version = "0.1.3" 1056 + version = "0.1.4" 1057 1057 dependencies = [ 1058 1058 "anyhow", 1059 1059 "async-trait", 1060 1060 "derive_builder", 1061 1061 "emojis", 1062 1062 "ezsockets", 1063 - "fluxer-rs-macros 0.1.0 (git+https://git.awoo.ren/roufpup/fluxer-rs)", 1063 + "fluxer-rs-macros", 1064 1064 "log", 1065 1065 "reqwest", 1066 1066 "serde", ··· 1072 1072 1073 1073 [[package]] 1074 1074 name = "fluxer-rs-macros" 1075 - version = "0.1.0" 1076 - dependencies = [ 1077 - "heck", 1078 - "log", 1079 - "proc-macro2", 1080 - "quote", 1081 - "syn 2.0.117", 1082 - ] 1083 - 1084 - [[package]] 1085 - name = "fluxer-rs-macros" 1086 - version = "0.1.0" 1087 - source = "git+https://git.awoo.ren/roufpup/fluxer-rs#72e6ffe1d786eabeddf47a9f04b66e2f2d30e155" 1075 + version = "0.1.1" 1088 1076 dependencies = [ 1089 1077 "heck", 1090 1078 "log",
+4 -4
Cargo.toml
··· 1 1 [package] 2 2 name = "fluxer-rs" 3 - version = "0.1.3" 3 + version = "0.1.4" 4 4 edition = "2024" 5 5 license = "MIT" 6 6 description = "A rust framework for interacting with fluxer instances" 7 - homepage = "https://git.killuaa.dev/roufpup/fluxer-rs" 8 - repository = "https://git.killuaa.dev/roufpup/fluxer-rs" 7 + homepage = "https://tangled.org/awoo.ren/fluxer-rs" 8 + repository = "https://tangled.org/awoo.ren/fluxer-rs" 9 9 readme = "README.md" 10 10 11 11 [workspace] ··· 18 18 log = "0.4.29" 19 19 serde_json = "1.0.149" 20 20 serde_with = "3.16.1" 21 - fluxer-rs-macros = { git = "https://git.awoo.ren/roufpup/fluxer-rs", version = "0.1.0" } 21 + fluxer-rs-macros = { path = "./macros", version = "0.1.0" } 22 22 thiserror = "2.0.18" 23 23 reqwest = "0.13.2" 24 24 emojis = "0.8.0"
+1 -1
examples/examplebot/src/commands.rs
··· 1 - pub mod addrole; 1 + pub mod giverole; 2 2 pub mod createrole; 3 3 pub mod deleterole; 4 4 pub mod edit;
-23
examples/examplebot/src/commands/addrole.rs
··· 1 - use fluxer_rs::api::common::{give_role, send_message}; 2 - use fluxer_rs::command; 3 - 4 - #[command(AddRoleCommand)] 5 - async fn execute(api: &FluxerApiHandler, feedback: &CommandFeedback) { 6 - let data = feedback.data; 7 - let args = &feedback.args; 8 - 9 - if args.len() != 3 { 10 - send_message(api, &data.channel_id, "Invalid syntax").await?; 11 - return Ok(()); 12 - } 13 - 14 - give_role( 15 - api, 16 - args.first().unwrap(), 17 - args.get(1).unwrap(), 18 - args.get(2).unwrap(), 19 - ) 20 - .await?; 21 - 22 - Ok(()) 23 - }
+11 -10
examples/examplebot/src/commands/createrole.rs
··· 1 1 use fluxer_rs::{ 2 - api::common::{create_role, send_message}, 2 + api::{channels::Channel, guilds::Guild}, 3 3 command, 4 4 }; 5 5 ··· 7 7 async fn execute(api: &FluxerApiHandler, feedback: &CommandFeedback) { 8 8 let data = feedback.data; 9 9 let args = &feedback.args; 10 + let channel = Channel::new(&data.channel_id, api); 11 + let guild = Guild::new(data.guild_id.clone().unwrap(), api); 10 12 11 13 if args.len() != 4 { 12 - send_message(api, &data.channel_id, "Invalid syntax").await?; 14 + channel.send_message("Invalid syntax").await?; 13 15 return Ok(()); 14 16 } 15 17 16 - create_role( 17 - api, 18 - args.first().unwrap(), 19 - args.get(1).unwrap(), 20 - args.get(2).unwrap(), 21 - args.get(3).unwrap(), 22 - ) 23 - .await?; 18 + guild 19 + .create_role( 20 + args.first().unwrap(), 21 + args.get(1).unwrap(), 22 + args.get(2).unwrap(), 23 + ) 24 + .await?; 24 25 25 26 Ok(()) 26 27 }
+5 -3
examples/examplebot/src/commands/deleterole.rs
··· 1 1 use fluxer_rs::{ 2 - api::common::{delete_role, send_message}, 2 + api::{channels::Channel, guilds::Guild}, 3 3 command, 4 4 }; 5 5 ··· 7 7 async fn execute(api: &FluxerApiHandler, feedback: &CommandFeedback) { 8 8 let data = feedback.data; 9 9 let args = &feedback.args; 10 + let channel = Channel::new(&data.channel_id, api); 11 + let guild = Guild::new(data.guild_id.clone().unwrap(), api); 10 12 11 13 if args.len() != 2 || args.is_empty() { 12 - send_message(api, &data.channel_id, "Invalid syntax").await?; 14 + channel.send_message("Invalid syntax").await?; 13 15 return Ok(()); 14 16 } 15 17 16 - delete_role(api, args.first().unwrap(), args.get(1).unwrap()).await?; 18 + guild.delete_role(args.first().unwrap()).await?; 17 19 18 20 Ok(()) 19 21 }
+9 -15
examples/examplebot/src/commands/edit.rs
··· 1 - use fluxer_rs::{ 2 - api::common::{edit_message_with_embeds, fetch_message, send_message}, 3 - command, 4 - }; 1 + use fluxer_rs::{api::channels::Channel, command}; 5 2 6 3 #[command(EditCommand)] 7 4 async fn execute(api: &FluxerApiHandler, feedback: &CommandFeedback) { 8 5 let data = feedback.data; 9 6 let args = &feedback.args; 7 + let channel = Channel::new(&data.channel_id, api); 10 8 11 - if args.len() != 2 { 12 - send_message(api, &data.channel_id, "Invalid syntax").await?; 9 + if args.len() != 1 { 10 + channel.send_message("Invalid syntax").await?; 13 11 return Ok(()); 14 12 } 15 13 16 - let resp = fetch_message(api, args.first().unwrap(), args.get(1).unwrap()).await?; 17 - edit_message_with_embeds( 18 - api, 19 - &resp.channel_id, 20 - &resp.id, 21 - "EEEEEEEEEEEEE", 22 - resp.embeds.unwrap(), 23 - ) 24 - .await?; 14 + let message = channel.message(args.first().unwrap()); 15 + let message_data = message.get_data().await?; 16 + message 17 + .edit_with_embeds("EEEEEEEEEEEEE", message_data.embeds.unwrap()) 18 + .await?; 25 19 26 20 Ok(()) 27 21 }
+31
examples/examplebot/src/commands/giverole.rs
··· 1 + use fluxer_rs::api::channels::Channel; 2 + use fluxer_rs::api::guilds::Guild; 3 + use fluxer_rs::command; 4 + use fluxer_rs::high_level::command_context::CommandContext; 5 + 6 + #[command(GiveRoleCommand)] 7 + async fn execute(api: &FluxerApiHandler, feedback: &CommandFeedback) { 8 + let data = feedback.data; 9 + let args = &feedback.args; 10 + let channel = Channel::new(&data.channel_id, api); 11 + let guild = Guild::new(data.guild_id.clone().unwrap(), api); 12 + 13 + let context = CommandContext::new(["<user_id>", "<role_id>"]); 14 + 15 + context 16 + .handle( 17 + &channel, 18 + args.clone(), 19 + format!( 20 + "Invalid number of arguments.\n\nExpected arguments: {}", 21 + context.args 22 + ), 23 + 0x9e1c1c, 24 + ) 25 + .await?; 26 + 27 + let member = guild.member(*args.first().unwrap()); 28 + member.give_role(args.get(1).unwrap()).await?; 29 + 30 + Ok(()) 31 + }
+4 -5
examples/examplebot/src/commands/ping.rs
··· 1 - use fluxer_rs::{ 2 - api::{common::send_reply}, 3 - command, 4 - }; 1 + use fluxer_rs::{api::channels::Channel, command}; 5 2 6 3 #[command(PingCommand)] 7 4 async fn execute(api: &FluxerApiHandler, feedback: &CommandFeedback) { 8 5 let data = feedback.data; 6 + let channel = Channel::new(&data.channel_id, api); 7 + let message = channel.message(&data.id); 8 + message.reply("pong").await?; 9 9 10 - send_reply(api, &data.channel_id, &data.id, "pong").await?; 11 10 Ok(()) 12 11 }
+6 -14
examples/examplebot/src/commands/react.rs
··· 1 - use fluxer_rs::{ 2 - api::common::{react, send_message}, 3 - command, 4 - util::get_emoji, 5 - }; 1 + use fluxer_rs::{api::channels::Channel, command, util::get_emoji}; 6 2 7 3 #[command(ReactCommand)] 8 4 async fn execute(api: &FluxerApiHandler, feedback: &CommandFeedback) { 9 5 let data = feedback.data; 10 6 let args = &feedback.args; 7 + let channel = Channel::new(&data.channel_id, api); 8 + let message = channel.message(args.get(1).unwrap()); 11 9 12 - if args.len() != 3 { 13 - send_message(api, &data.channel_id, "Invalid syntax").await?; 10 + if args.len() != 2 { 11 + channel.send_message("Invalid syntax").await?; 14 12 return Ok(()); 15 13 } 16 14 17 - react( 18 - api, 19 - args.first().unwrap(), 20 - args.get(1).unwrap(), 21 - &get_emoji(args.get(2).unwrap()), 22 - ) 23 - .await?; 15 + message.add_react(&get_emoji(args.get(2).unwrap())).await?; 24 16 25 17 Ok(()) 26 18 }
+8 -13
examples/examplebot/src/commands/remove_react.rs
··· 1 - use fluxer_rs::{ 2 - api::common::{remove_all_emoji_reactions, send_message}, 3 - command, util::get_emoji, 4 - }; 1 + use fluxer_rs::{api::channels::Channel, command, util::get_emoji}; 5 2 6 3 #[command(RemoveReactCommand)] 7 4 async fn execute(api: &FluxerApiHandler, feedback: &CommandFeedback) { 8 5 let data = feedback.data; 9 6 let args = &feedback.args; 7 + let channel = Channel::new(&data.channel_id, api); 8 + let message = channel.message(args.get(1).unwrap()); 10 9 11 - if args.len() != 3 { 12 - send_message(api, &data.channel_id, "Invalid syntax").await?; 10 + if args.len() != 2 { 11 + channel.send_message("Invalid syntax").await?; 13 12 return Ok(()); 14 13 } 15 14 16 - remove_all_emoji_reactions( 17 - api, 18 - args.first().unwrap(), 19 - args.get(1).unwrap(), 20 - &get_emoji(args.get(2).unwrap()), 21 - ) 22 - .await?; 15 + message 16 + .remove_all_emoji_reactions(&get_emoji(args.get(2).unwrap())) 17 + .await?; 23 18 24 19 Ok(()) 25 20 }
+6 -11
examples/examplebot/src/commands/removerole.rs
··· 1 1 use fluxer_rs::{ 2 - api::{ 3 - common::{remove_role, send_message}, 4 - }, 2 + api::{channels::Channel, guilds::Guild}, 5 3 command, 6 4 }; 7 5 ··· 9 7 async fn execute(api: &FluxerApiHandler, feedback: &CommandFeedback) { 10 8 let data = feedback.data; 11 9 let args = &feedback.args; 10 + let channel = Channel::new(&data.channel_id, api); 11 + let guild = Guild::new(data.guild_id.clone().unwrap(), api); 12 12 13 13 if args.len() != 3 { 14 - send_message(api, &data.channel_id, "Invalid syntax").await?; 14 + channel.send_message("Invalid syntax").await?; 15 15 return Ok(()); 16 16 } 17 17 18 - remove_role( 19 - api, 20 - args.first().unwrap(), 21 - args.get(1).unwrap(), 22 - args.get(2).unwrap(), 23 - ) 24 - .await?; 18 + let member = guild.member(*args.first().unwrap()); 19 + member.remove_role(args.get(1).unwrap()).await?; 25 20 26 21 Ok(()) 27 22 }
+6 -6
examples/examplebot/src/dispatch.rs
··· 14 14 }; 15 15 16 16 use crate::commands::{ 17 - addrole::AddRoleCommand, createrole::CreateRoleCommand, deleterole::DeleteRoleCommand, 18 - edit::EditCommand, ping::PingCommand, react::ReactCommand, remove_react::RemoveReactCommand, 19 - removerole::RemoveRoleCommand, 17 + createrole::CreateRoleCommand, deleterole::DeleteRoleCommand, edit::EditCommand, 18 + giverole::GiveRoleCommand, ping::PingCommand, react::ReactCommand, 19 + remove_react::RemoveReactCommand, removerole::RemoveRoleCommand, 20 20 }; 21 21 22 22 pub struct ColorbotDispatchHandler {} ··· 30 30 let mut cmd_handler = CommandHandler::init("!".to_string()); 31 31 32 32 register_commands!(cmd_handler,[ 33 - {"addrole", AddRoleCommand}, 33 + {"giverole", GiveRoleCommand}, 34 34 {"ping", PingCommand}, 35 35 {"edit", EditCommand}, 36 36 {"react", ReactCommand}, ··· 48 48 data: GuildCreateData, 49 49 api: &FluxerApiHandler, 50 50 ) -> Result<(), FluxerRsError> { 51 - if data.id == "1477453554678525954" { 51 + if data.id == "1473686979970875591" { 52 52 api 53 53 .execute_call( 54 54 SendMessageBuilder::default() 55 55 .content("Mhyello i am online".to_string()) 56 - .channel_id("1477938338794254343".to_string()) 56 + .channel_id("1474424011696861241".to_string()) 57 57 .embeds(vec![ 58 58 EmbedBuilder::default() 59 59 .title("Added to Queue".to_string())
+5 -1
macros/Cargo.toml
··· 1 1 [package] 2 2 name = "fluxer-rs-macros" 3 - version = "0.1.0" 3 + version = "0.1.1" 4 4 edition = "2024" 5 + license = "MIT" 6 + description = "Proc macros for use in fluxer-rs" 7 + homepage = "https://tangled.org/awoo.ren/fluxer-rs" 8 + repository = "https://tangled.org/awoo.ren/fluxer-rs" 5 9 6 10 [lib] 7 11 proc-macro = true
+1 -1
macros/src/command.rs
··· 55 55 56 56 impl fluxer_rs::high_level::command_handler::CommandTrait for #struct_ident { 57 57 async fn execute<'a>(&self, api: &'a fluxer_rs::api::FluxerApiHandler, feedback: &'a fluxer_rs::high_level::command_handler::CommandFeedback<'a>) -> Result<(), fluxer_rs::error::FluxerRsError> { 58 - let _ = #fn_name(api, feedback).await; 58 + #fn_name(api, feedback).await?; 59 59 Ok(()) 60 60 } 61 61 }
+1 -1
macros/src/dispatch.rs
··· 157 157 use log::{info,error}; 158 158 use serde::{Deserialize, de}; 159 159 160 - 160 + #[derive(Debug)] 161 161 pub enum DispatchEvent { 162 162 #(#enum_elems,)* 163 163 }
+3 -1
src/api/channels/messages.rs
··· 50 50 pub struct SendMessage { 51 51 pub channel_id: String, 52 52 53 - pub content: String, 53 + // Usually content is unset when we send only an embed 54 + #[builder(default)] 55 + pub content: Option<String>, 54 56 #[builder(default)] 55 57 pub embeds: Option<Vec<Embed>>, 56 58 #[builder(default)]
+165
src/api/channels/mod.rs
··· 1 + use anyhow::Result; 2 + 3 + use crate::{ 4 + api::{ 5 + FluxerApiHandler, 6 + channels::{ 7 + messages::{EditMessageBuilder, FetchMessageBuilder, SendMessageBuilder}, 8 + reactions::{AddOwnReactionBuilder, RemoveAllEmojiReactionsBuilder}, 9 + }, 10 + }, 11 + error::{ApiHandlerError, FluxerRsError}, 12 + serde::types::message::{Embed, MessageData, MessageReferenceBuilder}, 13 + }; 14 + 1 15 pub mod messages; 2 16 pub mod reactions; 17 + 18 + pub struct Channel<'a> { 19 + pub channel_id: String, 20 + pub api: &'a FluxerApiHandler, 21 + } 22 + 23 + pub struct Message<'a> { 24 + message_id: String, 25 + channel: &'a Channel<'a>, 26 + } 27 + 28 + impl<'a> Channel<'a> { 29 + pub fn new(channel_id: impl Into<String>, api: &'a FluxerApiHandler) -> Self { 30 + Channel { 31 + channel_id: channel_id.into(), 32 + api, 33 + } 34 + } 35 + 36 + pub fn message(&self, message_id: &str) -> Message<'_> { 37 + Message { 38 + message_id: message_id.into(), 39 + channel: self, 40 + } 41 + } 42 + 43 + pub async fn send_message(&self, content: &str) -> Result<MessageData, FluxerRsError> { 44 + let call = SendMessageBuilder::default() 45 + .channel_id(&self.channel_id) 46 + .content(Some(content.into())) 47 + .build() 48 + .map_err(ApiHandlerError::from)?; 49 + 50 + let result = self.api.execute_call(call).await?; 51 + 52 + Ok(result) 53 + } 54 + 55 + pub async fn send_embed_message( 56 + &self, 57 + embed: Vec<Embed>, 58 + ) -> Result<MessageData, FluxerRsError> { 59 + let call = SendMessageBuilder::default() 60 + .channel_id(&self.channel_id) 61 + .embeds(embed) 62 + .build() 63 + .map_err(ApiHandlerError::from)?; 64 + 65 + let result = self.api.execute_call(call).await?; 66 + 67 + Ok(result) 68 + } 69 + } 70 + 71 + impl<'a> Message<'a> { 72 + pub async fn get_data(&self) -> Result<MessageData, FluxerRsError> { 73 + let result = self 74 + .channel 75 + .api 76 + .execute_call( 77 + FetchMessageBuilder::default() 78 + .channel_id(&self.channel.channel_id) 79 + .message_id(&self.message_id) 80 + .build() 81 + .map_err(ApiHandlerError::from)?, 82 + ) 83 + .await?; 84 + 85 + Ok(result) 86 + } 87 + 88 + pub async fn reply(&self, content: &str) -> Result<MessageData, FluxerRsError> { 89 + let call = SendMessageBuilder::default() 90 + .channel_id(&self.channel.channel_id) 91 + .content(Some(content.into())) 92 + .message_reference( 93 + MessageReferenceBuilder::default() 94 + .message_id(&self.message_id) 95 + .build() 96 + .map_err(ApiHandlerError::from)?, 97 + ) 98 + .build() 99 + .map_err(ApiHandlerError::from)?; 100 + 101 + let result = self.channel.api.execute_call(call).await?; 102 + 103 + Ok(result) 104 + } 105 + 106 + pub async fn edit(&self, content: &str) -> Result<MessageData, FluxerRsError> { 107 + let call = EditMessageBuilder::default() 108 + .channel_id(&self.channel.channel_id) 109 + .message_id(&self.message_id) 110 + .content(content) 111 + .build() 112 + .map_err(ApiHandlerError::from)?; 113 + 114 + let result = self.channel.api.execute_call(call).await?; 115 + 116 + Ok(result) 117 + } 118 + 119 + pub async fn edit_with_embeds( 120 + &self, 121 + content: &str, 122 + embeds: Vec<Embed>, 123 + ) -> Result<MessageData, FluxerRsError> { 124 + let call = EditMessageBuilder::default() 125 + .channel_id(&self.channel.channel_id) 126 + .message_id(&self.message_id) 127 + .content(content) 128 + .embeds(embeds) 129 + .build() 130 + .map_err(ApiHandlerError::from)?; 131 + 132 + let result = self.channel.api.execute_call(call).await?; 133 + Ok(result) 134 + } 135 + 136 + pub async fn add_react(&self, emoji: &str) -> Result<(), FluxerRsError> { 137 + let emoji = if emoji.starts_with("<") { 138 + emoji.trim_start_matches('<').trim_end_matches('>') 139 + } else { 140 + emoji 141 + }; 142 + 143 + let call = AddOwnReactionBuilder::default() 144 + .channel_id(&self.channel.channel_id) 145 + .message_id(&self.message_id) 146 + .emoji(emoji) 147 + .build() 148 + .map_err(ApiHandlerError::from)?; 149 + 150 + self.channel.api.execute_call(call).await?; 151 + 152 + Ok(()) 153 + } 154 + 155 + pub async fn remove_all_emoji_reactions(&self, emoji: &str) -> Result<(), FluxerRsError> { 156 + let call = RemoveAllEmojiReactionsBuilder::default() 157 + .channel_id(&self.channel.channel_id) 158 + .message_id(&self.message_id) 159 + .emoji(emoji.trim_matches('<').trim_matches('>')) 160 + .build() 161 + .map_err(ApiHandlerError::from)?; 162 + 163 + self.channel.api.execute_call(call).await?; 164 + 165 + Ok(()) 166 + } 167 + }
-259
src/api/common.rs
··· 1 - use crate::{ 2 - api::{ 3 - FluxerApiHandler, 4 - channels::{ 5 - messages::{EditMessageBuilder, FetchMessageBuilder, SendMessageBuilder}, 6 - reactions::{AddOwnReactionBuilder, RemoveAllEmojiReactionsBuilder}, 7 - }, 8 - guilds::roles::{ 9 - AddRoleToMemberBuilder, CreateRoleBuilder, DeleteRoleBuilder, 10 - RemoveRoleFromMemberBuilder, 11 - }, 12 - }, 13 - error::{ApiHandlerError, FluxerRsError}, 14 - serde::types::{ 15 - common::RoleData, 16 - message::{Embed, MessageData, MessageReferenceBuilder}, 17 - }, 18 - }; 19 - 20 - // 21 - // Message helper functions 22 - // 23 - 24 - /// Get a message by it's id 25 - /// Required fields: `channel_id` and `message_id` of the message that will be fetched 26 - pub async fn fetch_message( 27 - api: &FluxerApiHandler, 28 - channel_id: &str, 29 - message_id: &str, 30 - ) -> Result<MessageData, FluxerRsError> { 31 - let call = FetchMessageBuilder::default() 32 - .channel_id(channel_id) 33 - .message_id(message_id) 34 - .build() 35 - .map_err(ApiHandlerError::from)?; 36 - 37 - let result = api.execute_call(call).await?; 38 - 39 - Ok(result) 40 - } 41 - 42 - /// Send a message 43 - /// Required fields: `channel_id` and `content` of the message that will be sent 44 - pub async fn send_message( 45 - api: &FluxerApiHandler, 46 - channel_id: &str, 47 - content: &str, 48 - ) -> Result<MessageData, FluxerRsError> { 49 - let call = SendMessageBuilder::default() 50 - .channel_id(channel_id) 51 - .content(content) 52 - .build() 53 - .map_err(ApiHandlerError::from)?; 54 - 55 - let result = api.execute_call(call).await?; 56 - 57 - Ok(result) 58 - } 59 - 60 - /// Send a message reply 61 - /// Required fields: `channel_id` and `message_id` of the original message and `content` of the response 62 - pub async fn send_reply( 63 - api: &FluxerApiHandler, 64 - channel_id: &str, 65 - message_id: &str, 66 - content: &str, 67 - ) -> Result<MessageData, FluxerRsError> { 68 - let call = SendMessageBuilder::default() 69 - .channel_id(channel_id) 70 - .content(content) 71 - .message_reference( 72 - MessageReferenceBuilder::default() 73 - .message_id(message_id) 74 - .build() 75 - .map_err(ApiHandlerError::from)?, 76 - ) 77 - .build() 78 - .map_err(ApiHandlerError::from)?; 79 - 80 - let result = api.execute_call(call).await?; 81 - 82 - Ok(result) 83 - } 84 - 85 - /// Edit a message 86 - /// Required fields: `channel_id` and `message_id` of the original message and `content` of the edited message 87 - pub async fn edit_message( 88 - api: &FluxerApiHandler, 89 - channel_id: &str, 90 - message_id: &str, 91 - content: &str, 92 - ) -> Result<MessageData, FluxerRsError> { 93 - let call = EditMessageBuilder::default() 94 - .channel_id(channel_id) 95 - .message_id(message_id) 96 - .content(content) 97 - .build() 98 - .map_err(ApiHandlerError::from)?; 99 - 100 - let result = api.execute_call(call).await?; 101 - 102 - Ok(result) 103 - } 104 - 105 - /// Edit a message that has embeds 106 - /// Required fields: `channel_id` and `message_id` of the original message, `content` and `embeds` of the edited message 107 - pub async fn edit_message_with_embeds( 108 - api: &FluxerApiHandler, 109 - channel_id: &str, 110 - message_id: &str, 111 - content: &str, 112 - embeds: Vec<Embed>, 113 - ) -> Result<MessageData, FluxerRsError> { 114 - let call = EditMessageBuilder::default() 115 - .channel_id(channel_id) 116 - .message_id(message_id) 117 - .content(content) 118 - .embeds(embeds) 119 - .build() 120 - .map_err(ApiHandlerError::from)?; 121 - 122 - let result = api.execute_call(call).await?; 123 - 124 - Ok(result) 125 - } 126 - 127 - // 128 - // Role helper functions 129 - // 130 - 131 - /// Give a role to a member 132 - /// Required fields: `guild_id`, `role_id` and `user_id` 133 - pub async fn give_role( 134 - api: &FluxerApiHandler, 135 - guild_id: &str, 136 - role_id: &str, 137 - user_id: &str, 138 - ) -> Result<(), FluxerRsError> { 139 - let call = AddRoleToMemberBuilder::default() 140 - .guild_id(guild_id) 141 - .role_id(role_id) 142 - .user_id(user_id) 143 - .build() 144 - .map_err(ApiHandlerError::from)?; 145 - 146 - api.execute_call(call).await?; 147 - 148 - Ok(()) 149 - } 150 - 151 - /// Remove a role from a user 152 - /// Required fields: `guild_id`, `role_id` and `user_id` 153 - pub async fn remove_role( 154 - api: &FluxerApiHandler, 155 - guild_id: &str, 156 - role_id: &str, 157 - user_id: &str, 158 - ) -> Result<(), FluxerRsError> { 159 - let call = RemoveRoleFromMemberBuilder::default() 160 - .guild_id(guild_id) 161 - .role_id(role_id) 162 - .user_id(user_id) 163 - .build() 164 - .map_err(ApiHandlerError::from)?; 165 - 166 - api.execute_call(call).await?; 167 - 168 - Ok(()) 169 - } 170 - 171 - /// Create a new role 172 - /// Required fields: `guild_id`, `name`, `permission` and `color` 173 - pub async fn create_role( 174 - api: &FluxerApiHandler, 175 - guild_id: &str, 176 - name: &str, 177 - permission: &str, 178 - color: &str, 179 - ) -> Result<RoleData, FluxerRsError> { 180 - let call = CreateRoleBuilder::default() 181 - .guild_id(guild_id) 182 - .name(name) 183 - .permission(permission) 184 - .color(u32::from_str_radix(color, 16).map_err(FluxerRsError::from)?) 185 - .build() 186 - .map_err(ApiHandlerError::from)?; 187 - 188 - let result = api.execute_call(call).await?; 189 - 190 - Ok(result) 191 - } 192 - 193 - /// Delete an existing role 194 - /// Required fields: `guild_id` and `role_id` 195 - pub async fn delete_role( 196 - api: &FluxerApiHandler, 197 - guild_id: &str, 198 - role_id: &str, 199 - ) -> Result<(), FluxerRsError> { 200 - let call = DeleteRoleBuilder::default() 201 - .guild_id(guild_id) 202 - .role_id(role_id) 203 - .build() 204 - .map_err(ApiHandlerError::from)?; 205 - 206 - api.execute_call(call).await?; 207 - 208 - Ok(()) 209 - } 210 - 211 - // 212 - // Reaction helper functions 213 - // 214 - 215 - /// Add the bot's reaction to a message 216 - /// Required fields: `channel_id`, `message_id` and `emoji` 217 - pub async fn react( 218 - api: &FluxerApiHandler, 219 - channel_id: &str, 220 - message_id: &str, 221 - emoji: &str, 222 - ) -> Result<(), FluxerRsError> { 223 - let emoji = if emoji.starts_with("<") { 224 - emoji.trim_start_matches('<').trim_end_matches('>') 225 - } else { 226 - emoji 227 - }; 228 - 229 - let call = AddOwnReactionBuilder::default() 230 - .channel_id(channel_id) 231 - .message_id(message_id) 232 - .emoji(emoji) 233 - .build() 234 - .map_err(ApiHandlerError::from)?; 235 - 236 - api.execute_call(call).await?; 237 - 238 - Ok(()) 239 - } 240 - 241 - /// Remove all emoji reactions from a given message 242 - /// Required fields: `channel_id`, `message_id` and `emoji` 243 - pub async fn remove_all_emoji_reactions( 244 - api: &FluxerApiHandler, 245 - channel_id: &str, 246 - message_id: &str, 247 - emoji: &str, 248 - ) -> Result<(), FluxerRsError> { 249 - let call = RemoveAllEmojiReactionsBuilder::default() 250 - .channel_id(channel_id) 251 - .message_id(message_id) 252 - .emoji(emoji.trim_matches('<').trim_matches('>')) 253 - .build() 254 - .map_err(ApiHandlerError::from)?; 255 - 256 - api.execute_call(call).await?; 257 - 258 - Ok(()) 259 - }
+97
src/api/guilds/mod.rs
··· 1 + use crate::{ 2 + api::{ 3 + FluxerApiHandler, 4 + guilds::roles::{ 5 + AddRoleToMemberBuilder, CreateRoleBuilder, DeleteRoleBuilder, 6 + RemoveRoleFromMemberBuilder, 7 + }, 8 + }, 9 + error::{ApiHandlerError, FluxerRsError}, 10 + serde::types::common::RoleData, 11 + }; 12 + 1 13 pub mod roles; 14 + 15 + pub struct Guild<'a> { 16 + guild_id: String, 17 + api: &'a FluxerApiHandler, 18 + } 19 + 20 + pub struct GuildMember<'a> { 21 + guild: &'a Guild<'a>, 22 + user_id: String, 23 + } 24 + 25 + impl<'a> Guild<'a> { 26 + pub fn new(guild_id: impl Into<String>, api: &'a FluxerApiHandler) -> Self { 27 + Guild { 28 + guild_id: guild_id.into(), 29 + api, 30 + } 31 + } 32 + 33 + pub fn member(&self, user_id: impl Into<String>) -> GuildMember<'_> { 34 + GuildMember { 35 + guild: self, 36 + user_id: user_id.into(), 37 + } 38 + } 39 + 40 + pub async fn create_role( 41 + &self, 42 + name: &str, 43 + permission: &str, 44 + color: &str, 45 + ) -> Result<RoleData, FluxerRsError> { 46 + let call = CreateRoleBuilder::default() 47 + .guild_id(&self.guild_id) 48 + .name(name) 49 + .permission(permission) 50 + .color(u32::from_str_radix(color, 16).map_err(FluxerRsError::from)?) 51 + .build() 52 + .map_err(ApiHandlerError::from)?; 53 + 54 + let result = self.api.execute_call(call).await?; 55 + 56 + Ok(result) 57 + } 58 + 59 + pub async fn delete_role(&self, role_id: &str) -> Result<(), FluxerRsError> { 60 + let call = DeleteRoleBuilder::default() 61 + .guild_id(&self.guild_id) 62 + .role_id(role_id) 63 + .build() 64 + .map_err(ApiHandlerError::from)?; 65 + 66 + self.api.execute_call(call).await?; 67 + 68 + Ok(()) 69 + } 70 + } 71 + 72 + impl<'a> GuildMember<'a> { 73 + pub async fn give_role(&self, role_id: &str) -> Result<(), FluxerRsError> { 74 + let call = AddRoleToMemberBuilder::default() 75 + .guild_id(&self.guild.guild_id) 76 + .role_id(role_id) 77 + .user_id(&self.user_id) 78 + .build() 79 + .map_err(ApiHandlerError::from)?; 80 + 81 + self.guild.api.execute_call(call).await?; 82 + 83 + Ok(()) 84 + } 85 + 86 + pub async fn remove_role(&self, role_id: &str) -> Result<(), FluxerRsError> { 87 + let call = RemoveRoleFromMemberBuilder::default() 88 + .guild_id(&self.guild.guild_id) 89 + .role_id(role_id) 90 + .user_id(&self.user_id) 91 + .build() 92 + .map_err(ApiHandlerError::from)?; 93 + 94 + self.guild.api.execute_call(call).await?; 95 + 96 + Ok(()) 97 + } 98 + }
+1 -2
src/api/mod.rs
··· 1 1 pub mod channels; 2 - pub mod common; 3 2 pub mod guilds; 4 3 5 4 use anyhow::Result; ··· 16 15 Delete, 17 16 } 18 17 19 - /// Trait to use when implementing a new API call 18 + /// Trait to use when implementing a new API call 20 19 pub trait ApiCall { 21 20 type ReturnType; 22 21
+27 -4
src/fluxerbot.rs
··· 19 19 20 20 pub struct FluxerWebsocket<T: DispatchHandlerTrait + Send + Sync + 'static> { 21 21 pub dispatch_handler: T, 22 + pub sequence_num: u32, 22 23 pub ws_handle: Client<FluxerWebsocket<T>>, 23 24 bot: FluxerBot, 24 25 } ··· 51 52 dispatch_handler, 52 53 ws_handle, 53 54 bot: self, 55 + sequence_num: 0, 54 56 }, 55 57 config, 56 58 ) ··· 73 75 type Call = (); 74 76 75 77 async fn on_text(&mut self, text: Utf8Bytes) -> Result<(), Error> { 78 + debug!( 79 + "⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️ DEBUG TEXT: {}", 80 + text 81 + ); 82 + 76 83 let result: Option<ReceiveData> = match serde_json::from_slice(text.as_bytes()) { 77 84 Ok(value) => value, 85 + 78 86 Err(err) => { 79 87 error!("Unhandled behavior: {err}"); 80 88 error!("{}", text); ··· 83 91 }; 84 92 85 93 if let Some(result) = result { 94 + if let Some(seq) = result.s { 95 + self.sequence_num = seq as u32; 96 + }; 97 + 86 98 match result.d { 87 99 ReceiveDataType::OP0(dispatch_event) => { 88 - handle_dispatch_events(dispatch_event, &self.dispatch_handler, &self.bot.api) 89 - .await? 100 + if let Err(err) = handle_dispatch_events( 101 + dispatch_event, 102 + &self.dispatch_handler, 103 + &self.bot.api, 104 + ) 105 + .await 106 + { 107 + error!("{err}"); 108 + } 109 + } 110 + ReceiveDataType::OP1(_op1_d) => { 111 + heartbeat_handler(text, &self.ws_handle, self.sequence_num).await? 90 112 } 91 - ReceiveDataType::OP1(_op1_d) => heartbeat_handler(text, &self.ws_handle).await?, 92 113 ReceiveDataType::OP9(op9_d) => { 93 114 if !op9_d { 94 115 debug!("-> {} Connection Invalid, Reauthenticating", text); ··· 100 121 panic!() 101 122 } 102 123 } 103 - ReceiveDataType::OP10(_data) => heartbeat_handler(text, &self.ws_handle).await?, 124 + ReceiveDataType::OP10(_data) => { 125 + heartbeat_handler(text, &self.ws_handle, self.sequence_num).await? 126 + } 104 127 ReceiveDataType::OP11 => heartbeat_ack_handler::<T>(text).await, 105 128 } 106 129 }
+1
src/gateway/dispatch.rs
··· 11 11 ["GUILD_ROLE_UPDATE_BULK", GuildRoleUpdateBulkData], 12 12 ["GUILD_ROLE_DELETE", GuildRoleDeleteData], 13 13 ["GUILD_MEMBER_UPDATE", MemberData], 14 + ["GUILD_AUDIT_LOG_ENTRY_CREATE", GuildAuditLogCreateData], 14 15 ["MESSAGE_CREATE", MessageData], 15 16 ["MESSAGE_DELETE", MessageData], 16 17 ["MESSAGE_UPDATE", MessageData],
+2 -1
src/gateway/heartbeat.rs
··· 12 12 pub async fn heartbeat_handler<T: DispatchHandlerTrait + Send + Sync + 'static>( 13 13 text: Utf8Bytes, 14 14 client_handle: &Client<FluxerWebsocket<T>>, 15 + sequence: u32 15 16 ) -> Result<(), FluxerRsError> { 16 17 debug!("-> {}", text); 17 18 18 19 let heartbeat = SendData { 19 20 op: 1, 20 - d: SendDataType::OP1(None), 21 + d: SendDataType::OP1(Some(sequence)), 21 22 }; 22 23 23 24 let heartbeat_string = serde_json::to_string(&heartbeat)?;
+66
src/high_level/command_context.rs
··· 1 + use std::fmt::Display; 2 + 3 + use crate::{ 4 + api::channels::Channel, 5 + error::{CommandHandlerError, FluxerRsError}, 6 + serde::types::message::EmbedBuilder, 7 + }; 8 + 9 + pub struct CommandArgs(Vec<String>); 10 + 11 + impl FromIterator<String> for CommandArgs { 12 + fn from_iter<B: IntoIterator<Item = String>>(iter: B) -> Self { 13 + CommandArgs(iter.into_iter().collect()) 14 + } 15 + } 16 + 17 + impl Display for CommandArgs { 18 + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 19 + for elem in &self.0 { 20 + write!(f, "`{}` ", elem)?; 21 + } 22 + 23 + Ok(()) 24 + } 25 + } 26 + 27 + pub struct CommandContext { 28 + pub args: CommandArgs, 29 + } 30 + 31 + impl CommandContext { 32 + pub fn new(args: impl IntoIterator<Item = impl Into<String>>) -> Self { 33 + CommandContext { 34 + args: args.into_iter().map(Into::into).collect(), 35 + } 36 + } 37 + 38 + pub async fn handle<'a>( 39 + &self, 40 + channel: &Channel<'a>, 41 + args: impl IntoIterator<Item = &'a str>, 42 + err_desc: impl Into<String>, 43 + color: u32, 44 + ) -> Result<(), FluxerRsError> { 45 + let args: Vec<&str> = args.into_iter().collect(); 46 + 47 + if args.len() != self.args.0.len() { 48 + channel 49 + .send_embed_message(vec![ 50 + EmbedBuilder::default() 51 + .title("Command error".to_string()) 52 + .description(err_desc.into()) 53 + .color(color) 54 + .build() 55 + .unwrap(), 56 + ]) 57 + .await?; 58 + 59 + return Err(FluxerRsError::CommandHandlerError( 60 + CommandHandlerError::Custom("Invalid number of arguments".into()), 61 + )); 62 + } 63 + 64 + Ok(()) 65 + } 66 + }
+7 -2
src/high_level/command_handler.rs
··· 67 67 data: &MessageData, 68 68 api: &FluxerApiHandler, 69 69 ) -> Result<(), FluxerRsError> { 70 + let content = match &data.content { 71 + Some(data) => data, 72 + None => return Ok(()), 73 + }; 74 + 70 75 // Checking whether the message content really starts with the prefix and if it's not just the prefix itself 71 - if !data.content.starts_with(&self.prefix) || data.content.len() < 2 { 76 + if !content.starts_with(&self.prefix) || content.len() < 2 { 72 77 return Ok(()); 73 78 } 74 79 75 - let (cmd, args) = Self::command_data(&self.prefix, &data.content)?; 80 + let (cmd, args) = Self::command_data(&self.prefix, content)?; 76 81 77 82 let split_args = Self::split_args(args); 78 83
+1
src/high_level/mod.rs
··· 1 1 pub mod command_handler; 2 + pub mod command_context;
+4 -1
src/serde/gateway.rs
··· 32 32 .as_u64() 33 33 .ok_or_else(|| de::Error::missing_field("op"))? as u8; 34 34 35 + let s = value["s"] 36 + .as_u64(); 37 + 35 38 let d = match op { 36 39 0 => ReceiveDataType::OP0(Box::new( 37 40 dispatch_deserialize(&value).map_err(de::Error::custom)?, ··· 53 56 _ => return Err(de::Error::custom(format!("unknown op: {}", op))), 54 57 }; 55 58 56 - Ok(ReceiveData { d, op }) 59 + Ok(ReceiveData { d, op, s }) 57 60 } 58 61 }
+4 -1
src/serde/types/gateway.rs
··· 2 2 3 3 use crate::gateway::dispatch::DispatchEvent; 4 4 5 + #[derive(Debug)] 5 6 pub struct ReceiveData { 6 7 pub d: ReceiveDataType, 7 8 pub op: u8, 9 + pub s: Option<u64>, 8 10 } 9 11 10 12 pub struct SendData { ··· 12 14 pub op: u8, 13 15 } 14 16 17 + #[derive(Debug)] 15 18 pub enum ReceiveDataType { 16 19 OP0(Box<DispatchEvent>), 17 20 OP1(Option<u32>), ··· 38 41 pub device: String, 39 42 } 40 43 41 - #[derive(Serialize, Deserialize)] 44 + #[derive(Serialize, Deserialize, Debug)] 42 45 pub struct OP10D { 43 46 pub heartbeat_interval: u32, 44 47 }
+17 -2
src/serde/types/guild.rs
··· 6 6 }; 7 7 8 8 /// Data returned by the `GUILD_DELETE` dispatch event 9 - #[derive(Deserialize)] 9 + #[derive(Deserialize, Debug)] 10 10 pub struct GuildDeleteData { 11 11 #[serde(rename = "id")] 12 12 pub guild_id: String, ··· 14 14 } 15 15 16 16 /// Data returned by the `GUILD_CREATE` dispatch event 17 - #[derive(Deserialize)] 17 + #[derive(Deserialize, Debug)] 18 18 pub struct GuildCreateData { 19 19 pub channels: Vec<ChannelData>, 20 20 pub emojis: Vec<Value>, ··· 57 57 pub guild_id: String, 58 58 pub role_id: String, 59 59 } 60 + 61 + #[derive(Deserialize, Debug)] 62 + pub struct GuildAuditLogCreateData { 63 + pub action_type: i64, 64 + pub guild_id: String, 65 + pub id: String, 66 + pub options: GuildAuditLogOptions, 67 + pub target_id: Option<String>, 68 + pub user_id: String, 69 + } 70 + 71 + #[derive(Deserialize, Debug)] 72 + pub struct GuildAuditLogOptions { 73 + pub channel_id: String, 74 + }
+1 -1
src/serde/types/message.rs
··· 12 12 pub author: Option<AuthorData>, 13 13 pub channel_id: String, 14 14 pub channel_type: Option<i64>, 15 - pub content: String, 15 + pub content: Option<String>, 16 16 pub edited_timestamp: Option<Value>, 17 17 pub embeds: Option<Vec<Embed>>, 18 18 pub flags: Option<i64>,
+5 -5
src/serde/types/user.rs
··· 2 2 use serde_json::Value; 3 3 4 4 /// Data returned by the `READY` dispatch event 5 - #[derive(Deserialize)] 5 + #[derive(Deserialize, Debug)] 6 6 pub struct ReadyData { 7 7 pub country_code: String, 8 8 pub favorite_memes: Vec<Value>, ··· 24 24 } 25 25 26 26 /// Data for a session used in [`ReadyData`] 27 - #[derive(Deserialize)] 27 + #[derive(Deserialize, Debug)] 28 28 pub struct Session { 29 29 pub afk: bool, 30 30 pub mobile: bool, ··· 33 33 } 34 34 35 35 /// Data for a logged in user used in [`ReadyData`] 36 - #[derive(Deserialize)] 36 + #[derive(Deserialize, Debug)] 37 37 pub struct LoggedInUser { 38 38 pub accent_color: Value, 39 39 pub acls: Vec<Value>, ··· 89 89 } 90 90 91 91 /// Data for notes used in [`ReadyData`] 92 - #[derive(Deserialize)] 92 + #[derive(Deserialize, Debug)] 93 93 pub struct Notes {} 94 94 95 95 /// Data returned by the `SESSIONS_REPLACE` dispatch event 96 - #[derive(Deserialize)] 96 + #[derive(Deserialize, Debug)] 97 97 pub struct SessionReplaceData { 98 98 pub afk: bool, 99 99 pub mobile: bool,