source/github: implement

This commit is contained in:
2026-01-16 14:03:41 +09:00
parent bc077376cc
commit eb61f4cd05
5 changed files with 217 additions and 238 deletions

229
Cargo.lock generated
View File

@@ -144,28 +144,6 @@ version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
[[package]]
name = "aws-lc-rs"
version = "1.15.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e84ce723ab67259cfeb9877c6a639ee9eb7a27b28123abd71db7f0d5d0cc9d86"
dependencies = [
"aws-lc-sys",
"zeroize",
]
[[package]]
name = "aws-lc-sys"
version = "0.36.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43a442ece363113bd4bd4c8b18977a7798dd4d3c3383f34fb61936960e8f4ad8"
dependencies = [
"cc",
"cmake",
"dunce",
"fs_extra",
]
[[package]]
name = "backtrace"
version = "0.3.76"
@@ -294,17 +272,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd4932aefd12402b36c60956a4fe0035421f544799057659ff86f923657aada3"
dependencies = [
"find-msvc-tools",
"jobserver",
"libc",
"shlex",
]
[[package]]
name = "cesu8"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c"
[[package]]
name = "cfg-if"
version = "1.0.0"
@@ -386,15 +356,6 @@ version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cbd0f76e066e64fdc5631e3bb46381254deab9ef1158292f27c8c57e3bf3fe59"
[[package]]
name = "cmake"
version = "0.1.57"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75443c44cd6b379beb8c5b45d85d0773baf31cce901fe7bb252f4eff3008ef7d"
dependencies = [
"cc",
]
[[package]]
name = "colorchoice"
version = "1.0.3"
@@ -851,7 +812,7 @@ dependencies = [
"base64ct",
"bytes",
"futures",
"reqwest 0.12.28",
"reqwest",
"serde",
"serde_json",
"soft_assert",
@@ -871,12 +832,6 @@ dependencies = [
"percent-encoding",
]
[[package]]
name = "fs_extra"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c"
[[package]]
name = "futures"
version = "0.3.31"
@@ -1018,7 +973,7 @@ checksum = "d86c6c1d8f0dfd7780151092ae4cbd84699678bb7640654dee2297c830a014a3"
dependencies = [
"base64",
"build-it",
"reqwest 0.12.28",
"reqwest",
"serde",
"serde_json",
]
@@ -1042,7 +997,7 @@ dependencies = [
"itertools",
"log",
"percent-encoding",
"reqwest 0.12.28",
"reqwest",
"serde",
"serde_json",
"serde_urlencoded",
@@ -2480,38 +2435,6 @@ dependencies = [
"jiff-tzdb",
]
[[package]]
name = "jni"
version = "0.21.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97"
dependencies = [
"cesu8",
"cfg-if",
"combine",
"jni-sys",
"log",
"thiserror 1.0.69",
"walkdir",
"windows-sys 0.45.0",
]
[[package]]
name = "jni-sys"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130"
[[package]]
name = "jobserver"
version = "0.1.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33"
dependencies = [
"getrandom 0.3.2",
"libc",
]
[[package]]
name = "js-sys"
version = "0.3.85"
@@ -2558,7 +2481,6 @@ dependencies = [
"gix",
"octocrab",
"regex",
"reqwest 0.13.1",
"sd-notify",
"serde",
"serde_json",
@@ -3113,7 +3035,6 @@ version = "0.11.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31"
dependencies = [
"aws-lc-rs",
"bytes",
"getrandom 0.3.2",
"lru-slab",
@@ -3302,46 +3223,6 @@ dependencies = [
"webpki-roots",
]
[[package]]
name = "reqwest"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04e9018c9d814e5f30cc16a0f03271aeab3571e609612d9fe78c1aa8d11c2f62"
dependencies = [
"base64",
"bytes",
"encoding_rs",
"futures-core",
"h2",
"http",
"http-body",
"http-body-util",
"hyper",
"hyper-rustls",
"hyper-util",
"js-sys",
"log",
"mime",
"percent-encoding",
"pin-project-lite",
"quinn",
"rustls",
"rustls-pki-types",
"rustls-platform-verifier",
"serde",
"serde_json",
"sync_wrapper",
"tokio",
"tokio-rustls",
"tower",
"tower-http",
"tower-service",
"url",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
]
[[package]]
name = "rfc6979"
version = "0.4.0"
@@ -3439,7 +3320,6 @@ version = "0.23.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b"
dependencies = [
"aws-lc-rs",
"log",
"once_cell",
"ring",
@@ -3471,40 +3351,12 @@ dependencies = [
"zeroize",
]
[[package]]
name = "rustls-platform-verifier"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784"
dependencies = [
"core-foundation 0.10.1",
"core-foundation-sys",
"jni",
"log",
"once_cell",
"rustls",
"rustls-native-certs",
"rustls-platform-verifier-android",
"rustls-webpki",
"security-framework 3.5.1",
"security-framework-sys",
"webpki-root-certs",
"windows-sys 0.61.2",
]
[[package]]
name = "rustls-platform-verifier-android"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f"
[[package]]
name = "rustls-webpki"
version = "0.103.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52"
dependencies = [
"aws-lc-rs",
"ring",
"rustls-pki-types",
"untrusted",
@@ -4418,15 +4270,6 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "webpki-root-certs"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36a29fc0408b113f68cf32637857ab740edfafdf460c326cd2afaa2d84cc05dc"
dependencies = [
"rustls-pki-types",
]
[[package]]
name = "webpki-roots"
version = "1.0.5"
@@ -4537,15 +4380,6 @@ dependencies = [
"windows-link",
]
[[package]]
name = "windows-sys"
version = "0.45.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
dependencies = [
"windows-targets 0.42.2",
]
[[package]]
name = "windows-sys"
version = "0.52.0"
@@ -4582,21 +4416,6 @@ dependencies = [
"windows-link",
]
[[package]]
name = "windows-targets"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
dependencies = [
"windows_aarch64_gnullvm 0.42.2",
"windows_aarch64_msvc 0.42.2",
"windows_i686_gnu 0.42.2",
"windows_i686_msvc 0.42.2",
"windows_x86_64_gnu 0.42.2",
"windows_x86_64_gnullvm 0.42.2",
"windows_x86_64_msvc 0.42.2",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
@@ -4630,12 +4449,6 @@ dependencies = [
"windows_x86_64_msvc 0.53.1",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
@@ -4648,12 +4461,6 @@ version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
[[package]]
name = "windows_aarch64_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
@@ -4666,12 +4473,6 @@ version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
[[package]]
name = "windows_i686_gnu"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
@@ -4696,12 +4497,6 @@ version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
[[package]]
name = "windows_i686_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
@@ -4714,12 +4509,6 @@ version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2"
[[package]]
name = "windows_x86_64_gnu"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
@@ -4732,12 +4521,6 @@ version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
@@ -4750,12 +4533,6 @@ version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1"
[[package]]
name = "windows_x86_64_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"

View File

@@ -13,7 +13,6 @@ gitlab = "0.1807.0"
gix = "0.70.0"
octocrab = "0.49.5"
regex = "1.12.2"
reqwest = { version = "0.13.1", features = ["json"] }
sd-notify = "0.4.5"
serde = "1.0.228"
serde_json = "1.0.149"

View File

@@ -92,7 +92,6 @@ impl Default for RepositoryFilter {
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct RepositoryInfo {
pub name: String,
pub full_name: String,
pub url: String,
pub icon_url: Option<String>,
pub is_fork: bool,

View File

@@ -25,7 +25,10 @@ impl KagamiSource for KagamiSourceGitea {
const DEFAULT_HOST: &'static str = "gitea.com";
fn api_base(&self) -> String {
format!("https://{}", self.host.as_deref().unwrap_or(Self::DEFAULT_HOST))
format!(
"https://{}",
self.host.as_deref().unwrap_or(Self::DEFAULT_HOST)
)
}
async fn get_repositories_by_user(
@@ -61,7 +64,6 @@ impl KagamiSource for KagamiSourceGitea {
for r in repos_resp.iter() {
let info = RepositoryInfo {
name: r.name.clone(),
full_name: r.full_name.clone(),
url: r.html_url.clone(),
icon_url: if r.avatar_url.is_empty() {
None
@@ -106,7 +108,7 @@ impl KagamiSource for KagamiSourceGitea {
) -> anyhow::Result<HashSet<RepositoryInfo>> {
let base = self.api_base();
let client: GiteaClient = match token {
let client: GiteaClient = match token {
Some(tok) if !tok.is_empty() => GiteaClient::new(&base, Token(tok.to_string())),
_ => GiteaClient::new(&base, gitea_sdk::Auth::<String>::None),
};
@@ -131,7 +133,6 @@ impl KagamiSource for KagamiSourceGitea {
for r in repos_resp.iter() {
let info = RepositoryInfo {
name: r.name.clone(),
full_name: r.full_name.clone(),
url: r.html_url.clone(),
icon_url: if r.avatar_url.is_empty() {
None
@@ -199,7 +200,6 @@ impl KagamiSource for KagamiSourceGitea {
for r in repos_resp.iter() {
let info = RepositoryInfo {
name: r.name.clone(),
full_name: r.full_name.clone(),
url: r.html_url.clone(),
icon_url: if r.avatar_url.is_empty() {
None

View File

@@ -1,11 +1,215 @@
use std::collections::HashSet;
use anyhow::Context;
use octocrab::{Octocrab, Page, models};
use crate::sources::{KagamiSource, RepositoryFilter, RepositoryInfo, RepositoryVisibility};
pub struct KagamiSourceGithub {
host: String,
host: Option<String>,
}
impl KagamiSourceGithub {
pub fn new(host: &str) -> Self {
Self {
host: host.to_string(),
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 &current.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 &current.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 &current.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(),
}
}
}