at main 4.1 kB view raw
1use crate::types::Data; 2use poise::serenity_prelude::{Context, GuildId}; 3use serde::Serialize; 4use std::collections::HashMap; 5use std::sync::Arc; 6use tokio::io::{AsyncReadExt, AsyncWriteExt}; 7use tokio::net::{TcpListener, TcpStream}; 8 9#[derive(Serialize)] 10struct MemberInfo { 11 id: String, 12 username: String, 13 avatar_url: Option<String>, 14} 15 16#[derive(Serialize)] 17struct MembersResponse { 18 guild_id: String, 19 members: Vec<MemberInfo>, 20} 21 22pub async fn start_http_server( 23 ctx: Arc<Context>, 24 data: Arc<Data>, 25 port: u16, 26) -> color_eyre::eyre::Result<()> { 27 let addr = format!("127.0.0.1:{port}"); 28 let listener = TcpListener::bind(&addr).await?; 29 30 println!("HTTP server listening on http://{addr}"); 31 32 loop { 33 let (stream, _) = listener.accept().await?; 34 let ctx = Arc::clone(&ctx); 35 let data = Arc::clone(&data); 36 37 tokio::spawn(async move { 38 if let Err(e) = handle_connection(stream, ctx, data).await { 39 eprintln!("Error handling connection: {e}"); 40 } 41 }); 42 } 43} 44 45async fn handle_connection( 46 mut stream: TcpStream, 47 ctx: Arc<Context>, 48 _data: Arc<Data>, 49) -> color_eyre::eyre::Result<()> { 50 let mut buffer = [0; 1024]; 51 stream.read(&mut buffer).await?; 52 53 let request = String::from_utf8_lossy(&buffer[..]); 54 let request_line = request.lines().next().unwrap_or(""); 55 56 if request_line.starts_with("GET /members") { 57 let guild_id = extract_guild_id_from_request(request_line) 58 .unwrap_or(GuildId::new(1095080242219073606)); 59 60 match get_guild_members(&ctx, guild_id).await { 61 Ok(response_body) => { 62 let response = format!( 63 "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: {}\r\n\r\n{}", 64 response_body.len(), 65 response_body 66 ); 67 stream.write_all(response.as_bytes()).await?; 68 } 69 Err(e) => { 70 let error_body = format!(r#"{{"error": "Failed to fetch members: {e}"}}"#); 71 let response = format!( 72 "HTTP/1.1 500 Internal Server Error\r\nContent-Type: application/json\r\nContent-Length: {}\r\n\r\n{}", 73 error_body.len(), 74 error_body 75 ); 76 stream.write_all(response.as_bytes()).await?; 77 } 78 } 79 } else { 80 let not_found = r#"{"error": "Not found. Use /members?guild_id=YOUR_GUILD_ID"}"#; 81 let response = format!( 82 "HTTP/1.1 404 Not Found\r\nContent-Type: application/json\r\nContent-Length: {}\r\n\r\n{}", 83 not_found.len(), 84 not_found 85 ); 86 stream.write_all(response.as_bytes()).await?; 87 } 88 89 Ok(()) 90} 91 92fn extract_guild_id_from_request(request_line: &str) -> Option<GuildId> { 93 if let Some(query_start) = request_line.find('?') { 94 let query = &request_line[query_start + 1..]; 95 let params: HashMap<&str, &str> = query 96 .split('&') 97 .filter_map(|param| { 98 let mut parts = param.split('='); 99 Some((parts.next()?, parts.next()?)) 100 }) 101 .collect(); 102 103 if let Some(guild_id_str) = params.get("guild_id") { 104 if let Ok(guild_id) = guild_id_str.parse::<u64>() { 105 return Some(GuildId::new(guild_id)); 106 } 107 } 108 } 109 None 110} 111 112async fn get_guild_members(ctx: &Context, guild_id: GuildId) -> color_eyre::eyre::Result<String> { 113 let members = guild_id.members(&ctx.http, None, None).await?; 114 115 let member_infos: Vec<MemberInfo> = members 116 .iter() 117 .map(|member| MemberInfo { 118 id: member.user.id.to_string(), 119 username: member.user.name.clone(), 120 avatar_url: member.user.avatar_url(), 121 }) 122 .collect(); 123 124 let response = MembersResponse { 125 guild_id: guild_id.to_string(), 126 members: member_infos, 127 }; 128 129 Ok(serde_json::to_string_pretty(&response)?) 130}