diff --git a/.env.example b/.env.example index a548fa4..5e839f4 100644 --- a/.env.example +++ b/.env.example @@ -1,5 +1,7 @@ DISCORD_TOKEN= +GITHUB_TOKEN= + REDIS_URL=redis://default:password@localhost:6379/0 DATABASE_URL=postgres://vaffelbot:vaffelbot@localhost:5432/vaffelbot diff --git a/Cargo.lock b/Cargo.lock index d96771a..eb3e3b7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -648,6 +648,15 @@ dependencies = [ "serde", ] +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -918,6 +927,25 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" +[[package]] +name = "h2" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap 2.13.0", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -1056,6 +1084,7 @@ dependencies = [ "bytes", "futures-channel", "futures-core", + "h2", "http", "http-body", "httparse", @@ -1100,6 +1129,22 @@ dependencies = [ "webpki-roots 1.0.6", ] +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + [[package]] name = "hyper-util" version = "0.1.20" @@ -1118,9 +1163,11 @@ dependencies = [ "percent-encoding", "pin-project-lite", "socket2 0.6.2", + "system-configuration", "tokio", "tower-service", "tracing", + "windows-registry", ] [[package]] @@ -2052,17 +2099,22 @@ checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" dependencies = [ "base64 0.22.1", "bytes", + "encoding_rs", "futures-core", "futures-util", + "h2", "http", "http-body", "http-body-util", "hyper", "hyper-rustls", + "hyper-tls", "hyper-util", "js-sys", "log", + "mime", "mime_guess", + "native-tls", "percent-encoding", "pin-project-lite", "quinn", @@ -2073,6 +2125,7 @@ dependencies = [ "serde_urlencoded", "sync_wrapper", "tokio", + "tokio-native-tls", "tokio-rustls 0.26.4", "tokio-util", "tower", @@ -2921,6 +2974,27 @@ dependencies = [ "syn 2.0.114", ] +[[package]] +name = "system-configuration" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" +dependencies = [ + "bitflags 2.10.0", + "core-foundation 0.9.4", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "tagptr" version = "0.2.0" @@ -3111,6 +3185,16 @@ dependencies = [ "syn 2.0.114", ] +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + [[package]] name = "tokio-rustls" version = "0.25.0" @@ -3475,6 +3559,7 @@ dependencies = [ "futures", "poise", "redis", + "reqwest", "serde", "serde_json", "serenity", @@ -3739,6 +3824,17 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[package]] +name = "windows-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" +dependencies = [ + "windows-link", + "windows-result", + "windows-strings", +] + [[package]] name = "windows-result" version = "0.4.1" diff --git a/Cargo.toml b/Cargo.toml index c2d7961..0d258fa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ chrono = { version = "0.4", features = ["serde"] } dotenv = "0.15.0" poise = "0.6.1" redis = { version = "0.30.0", features = ["tokio-comp", "connection-manager"] } +reqwest = { version = "0.12", features = ["json"] } serde = { version = "1.0.228", features = ["derive"] } serde_json = "1.0.149" serenity = "0.12.5" diff --git a/src/adapters/discord/commands/feedback.rs b/src/adapters/discord/commands/feedback.rs new file mode 100644 index 0000000..82b8ea5 --- /dev/null +++ b/src/adapters/discord/commands/feedback.rs @@ -0,0 +1,65 @@ +use poise::CreateReply; +use serde_json::json; + +use crate::adapters::discord::{Context, Error}; + +/// Send inn tilbakemelding til vaffelbot +#[tracing::instrument(name = "feedback", skip(ctx))] +#[poise::command(slash_command, prefix_command, rename = "tilbakemelding")] +pub async fn feedback( + ctx: Context<'_>, + #[description = "Tilbakemeldingen din"] message: String, +) -> Result<(), Error> { + let github_token = match &ctx.data().github_token { + Some(token) => token.clone(), + None => { + ctx.send( + CreateReply::default() + .content("❌ Tilbakemelding er ikke konfigurert.") + .ephemeral(true), + ) + .await?; + return Ok(()); + } + }; + + let author = ctx.author(); + let title = "Tilbakemelding fra Discord".to_string(); + let body = format!( + "**Tilbakemelding fra Discord-bruker:** {}\n\n---\n\n{}", + author.name, message + ); + + let client = reqwest::Client::new(); + let response = client + .post("https://api.github.com/repos/echo-webkom/vaffelbot/issues") + .header("Accept", "application/vnd.github+json") + .header("Authorization", format!("Bearer {}", github_token)) + .header("X-GitHub-Api-Version", "2026-03-10") + .header("User-Agent", "vaffelbot") + .json(&json!({ + "title": title, + "body": body, + "labels": ["feedback"] + })) + .send() + .await?; + + if response.status().is_success() { + ctx.send( + CreateReply::default() + .content("✅ Takk for tilbakemeldingen! Den er sendt inn.") + .ephemeral(true), + ) + .await?; + } else { + ctx.send( + CreateReply::default() + .content("❌ Noe gikk galt. Prøv igjen senere.") + .ephemeral(true), + ) + .await?; + } + + Ok(()) +} diff --git a/src/adapters/discord/commands/mod.rs b/src/adapters/discord/commands/mod.rs index de8170c..e73e632 100644 --- a/src/adapters/discord/commands/mod.rs +++ b/src/adapters/discord/commands/mod.rs @@ -1,5 +1,6 @@ pub mod bake; pub mod close; +pub mod feedback; pub mod github; pub mod open; pub mod ping; diff --git a/src/adapters/discord/mod.rs b/src/adapters/discord/mod.rs index fa36423..264e5b9 100644 --- a/src/adapters/discord/mod.rs +++ b/src/adapters/discord/mod.rs @@ -16,12 +16,14 @@ pub type Context<'a> = poise::Context<'a, Data, Error>; pub struct Data { pub queue: Arc, pub orders: Arc, + pub github_token: Option, } pub struct DiscordAdapter { token: String, queue: Arc, orders: Arc, + github_token: Option, } impl DiscordAdapter { @@ -29,11 +31,13 @@ impl DiscordAdapter { token: String, queue: Arc, orders: Arc, + github_token: Option, ) -> Self { Self { token, queue, orders, + github_token, } } @@ -43,6 +47,7 @@ impl DiscordAdapter { commands::bake::bake(), commands::close::close(), commands::github::github(), + commands::feedback::feedback(), commands::open::open(), commands::ping::ping(), commands::queue_size::queue(), @@ -62,6 +67,7 @@ impl DiscordAdapter { Ok(Data { queue: self.queue.clone(), orders: self.orders.clone(), + github_token: self.github_token.clone(), }) }) }) diff --git a/src/config.rs b/src/config.rs index f2f743c..ffabcf8 100644 --- a/src/config.rs +++ b/src/config.rs @@ -2,6 +2,7 @@ pub struct Config { pub redis_url: String, pub discord_token: String, pub database_url: String, + pub github_token: Option, } impl Config { @@ -13,11 +14,13 @@ impl Config { std::env::var("DISCORD_TOKEN").expect("Expected a token in the environment"); let database_url = std::env::var("DATABASE_URL").expect("Expected DATABASE_URL in environment"); + let github_token = std::env::var("GITHUB_TOKEN").ok(); Self { redis_url, discord_token, database_url, + github_token, } } } diff --git a/src/lib.rs b/src/lib.rs index 96d6d04..2ea4cf0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -44,6 +44,7 @@ impl VaffelBot { self.config.discord_token.clone(), queue.clone(), orders.clone(), + self.config.github_token.clone(), ); let http_adapter = HttpAdapter::new(queue.clone(), orders.clone());