216 lines
6.6 KiB
Rust
216 lines
6.6 KiB
Rust
use std::collections::HashSet;
|
|
|
|
use anyhow::Context;
|
|
use octocrab::{Octocrab, Page, models};
|
|
|
|
use crate::sources::{KagamiSource, RepositoryFilter, RepositoryInfo, RepositoryVisibility};
|
|
|
|
pub struct KagamiSourceGithub {
|
|
host: Option<String>,
|
|
}
|
|
|
|
impl KagamiSourceGithub {
|
|
pub fn new(host: Option<&str>) -> Self {
|
|
Self {
|
|
host: host.map(|s| s.to_string()),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl KagamiSource for KagamiSourceGithub {
|
|
const NAME: &'static str = "github";
|
|
const DEFAULT_HOST: &'static str = "github.com";
|
|
|
|
fn api_base(&self) -> String {
|
|
match self.host.as_deref() {
|
|
Some(host) if host != "github.com" => format!("https://{}/api/v3", host),
|
|
_ => "https://api.github.com".to_string(),
|
|
}
|
|
}
|
|
|
|
async fn get_repositories_by_user(
|
|
&self,
|
|
owner: &str,
|
|
token: Option<&str>,
|
|
_filter: &RepositoryFilter,
|
|
) -> anyhow::Result<HashSet<RepositoryInfo>> {
|
|
let octocrab = {
|
|
let mut builder = Octocrab::builder();
|
|
if let Some(t) = token {
|
|
builder = builder.personal_token(t.to_owned());
|
|
}
|
|
if self.host.is_some() {
|
|
builder = builder
|
|
.base_uri(self.api_base())
|
|
.context("failed to set octocrab base_uri")?;
|
|
}
|
|
builder.build().context("failed to build octocrab client")?
|
|
};
|
|
|
|
let mut page: Option<Page<models::Repository>> = None;
|
|
let mut repos = HashSet::new();
|
|
|
|
loop {
|
|
let current: Page<models::Repository> = match page {
|
|
None => octocrab
|
|
.get(
|
|
&format!("/users/{}/repos", owner),
|
|
Some(&[("per_page", "100")]),
|
|
)
|
|
.await
|
|
.context("failed to fetch user repositories")?,
|
|
Some(ref p) => {
|
|
match octocrab.get_page(&p.next).await {
|
|
Ok(Some(next_page)) => next_page,
|
|
Ok(None) => break,
|
|
Err(_) => break,
|
|
}
|
|
}
|
|
};
|
|
|
|
for r in ¤t.items {
|
|
let ri = map_github_repo_to_info(r);
|
|
repos.insert(ri);
|
|
}
|
|
|
|
if current.next.is_none() {
|
|
break;
|
|
} else {
|
|
page = Some(current);
|
|
}
|
|
}
|
|
|
|
Ok(repos)
|
|
}
|
|
|
|
async fn get_repositories_by_organization(
|
|
&self,
|
|
owner: &str,
|
|
token: Option<&str>,
|
|
// NOTE: GitHub nested teams doesn't remove the organization repos; this flag is unused.
|
|
_recurse_groups: bool,
|
|
_filter: &RepositoryFilter,
|
|
) -> anyhow::Result<HashSet<RepositoryInfo>> {
|
|
let octocrab = {
|
|
let mut builder = Octocrab::builder();
|
|
if let Some(t) = token {
|
|
builder = builder.personal_token(t.to_owned());
|
|
}
|
|
if self.host.is_some() {
|
|
builder = builder
|
|
.base_uri(self.api_base())
|
|
.context("failed to set octocrab base_uri")?;
|
|
}
|
|
builder.build().context("failed to build octocrab client")?
|
|
};
|
|
|
|
let mut repos = HashSet::new();
|
|
let mut page: Option<Page<models::Repository>> = None;
|
|
|
|
loop {
|
|
let current: Page<models::Repository> = match page {
|
|
None => octocrab
|
|
.get(
|
|
&format!("/orgs/{}/repos", owner),
|
|
Some(&[("per_page", "100")]),
|
|
)
|
|
.await
|
|
.context("failed to fetch organization repositories")?,
|
|
Some(ref p) => match octocrab.get_page(&p.next).await {
|
|
Ok(Some(next_page)) => next_page,
|
|
Ok(None) => break,
|
|
Err(_) => break,
|
|
},
|
|
};
|
|
|
|
for r in ¤t.items {
|
|
let ri = map_github_repo_to_info(r);
|
|
repos.insert(ri);
|
|
}
|
|
|
|
if current.next.is_none() {
|
|
break;
|
|
} else {
|
|
page = Some(current);
|
|
}
|
|
}
|
|
|
|
Ok(repos)
|
|
}
|
|
|
|
async fn get_repositories_by_stars(
|
|
&self,
|
|
owner: &str,
|
|
token: Option<&str>,
|
|
_filter: &RepositoryFilter,
|
|
) -> anyhow::Result<HashSet<RepositoryInfo>> {
|
|
let octocrab = {
|
|
let mut builder = Octocrab::builder();
|
|
if let Some(t) = token {
|
|
builder = builder.personal_token(t.to_owned());
|
|
}
|
|
if self.host.is_some() {
|
|
builder = builder
|
|
.base_uri(self.api_base())
|
|
.context("failed to set octocrab base_uri")?;
|
|
}
|
|
builder.build().context("failed to build octocrab client")?
|
|
};
|
|
|
|
let mut page: Option<Page<models::Repository>> = None;
|
|
let mut repos = HashSet::new();
|
|
|
|
loop {
|
|
let current: Page<models::Repository> = match page {
|
|
None => octocrab
|
|
.get(
|
|
&format!("/users/{}/starred", owner),
|
|
Some(&[("per_page", "100")]),
|
|
)
|
|
.await
|
|
.context("failed to fetch starred repositories")?,
|
|
Some(ref p) => match octocrab.get_page(&p.next).await {
|
|
Ok(Some(next_page)) => next_page,
|
|
Ok(None) => break,
|
|
Err(_) => break,
|
|
},
|
|
};
|
|
|
|
for r in ¤t.items {
|
|
let ri = map_github_repo_to_info(r);
|
|
repos.insert(ri);
|
|
}
|
|
|
|
if current.next.is_none() {
|
|
break;
|
|
} else {
|
|
page = Some(current);
|
|
}
|
|
}
|
|
|
|
Ok(repos)
|
|
}
|
|
}
|
|
|
|
fn map_github_repo_to_info(r: &models::Repository) -> RepositoryInfo {
|
|
RepositoryInfo {
|
|
name: r.name.clone(),
|
|
// TODO: don't unwrap with string default here
|
|
url: r
|
|
.html_url
|
|
.clone()
|
|
.map(|u| u.to_string())
|
|
.unwrap_or_default(),
|
|
icon_url: r.owner.as_ref().map(|o| o.avatar_url.to_string()),
|
|
is_fork: r.fork.unwrap_or(false),
|
|
is_archived: r.archived.unwrap_or(false),
|
|
visibility: if r.private.unwrap_or(false) {
|
|
RepositoryVisibility::Private
|
|
} else {
|
|
RepositoryVisibility::Public
|
|
},
|
|
stars: r.stargazers_count.unwrap_or(0),
|
|
description: r.description.clone(),
|
|
}
|
|
}
|