use std::collections::HashSet;
use std::ops::{Deref, DerefMut};
use futures::TryFutureExt;
use reqwest::{header, Client, ClientBuilder, Response, Result};
use tracing::{debug, info, instrument, warn, Level};
use url::Url;
pub trait BuilderExt {
fn token(self, token: &str) -> Self;
fn endpoint(self, endpoint: Url) -> Result<GitHub>;
}
impl BuilderExt for ClientBuilder {
fn token(self, token: &str) -> Self {
let mut headers = header::HeaderMap::new();
headers.insert("User-Agent", header::HeaderValue::from_static("gfas"));
headers.insert("Authorization", format!("token {token}").parse().unwrap());
self.default_headers(headers)
}
fn endpoint(self, endpoint: Url) -> Result<GitHub> {
Ok(GitHub { client: self.build()?, endpoint })
}
}
#[derive(Debug, Clone)]
pub struct GitHub {
client: Client,
endpoint: Url
}
impl Deref for GitHub {
type Target = Client;
fn deref(&self) -> &Self::Target {
&self.client
}
}
impl DerefMut for GitHub {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.client
}
}
impl GitHub {
pub fn builder() -> ClientBuilder {
ClientBuilder::new()
}
#[instrument(skip(self), ret(level = Level::TRACE), err)]
pub async fn explore(&self, user: &str, role: &str) -> Result<HashSet<String>> {
let mut res = HashSet::new();
let url = self.endpoint.join(&format!("users/{user}/{role}")).unwrap();
const PER_PAGE: usize = 100;
for page in 1.. {
debug!("page {page}");
let users: Vec<_> = self
.get(url.clone())
.query(&[("page", page), ("per_page", PER_PAGE)])
.send()
.and_then(|r| r.json::<Vec<octocrab::models::SimpleUser>>())
.await?
.into_iter()
.map(|u| u.login)
.collect();
let len = users.len();
res.extend(users);
info!("{}(+{len})", res.len());
if len < PER_PAGE {
break;
}
}
Ok(res)
}
#[instrument(skip(self), ret(level = Level::TRACE), err)]
pub async fn follow(&self, user: &str) -> Result<Response> {
warn!("");
let url = self.endpoint.join(&format!("/user/following/{user}")).unwrap();
self.put(url).send().await
}
#[instrument(skip(self), ret(level = Level::TRACE), err)]
pub async fn unfollow(&self, user: &str) -> Result<Response> {
warn!("");
let url = self.endpoint.join(&format!("/user/following/{user}")).unwrap();
self.delete(url).send().await
}
}