init - arche setup, empty API, discord bot
This commit is contained in:
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
/target
|
||||||
|
*.env
|
||||||
2606
Cargo.lock
generated
Normal file
2606
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
13
Cargo.toml
Normal file
13
Cargo.toml
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
[package]
|
||||||
|
name = "arche"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
axum = "0.8.3"
|
||||||
|
dotenvy = "0.15.7"
|
||||||
|
rand = "0.9.1"
|
||||||
|
serenity = "0.12.4"
|
||||||
|
tokio = { version = "1.44.2", features = ["full"] }
|
||||||
|
tracing = "0.1.41"
|
||||||
|
tracing-subscriber = "0.3.19"
|
||||||
110
src/discordbot/commands/kiss.rs
Normal file
110
src/discordbot/commands/kiss.rs
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
use rand::seq::IndexedRandom;
|
||||||
|
use serenity::all::{
|
||||||
|
CommandInteraction, CommandOptionType, Context, CreateCommandOption, CreateEmbed,
|
||||||
|
CreateInteractionResponse, CreateInteractionResponseMessage, MessageBuilder, ResolvedValue,
|
||||||
|
};
|
||||||
|
use serenity::builder::CreateCommand;
|
||||||
|
|
||||||
|
const KISS_GIF_LINK: &str = "https://media.discordapp.net/attachments/594222795999805643/1078363884190183485/ezgif-1-96508f9a03.gif?ex=681108a1&is=680fb721&hm=719ea63308d9f2989fbdc8110735805c2e9c26a378e749785fa141ddb180203f";
|
||||||
|
|
||||||
|
const KISS_TEXTS: &[&str] = &[
|
||||||
|
"How endearing... :3 :3",
|
||||||
|
"What does this imply!?",
|
||||||
|
"What a cutie!",
|
||||||
|
"That's a little gay...",
|
||||||
|
"With tongue though?!",
|
||||||
|
"Will they reciprocate?",
|
||||||
|
"And they were roommates.",
|
||||||
|
"How brave!",
|
||||||
|
"No homo.",
|
||||||
|
"Better than \"kys\".",
|
||||||
|
"What comes next?",
|
||||||
|
];
|
||||||
|
|
||||||
|
pub async fn run(ctx: &Context, interaction: &CommandInteraction) -> Result<(), serenity::Error> {
|
||||||
|
let options = &interaction.data.options();
|
||||||
|
|
||||||
|
let target_user = options
|
||||||
|
.iter()
|
||||||
|
.find_map(|opt| {
|
||||||
|
if opt.name == "target" {
|
||||||
|
match &opt.value {
|
||||||
|
ResolvedValue::User(user, member) => Some((user, member.as_ref())),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.expect("Target user should be present");
|
||||||
|
|
||||||
|
let use_bigger = options
|
||||||
|
.iter()
|
||||||
|
.find_map(|opt| {
|
||||||
|
if opt.name == "bigger" {
|
||||||
|
match &opt.value {
|
||||||
|
ResolvedValue::Boolean(v) => Some(*v),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.unwrap_or(false);
|
||||||
|
|
||||||
|
let caller_name = interaction
|
||||||
|
.member
|
||||||
|
.as_ref()
|
||||||
|
.map(|m| m.nick.clone().unwrap_or_else(|| m.user.name.clone()))
|
||||||
|
.unwrap_or_else(|| interaction.user.name.clone());
|
||||||
|
|
||||||
|
let target_name = match target_user {
|
||||||
|
(user, Some(member)) => member.nick.clone().unwrap_or_else(|| user.name.clone()),
|
||||||
|
(user, None) => user.name.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let rand_text = KISS_TEXTS.choose(&mut rand::rng()).unwrap();
|
||||||
|
let mention = MessageBuilder::new()
|
||||||
|
.mention(target_user.0.to_owned())
|
||||||
|
.build();
|
||||||
|
let mut embed = CreateEmbed::new()
|
||||||
|
.title(format!("{} kisses {}", caller_name, target_name))
|
||||||
|
.description(format!("{rand_text}\n{mention}"))
|
||||||
|
.color(0xFF89B4);
|
||||||
|
|
||||||
|
if use_bigger {
|
||||||
|
embed = embed.image(KISS_GIF_LINK.to_string())
|
||||||
|
} else {
|
||||||
|
embed = embed.thumbnail(KISS_GIF_LINK.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
interaction
|
||||||
|
.create_response(
|
||||||
|
&ctx.http,
|
||||||
|
CreateInteractionResponse::Message(
|
||||||
|
CreateInteractionResponseMessage::new().add_embed(embed),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn register() -> CreateCommand {
|
||||||
|
CreateCommand::new("kiss")
|
||||||
|
.description("pocałuj kogoś.")
|
||||||
|
.add_option(
|
||||||
|
CreateCommandOption::new(
|
||||||
|
CommandOptionType::User,
|
||||||
|
"target",
|
||||||
|
"użytkownik do pocałowania",
|
||||||
|
)
|
||||||
|
.required(true),
|
||||||
|
)
|
||||||
|
.add_option(
|
||||||
|
CreateCommandOption::new(
|
||||||
|
CommandOptionType::Boolean,
|
||||||
|
"bigger",
|
||||||
|
"czy użyć wersji z większym obrazkiem?",
|
||||||
|
)
|
||||||
|
.required(false),
|
||||||
|
)
|
||||||
|
}
|
||||||
2
src/discordbot/commands/mod.rs
Normal file
2
src/discordbot/commands/mod.rs
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
pub mod kiss;
|
||||||
|
pub mod ping;
|
||||||
19
src/discordbot/commands/ping.rs
Normal file
19
src/discordbot/commands/ping.rs
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
use serenity::all::{
|
||||||
|
CommandInteraction, Context, CreateInteractionResponse, CreateInteractionResponseMessage,
|
||||||
|
};
|
||||||
|
use serenity::builder::CreateCommand;
|
||||||
|
|
||||||
|
pub async fn run(ctx: &Context, command: &CommandInteraction) -> Result<(), serenity::Error> {
|
||||||
|
command
|
||||||
|
.create_response(
|
||||||
|
&ctx.http,
|
||||||
|
CreateInteractionResponse::Message(
|
||||||
|
CreateInteractionResponseMessage::new().content("P-p-ping!"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn register() -> CreateCommand {
|
||||||
|
CreateCommand::new("ping").description("komenda testowa.")
|
||||||
|
}
|
||||||
84
src/discordbot/mod.rs
Normal file
84
src/discordbot/mod.rs
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
use serenity::{
|
||||||
|
all::{CreateInteractionResponse, CreateInteractionResponseMessage, Interaction, Ready},
|
||||||
|
async_trait,
|
||||||
|
prelude::*,
|
||||||
|
};
|
||||||
|
use tracing::info;
|
||||||
|
|
||||||
|
const TOKEN_ENV: &str = "DISCORD_GRA_MAIN_TOKEN";
|
||||||
|
const GUILD_ID1: &str = "DISCORD_MAIN_SERVER_ID";
|
||||||
|
const GUILD_ID2: &str = "DISCORD_RD_SERVER_ID";
|
||||||
|
|
||||||
|
struct Handler;
|
||||||
|
|
||||||
|
mod commands;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl EventHandler for Handler {
|
||||||
|
async fn ready(&self, ctx: Context, ready: Ready) {
|
||||||
|
info!("{} is connected!", ready.user.name);
|
||||||
|
|
||||||
|
let cmds = vec![commands::ping::register(), commands::kiss::register()];
|
||||||
|
|
||||||
|
let main_guild_id = serenity::model::id::GuildId::from(
|
||||||
|
std::env::var(GUILD_ID1).unwrap().parse::<u64>().unwrap(),
|
||||||
|
);
|
||||||
|
let dev_guild_id = serenity::model::id::GuildId::from(
|
||||||
|
std::env::var(GUILD_ID2).unwrap().parse::<u64>().unwrap(),
|
||||||
|
);
|
||||||
|
|
||||||
|
match main_guild_id.set_commands(&ctx.http, cmds.clone()).await {
|
||||||
|
Ok(_) => info!("Successfully registered commands on main guild.",),
|
||||||
|
Err(why) => info!("Failed to register commands on main guild: {why:?}"),
|
||||||
|
};
|
||||||
|
match dev_guild_id.set_commands(&ctx.http, cmds.clone()).await {
|
||||||
|
Ok(_) => info!("Successfully registered commands on dev guild."),
|
||||||
|
Err(why) => info!("Failed to register commands on dev guild: {why:?}"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn interaction_create(&self, ctx: Context, interaction: Interaction) {
|
||||||
|
if let Interaction::Command(command) = interaction {
|
||||||
|
let result = match command.data.name.as_str() {
|
||||||
|
"ping" => commands::ping::run(&ctx, &command).await,
|
||||||
|
"kiss" => commands::kiss::run(&ctx, &command).await,
|
||||||
|
_ => {
|
||||||
|
command
|
||||||
|
.create_response(
|
||||||
|
&ctx.http,
|
||||||
|
CreateInteractionResponse::Message(
|
||||||
|
CreateInteractionResponseMessage::new().content("Not implemented"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Err(why) = result {
|
||||||
|
println!("Error executing command: {:?}", why);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn init() {
|
||||||
|
tokio::spawn(async {
|
||||||
|
init_bot().await;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn init_bot() {
|
||||||
|
let token = std::env::var(TOKEN_ENV).unwrap();
|
||||||
|
let intents = GatewayIntents::GUILD_MESSAGES
|
||||||
|
| GatewayIntents::DIRECT_MESSAGES
|
||||||
|
| GatewayIntents::MESSAGE_CONTENT;
|
||||||
|
|
||||||
|
let mut client = Client::builder(&token, intents)
|
||||||
|
.event_handler(Handler)
|
||||||
|
.await
|
||||||
|
.expect("errored creating client");
|
||||||
|
|
||||||
|
if let Err(why) = client.start().await {
|
||||||
|
info!("client error: {why:?}");
|
||||||
|
}
|
||||||
|
}
|
||||||
18
src/main.rs
Normal file
18
src/main.rs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
use std::error::Error;
|
||||||
|
use tokio::net::TcpListener;
|
||||||
|
|
||||||
|
mod discordbot;
|
||||||
|
mod router;
|
||||||
|
mod setup;
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> Result<(), Box<dyn Error>> {
|
||||||
|
setup::dotenv_and_tracing();
|
||||||
|
|
||||||
|
let r = router::init();
|
||||||
|
let l = TcpListener::bind("0.0.0.0:2020").await?;
|
||||||
|
|
||||||
|
discordbot::init().await;
|
||||||
|
axum::serve(l, r).await.unwrap();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
5
src/router/mod.rs
Normal file
5
src/router/mod.rs
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
use axum::{Router, routing::get};
|
||||||
|
|
||||||
|
pub fn init() -> Router {
|
||||||
|
Router::new().route("/", get(async || "root"))
|
||||||
|
}
|
||||||
51
src/setup.rs
Normal file
51
src/setup.rs
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
use dotenvy::Error;
|
||||||
|
use tracing::{Level, error, info, subscriber, warn};
|
||||||
|
use tracing_subscriber::FmtSubscriber;
|
||||||
|
|
||||||
|
pub fn dotenv_and_tracing() {
|
||||||
|
let dotenv = match dotenvy::dotenv() {
|
||||||
|
Ok(_) => DotenvStatus::LoadedFine,
|
||||||
|
Err(e) => match e.not_found() {
|
||||||
|
true => DotenvStatus::NotFound,
|
||||||
|
false => DotenvStatus::Error(e),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let lvl = match std::env::var("RUST_LOG") {
|
||||||
|
Ok(s) => Level::try_from_str(&s),
|
||||||
|
Err(e) => match e {
|
||||||
|
std::env::VarError::NotPresent => Level::INFO,
|
||||||
|
std::env::VarError::NotUnicode(s) => panic!("{:?} is not a valid log level.", s),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let s = FmtSubscriber::builder().with_max_level(lvl).finish();
|
||||||
|
subscriber::set_global_default(s).expect(SETGLOBAL_FAIL);
|
||||||
|
info!("tracing initialised ({}).", lvl);
|
||||||
|
match dotenv {
|
||||||
|
DotenvStatus::LoadedFine => info!(".env loaded successfully."),
|
||||||
|
DotenvStatus::NotFound => warn!(".env not found; skipping..."),
|
||||||
|
DotenvStatus::Error(e) => error!(".env error: {e}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const SETGLOBAL_FAIL: &str = "tracing subscriber setup failed.";
|
||||||
|
enum DotenvStatus {
|
||||||
|
LoadedFine,
|
||||||
|
NotFound,
|
||||||
|
Error(Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
trait StrToLevel {
|
||||||
|
fn try_from_str(value: &str) -> Level;
|
||||||
|
}
|
||||||
|
impl StrToLevel for Level {
|
||||||
|
fn try_from_str(value: &str) -> Level {
|
||||||
|
match value.to_lowercase().as_str() {
|
||||||
|
"error" => Level::ERROR,
|
||||||
|
"warn" => Level::WARN,
|
||||||
|
"info" => Level::INFO,
|
||||||
|
"debug" => Level::DEBUG,
|
||||||
|
"trace" => Level::TRACE,
|
||||||
|
_ => panic!("{value} is not a valid log level."),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user