silly goober bot

chore: cleanup code expantion

+79 -95
+79 -95
src/event_handler/code_expantion.rs
··· 1 1 // the logic here is pretty much ripped from https://github.com/uncenter/discord-forum-bot/blob/main/src/modules/expandGitHubLinks.ts 2 2 // with some modifications so I can make it work on diffrent git hosts 3 3 4 - use color_eyre::eyre::Result; 5 - use poise::serenity_prelude::{self as serenity, Context, CreateMessage, FullEvent}; 4 + use color_eyre::eyre::{self, Result}; 5 + use poise::serenity_prelude::{Context, FullEvent}; 6 6 use regex::Regex; 7 7 use reqwest::Client; 8 8 9 9 pub async fn handle(ctx: &Context, event: &FullEvent) -> Result<()> { 10 10 if let FullEvent::Message { new_message } = event { 11 - let mut embeds: Vec<serenity::CreateEmbed> = Vec::new(); 11 + let code_blocks = extract_code_blocks(new_message.content.clone()).await?; 12 12 13 - embeds.extend(github_compatable_embeds(new_message.content.clone()).await?); 14 - 15 - if !embeds.is_empty() { 13 + if !code_blocks.is_empty() { 16 14 new_message 17 15 .channel_id 18 - .send_message(&ctx, CreateMessage::default().embeds(embeds)) 16 + .say(ctx, code_blocks.join("\n")) 19 17 .await?; 20 18 } 21 19 } ··· 23 21 Ok(()) 24 22 } 25 23 26 - async fn github_compatable_embeds(msg: String) -> Result<Vec<serenity::CreateEmbed>> { 24 + async fn extract_code_blocks(msg: String) -> Result<Vec<String>> { 27 25 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 - ).unwrap(); 26 + 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+)?)?", 27 + )?; 30 28 31 - let mut embeds: Vec<serenity::CreateEmbed> = Vec::new(); 29 + let mut blocks: Vec<String> = Vec::new(); 30 + let client = Client::new(); 32 31 33 - let mut code_blocks = Vec::new(); 34 32 for caps in re.captures_iter(&msg) { 35 - let host = &caps["host"]; 36 - let repo = &caps["repo"]; 37 - let reference = &caps["reference"]; 38 - let file = &caps["file"]; 39 - let language = file.split('.').last().unwrap_or("").to_string(); 33 + let (host, repo, reference, file, start, end) = extract_url_components(&caps)?; 40 34 41 - let start = caps["start"].parse::<usize>().unwrap_or(1); 42 - let end = caps 43 - .name("end") 44 - .and_then(|end| end.as_str().parse::<usize>().ok()) 45 - .unwrap_or(start); 35 + let raw_url = construct_raw_url(host, repo, reference, file); 46 36 47 - let raw_url = if host == "github.com" { 48 - format!("https://raw.githubusercontent.com/{repo}/{reference}/{file}") 49 - } else { 50 - let refer = if reference.len() == 40 { 51 - format!("commit/{reference}") 52 - } else { 53 - format!("branch/{reference}") 54 - }; 55 - format!("https://{host}/{repo}/raw/{refer}/{file}") 56 - }; 37 + if let Ok(code_block) = fetch_code_block(&client, &raw_url, start, end, file).await { 38 + blocks.push(code_block); 39 + } 40 + } 57 41 58 - let response = Client::new().get(&raw_url).send().await; 42 + Ok(blocks) 43 + } 59 44 60 - if let Ok(response) = response { 61 - if !response.status().is_success() { 62 - println!("Failed to fetch content."); 63 - continue; 64 - } 45 + fn extract_url_components<'a>( 46 + caps: &'a regex::Captures<'a>, 47 + ) -> Result<(&'a str, &'a str, &'a str, &'a str, usize, usize)> { 48 + let host = &caps["host"]; 49 + let repo = &caps["repo"]; 50 + let reference = &caps["reference"]; 51 + let file = &caps["file"]; 52 + let start = caps["start"].parse::<usize>()?; 53 + let end = caps 54 + .name("end") 55 + .map_or(Ok(start), |end| end.as_str().parse::<usize>())?; 65 56 66 - let text = response.text().await?; 67 - let mut content = text 68 - .lines() 69 - .skip(start - 1) 70 - .take(end - start + 1) 71 - .collect::<Vec<&str>>() 72 - .join("\n"); 57 + Ok((host, repo, reference, file, start, end)) 58 + } 73 59 74 - let mut lines_sliced = 0; 75 - 76 - while content.len() > 1950 { 77 - let lines = content.lines().collect::<Vec<&str>>(); 78 - if lines.len() == 1 { 79 - content = content.chars().take(1950).collect(); 80 - break; 81 - } 82 - content = lines[..lines.len() - 1].join("\n"); 83 - lines_sliced += 1; 84 - } 85 - let end = end - lines_sliced; 60 + fn construct_raw_url(host: &str, repo: &str, reference: &str, file: &str) -> String { 61 + if host == "github.com" { 62 + format!("https://raw.githubusercontent.com/{repo}/{reference}/{file}") 63 + } else { 64 + let refer = if reference.len() == 40 { 65 + format!("commit/{reference}") 66 + } else { 67 + format!("branch/{reference}") 68 + }; 69 + format!("https://{host}/{repo}/raw/{refer}/{file}") 70 + } 71 + } 86 72 87 - let name = format!( 88 - "{repo}@{} {file} L{start}{}", 89 - if reference.len() == 40 { 90 - &reference[..8] 91 - } else { 92 - &reference 93 - }, 94 - if end > start { 95 - format!("-{end}") 96 - } else { 97 - String::new() 98 - } 99 - ); 73 + async fn fetch_code_block( 74 + client: &Client, 75 + raw_url: &str, 76 + start: usize, 77 + end: usize, 78 + file: &str, 79 + ) -> Result<String> { 80 + let response = client.get(raw_url).send().await?; 81 + if !response.status().is_success() { 82 + return Err(eyre::eyre!("Failed to fetch content from {}", raw_url)); 83 + } 100 84 101 - let body = if lines_sliced > 0 { 102 - format!("... ({lines_sliced} lines not displayed)") 103 - } else { 104 - String::new() 105 - }; 85 + let text = response.text().await?; 86 + let content = text 87 + .lines() 88 + .skip(start - 1) 89 + .take(end - start + 1) 90 + .collect::<Vec<&str>>() 91 + .join("\n"); 106 92 107 - code_blocks.push((name, language, content, body)); 108 - } 93 + let language = file 94 + .split('.') 95 + .last() 96 + .map_or("", remove_query_string) 97 + .to_lowercase(); 109 98 110 - code_blocks.retain(|(_, _, content, _)| !content.trim().is_empty()); 99 + Ok(format_code_block(language, content)) 100 + } 111 101 112 - if !code_blocks.is_empty() { 113 - embeds.extend(generic_codeblocks_to_embed(&code_blocks)); 114 - } 102 + fn format_code_block(language: String, content: String) -> String { 103 + if content.len() > 1950 { 104 + let truncated_content = content.lines().take(1950).collect::<Vec<&str>>().join("\n"); 105 + format!( 106 + "```{}\n{}\n```\n... (lines not displayed)", 107 + language, truncated_content 108 + ) 109 + } else { 110 + format!("```{}\n{}\n```", language, content) 115 111 } 116 - 117 - Ok(embeds) 118 112 } 119 113 120 - type CodeBlock = (String, String, String, String); 121 - 122 - fn generic_codeblocks_to_embed(codeblocks: &[CodeBlock]) -> Vec<serenity::CreateEmbed> { 123 - codeblocks 124 - .iter() 125 - .map(|(name, language, content, body)| { 126 - serenity::CreateEmbed::default() 127 - .title(name) 128 - .description(format!("```{language}\n{content}\n```").to_owned()) 129 - .footer(serenity::CreateEmbedFooter::new(body)) 130 - }) 131 - .collect::<Vec<_>>() 114 + fn remove_query_string(input: &str) -> &str { 115 + input.split('?').next().unwrap_or(input) 132 116 }