Thank you for considering contributing to our Discord bot written in Rust using the serenity
and Poise
libraries. We welcome contributions of all kinds, including bug reports, feature requests, and code improvements. This document provides a guide for contributing effectively.
- Rust installed.
- A Shuttle account and installation.
- A Discord Bot Token.
- Clone the repository:
git clone https://github.com/amfoss/amd.git
cd amd
- Create a
Secrets.toml
with your Discord token in it.
touch Secrets.toml
echo <YOUR TOKEN> >> Secrets.toml
- Run the bot locally with
cargo shuttle run
. For instructions on how to deploy, refer Shuttle docs.
If you encounter a bug, please check existing issues first to avoid duplicates. If none exist, create a new issue with the following details:
- Title: Concise summary.
- Description: A detailed description of the issue.
- Steps to Reproduce: If it's a bug, include steps to reproduce.
- Expected and Actual Behavior: Describe what you expected and what actually happened.
We welcome ideas! Please open an issue titled "Feature Request: <Feature Name>
" and provide:
- Problem: What problem does this feature solve?
- Solution: Describe how you envision it working.
- Alternatives Considered: Mention any alternatives you've considered.
If you'd like to fix a bug, add a feature, or improve code quality:
- Check the open issues to avoid redundancy.
- Open a draft PR if you'd like feedback on an ongoing contribution.
-
Follow Rust Conventions: Use idiomatic Rust patterns. Use
cargo fmt
andcargo clippy
to format and lint your code. -
Modularity: Write modular, reusable functions. Avoid monolithic code.
-
Descriptive Naming: Use descriptive names for variables, functions, and types.
This bot uses poise
, a command framework built on top of serenity
. You can add commands in the commands
module and get them registered using the get_commands
function.
// Example command in src/commands.rs
#[poise::command(prefix_command)]
async fn amdctl(ctx: Context<'_>) -> Result<(), Error> {
ctx.say("amD is up and running.").await?;
Ok(())
}
pub fn get_commands() -> Vec<poise::Command<Data, Error>> {
vec![amdctl()]
}
amD supports automatic role assignment based on emoji reactions to specific messages. You can configure which messages and reactions trigger role assignemnt by modifying the reaction_roles
Hashmap in the bot's Data
struct in the initialize_data()
function.
reaction_roles: HashMap::new(),
// Role IDs, use `\@<ROLE>` to get the ID on Discord
let archive_role_id = RoleId::new(ARCHIVE_ROLE_ID);
let mobile_role_id = RoleId::new(MOBILE_ROLE_ID);
let systems_role_id = RoleId::new(SYSTEMS_ROLE_ID);
... /* excluded for brevity */
let message_roles = [
// Give y role if reacted with x emoji in a hashmap pair (x, y)
(ReactionType::Unicode("📁".to_string()), archive_role_id),
(ReactionType::Unicode("📱".to_string()), mobile_role_id),
(ReactionType::Unicode("⚙️".to_string()), systems_role_id),
... /* excluded for brevity */
];
data.reaction_roles.extend::<HashMap<ReactionType, RoleId>>(message_roles.into());
The event handler takes care of the rest:
// On the event of a reaction being added
FullEvent::ReactionAdd { add_reaction } => {
let message_id = MessageId::new(ARCHIVE_MESSAGE_ID);
// Check if the reaction was added to the message we want and if it is reacted with the
// emoji we want
if add_reaction.message_id == message_id && data.reaction_roles.contains_key(&add_reaction.emoji) {
// Ensure it is in a server
if let Some(guild_id) = add_reaction.guild_id {
// Give the member the required role
if let Ok(member) =
guild_id.member(ctx, add_reaction.user_id.unwrap()).await
{
if let Err(e) = member.add_role(&ctx.http, data.reaction_roles.get(&add_reaction.emoji).expect("Hard coded value verified earlier.")).await {
eprintln!("Error: {:?}", e);
}
}
}
}
}
The scheduler system allows you to easily define tasks that should be repeated periodically. Simply define a struct that implements the task
trait and the scheduler
module will automatically spawn a thread for your task on startup.
#[async_trait]
pub trait Task: Send + Sync {
fn name(&self) -> &'static str;
fn run_in(&self) -> Duration;
async fn run(&self, ctx: Context);
}
Sample task that runs at 5 am every day:
pub struct StatusUpdateCheck;
#[async_trait]
impl Task for StatusUpdateCheck {
fn name(&self) -> &'static str {
"StatusUpdateCheck"
}
fn run_in(&self) -> Duration {
time_until(5, 0)
}
async fn run(&self, ctx: Context) {
... /* Excluded for brevity */
}