mirror of
https://github.com/YuzuZensai/Git-Identity-Audit.git
synced 2026-09-13 10:49:12 +00:00
✨ feat: Initial commit
This commit is contained in:
@@ -0,0 +1,132 @@
|
||||
package github
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const apiBase = "https://api.github.com"
|
||||
|
||||
type Client struct {
|
||||
token string
|
||||
http *http.Client
|
||||
}
|
||||
|
||||
func NewClient(token string) *Client {
|
||||
return &Client{
|
||||
token: token,
|
||||
http: &http.Client{Timeout: 30 * time.Second},
|
||||
}
|
||||
}
|
||||
|
||||
type Repo struct {
|
||||
FullName string `json:"full_name"`
|
||||
CloneURL string `json:"clone_url"`
|
||||
Private bool `json:"private"`
|
||||
}
|
||||
|
||||
type Event struct {
|
||||
Type string `json:"type"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
Actor struct {
|
||||
Login string `json:"login"`
|
||||
} `json:"actor"`
|
||||
Repo struct {
|
||||
Name string `json:"name"`
|
||||
} `json:"repo"`
|
||||
Payload struct {
|
||||
Ref string `json:"ref"`
|
||||
Head string `json:"head"`
|
||||
Before string `json:"before"`
|
||||
} `json:"payload"`
|
||||
}
|
||||
|
||||
func (c *Client) CurrentUserLogin() (string, error) {
|
||||
var user struct {
|
||||
Login string `json:"login"`
|
||||
}
|
||||
if _, err := c.get(apiBase+"/user", &user); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return user.Login, nil
|
||||
}
|
||||
|
||||
func (c *Client) PublicEvents(login string) ([]Event, error) {
|
||||
var all []Event
|
||||
url := apiBase + "/users/" + login + "/events/public?per_page=100"
|
||||
for url != "" {
|
||||
var page []Event
|
||||
next, err := c.get(url, &page)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
all = append(all, page...)
|
||||
url = next
|
||||
}
|
||||
return all, nil
|
||||
}
|
||||
|
||||
func (c *Client) ListAccessibleRepos() ([]Repo, error) {
|
||||
var all []Repo
|
||||
url := apiBase + "/user/repos?affiliation=owner,collaborator,organization_member&per_page=100"
|
||||
for url != "" {
|
||||
var page []Repo
|
||||
next, err := c.get(url, &page)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
all = append(all, page...)
|
||||
url = next
|
||||
}
|
||||
return all, nil
|
||||
}
|
||||
|
||||
func (c *Client) get(url string, out any) (string, error) {
|
||||
req, err := http.NewRequest(http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
req.Header.Set("Authorization", "Bearer "+c.token)
|
||||
req.Header.Set("Accept", "application/vnd.github+json")
|
||||
req.Header.Set("X-GitHub-Api-Version", "2022-11-28")
|
||||
|
||||
resp, err := c.http.Do(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode == http.StatusForbidden && resp.Header.Get("X-RateLimit-Remaining") == "0" {
|
||||
reset := resp.Header.Get("X-RateLimit-Reset")
|
||||
return "", fmt.Errorf("rate limited by GitHub API, resets at unix time %s", reset)
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return "", fmt.Errorf("GitHub API request to %s failed: %s", url, resp.Status)
|
||||
}
|
||||
|
||||
if err := json.NewDecoder(resp.Body).Decode(out); err != nil {
|
||||
return "", fmt.Errorf("decoding response from %s: %w", url, err)
|
||||
}
|
||||
|
||||
return parseNextLink(resp.Header.Get("Link")), nil
|
||||
}
|
||||
|
||||
func parseNextLink(header string) string {
|
||||
if header == "" {
|
||||
return ""
|
||||
}
|
||||
for _, part := range strings.Split(header, ",") {
|
||||
segments := strings.Split(part, ";")
|
||||
if len(segments) < 2 {
|
||||
continue
|
||||
}
|
||||
if strings.TrimSpace(segments[1]) == `rel="next"` {
|
||||
url := strings.TrimSpace(segments[0])
|
||||
return strings.Trim(url, "<>")
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
Reference in New Issue
Block a user