initial
This commit is contained in:
commit
97fd2fb78d
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
*.xml
|
||||
gqrss
|
15
LICENSE
Normal file
15
LICENSE
Normal file
@ -0,0 +1,15 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Aaron Bieber <aaron@bolddaemon.com>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
14
README.md
Normal file
14
README.md
Normal file
@ -0,0 +1,14 @@
|
||||
# [gqrss](https://github.com/qbit/gqrss)
|
||||
|
||||
A tool to query and display GitHub issues relating to OpenBSD.
|
||||
|
||||
## Libraries used
|
||||
|
||||
- [gorilla/feeds](https://github.com/gorilla/feeds) for generating RSS/Atom
|
||||
feeds.
|
||||
- [suah.dev/protect](https://suah.dev/protect) for OpenBSD's
|
||||
[pledge](https://man.openbsd.org/pledge)/[unveil](https://man.openbsd.org/unveil).
|
||||
|
||||
`gqrss` will produce a `rss.xml` and `atom.xml` file in the directory it was
|
||||
ran. It expects a GitHub authentication token to be present in the
|
||||
`GH_AUTH_TOKEN` environment variable.
|
8
go.mod
Normal file
8
go.mod
Normal file
@ -0,0 +1,8 @@
|
||||
module suah.dev/gqrss
|
||||
|
||||
go 1.15
|
||||
|
||||
require (
|
||||
github.com/gorilla/feeds v1.1.1
|
||||
suah.dev/protect v1.0.0
|
||||
)
|
6
go.sum
Normal file
6
go.sum
Normal file
@ -0,0 +1,6 @@
|
||||
github.com/gorilla/feeds v1.1.1 h1:HwKXxqzcRNg9to+BbvJog4+f3s/xzvtZXICcQGutYfY=
|
||||
github.com/gorilla/feeds v1.1.1/go.mod h1:Nk0jZrvPFZX1OBe5NPiddPw7CfwF6Q9eqzaBbaightA=
|
||||
golang.org/x/sys v0.0.0-20200501145240-bc7a7d42d5c3 h1:5B6i6EAiSYyejWfvc5Rc9BbI3rzIsrrXfAQBWnYfn+w=
|
||||
golang.org/x/sys v0.0.0-20200501145240-bc7a7d42d5c3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
suah.dev/protect v1.0.0 h1:X8pzDvDIZIiugmkmr6DES6JFO1XUdJWi34Ffmk6CMZY=
|
||||
suah.dev/protect v1.0.0/go.mod h1:ZSgyBM30JUwhVPWJzVHh0jlu5W6Qz1VR6tIhAzqJZ9Y=
|
196
main.go
Normal file
196
main.go
Normal file
@ -0,0 +1,196 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/feeds"
|
||||
"suah.dev/protect"
|
||||
)
|
||||
|
||||
// GQLQuery is what github wants in the POST request.
|
||||
type GQLQuery struct {
|
||||
Query string `json:"query"`
|
||||
}
|
||||
|
||||
// GHResp represents a response from GitHub's GraphQL API.
|
||||
type GHResp struct {
|
||||
Data Data `json:"data"`
|
||||
}
|
||||
|
||||
// PageInfo represents the page information.
|
||||
type PageInfo struct {
|
||||
HasNextPage bool `json:"hasNextPage"`
|
||||
EndCursor string `json:"endCursor"`
|
||||
}
|
||||
|
||||
// Repository information has repo specific bits.
|
||||
type Repository struct {
|
||||
Name string `json:"name"`
|
||||
URL string `json:"url"`
|
||||
StargazerCount int `json:"stargazerCount"`
|
||||
}
|
||||
|
||||
// Node is an entry from our "search".
|
||||
type Node struct {
|
||||
Repository Repository `json:"repository"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
Title string `json:"title"`
|
||||
URL string `json:"url"`
|
||||
Author Author `json:"author"`
|
||||
BodyHTML string `json:"bodyHTML"`
|
||||
}
|
||||
|
||||
// Edges are ... too edgy to tell..
|
||||
type Edges struct {
|
||||
Node Node `json:"node,omitempty"`
|
||||
}
|
||||
|
||||
// Search bundles our edges together
|
||||
type Search struct {
|
||||
IssueCount int `json:"issueCount"`
|
||||
PageInfo PageInfo `json:"pageInfo"`
|
||||
Edges []Edges `json:"edges"`
|
||||
}
|
||||
|
||||
// Author is an individual author
|
||||
type Author struct {
|
||||
Login string `json:"login"`
|
||||
URL string `json:"url"`
|
||||
}
|
||||
|
||||
// Data is the data returned from a search
|
||||
type Data struct {
|
||||
Search Search `json:"search"`
|
||||
}
|
||||
|
||||
const endPoint = "https://api.github.com/graphql"
|
||||
const ghQuery = `
|
||||
{
|
||||
search(first: 100, type: ISSUE, query: "state:open openbsd") {
|
||||
issueCount
|
||||
pageInfo {
|
||||
hasNextPage
|
||||
endCursor
|
||||
}
|
||||
edges {
|
||||
node {
|
||||
... on Issue {
|
||||
repository {
|
||||
name
|
||||
url
|
||||
stargazerCount
|
||||
}
|
||||
createdAt
|
||||
title
|
||||
url
|
||||
bodyHTML
|
||||
author {
|
||||
login
|
||||
url
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
func getData(q GQLQuery) (re *GHResp, err error) {
|
||||
var req *http.Request
|
||||
client := &http.Client{}
|
||||
buf := new(bytes.Buffer)
|
||||
if err := json.NewEncoder(buf).Encode(q); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req, err = http.NewRequest("POST", endPoint, buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header.Set("Authorization", fmt.Sprintf("bearer %s", os.Getenv("GH_AUTH_TOKEN")))
|
||||
|
||||
res, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer res.Body.Close()
|
||||
|
||||
data, err := ioutil.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = json.Unmarshal(data, &re); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return re, nil
|
||||
}
|
||||
|
||||
func makeRSS(q *GHResp) {
|
||||
feed := &feeds.Feed{
|
||||
Title: "OpenBSD GitHub Issues",
|
||||
Link: &feeds.Link{Href: "https://github.com/qbit/gqrss"},
|
||||
Description: "Open GitHub issues relating to OpenBSD",
|
||||
Author: &feeds.Author{Name: "Aaron Bieber", Email: "aaron@bolddaemon.com"},
|
||||
Copyright: "This work is copyright © Aaron Bieber",
|
||||
}
|
||||
|
||||
for _, e := range q.Data.Search.Edges {
|
||||
if e.Node.Title == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
f := &feeds.Item{
|
||||
Title: fmt.Sprintf("%s: %s", e.Node.Repository.Name, e.Node.Title),
|
||||
Link: &feeds.Link{Href: e.Node.URL},
|
||||
Created: e.Node.CreatedAt,
|
||||
Description: e.Node.BodyHTML,
|
||||
Author: &feeds.Author{
|
||||
Name: e.Node.Author.Login,
|
||||
},
|
||||
}
|
||||
|
||||
feed.Items = append(feed.Items, f)
|
||||
}
|
||||
|
||||
atomFile, err := os.Create("atom.xml")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
rssFile, err := os.Create("rss.xml")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
feed.WriteAtom(atomFile)
|
||||
feed.WriteRss(rssFile)
|
||||
}
|
||||
|
||||
func main() {
|
||||
protect.Unveil("./", "rwc")
|
||||
protect.Unveil("/etc/ssl/cert.pem", "r")
|
||||
protect.Pledge("stdio unveil rpath wpath cpath flock dns inet")
|
||||
|
||||
protect.UnveilBlock()
|
||||
|
||||
var q GQLQuery
|
||||
q.Query = ghQuery
|
||||
|
||||
resp, err := getData(q)
|
||||
if err != nil {
|
||||
fmt.Printf("%+v\n", err)
|
||||
}
|
||||
|
||||
makeRSS(resp)
|
||||
}
|
Loading…
Reference in New Issue
Block a user