use std::env; use serde::{Deserialize, Serialize}; use serenity::futures::stream::{SplitSink, SplitStream}; use serenity::futures::{StreamExt, TryFutureExt}; use serenity::http::{Http, StatusCode}; use serenity::model::prelude::*; use serenity::prelude::*; use serenity::{async_trait, futures::SinkExt}; use tokio::net::{TcpListener, TcpStream}; use tokio_tungstenite::tungstenite::handshake::server::{ErrorResponse, Request, Response}; use tokio_tungstenite::tungstenite::Message as WsMessage; use tokio_tungstenite::WebSocketStream; #[derive(Serialize, Deserialize, Clone, Debug)] struct ChatMessage { user: String, user_id: String, message: String, } impl From for ChatMessage { fn from(value: Message) -> Self { Self { user: value .member .and_then(|member| member.nick) .unwrap_or(value.author.name), user_id: value.author.id.to_string(), message: value.content, } } } struct Handler( Mutex, WsMessage>>, ChannelId, ); #[async_trait] impl EventHandler for Handler { async fn message(&self, _ctx: Context, new_message: Message) { if new_message.webhook_id.is_some() { return; } if new_message.author.bot { return; } if new_message.channel_id != self.1 { return; } self.0 .lock() .await .send(WsMessage::Text( serde_json::to_string::(&new_message.into()).unwrap(), )) .await .unwrap(); } } async fn handle_recv( mut recv: SplitStream>, http: Http, webhook: Webhook, ) -> anyhow::Result<()> { while let Some(msg) = recv.next().await { if let WsMessage::Text(content) = msg? { let message: ChatMessage = serde_json::from_str(&content)?; webhook .execute(&http, false, |w| { w.content(message.message) .username(message.user) .avatar_url(format!( "https://minotar.net/helm/{}/256.png", message.user_id )) }) .await?; } } Ok(()) } #[tokio::main] async fn main() -> anyhow::Result<()> { let http = Http::new(""); let webhook = Webhook::from_url(&http, &env::var("WEBHOOK_URL").expect("webhook url")).await?; let auth_token = env::var("AUTH_TOKEN").expect("auth token"); let channel_id = env::var("CHANNEL_ID").expect("channel id").parse()?; let listener = TcpListener::bind(env::var("BIND_ADDR").expect("bind address")).await?; let (stream, _) = listener.accept().await?; let (send, recv) = tokio_tungstenite::accept_hdr_async(stream, |req: &Request, resp: Response| { if let Some(header) = req.headers().get("X-Token") { if header.as_bytes() == auth_token.as_bytes() { Ok(resp) } else { let mut resp = ErrorResponse::new(None); *resp.status_mut() = StatusCode::UNAUTHORIZED; Err(resp) } } else { let mut resp = ErrorResponse::new(None); *resp.status_mut() = StatusCode::UNAUTHORIZED; Err(resp) } }) .await? .split(); // Login with a bot token from the environment let token = env::var("DISCORD_TOKEN").expect("token"); let intents = GatewayIntents::non_privileged() | GatewayIntents::MESSAGE_CONTENT; let mut client = Client::builder(token, intents) .event_handler(Handler(Mutex::new(send), channel_id)) .await?; // start listening for events by starting a single shard tokio::try_join!( client.start().map_err(|e| e.into()), handle_recv(recv, http, webhook) )?; Ok(()) }