-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
10 changed files
with
317 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -23,3 +23,4 @@ Cargo.lock | |
# Added by cargo | ||
|
||
/target | ||
.env |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,7 @@ | ||
{ | ||
"files.insertFinalNewline": true, | ||
"files.trimFinalNewlines": true, | ||
"files.trimTrailingWhitespace": true, | ||
"editor.formatOnSave": true, | ||
"rust-analyzer.check.command": "clippy", | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
use serenity::{ | ||
all::{Context, EventHandler, GuildId, GuildMemberUpdateEvent, Member, User}, | ||
async_trait, | ||
}; | ||
|
||
pub struct AuditHandler; | ||
|
||
#[async_trait] | ||
#[allow(unused_variables)] | ||
impl EventHandler for AuditHandler { | ||
async fn guild_member_addition(&self, ctx: Context, new_member: Member) {} | ||
|
||
async fn guild_member_removal( | ||
&self, | ||
ctx: Context, | ||
guild_id: GuildId, | ||
user: User, | ||
member_data_if_available: Option<Member>, | ||
) { | ||
} | ||
|
||
async fn guild_member_update( | ||
&self, | ||
ctx: Context, | ||
old_if_available: Option<Member>, | ||
new: Option<Member>, | ||
event: GuildMemberUpdateEvent, | ||
) { | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
use serenity::{ | ||
all::{CommandInteraction, Context, CreateCommandOption, Permissions}, | ||
async_trait, | ||
}; | ||
|
||
#[async_trait] | ||
pub trait Command { | ||
fn name(&self) -> &str; | ||
fn description(&self) -> &str; | ||
fn options(&self) -> Vec<CreateCommandOption>; | ||
fn default_permission(&self) -> Permissions; | ||
async fn execute(&self, ctx: Context, interaction: CommandInteraction); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
use std::process::exit; | ||
|
||
use serenity::all::{CommandInteraction, EditInteractionResponse}; | ||
use serenity::{ | ||
all::{ | ||
Context, CreateCommand, CreateInteractionResponse, CreateInteractionResponseMessage, | ||
EventHandler, GuildId, Interaction, | ||
}, | ||
async_trait, | ||
model::gateway::Ready, | ||
}; | ||
|
||
use crate::get_guild_id; | ||
|
||
pub mod base; | ||
|
||
pub mod tgverify; | ||
|
||
pub const COMMANDS: [&(dyn base::Command + Sync); 1] = [&tgverify::TgVerifyCommand]; | ||
|
||
pub struct CommandHandler; | ||
|
||
#[async_trait] | ||
impl EventHandler for CommandHandler { | ||
async fn interaction_create(&self, ctx: Context, interaction: Interaction) { | ||
let command = interaction.command(); | ||
if command.is_none() { | ||
return; | ||
} | ||
let command_interaction = command.unwrap(); | ||
let name = command_interaction.data.name.clone(); | ||
println!("Received command: {}", name); | ||
|
||
let command = COMMANDS.iter().find(|command| command.name() == name); | ||
if command.is_none() { | ||
return; | ||
} | ||
|
||
if let Err(why) = command_interaction | ||
.create_response( | ||
&ctx.http, | ||
CreateInteractionResponse::Defer(CreateInteractionResponseMessage::default()), | ||
) | ||
.await | ||
{ | ||
println!("Failed to acknowledge command: {:?}", why); | ||
return; | ||
} | ||
|
||
let command = command.unwrap(); | ||
command.execute(ctx, command_interaction).await; | ||
} | ||
|
||
async fn ready(&self, ctx: Context, _ready: Ready) { | ||
let id = get_guild_id(); | ||
if id == 0 { | ||
println!("GUILD_ID is invalid."); | ||
exit(1); | ||
} | ||
|
||
let guild = GuildId::new(id); | ||
let mut commands = vec![]; | ||
for command in COMMANDS.iter() { | ||
commands.push(CommandHandler::create_command(*command)); | ||
} | ||
|
||
if let Err(why) = guild.set_commands(&ctx.http, commands).await { | ||
println!("Failed to set application commands: {:?}", why); | ||
exit(1); | ||
} | ||
println!("Commands set."); | ||
} | ||
} | ||
|
||
impl CommandHandler { | ||
fn create_command(command: &dyn base::Command) -> CreateCommand { | ||
let mut create_command = CreateCommand::new(command.name()) | ||
.description(command.description()) | ||
.dm_permission(false); | ||
for option in command.options() { | ||
create_command = create_command.add_option(option); | ||
} | ||
create_command.default_member_permissions(command.default_permission()) | ||
} | ||
} | ||
|
||
pub async fn send_response(ctx: &Context, interaction: &CommandInteraction, content: &str) { | ||
println!("Sending response: {}", content); | ||
if let Err(why) = interaction | ||
.edit_response(&ctx.http, EditInteractionResponse::new().content(content)) | ||
.await | ||
{ | ||
println!("Failed to send response: {:?}", why); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
use serenity::{ | ||
all::{CommandInteraction, CommandOptionType, Context, CreateCommandOption, Permissions}, | ||
async_trait, | ||
}; | ||
|
||
use crate::commands::send_response; | ||
|
||
use super::base::Command; | ||
|
||
pub struct TgVerifyCommand; | ||
|
||
#[async_trait] | ||
impl Command for TgVerifyCommand { | ||
fn name(&self) -> &str { | ||
"tgverify" | ||
} | ||
|
||
fn description(&self) -> &str { | ||
"Verify your BYOND account." | ||
} | ||
|
||
fn options(&self) -> Vec<CreateCommandOption> { | ||
vec![CreateCommandOption::new( | ||
CommandOptionType::String, | ||
"token", | ||
"The token you received from the game server.", | ||
) | ||
.required(true)] | ||
} | ||
|
||
async fn execute(&self, ctx: Context, interaction: CommandInteraction) { | ||
println!("TODO: Implement tgverify command"); | ||
send_response(&ctx, &interaction, "TODO").await; | ||
} | ||
|
||
fn default_permission(&self) -> serenity::all::Permissions { | ||
Permissions::empty() | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
use std::collections::HashMap; | ||
|
||
use serde::{Deserialize, Serialize}; | ||
|
||
#[derive(Serialize, Deserialize)] | ||
pub struct Config { | ||
guild_id: u64, | ||
values: HashMap<String, String>, | ||
} | ||
|
||
impl Config { | ||
pub fn new(guild_id: u64) -> Self { | ||
Self { | ||
guild_id, | ||
values: HashMap::new(), | ||
} | ||
} | ||
|
||
pub fn save(&self) { | ||
let path = std::env::var("CONFIG_PATH").unwrap(); | ||
let path = std::path::PathBuf::from(path); | ||
let path = path.join(format!("{}.json", self.guild_id)); | ||
let data = serde_json::to_string_pretty(&self).unwrap(); | ||
std::fs::write(&path, data).unwrap(); | ||
} | ||
|
||
pub fn get(&self, key: &str) -> Option<&str> { | ||
self.values.get(key).map(|s| s.as_str()) | ||
} | ||
|
||
pub fn set(&mut self, key: &str, value: &str) { | ||
self.values.insert(key.to_string(), value.to_string()); | ||
} | ||
} | ||
|
||
impl Drop for Config { | ||
fn drop(&mut self) { | ||
self.save(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,92 @@ | ||
use std::thread; | ||
use std::{ | ||
collections::HashMap, | ||
path::PathBuf, | ||
process::exit, | ||
sync::{Arc, LazyLock, Mutex}, | ||
}; | ||
|
||
fn main() { | ||
loop { | ||
thread::park(); | ||
use config::Config; | ||
use serenity::all::GatewayIntents; | ||
|
||
pub mod audit; | ||
pub mod commands; | ||
pub mod config; | ||
|
||
#[tokio::main] | ||
async fn main() { | ||
dotenv::dotenv().ok(); | ||
ensure_env_vars(); | ||
|
||
let client = serenity::Client::builder( | ||
get_discord_token(), | ||
GatewayIntents::GUILD_MESSAGES | ||
| GatewayIntents::GUILD_MESSAGE_REACTIONS | ||
| GatewayIntents::GUILD_MEMBERS | ||
| GatewayIntents::MESSAGE_CONTENT, | ||
) | ||
.event_handler(commands::CommandHandler) | ||
.event_handler(audit::AuditHandler) | ||
.await; | ||
if client.is_err() { | ||
println!("Failed to create client: {:?}", client.err().unwrap()); | ||
exit(1); | ||
} | ||
let mut client = client.unwrap(); | ||
|
||
let invite_url = format!( | ||
"https://discord.com/api/oauth2/authorize?client_id={}&permissions=8&scope=bot%20applications.commands", | ||
client.http.get_current_application_info().await.unwrap().id | ||
); | ||
|
||
println!("Invite URL: {}", invite_url); | ||
println!("Starting client..."); | ||
if let Err(why) = client.start().await { | ||
println!("Client error: {:?}", why); | ||
} | ||
println!("Client closed."); | ||
} | ||
|
||
const DISCORD_TOKEN: &str = "DISCORD_TOKEN"; | ||
const GUILD_ID: &str = "GUILD_ID"; | ||
|
||
pub fn ensure_env_vars() { | ||
if std::env::var(DISCORD_TOKEN).is_err() { | ||
println!("{} is not set", DISCORD_TOKEN); | ||
exit(1); | ||
} | ||
if std::env::var(GUILD_ID).is_err() { | ||
println!("{} is not set", GUILD_ID); | ||
exit(1); | ||
} | ||
if std::env::var("CONFIG_PATH").is_err() { | ||
println!("CONFIG_PATH is not set"); | ||
exit(1); | ||
} | ||
} | ||
|
||
pub fn get_discord_token() -> String { | ||
std::env::var(DISCORD_TOKEN).unwrap() | ||
} | ||
|
||
pub fn get_guild_id() -> u64 { | ||
std::env::var(GUILD_ID).unwrap().parse().unwrap() | ||
} | ||
|
||
pub fn get_config(guild: u64) -> Arc<Config> { | ||
static CONFIGS: LazyLock<Mutex<HashMap<u64, Arc<Config>>>> = | ||
LazyLock::new(|| Mutex::new(HashMap::new())); | ||
|
||
let mut configs = CONFIGS.lock().expect("failed to lock CONFIGS"); | ||
configs.entry(guild).or_insert_with(|| { | ||
let path = PathBuf::from(std::env::var("CONFIG_PATH").unwrap()); | ||
let path = path.join(format!("{}.json", guild)); | ||
if !path.exists() { | ||
let config = Config::new(guild); | ||
config.save(); | ||
} | ||
let data = std::fs::read_to_string(path).unwrap(); | ||
Arc::new(serde_json::from_str(&data).unwrap()) | ||
}); | ||
|
||
configs.get(&guild).unwrap().clone() | ||
} |