| // Copyright 2019 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package main |
| |
| import ( |
| "encoding/json" |
| "fmt" |
| "io/ioutil" |
| "net/http" |
| "os" |
| "path/filepath" |
| "strconv" |
| "strings" |
| ) |
| |
| // GitHubInfo is a subset of the GH API info. |
| type GitHubInfo struct { |
| ID int |
| Name string |
| Login string |
| } |
| |
| // FetchGitHubInfo fetches information about the GitHub user associated |
| // with who. If no such user exists, it returns nil. |
| func FetchGitHubInfo(who *acLine) (*GitHubInfo, error) { |
| id, err := fetchGitHubUserID(who) |
| if err != nil { |
| return nil, err |
| } |
| if id == "" { |
| // There is no GitHub user associated with who. |
| return nil, nil |
| } |
| |
| cacheDir, err := githubCacheDir() |
| if err != nil { |
| return nil, err |
| } |
| cacheFile := filepath.Join(cacheDir, fmt.Sprintf("user-id-%s", id)) |
| if slurp, err := ioutil.ReadFile(cacheFile); err == nil { |
| res := &GitHubInfo{} |
| if err := json.Unmarshal(slurp, res); err != nil { |
| return nil, fmt.Errorf("%s: %v", cacheFile, err) |
| } |
| return res, nil |
| } |
| |
| jsonURL := fmt.Sprintf("https://api.github.com/user/%s", id) // undocumented but it works |
| req, _ := http.NewRequest("GET", jsonURL, nil) |
| if token, err := ioutil.ReadFile(githubTokenFile()); err == nil { |
| req.Header.Set("Authorization", "token "+strings.TrimSpace(string(token))) |
| } |
| res, err := http.DefaultClient.Do(req) |
| if err != nil { |
| return nil, err |
| } |
| defer res.Body.Close() |
| if res.StatusCode != 200 { |
| return nil, fmt.Errorf("%s: %v", jsonURL, res.Status) |
| } |
| body, err := ioutil.ReadAll(res.Body) |
| if err != nil { |
| return nil, fmt.Errorf("%s: %v", jsonURL, err) |
| } |
| jres := &GitHubInfo{} |
| if err := json.Unmarshal(body, jres); err != nil { |
| return nil, fmt.Errorf("%s: %v", jsonURL, err) |
| } |
| if jres.ID == 0 { |
| return nil, fmt.Errorf("%s: malformed response", jsonURL) |
| } |
| |
| os.MkdirAll(cacheDir, 0700) |
| ioutil.WriteFile(cacheFile, body, 0600) |
| |
| return jres, nil |
| } |
| |
| // fetchGitHubUserID fetches the ID of the GitHub user associated |
| // with who. If no such user exists, it returns the empty string. |
| func fetchGitHubUserID(who *acLine) (string, error) { |
| org, repo := githubOrgRepo(who.firstRepo) |
| |
| cacheDir, err := githubCacheDir() |
| if err != nil { |
| return "", err |
| } |
| cacheFile := filepath.Join(cacheDir, fmt.Sprintf("%s-%s-%s-id", org, repo, who.firstCommit)) |
| if slurp, err := ioutil.ReadFile(cacheFile); err == nil { |
| return string(slurp), nil |
| } |
| |
| jsonURL := fmt.Sprintf("https://api.github.com/repos/%s/%s/commits/%s", org, repo, who.firstCommit) |
| req, _ := http.NewRequest("GET", jsonURL, nil) |
| if token, err := ioutil.ReadFile(githubTokenFile()); err == nil { |
| req.Header.Set("Authorization", "token "+strings.TrimSpace(string(token))) |
| } |
| var jres struct { |
| Author struct { |
| ID int |
| } |
| } |
| res, err := http.DefaultClient.Do(req) |
| if err != nil { |
| return "", err |
| } |
| defer res.Body.Close() |
| if res.StatusCode != 200 { |
| return "", fmt.Errorf("%s: %v", jsonURL, res.Status) |
| } |
| if err := json.NewDecoder(res.Body).Decode(&jres); err != nil { |
| return "", fmt.Errorf("%s: %v", jsonURL, err) |
| } |
| if jres.Author.ID == 0 { |
| return "", nil // not a registered GitHub user |
| } |
| |
| os.MkdirAll(cacheDir, 0700) |
| ioutil.WriteFile(cacheFile, []byte(strconv.Itoa(jres.Author.ID)), 0600) |
| |
| return strconv.Itoa(jres.Author.ID), nil |
| } |
| |
| func githubCacheDir() (string, error) { |
| userCacheDir, err := os.UserCacheDir() |
| if err != nil { |
| return "", err |
| } |
| return filepath.Join(userCacheDir, "updatecontrib-github"), nil |
| } |
| |
| func githubTokenFile() string { |
| return filepath.Join(os.Getenv("HOME"), ".github-updatecontrib-token") |
| } |