silly goober bot
at main 114 lines 3.5 kB view raw
1// the logic here is pretty much ripped from https://github.com/uncenter/discord-forum-bot/blob/main/src/modules/expandGitHubLinks.ts 2// with some modifications so I can make it work on diffrent git hosts 3 4use color_eyre::eyre::{eyre, Result}; 5use poise::serenity_prelude::{Context, FullEvent}; 6use regex::Regex; 7use reqwest::Client; 8 9use crate::types::Data; 10 11pub async fn handle(ctx: &Context, event: &FullEvent, data: &Data) -> Result<()> { 12 if let FullEvent::Message { new_message } = event { 13 let code_blocks = extract_code_blocks(new_message.content.clone(), &data.client).await?; 14 15 if !code_blocks.is_empty() { 16 new_message 17 .channel_id 18 .say(ctx, code_blocks.join("\n")) 19 .await?; 20 } 21 } 22 23 Ok(()) 24} 25 26async fn extract_code_blocks(msg: String, client: &Client) -> Result<Vec<String>> { 27 let re = Regex::new( 28 r"https?://(?P<host>(git.*|codeberg\.org))/(?P<repo>[\w-]+/[\w.-]+)/(blob|(src/(commit|branch)))?/(?P<reference>\S+?)/(?P<file>\S+)#L(?P<start>\d+)(?:[~-]L?(?P<end>\d+)?)?", 29 )?; 30 31 let mut blocks: Vec<String> = Vec::new(); 32 33 for caps in re.captures_iter(&msg) { 34 let (host, repo, reference, file, start, end) = extract_url_components(&caps)?; 35 36 let raw_url = construct_raw_url(host, repo, reference, file); 37 38 if let Ok(code_block) = fetch_code_block(client, &raw_url, start, end, file).await { 39 blocks.push(code_block); 40 } 41 } 42 43 Ok(blocks) 44} 45 46fn extract_url_components<'a>( 47 caps: &'a regex::Captures<'a>, 48) -> Result<(&'a str, &'a str, &'a str, &'a str, usize, usize)> { 49 let host = &caps["host"]; 50 let repo = &caps["repo"]; 51 let reference = &caps["reference"]; 52 let file = &caps["file"]; 53 let start = caps["start"].parse::<usize>()?; 54 let end = caps 55 .name("end") 56 .map_or(Ok(start), |end| end.as_str().parse::<usize>())?; 57 58 Ok((host, repo, reference, file, start, end)) 59} 60 61fn construct_raw_url(host: &str, repo: &str, reference: &str, file: &str) -> String { 62 if host == "github.com" { 63 format!("https://raw.githubusercontent.com/{repo}/{reference}/{file}") 64 } else { 65 let refer = if reference.len() == 40 { 66 format!("commit/{reference}") 67 } else { 68 format!("branch/{reference}") 69 }; 70 format!("https://{host}/{repo}/raw/{refer}/{file}") 71 } 72} 73 74async fn fetch_code_block( 75 client: &Client, 76 raw_url: &str, 77 start: usize, 78 end: usize, 79 file: &str, 80) -> Result<String> { 81 let response = client.get(raw_url).send().await?; 82 if !response.status().is_success() { 83 return Err(eyre!("Failed to fetch content from {}", raw_url)); 84 } 85 86 let text = response.text().await?; 87 let content = text 88 .lines() 89 .skip(start - 1) 90 .take(end - start + 1) 91 .collect::<Vec<&str>>() 92 .join("\n"); 93 94 let language = file 95 .split('.') 96 .next_back() 97 .map_or("", remove_query_string) 98 .to_lowercase(); 99 100 Ok(format_code_block(&language, &content)) 101} 102 103fn format_code_block(language: &str, content: &str) -> String { 104 if content.len() > 1950 { 105 let truncated_content = content.lines().take(1950).collect::<Vec<&str>>().join("\n"); 106 format!("```{language}\n{truncated_content}\n```\n... (lines not displayed)") 107 } else { 108 format!("```{language}\n{content}\n```") 109 } 110} 111 112fn remove_query_string(input: &str) -> &str { 113 input.split('?').next().unwrap_or(input) 114}