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