use ureq::Response;
#[non_exhaustive]
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("Regex error: {0}")]
Regex(#[from] regex::Error),
#[error("I/O error: {0}")]
IO(#[from] std::io::Error),
#[error("ureq error: {0}")]
UReq(#[from] ureq::Error),
#[error("No such release found")]
ReleaseNotFound,
#[error("No matching artifacts found")]
NoMatches,
#[error("Artifact download URL not found")]
UrlNotFound,
#[error("{0}")]
Misc(String),
}
pub mod github {
pub fn download_release<'e, E, S>(
artifact: impl AsRef<str>,
exclude: E,
user: impl AsRef<str>,
repo: impl AsRef<str>,
) -> Result<super::Response, super::Error>
where
E: IntoIterator<Item = S>,
S: AsRef<str>,
{
let user = user.as_ref();
let repo = repo.as_ref();
let artifact = artifact.as_ref();
let exclude: Vec<_> = exclude
.into_iter()
.map(|x| x.as_ref().to_string())
.collect();
let agent = ureq::AgentBuilder::new().redirects(0).build();
let artifact_pattern =
regex::RegexBuilder::new(r#" href="(.*?/releases/download/.*?)""#).build()?;
let Some(tag) = agent
.get(format!("https://github.com/{}/{}/releases/latest", user, repo).as_str())
.call()?
.header("Location")
.and_then(|loc| loc.rsplit_once("/tag/").map(|parts| parts.1.to_string()))
else {
return Err(super::Error::ReleaseNotFound);
};
let artifacts_page = agent
.get(
format!(
"https://github.com/{}/{}/releases/expanded_assets/{}",
user, repo, &tag
)
.as_str(),
)
.call()?
.into_string()?;
let artifact_urls: Vec<_> = artifact_pattern
.captures_iter(artifacts_page.as_str())
.flat_map(|c| {
c.get(1).and_then(|m| {
let m = m.as_str();
if m.contains(artifact) {
if exclude.iter().any(|e| m.contains(e.as_str())) {
None
} else {
Some(String::from(m))
}
} else {
None
}
})
})
.collect();
if let Some(path) = artifact_urls.into_iter().next() {
let Some(redir) = agent
.get(format!("https://github.com{}", &path).as_str())
.call()?
.header("Location")
.map(|loc| loc.to_string())
else {
return Err(super::Error::UrlNotFound);
};
Ok(agent.get(&redir).call()?)
} else {
Err(super::Error::NoMatches)
}
}
}