feat: Initial commit

This commit is contained in:
2026-07-13 05:45:48 +07:00
commit 1f616240db
12 changed files with 810 additions and 0 deletions
+132
View File
@@ -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 ""
}