package main import ( "bytes" "encoding/json" "fmt" "io/ioutil" "net/http" "net/url" "strings" "time" ) type Repo struct { Name string `json:"name"` Description string `json:"description"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } type ContentsResponse struct { Content []byte `json:"content"` // Assume base64 "encoding" parameter in Gitea response, and use Go's auto decode } type TopicsResponse struct { Topics []string `json:"topics"` } type ReaddirEntry struct { Name string `json:"name"` Path string `json:"path"` Size int `json:"size"` RawURL string `json:"download_url"` } func (rde ReaddirEntry) isImage() bool { return strings.HasSuffix(rde.Name, `.png`) || strings.HasSuffix(rde.Name, `.jpg`) || strings.HasSuffix(rde.Name, `.jpeg`) } type MarkdownRequest struct { Context string Mode string Text string Wiki bool } // repos gets a list of Git repositories in this organisation. func (this *Application) repos() ([]Repo, error) { resp, err := http.Get(this.cfg.Gitea.URL + `api/v1/orgs/` + url.PathEscape(this.cfg.Gitea.Org) + `/repos`) if err != nil { return nil, err } defer resp.Body.Close() if resp.StatusCode != 200 { return nil, fmt.Errorf("HTTP %d", resp.StatusCode) } var repos []Repo err = json.NewDecoder(resp.Body).Decode(&repos) if err != nil { return nil, err } return repos, nil } // repoFile gets a single file from the default branch of the git repository // Usually the default branch is `master`. func (this *Application) repoFile(repo, filename string) ([]byte, error) { resp, err := http.Get(this.cfg.Gitea.URL + `api/v1/repos/` + url.PathEscape(this.cfg.Gitea.Org) + `/` + url.PathEscape(repo) + `/contents/` + url.PathEscape(filename)) if err != nil { return nil, err } defer resp.Body.Close() if resp.StatusCode != 200 { return nil, fmt.Errorf("HTTP %d", resp.StatusCode) } var cr ContentsResponse err = json.NewDecoder(resp.Body).Decode(&cr) if err != nil { return nil, err } return cr.Content, nil } func (this *Application) filesInDirectory(repo, dir string) ([]ReaddirEntry, error) { resp, err := http.Get(this.cfg.Gitea.URL + `api/v1/repos/` + url.PathEscape(this.cfg.Gitea.Org) + `/` + url.PathEscape(repo) + `/contents/` + dir) // n.b. $dir param not escaped if err != nil { return nil, err } defer resp.Body.Close() if resp.StatusCode != 200 { // "No files found" happens with a HTTP 500/404 error depending on Gitea version. Catch this special case if resp.StatusCode == 500 || resp.StatusCode == 404 { b, err := ioutil.ReadAll(resp.Body) if err != nil { return nil, err } if strings.Contains(string(b), `does not exist`) { return []ReaddirEntry{}, nil // no files found } } return nil, fmt.Errorf("HTTP %d", resp.StatusCode) } var ret []ReaddirEntry err = json.NewDecoder(resp.Body).Decode(&ret) if err != nil { return nil, err } return ret, nil } // imageFilesForRepo finds documentation images for the repository. // It searches the dist/ and doc/ subdirectories. func (this *Application) imageFilesForRepo(repo string) ([]ReaddirEntry, error) { ret := []ReaddirEntry{} for _, dirName := range []string{`dist`, `doc`} { files, err := this.filesInDirectory(repo, dirName) if err != nil { return nil, fmt.Errorf("readdir(%s): %w", dirName, err) } for _, f := range files { if f.isImage() { ret = append(ret, f) } } } return ret, nil } func (this *Application) topicsForRepo(repo string) ([]string, error) { resp, err := http.Get(this.cfg.Gitea.URL + `api/v1/repos/` + url.PathEscape(this.cfg.Gitea.Org) + `/` + url.PathEscape(repo) + `/topics`) if err != nil { return nil, err } defer resp.Body.Close() if resp.StatusCode != 200 { return nil, fmt.Errorf("HTTP %d", resp.StatusCode) } var tr TopicsResponse err = json.NewDecoder(resp.Body).Decode(&tr) if err != nil { return nil, err } return tr.Topics, nil } // renderMarkdown calls the remote Gitea server's own markdown renderer. func (this *Application) renderMarkdown(repoName string, body string) ([]byte, error) { req := MarkdownRequest{ Context: this.cfg.Gitea.URL + url.PathEscape(this.cfg.Gitea.Org) + `/` + url.PathEscape(repoName) + `/src/branch/master`, Mode: "gfm", // magic constant - Github Flavoured Markdown Text: body, } jb, err := json.Marshal(req) if err != nil { return nil, err } resp, err := http.Post(this.cfg.Gitea.URL+`api/v1/markdown/`, `application/json`, bytes.NewReader(jb)) if err != nil { return nil, err } defer resp.Body.Close() if resp.StatusCode != 200 { return nil, fmt.Errorf("HTTP %d", resp.StatusCode) } return ioutil.ReadAll(resp.Body) } // renderMarkdownRaw calls the remote Gitea server's own markdown renderer. func (this *Application) renderMarkdownRaw(body []byte) ([]byte, error) { resp, err := http.Post(this.cfg.Gitea.URL+`api/v1/markdown/raw`, `text/plain`, bytes.NewReader(body)) if err != nil { return nil, err } defer resp.Body.Close() if resp.StatusCode != 200 { return nil, fmt.Errorf("HTTP %d", resp.StatusCode) } return ioutil.ReadAll(resp.Body) }