mirror of
https://github.com/Luzifer/badge-gen.git
synced 2024-12-20 08:31:17 +00:00
Fix linter errors
Signed-off-by: Knut Ahlers <knut@ahlers.me>
This commit is contained in:
parent
73b722d998
commit
dc3fc39a2c
13 changed files with 318 additions and 273 deletions
126
app.go
126
app.go
|
@ -2,8 +2,7 @@ package main
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha1"
|
||||
"errors"
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
@ -14,7 +13,8 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/tdewolff/minify"
|
||||
"github.com/tdewolff/minify/svg"
|
||||
"golang.org/x/net/context"
|
||||
|
@ -26,12 +26,26 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
xSpacing = 8
|
||||
defaultColor = "4c1"
|
||||
badgeGenerationTimeout = 1500 * time.Millisecond
|
||||
xSpacing = 8
|
||||
defaultColor = "4c1"
|
||||
)
|
||||
|
||||
const (
|
||||
colorNameBlue = "blue"
|
||||
colorNameBrightGreen = "brightgreen"
|
||||
colorNameGray = "gray"
|
||||
colorNameGreen = "green"
|
||||
colorNameLightGray = "lightgray"
|
||||
colorNameOrange = "orange"
|
||||
colorNameRed = "red"
|
||||
colorNameYellow = "yellow"
|
||||
colorNameYellowGreen = "yellowgreen"
|
||||
)
|
||||
|
||||
var (
|
||||
cfg = struct {
|
||||
LogLevel string `flag:"log-level" default:"info" description:"Log level (debug, info, warn, error, fatal)"`
|
||||
Port int64 `env:"PORT"`
|
||||
Listen string `flag:"listen" default:":3000" description:"Port/IP to listen on"`
|
||||
Cache string `flag:"cache" default:"mem://" description:"Where to cache query results from thirdparty APIs"`
|
||||
|
@ -42,17 +56,15 @@ var (
|
|||
version = "dev"
|
||||
|
||||
colorList = map[string]string{
|
||||
"brightgreen": "4c1",
|
||||
"green": "97CA00",
|
||||
"yellow": "dfb317",
|
||||
"yellowgreen": "a4a61d",
|
||||
"orange": "fe7d37",
|
||||
"red": "e05d44",
|
||||
"blue": "007ec6",
|
||||
"grey": "555",
|
||||
"gray": "555",
|
||||
"lightgrey": "9f9f9f",
|
||||
"lightgray": "9f9f9f",
|
||||
colorNameBlue: "007ec6",
|
||||
colorNameBrightGreen: "4c1",
|
||||
colorNameGray: "555",
|
||||
colorNameGreen: "97CA00",
|
||||
colorNameLightGray: "9f9f9f",
|
||||
colorNameOrange: "fe7d37",
|
||||
colorNameRed: "e05d44",
|
||||
colorNameYellow: "dfb317",
|
||||
colorNameYellowGreen: "a4a61d",
|
||||
}
|
||||
|
||||
cacheStore cache.Cache
|
||||
|
@ -84,26 +96,44 @@ type serviceHandler interface {
|
|||
Handle(ctx context.Context, params []string) (title, text, color string, err error)
|
||||
}
|
||||
|
||||
func registerServiceHandler(service string, f serviceHandler) error {
|
||||
func registerServiceHandler(service string, f serviceHandler) {
|
||||
if _, ok := serviceHandlers[service]; ok {
|
||||
return errors.New("Duplicate service handler")
|
||||
panic("duplicate service handler")
|
||||
}
|
||||
|
||||
serviceHandlers[service] = f
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
rconfig.Parse(&cfg)
|
||||
func initApp() error {
|
||||
rconfig.AutoEnv(true)
|
||||
if err := rconfig.ParseAndValidate(&cfg); err != nil {
|
||||
return errors.Wrap(err, "parsing commandline options")
|
||||
}
|
||||
|
||||
l, err := logrus.ParseLevel(cfg.LogLevel)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "parsing log level")
|
||||
}
|
||||
logrus.SetLevel(l)
|
||||
|
||||
if cfg.Port != 0 {
|
||||
cfg.Listen = fmt.Sprintf(":%d", cfg.Port)
|
||||
}
|
||||
|
||||
log.Infof("badge-gen %s started...", version)
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
var err error
|
||||
if err = initApp(); err != nil {
|
||||
logrus.WithError(err).Fatal("initializing app")
|
||||
}
|
||||
|
||||
logrus.Infof("badge-gen %s started...", version)
|
||||
|
||||
cacheStore, err = cache.GetCacheByURI(cfg.Cache)
|
||||
if err != nil {
|
||||
log.WithError(err).Fatal("Unable to open cache")
|
||||
logrus.WithError(err).Fatal("Unable to open cache")
|
||||
}
|
||||
|
||||
f, err := os.Open(cfg.ConfStorage)
|
||||
|
@ -112,17 +142,19 @@ func main() {
|
|||
yamlDecoder := yaml.NewDecoder(f)
|
||||
yamlDecoder.SetStrict(true)
|
||||
if err = yamlDecoder.Decode(&configStore); err != nil {
|
||||
log.WithError(err).Fatal("Unable to parse config")
|
||||
logrus.WithError(err).Fatal("Unable to parse config")
|
||||
}
|
||||
log.Printf("Loaded %d value pairs into configuration store", len(configStore))
|
||||
logrus.Printf("Loaded %d value pairs into configuration store", len(configStore))
|
||||
|
||||
f.Close()
|
||||
if err = f.Close(); err != nil {
|
||||
logrus.WithError(err).Error("closing config file (leaked fd)")
|
||||
}
|
||||
|
||||
case os.IsNotExist(err):
|
||||
// Do nothing
|
||||
|
||||
default:
|
||||
log.WithError(err).Fatal("Unable to open config")
|
||||
logrus.WithError(err).Fatal("Unable to open config")
|
||||
}
|
||||
|
||||
r := mux.NewRouter().UseEncodedPath()
|
||||
|
@ -130,7 +162,15 @@ func main() {
|
|||
r.HandleFunc("/{service}/{parameters:.*}", generateServiceBadge).Methods("GET")
|
||||
r.HandleFunc("/", handleDemoPage)
|
||||
|
||||
http.ListenAndServe(cfg.Listen, r)
|
||||
server := &http.Server{
|
||||
Addr: cfg.Listen,
|
||||
Handler: r,
|
||||
ReadHeaderTimeout: time.Second,
|
||||
}
|
||||
|
||||
if err = server.ListenAndServe(); err != nil {
|
||||
logrus.WithError(err).Fatal("HTTP server exited unexpectedly")
|
||||
}
|
||||
}
|
||||
|
||||
func generateServiceBadge(res http.ResponseWriter, r *http.Request) {
|
||||
|
@ -148,7 +188,7 @@ func generateServiceBadge(res http.ResponseWriter, r *http.Request) {
|
|||
|
||||
al := accessLogger.New(res)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 1500*time.Millisecond)
|
||||
ctx, cancel := context.WithTimeout(r.Context(), badgeGenerationTimeout)
|
||||
defer cancel()
|
||||
|
||||
handler, ok := serviceHandlers[service]
|
||||
|
@ -188,7 +228,7 @@ func generateBadge(res http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
func renderBadgeToResponse(res http.ResponseWriter, r *http.Request, title, text, color string) {
|
||||
cacheKey := fmt.Sprintf("%x", sha1.Sum([]byte(fmt.Sprintf("%s::::%s::::%s", title, text, color))))
|
||||
cacheKey := fmt.Sprintf("%x", sha256.Sum256([]byte(fmt.Sprintf("%s::::%s::::%s", title, text, color))))
|
||||
storedTag, _ := cacheStore.Get("eTag", cacheKey)
|
||||
|
||||
res.Header().Add("Cache-Control", "no-cache")
|
||||
|
@ -200,7 +240,7 @@ func renderBadgeToResponse(res http.ResponseWriter, r *http.Request, title, text
|
|||
}
|
||||
|
||||
badge, eTag := createBadge(title, text, color)
|
||||
cacheStore.Set("eTag", cacheKey, eTag, time.Hour)
|
||||
_ = cacheStore.Set("eTag", cacheKey, eTag, time.Hour)
|
||||
|
||||
res.Header().Add("ETag", eTag)
|
||||
res.Header().Add("Content-Type", "image/svg+xml")
|
||||
|
@ -210,7 +250,9 @@ func renderBadgeToResponse(res http.ResponseWriter, r *http.Request, title, text
|
|||
|
||||
badge, _ = m.Bytes("image/svg+xml", badge)
|
||||
|
||||
res.Write(badge)
|
||||
if _, err := res.Write(badge); err != nil {
|
||||
logrus.WithError(err).Error("writing badge")
|
||||
}
|
||||
}
|
||||
|
||||
func createBadge(title, text, color string) ([]byte, string) {
|
||||
|
@ -219,7 +261,7 @@ func createBadge(title, text, color string) ([]byte, string) {
|
|||
titleW, _ := calculateTextWidth(title)
|
||||
textW, _ := calculateTextWidth(text)
|
||||
|
||||
width := titleW + textW + 4*xSpacing
|
||||
width := titleW + textW + 4*xSpacing //nolint:gomnd
|
||||
|
||||
t, _ := assets.ReadFile("assets/badgeTemplate.tpl")
|
||||
tpl, _ := template.New("svg").Parse(string(t))
|
||||
|
@ -228,7 +270,7 @@ func createBadge(title, text, color string) ([]byte, string) {
|
|||
color = c
|
||||
}
|
||||
|
||||
tpl.Execute(&buf, map[string]interface{}{
|
||||
_ = tpl.Execute(&buf, map[string]any{
|
||||
"Width": width,
|
||||
"TitleWidth": titleW + 2*xSpacing,
|
||||
"Title": title,
|
||||
|
@ -242,10 +284,10 @@ func createBadge(title, text, color string) ([]byte, string) {
|
|||
}
|
||||
|
||||
func generateETag(in []byte) string {
|
||||
return fmt.Sprintf("%x", sha1.Sum(in))
|
||||
return fmt.Sprintf("%x", sha256.Sum256(in))
|
||||
}
|
||||
|
||||
func handleDemoPage(res http.ResponseWriter, r *http.Request) {
|
||||
func handleDemoPage(res http.ResponseWriter, _ *http.Request) {
|
||||
t, _ := assets.ReadFile("assets/demoPage.tpl.html")
|
||||
tpl, _ := template.New("demoPage").Parse(string(t))
|
||||
|
||||
|
@ -265,8 +307,16 @@ func handleDemoPage(res http.ResponseWriter, r *http.Request) {
|
|||
|
||||
sort.Sort(examples)
|
||||
|
||||
tpl.Execute(res, map[string]interface{}{
|
||||
if err := tpl.Execute(res, map[string]interface{}{
|
||||
"Examples": examples,
|
||||
"Version": version,
|
||||
})
|
||||
}); err != nil {
|
||||
logrus.WithError(err).Error("rendering demo page")
|
||||
}
|
||||
}
|
||||
|
||||
func logErr(err error, text string) {
|
||||
if err != nil {
|
||||
logrus.WithError(err).Error(text)
|
||||
}
|
||||
}
|
||||
|
|
21
cache/cache.go
vendored
21
cache/cache.go
vendored
|
@ -1,33 +1,34 @@
|
|||
// Package cache contains caching implementation for retrieved data
|
||||
package cache
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type KeyNotFoundError struct{}
|
||||
|
||||
func (k KeyNotFoundError) Error() string {
|
||||
return "Requested key was not found in database"
|
||||
}
|
||||
// ErrKeyNotFound signalized the key is not present in the cache
|
||||
var ErrKeyNotFound = errors.New("requested key was not found in database")
|
||||
|
||||
// Cache describes an interface used to store generated data
|
||||
type Cache interface {
|
||||
Get(namespace, key string) (value string, err error)
|
||||
Set(namespace, key, value string, ttl time.Duration) (err error)
|
||||
Delete(namespace, key string) (err error)
|
||||
}
|
||||
|
||||
// GetCacheByURI instantiates a new Cache by the given URI string
|
||||
func GetCacheByURI(uri string) (Cache, error) {
|
||||
url, err := url.Parse(uri)
|
||||
u, err := url.Parse(uri)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, errors.Wrap(err, "parsing uri")
|
||||
}
|
||||
|
||||
switch url.Scheme {
|
||||
switch u.Scheme {
|
||||
case "mem":
|
||||
return NewInMemCache(), nil
|
||||
default:
|
||||
return nil, errors.New("Invalid cache scheme: " + url.Scheme)
|
||||
return nil, errors.New("Invalid cache scheme: " + u.Scheme)
|
||||
}
|
||||
}
|
||||
|
|
13
cache/inMemCache.go
vendored
13
cache/inMemCache.go
vendored
|
@ -10,29 +10,33 @@ type inMemCacheEntry struct {
|
|||
Expires time.Time
|
||||
}
|
||||
|
||||
// InMemCache implements the Cache interface for storage in memory
|
||||
type InMemCache struct {
|
||||
cache map[string]inMemCacheEntry
|
||||
lock sync.RWMutex
|
||||
}
|
||||
|
||||
// NewInMemCache creates a new InMemCache
|
||||
func NewInMemCache() *InMemCache {
|
||||
return &InMemCache{
|
||||
cache: map[string]inMemCacheEntry{},
|
||||
}
|
||||
}
|
||||
|
||||
func (i InMemCache) Get(namespace, key string) (value string, err error) {
|
||||
// Get retrieves stored data
|
||||
func (i *InMemCache) Get(namespace, key string) (value string, err error) {
|
||||
i.lock.RLock()
|
||||
defer i.lock.RUnlock()
|
||||
|
||||
e, ok := i.cache[namespace+"::"+key]
|
||||
if !ok || e.Expires.Before(time.Now()) {
|
||||
return "", KeyNotFoundError{}
|
||||
return "", ErrKeyNotFound
|
||||
}
|
||||
return e.Value, nil
|
||||
}
|
||||
|
||||
func (i InMemCache) Set(namespace, key, value string, ttl time.Duration) (err error) {
|
||||
// Set stores data
|
||||
func (i *InMemCache) Set(namespace, key, value string, ttl time.Duration) (err error) {
|
||||
i.lock.Lock()
|
||||
defer i.lock.Unlock()
|
||||
|
||||
|
@ -44,7 +48,8 @@ func (i InMemCache) Set(namespace, key, value string, ttl time.Duration) (err er
|
|||
return nil
|
||||
}
|
||||
|
||||
func (i InMemCache) Delete(namespace, key string) (err error) {
|
||||
// Delete deletes data
|
||||
func (i *InMemCache) Delete(namespace, key string) (err error) {
|
||||
i.lock.Lock()
|
||||
defer i.lock.Unlock()
|
||||
|
||||
|
|
3
font.go
3
font.go
|
@ -7,6 +7,7 @@ package main
|
|||
|
||||
import (
|
||||
"github.com/golang/freetype/truetype"
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/image/math/fixed"
|
||||
)
|
||||
|
||||
|
@ -18,7 +19,7 @@ func calculateTextWidth(text string) (int, error) {
|
|||
binFont, _ := assets.ReadFile("assets/DejaVuSans.ttf")
|
||||
font, err := truetype.Parse(binFont)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
return 0, errors.Wrap(err, "parsing truetype font")
|
||||
}
|
||||
|
||||
scale := fontSize / float64(font.FUnitsPerEm())
|
||||
|
|
4
go.mod
4
go.mod
|
@ -9,6 +9,7 @@ require (
|
|||
github.com/gorilla/mux v1.8.0
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
github.com/stretchr/testify v1.8.4
|
||||
github.com/tdewolff/minify v2.3.6+incompatible
|
||||
golang.org/x/image v0.12.0
|
||||
golang.org/x/net v0.15.0
|
||||
|
@ -16,9 +17,12 @@ require (
|
|||
)
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/tdewolff/parse v2.3.4+incompatible // indirect
|
||||
github.com/tdewolff/test v1.0.6 // indirect
|
||||
golang.org/x/sys v0.12.0 // indirect
|
||||
gopkg.in/validator.v2 v2.0.1 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
|
127
main_test.go
127
main_test.go
|
@ -1,132 +1,93 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"strings"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/Luzifer/badge-gen/cache"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func testGenerateMux() *mux.Router {
|
||||
m := mux.NewRouter()
|
||||
m.HandleFunc("/v1/badge", generateBadge).Methods("GET")
|
||||
m.HandleFunc("/{service}/{parameters:.*}", generateServiceBadge).Methods("GET")
|
||||
m.HandleFunc("/", handleDemoPage)
|
||||
return m
|
||||
}
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
cacheStore = cache.NewInMemCache()
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
func TestCreateBadge(t *testing.T) {
|
||||
badge := string(createBadge("API", "Documentation", "4c1"))
|
||||
badgeData, _ := createBadge("API", "Documentation", "4c1")
|
||||
badge := string(badgeData)
|
||||
|
||||
if !strings.Contains(badge, ">API</text>") {
|
||||
t.Error("Did not found node with text 'API'")
|
||||
}
|
||||
|
||||
if !strings.Contains(badge, "<path fill=\"#4c1\"") {
|
||||
t.Error("Did not find color coding for path")
|
||||
}
|
||||
|
||||
if !strings.Contains(badge, ">Documentation</text>") {
|
||||
t.Error("Did not found node with text 'Documentation'")
|
||||
}
|
||||
assert.Contains(t, badge, ">API</text>")
|
||||
assert.Contains(t, badge, "<path fill=\"#4c1\"")
|
||||
assert.Contains(t, badge, ">Documentation</text>")
|
||||
}
|
||||
|
||||
func TestHttpResponseMissingParameters(t *testing.T) {
|
||||
resp := httptest.NewRecorder()
|
||||
|
||||
uri := "/v1/badge"
|
||||
|
||||
req, err := http.NewRequest("GET", uri, nil)
|
||||
req, err := http.NewRequest("GET", "/v1/badge", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
generateMux().ServeHTTP(resp, req)
|
||||
if p, err := ioutil.ReadAll(resp.Body); err != nil {
|
||||
testGenerateMux().ServeHTTP(resp, req)
|
||||
if p, err := io.ReadAll(resp.Body); err != nil {
|
||||
t.Fail()
|
||||
} else {
|
||||
if resp.Code != http.StatusInternalServerError {
|
||||
t.Errorf("Response code should be %d, is %d", http.StatusInternalServerError, resp.Code)
|
||||
}
|
||||
|
||||
if string(p) != "You must specify parameters 'title' and 'text'.\n" {
|
||||
t.Error("Response message did not match test")
|
||||
}
|
||||
assert.Equal(t, http.StatusInternalServerError, resp.Code)
|
||||
assert.Contains(t, string(p), "You must specify parameters 'title' and 'text'.")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestHttpResponseWithoutColor(t *testing.T) {
|
||||
resp := httptest.NewRecorder()
|
||||
|
||||
uri := "/v1/badge?"
|
||||
params := url.Values{
|
||||
"title": []string{"API"},
|
||||
"text": []string{"Documentation"},
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("GET", uri+params.Encode(), nil)
|
||||
req, err := http.NewRequest("GET", "/static/API/Documentation", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
generateMux().ServeHTTP(resp, req)
|
||||
if p, err := ioutil.ReadAll(resp.Body); err != nil {
|
||||
testGenerateMux().ServeHTTP(resp, req)
|
||||
if p, err := io.ReadAll(resp.Body); err != nil {
|
||||
t.Fail()
|
||||
} else {
|
||||
if resp.Code != http.StatusOK {
|
||||
t.Errorf("Response code should be %d, is %d", http.StatusInternalServerError, resp.Code)
|
||||
}
|
||||
|
||||
if resp.Header().Get("Content-Type") != "image/svg+xml" {
|
||||
t.Errorf("Response had wrong Content-Type: %s", resp.Header().Get("Content-Type"))
|
||||
}
|
||||
|
||||
assert.Equal(t, http.StatusOK, resp.Code)
|
||||
assert.Equal(t, "image/svg+xml", resp.Header().Get("Content-Type"))
|
||||
// Check whether there is a SVG in the response, format checks are in other checks
|
||||
if !strings.Contains(string(p), "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"133\" height=\"20\">") {
|
||||
t.Error("Response message did not match test")
|
||||
}
|
||||
|
||||
if !strings.Contains(string(p), "#4c1") {
|
||||
t.Error("Default color was not set")
|
||||
}
|
||||
assert.Contains(t, string(p), "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"133\" height=\"20\">")
|
||||
assert.Contains(t, string(p), "#4c1", "default color should be set")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestHttpResponseWithColor(t *testing.T) {
|
||||
resp := httptest.NewRecorder()
|
||||
|
||||
uri := "/v1/badge?"
|
||||
params := url.Values{
|
||||
"title": []string{"API"},
|
||||
"text": []string{"Documentation"},
|
||||
"color": []string{"572"},
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("GET", uri+params.Encode(), nil)
|
||||
req, err := http.NewRequest("GET", "/static/API/Documentation/572", nil) //nolint:noctx // fine for an internal test
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
generateMux().ServeHTTP(resp, req)
|
||||
if p, err := ioutil.ReadAll(resp.Body); err != nil {
|
||||
testGenerateMux().ServeHTTP(resp, req)
|
||||
if p, err := io.ReadAll(resp.Body); err != nil {
|
||||
t.Fail()
|
||||
} else {
|
||||
if resp.Code != http.StatusOK {
|
||||
t.Errorf("Response code should be %d, is %d", http.StatusInternalServerError, resp.Code)
|
||||
}
|
||||
|
||||
if resp.Header().Get("Content-Type") != "image/svg+xml" {
|
||||
t.Errorf("Response had wrong Content-Type: %s", resp.Header().Get("Content-Type"))
|
||||
}
|
||||
|
||||
assert.Equal(t, http.StatusOK, resp.Code)
|
||||
assert.Equal(t, "image/svg+xml", resp.Header().Get("Content-Type"))
|
||||
// Check whether there is a SVG in the response, format checks are in other checks
|
||||
if !strings.Contains(string(p), "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"133\" height=\"20\">") {
|
||||
t.Error("Response message did not match test")
|
||||
}
|
||||
|
||||
if strings.Contains(string(p), "#4c1") {
|
||||
t.Error("Default color is present with color given")
|
||||
}
|
||||
|
||||
if !strings.Contains(string(p), "#572") {
|
||||
t.Error("Given color is not present in SVG")
|
||||
}
|
||||
assert.Contains(t, string(p), "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"133\" height=\"20\">")
|
||||
assert.NotContains(t, string(p), "#4c1", "default color should not be set")
|
||||
assert.Contains(t, string(p), "#572", "given color should be set")
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ import (
|
|||
func metricFormat(in int64) string {
|
||||
siUnits := []string{"k", "M", "G", "T", "P", "E"}
|
||||
for i := len(siUnits) - 1; i >= 0; i-- {
|
||||
p := int64(math.Pow(1000, float64(i+1)))
|
||||
p := int64(math.Pow(1000, float64(i+1))) //nolint:gomnd // Makes no sense to extract
|
||||
if in >= p {
|
||||
return fmt.Sprintf("%d%s", in/p, siUnits[i])
|
||||
}
|
||||
|
|
|
@ -9,9 +9,12 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
const aurCacheDuration = 10 * time.Minute
|
||||
|
||||
func init() {
|
||||
registerServiceHandler("aur", aurServiceHandler{})
|
||||
}
|
||||
|
@ -44,7 +47,7 @@ type aurInfoResult struct {
|
|||
} `json:"results"`
|
||||
}
|
||||
|
||||
func (a aurServiceHandler) GetDocumentation() serviceHandlerDocumentationList {
|
||||
func (aurServiceHandler) GetDocumentation() serviceHandlerDocumentationList {
|
||||
return serviceHandlerDocumentationList{
|
||||
{
|
||||
ServiceName: "AUR package version",
|
||||
|
@ -72,12 +75,12 @@ func (a aurServiceHandler) GetDocumentation() serviceHandlerDocumentationList {
|
|||
func (aurServiceHandler) IsEnabled() bool { return true }
|
||||
|
||||
func (a aurServiceHandler) Handle(ctx context.Context, params []string) (title, text, color string, err error) {
|
||||
if len(params) < 2 {
|
||||
if len(params) < 2 { //nolint:gomnd
|
||||
return title, text, color, errors.New("No service-command / parameters were given")
|
||||
}
|
||||
|
||||
switch params[0] {
|
||||
case "license":
|
||||
case "license": //nolint:goconst
|
||||
return a.handleAURLicense(ctx, params[1:])
|
||||
case "updated":
|
||||
return a.handleAURUpdated(ctx, params[1:])
|
||||
|
@ -102,10 +105,10 @@ func (a aurServiceHandler) handleAURLicense(ctx context.Context, params []string
|
|||
|
||||
text = strings.Join(info.Results[0].License, ", ")
|
||||
|
||||
cacheStore.Set("aur_license", title, text, 10*time.Minute)
|
||||
logErr(cacheStore.Set("aur_license", title, text, aurCacheDuration), "writing AUR license to cache")
|
||||
}
|
||||
|
||||
return "license", text, "blue", nil
|
||||
return "license", text, colorNameBlue, nil
|
||||
}
|
||||
|
||||
func (a aurServiceHandler) handleAURVersion(ctx context.Context, params []string) (title, text, color string, err error) {
|
||||
|
@ -120,10 +123,10 @@ func (a aurServiceHandler) handleAURVersion(ctx context.Context, params []string
|
|||
|
||||
text = info.Results[0].Version
|
||||
|
||||
cacheStore.Set("aur_version", title, text, 10*time.Minute)
|
||||
logErr(cacheStore.Set("aur_version", title, text, aurCacheDuration), "writing AUR version to cache")
|
||||
}
|
||||
|
||||
return title, text, "blue", nil
|
||||
return title, text, colorNameBlue, nil
|
||||
}
|
||||
|
||||
func (a aurServiceHandler) handleAURUpdated(ctx context.Context, params []string) (title, text, color string, err error) {
|
||||
|
@ -140,13 +143,13 @@ func (a aurServiceHandler) handleAURUpdated(ctx context.Context, params []string
|
|||
text = update.Format("2006-01-02 15:04:05")
|
||||
|
||||
if info.Results[0].OutOfDate > 0 {
|
||||
text = text + " (outdated)"
|
||||
text += " (outdated)"
|
||||
}
|
||||
|
||||
cacheStore.Set("aur_updated", title, text, 10*time.Minute)
|
||||
logErr(cacheStore.Set("aur_updated", title, text, aurCacheDuration), "writing AUR updated to cache")
|
||||
}
|
||||
|
||||
color = "blue"
|
||||
color = colorNameBlue
|
||||
if strings.Contains(text, "outdated") {
|
||||
color = "red"
|
||||
}
|
||||
|
@ -166,26 +169,30 @@ func (a aurServiceHandler) handleAURVotes(ctx context.Context, params []string)
|
|||
|
||||
text = strconv.Itoa(info.Results[0].NumVotes) + " votes"
|
||||
|
||||
cacheStore.Set("aur_votes", title, text, 10*time.Minute)
|
||||
logErr(cacheStore.Set("aur_votes", title, text, aurCacheDuration), "writing AUR votes to cache")
|
||||
}
|
||||
|
||||
return title, text, "brightgreen", nil
|
||||
return title, text, colorNameBrightGreen, nil
|
||||
}
|
||||
|
||||
func (a aurServiceHandler) fetchAURInfo(ctx context.Context, pkg string) (*aurInfoResult, error) {
|
||||
func (aurServiceHandler) fetchAURInfo(ctx context.Context, pkg string) (*aurInfoResult, error) {
|
||||
params := url.Values{
|
||||
"v": []string{"5"},
|
||||
"type": []string{"info"},
|
||||
"arg": []string{pkg},
|
||||
}
|
||||
url := "https://aur.archlinux.org/rpc/?" + params.Encode()
|
||||
u := "https://aur.archlinux.org/rpc/?" + params.Encode()
|
||||
|
||||
req, _ := http.NewRequest("GET", url, nil)
|
||||
req, _ := http.NewRequest("GET", u, nil)
|
||||
resp, err := http.DefaultClient.Do(req.WithContext(ctx))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Failed to fetch AUR info")
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
defer func() {
|
||||
if err := resp.Body.Close(); err != nil {
|
||||
logrus.WithError(err).Error("closing response body (leaked fd)")
|
||||
}
|
||||
}()
|
||||
|
||||
out := &aurInfoResult{}
|
||||
if err := json.NewDecoder(resp.Body).Decode(out); err != nil {
|
||||
|
|
|
@ -2,15 +2,18 @@ package main
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
const githubCacheDuration = 10 * time.Minute
|
||||
|
||||
func init() {
|
||||
registerServiceHandler("github", githubServiceHandler{})
|
||||
}
|
||||
|
@ -31,7 +34,7 @@ type githubRepo struct {
|
|||
|
||||
type githubServiceHandler struct{}
|
||||
|
||||
func (g githubServiceHandler) GetDocumentation() serviceHandlerDocumentationList {
|
||||
func (githubServiceHandler) GetDocumentation() serviceHandlerDocumentationList {
|
||||
return serviceHandlerDocumentationList{
|
||||
{
|
||||
ServiceName: "GitHub repo license",
|
||||
|
@ -74,9 +77,9 @@ func (g githubServiceHandler) GetDocumentation() serviceHandlerDocumentationList
|
|||
func (githubServiceHandler) IsEnabled() bool { return true }
|
||||
|
||||
func (g githubServiceHandler) Handle(ctx context.Context, params []string) (title, text, color string, err error) {
|
||||
if len(params) < 2 {
|
||||
err = errors.New("No service-command / parameters were given")
|
||||
return
|
||||
if len(params) < 2 { //nolint:gomnd
|
||||
err = errors.New("no service-command / parameters were given")
|
||||
return title, text, color, err
|
||||
}
|
||||
|
||||
switch params[0] {
|
||||
|
@ -86,15 +89,15 @@ func (g githubServiceHandler) Handle(ctx context.Context, params []string) (titl
|
|||
title, text, color, err = g.handleLatestTag(ctx, params[1:])
|
||||
case "latest-release":
|
||||
title, text, color, err = g.handleLatestRelease(ctx, params[1:])
|
||||
case "downloads":
|
||||
case "downloads": //nolint:goconst
|
||||
title, text, color, err = g.handleDownloads(ctx, params[1:])
|
||||
case "stars":
|
||||
title, text, color, err = g.handleStargazers(ctx, params[1:])
|
||||
default:
|
||||
err = errors.New("An unknown service command was called")
|
||||
err = errors.New("an unknown service command was called")
|
||||
}
|
||||
|
||||
return
|
||||
return title, text, color, err
|
||||
}
|
||||
|
||||
func (g githubServiceHandler) handleStargazers(ctx context.Context, params []string) (title, text, color string, err error) {
|
||||
|
@ -106,31 +109,31 @@ func (g githubServiceHandler) handleStargazers(ctx context.Context, params []str
|
|||
r := githubRepo{}
|
||||
|
||||
if err = g.fetchAPI(ctx, path, nil, &r); err != nil {
|
||||
return
|
||||
return title, text, color, err
|
||||
}
|
||||
|
||||
text = metricFormat(r.StargazersCount)
|
||||
cacheStore.Set("github_repo_stargazers", path, text, 10*time.Minute)
|
||||
logErr(cacheStore.Set("github_repo_stargazers", path, text, githubCacheDuration), "writing Github repo stargazers to cache")
|
||||
}
|
||||
|
||||
title = "stars"
|
||||
color = "brightgreen"
|
||||
return
|
||||
color = colorNameBrightGreen
|
||||
return title, text, color, err
|
||||
}
|
||||
|
||||
func (g githubServiceHandler) handleDownloads(ctx context.Context, params []string) (title, text, color string, err error) {
|
||||
switch len(params) {
|
||||
case 2:
|
||||
case 2: //nolint:gomnd
|
||||
title, text, color, err = g.handleRepoDownloads(ctx, params)
|
||||
case 3:
|
||||
case 3: //nolint:gomnd
|
||||
params = append(params, "total")
|
||||
fallthrough
|
||||
case 4:
|
||||
case 4: //nolint:gomnd
|
||||
title, text, color, err = g.handleReleaseDownloads(ctx, params)
|
||||
default:
|
||||
err = errors.New("Unsupported number of arguments")
|
||||
}
|
||||
return
|
||||
return title, text, color, err
|
||||
}
|
||||
|
||||
func (g githubServiceHandler) handleReleaseDownloads(ctx context.Context, params []string) (title, text, color string, err error) {
|
||||
|
@ -145,24 +148,24 @@ func (g githubServiceHandler) handleReleaseDownloads(ctx context.Context, params
|
|||
r := githubRelease{}
|
||||
|
||||
if err = g.fetchAPI(ctx, path, nil, &r); err != nil {
|
||||
return
|
||||
return title, text, color, err
|
||||
}
|
||||
|
||||
var sum int64
|
||||
|
||||
for _, rel := range r.Assets {
|
||||
if params[3] == "total" || rel.Name == params[3] {
|
||||
sum = sum + rel.Downloads
|
||||
sum += rel.Downloads
|
||||
}
|
||||
}
|
||||
|
||||
text = metricFormat(sum)
|
||||
cacheStore.Set("github_release_downloads", path, text, 10*time.Minute)
|
||||
logErr(cacheStore.Set("github_release_downloads", path, text, githubCacheDuration), "writing Github release downloads to cache")
|
||||
}
|
||||
|
||||
title = "downloads"
|
||||
color = "brightgreen"
|
||||
return
|
||||
color = colorNameBrightGreen
|
||||
return title, text, color, err
|
||||
}
|
||||
|
||||
func (g githubServiceHandler) handleRepoDownloads(ctx context.Context, params []string) (title, text, color string, err error) {
|
||||
|
@ -173,26 +176,26 @@ func (g githubServiceHandler) handleRepoDownloads(ctx context.Context, params []
|
|||
if err != nil {
|
||||
r := []githubRelease{}
|
||||
|
||||
// TODO: This does not respect pagination!
|
||||
// NOTE: This does not respect pagination!
|
||||
if err = g.fetchAPI(ctx, path, nil, &r); err != nil {
|
||||
return
|
||||
return title, text, color, err
|
||||
}
|
||||
|
||||
var sum int64
|
||||
|
||||
for _, rel := range r {
|
||||
for _, rea := range rel.Assets {
|
||||
sum = sum + rea.Downloads
|
||||
sum += rea.Downloads
|
||||
}
|
||||
}
|
||||
|
||||
text = metricFormat(sum)
|
||||
cacheStore.Set("github_repo_downloads", path, text, 10*time.Minute)
|
||||
logErr(cacheStore.Set("github_repo_downloads", path, text, githubCacheDuration), "writing Github repo downloads to cache")
|
||||
}
|
||||
|
||||
title = "downloads"
|
||||
color = "brightgreen"
|
||||
return
|
||||
color = colorNameBrightGreen
|
||||
return title, text, color, err
|
||||
}
|
||||
|
||||
func (g githubServiceHandler) handleLatestRelease(ctx context.Context, params []string) (title, text, color string, err error) {
|
||||
|
@ -204,24 +207,24 @@ func (g githubServiceHandler) handleLatestRelease(ctx context.Context, params []
|
|||
r := githubRelease{}
|
||||
|
||||
if err = g.fetchAPI(ctx, path, nil, &r); err != nil {
|
||||
return
|
||||
return title, text, color, err
|
||||
}
|
||||
|
||||
text = r.TagName
|
||||
if text == "" {
|
||||
text = "None"
|
||||
text = "None" //nolint:goconst
|
||||
}
|
||||
cacheStore.Set("github_latest_release", path, text, 10*time.Minute)
|
||||
logErr(cacheStore.Set("github_latest_release", path, text, githubCacheDuration), "writing Github last release to cache")
|
||||
}
|
||||
|
||||
title = "release"
|
||||
color = "blue"
|
||||
color = colorNameBlue
|
||||
|
||||
if regexp.MustCompile(`^v?0\.`).MatchString(text) {
|
||||
color = "orange"
|
||||
}
|
||||
|
||||
return
|
||||
return title, text, color, err
|
||||
}
|
||||
|
||||
func (g githubServiceHandler) handleLatestTag(ctx context.Context, params []string) (title, text, color string, err error) {
|
||||
|
@ -235,7 +238,7 @@ func (g githubServiceHandler) handleLatestTag(ctx context.Context, params []stri
|
|||
}{}
|
||||
|
||||
if err = g.fetchAPI(ctx, path, nil, &r); err != nil {
|
||||
return
|
||||
return title, text, color, err
|
||||
}
|
||||
|
||||
if len(r) > 0 {
|
||||
|
@ -243,17 +246,17 @@ func (g githubServiceHandler) handleLatestTag(ctx context.Context, params []stri
|
|||
} else {
|
||||
text = "None"
|
||||
}
|
||||
cacheStore.Set("github_latest_tag", path, text, 10*time.Minute)
|
||||
logErr(cacheStore.Set("github_latest_tag", path, text, githubCacheDuration), "writing Github last tag to cache")
|
||||
}
|
||||
|
||||
title = "tag"
|
||||
color = "blue"
|
||||
color = colorNameBlue
|
||||
|
||||
if regexp.MustCompile(`^v?0\.`).MatchString(text) {
|
||||
color = "orange"
|
||||
}
|
||||
|
||||
return
|
||||
return title, text, color, err
|
||||
}
|
||||
|
||||
func (g githubServiceHandler) handleLicense(ctx context.Context, params []string) (title, text, color string, err error) {
|
||||
|
@ -272,11 +275,11 @@ func (g githubServiceHandler) handleLicense(ctx context.Context, params []string
|
|||
"Accept": "application/vnd.github.drax-preview+json",
|
||||
}
|
||||
if err = g.fetchAPI(ctx, path, headers, &r); err != nil {
|
||||
return
|
||||
return title, text, color, err
|
||||
}
|
||||
|
||||
text = r.License.Name
|
||||
cacheStore.Set("github_license", path, text, 10*time.Minute)
|
||||
logErr(cacheStore.Set("github_license", path, text, githubCacheDuration), "writing Github license to cache")
|
||||
}
|
||||
|
||||
title = "license"
|
||||
|
@ -286,16 +289,13 @@ func (g githubServiceHandler) handleLicense(ctx context.Context, params []string
|
|||
text = "None"
|
||||
}
|
||||
|
||||
return
|
||||
return title, text, color, err
|
||||
}
|
||||
|
||||
func (g githubServiceHandler) fetchAPI(ctx context.Context, path string, headers map[string]string, out interface{}) error {
|
||||
req, _ := http.NewRequest("GET", "https://api.github.com/"+path, nil)
|
||||
|
||||
if headers != nil {
|
||||
for k, v := range headers {
|
||||
req.Header.Set(k, v)
|
||||
}
|
||||
func (githubServiceHandler) fetchAPI(ctx context.Context, path string, headers map[string]string, out interface{}) error {
|
||||
req, _ := http.NewRequestWithContext(ctx, "GET", "https://api.github.com/"+path, nil)
|
||||
for k, v := range headers {
|
||||
req.Header.Set(k, v)
|
||||
}
|
||||
|
||||
// #configStore github.username - string - Username for Github auth to increase API requests
|
||||
|
@ -304,11 +304,18 @@ func (g githubServiceHandler) fetchAPI(ctx context.Context, path string, headers
|
|||
req.SetBasicAuth(configStore.Str("github.username"), configStore.Str("github.personal_token"))
|
||||
}
|
||||
|
||||
resp, err := http.DefaultClient.Do(req.WithContext(ctx))
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
return errors.Wrap(err, "executing HTTP request")
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
defer func() {
|
||||
if err := resp.Body.Close(); err != nil {
|
||||
logrus.WithError(err).Error("closing request body (leaked fd)")
|
||||
}
|
||||
}()
|
||||
|
||||
return json.NewDecoder(resp.Body).Decode(out)
|
||||
return errors.Wrap(
|
||||
json.NewDecoder(resp.Body).Decode(out),
|
||||
"decoding JSON response",
|
||||
)
|
||||
}
|
||||
|
|
|
@ -2,16 +2,19 @@ package main
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Luzifer/go_helpers/v2/str"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
const liberapayCacheDuration = 60 * time.Minute
|
||||
|
||||
func init() {
|
||||
registerServiceHandler("liberapay", liberapayServiceHandler{})
|
||||
}
|
||||
|
@ -29,7 +32,7 @@ type liberapayPublicProfile struct {
|
|||
|
||||
type liberapayServiceHandler struct{}
|
||||
|
||||
func (s liberapayServiceHandler) GetDocumentation() serviceHandlerDocumentationList {
|
||||
func (liberapayServiceHandler) GetDocumentation() serviceHandlerDocumentationList {
|
||||
return serviceHandlerDocumentationList{
|
||||
{
|
||||
ServiceName: "LiberaPay Amount Receiving",
|
||||
|
@ -46,36 +49,40 @@ func (s liberapayServiceHandler) GetDocumentation() serviceHandlerDocumentationL
|
|||
|
||||
func (liberapayServiceHandler) IsEnabled() bool { return true }
|
||||
|
||||
func (s liberapayServiceHandler) Handle(ctx context.Context, params []string) (title, text, color string, err error) {
|
||||
if len(params) < 2 {
|
||||
err = errors.New("You need to provide user and payment direction")
|
||||
return
|
||||
func (liberapayServiceHandler) Handle(ctx context.Context, params []string) (title, text, color string, err error) {
|
||||
if len(params) < 2 { //nolint:gomnd
|
||||
err = errors.New("you need to provide user and payment direction")
|
||||
return title, text, color, err
|
||||
}
|
||||
|
||||
if !str.StringInSlice(params[1], []string{"receiving", "giving"}) {
|
||||
err = fmt.Errorf("%q is an invalid payment direction", params[1])
|
||||
return
|
||||
return title, text, color, err
|
||||
}
|
||||
|
||||
title = params[1]
|
||||
color = "brightgreen"
|
||||
color = colorNameBrightGreen
|
||||
|
||||
cacheKey := strings.Join([]string{params[0], params[1]}, ":")
|
||||
text, err = cacheStore.Get("liberapay", cacheKey)
|
||||
|
||||
if err != nil {
|
||||
req, _ := http.NewRequest("GET", fmt.Sprintf("https://liberapay.com/%s/public.json", params[0]), nil)
|
||||
req, _ := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("https://liberapay.com/%s/public.json", params[0]), nil)
|
||||
|
||||
var resp *http.Response
|
||||
resp, err = http.DefaultClient.Do(req.WithContext(ctx))
|
||||
resp, err = http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return
|
||||
return title, text, color, errors.Wrap(err, "executing request")
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
defer func() {
|
||||
if err := resp.Body.Close(); err != nil {
|
||||
logrus.WithError(err).Error("closing response body (leaked fd)")
|
||||
}
|
||||
}()
|
||||
|
||||
r := liberapayPublicProfile{}
|
||||
if err = json.NewDecoder(resp.Body).Decode(&r); err != nil {
|
||||
return
|
||||
return title, text, color, errors.Wrap(err, "decoding JSON response")
|
||||
}
|
||||
|
||||
switch params[1] {
|
||||
|
@ -93,8 +100,8 @@ func (s liberapayServiceHandler) Handle(ctx context.Context, params []string) (t
|
|||
}
|
||||
}
|
||||
|
||||
cacheStore.Set("liberapay", cacheKey, text, 60*time.Minute)
|
||||
logErr(cacheStore.Set("liberapay", cacheKey, text, liberapayCacheDuration), "writing liberapay result to cache")
|
||||
}
|
||||
|
||||
return
|
||||
return title, text, color, nil
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ func init() {
|
|||
|
||||
type staticServiceHandler struct{}
|
||||
|
||||
func (s staticServiceHandler) GetDocumentation() serviceHandlerDocumentationList {
|
||||
func (staticServiceHandler) GetDocumentation() serviceHandlerDocumentationList {
|
||||
return serviceHandlerDocumentationList{{
|
||||
ServiceName: "Static Badge",
|
||||
DemoPath: "/static/API/Documentation/4c1",
|
||||
|
@ -22,18 +22,18 @@ func (s staticServiceHandler) GetDocumentation() serviceHandlerDocumentationList
|
|||
|
||||
func (staticServiceHandler) IsEnabled() bool { return true }
|
||||
|
||||
func (s staticServiceHandler) Handle(ctx context.Context, params []string) (title, text, color string, err error) {
|
||||
if len(params) < 2 {
|
||||
err = errors.New("You need to provide title and text")
|
||||
return
|
||||
func (staticServiceHandler) Handle(_ context.Context, params []string) (title, text, color string, err error) {
|
||||
if len(params) < 2 { //nolint:gomnd
|
||||
err = errors.New("you need to provide title and text")
|
||||
return title, text, color, err
|
||||
}
|
||||
|
||||
if len(params) < 3 {
|
||||
if len(params) < 3 { //nolint:gomnd
|
||||
params = append(params, defaultColor)
|
||||
}
|
||||
|
||||
title = params[0]
|
||||
text = params[1]
|
||||
color = params[2]
|
||||
return
|
||||
return title, text, color, err
|
||||
}
|
||||
|
|
|
@ -2,21 +2,24 @@ package main
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
const travisCacheDuration = 5 * time.Minute
|
||||
|
||||
func init() {
|
||||
registerServiceHandler("travis", travisServiceHandler{})
|
||||
}
|
||||
|
||||
type travisServiceHandler struct{}
|
||||
|
||||
func (t travisServiceHandler) GetDocumentation() serviceHandlerDocumentationList {
|
||||
func (travisServiceHandler) GetDocumentation() serviceHandlerDocumentationList {
|
||||
return serviceHandlerDocumentationList{{
|
||||
ServiceName: "Travis-CI",
|
||||
DemoPath: "/travis/Luzifer/password",
|
||||
|
@ -26,13 +29,13 @@ func (t travisServiceHandler) GetDocumentation() serviceHandlerDocumentationList
|
|||
|
||||
func (travisServiceHandler) IsEnabled() bool { return true }
|
||||
|
||||
func (t travisServiceHandler) Handle(ctx context.Context, params []string) (title, text, color string, err error) {
|
||||
if len(params) < 2 {
|
||||
err = errors.New("You need to provide user and repo")
|
||||
return
|
||||
func (travisServiceHandler) Handle(ctx context.Context, params []string) (title, text, color string, err error) {
|
||||
if len(params) < 2 { //nolint:gomnd
|
||||
err = errors.New("you need to provide user and repo")
|
||||
return title, text, color, err
|
||||
}
|
||||
|
||||
if len(params) < 3 {
|
||||
if len(params) < 3 { //nolint:gomnd
|
||||
params = append(params, "master")
|
||||
}
|
||||
|
||||
|
@ -43,12 +46,16 @@ func (t travisServiceHandler) Handle(ctx context.Context, params []string) (titl
|
|||
|
||||
if err != nil {
|
||||
var resp *http.Response
|
||||
req, _ := http.NewRequest("GET", "https://api.travis-ci.org/"+path, nil)
|
||||
resp, err = http.DefaultClient.Do(req.WithContext(ctx))
|
||||
req, _ := http.NewRequestWithContext(ctx, "GET", "https://api.travis-ci.org/"+path, nil)
|
||||
resp, err = http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return
|
||||
return title, text, color, errors.Wrap(err, "executing request")
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
defer func() {
|
||||
if err := resp.Body.Close(); err != nil {
|
||||
logrus.WithError(err).Error("closing request body (leaked fd)")
|
||||
}
|
||||
}()
|
||||
|
||||
r := struct {
|
||||
File string `json:"file"`
|
||||
|
@ -58,10 +65,10 @@ func (t travisServiceHandler) Handle(ctx context.Context, params []string) (titl
|
|||
}{}
|
||||
|
||||
if err = json.NewDecoder(resp.Body).Decode(&r); err != nil {
|
||||
return
|
||||
return title, text, color, errors.Wrap(err, "decoding JSON response")
|
||||
}
|
||||
state = r.Branch.State
|
||||
cacheStore.Set("travis", path, state, 5*time.Minute)
|
||||
logErr(cacheStore.Set("travis", path, state, travisCacheDuration), "writing Travis status to cache")
|
||||
}
|
||||
|
||||
title = "travis"
|
||||
|
@ -76,5 +83,5 @@ func (t travisServiceHandler) Handle(ctx context.Context, params []string) (titl
|
|||
"canceled": "9f9f9f",
|
||||
}[text]
|
||||
|
||||
return
|
||||
return title, text, color, nil
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
|
@ -29,7 +30,7 @@ type twitchServiceHandler struct {
|
|||
accessTokenExpiry time.Time
|
||||
}
|
||||
|
||||
func (t twitchServiceHandler) GetDocumentation() serviceHandlerDocumentationList {
|
||||
func (twitchServiceHandler) GetDocumentation() serviceHandlerDocumentationList {
|
||||
return serviceHandlerDocumentationList{
|
||||
{
|
||||
ServiceName: "Twitch views",
|
||||
|
@ -44,9 +45,9 @@ func (twitchServiceHandler) IsEnabled() bool {
|
|||
}
|
||||
|
||||
func (t *twitchServiceHandler) Handle(ctx context.Context, params []string) (title, text, color string, err error) {
|
||||
if len(params) < 2 {
|
||||
if len(params) < 2 { //nolint:gomnd
|
||||
err = errors.New("No service-command / parameters were given")
|
||||
return
|
||||
return title, text, color, err
|
||||
}
|
||||
|
||||
switch params[0] {
|
||||
|
@ -56,7 +57,7 @@ func (t *twitchServiceHandler) Handle(ctx context.Context, params []string) (tit
|
|||
err = errors.New("An unknown service command was called")
|
||||
}
|
||||
|
||||
return
|
||||
return title, text, color, err
|
||||
}
|
||||
|
||||
func (t *twitchServiceHandler) handleViews(ctx context.Context, params []string) (title, text, color string, err error) {
|
||||
|
@ -72,7 +73,7 @@ func (t *twitchServiceHandler) handleViews(ctx context.Context, params []string)
|
|||
field = "id"
|
||||
}
|
||||
|
||||
if err := t.doTwitchRequest(http.MethodGet, fmt.Sprintf("https://api.twitch.tv/helix/users?%s=%s", field, params[0]), nil, &respData); err != nil {
|
||||
if err := t.doTwitchRequest(ctx, http.MethodGet, fmt.Sprintf("https://api.twitch.tv/helix/users?%s=%s", field, params[0]), nil, &respData); err != nil {
|
||||
return "", "", "", errors.Wrap(err, "requesting user list")
|
||||
}
|
||||
|
||||
|
@ -84,10 +85,10 @@ func (t *twitchServiceHandler) handleViews(ctx context.Context, params []string)
|
|||
title = "views"
|
||||
color = "9146FF"
|
||||
|
||||
return
|
||||
return title, text, color, err
|
||||
}
|
||||
|
||||
func (t *twitchServiceHandler) getAccessToken() (string, error) {
|
||||
func (t *twitchServiceHandler) getAccessToken(ctx context.Context) (string, error) {
|
||||
if time.Now().Before(t.accessTokenExpiry) && t.accessToken != "" {
|
||||
return t.accessToken, nil
|
||||
}
|
||||
|
@ -97,11 +98,20 @@ func (t *twitchServiceHandler) getAccessToken() (string, error) {
|
|||
params.Set("client_secret", configStore.Str(configKeyTwitchClientSecret))
|
||||
params.Set("grant_type", "client_credentials")
|
||||
|
||||
resp, err := http.Post(fmt.Sprintf("https://id.twitch.tv/oauth2/token?%s", params.Encode()), "application/json", nil)
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, fmt.Sprintf("https://id.twitch.tv/oauth2/token?%s", params.Encode()), nil)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "request access token")
|
||||
return "", errors.Wrap(err, "creating access token request")
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "executing access token request")
|
||||
}
|
||||
defer func() {
|
||||
if err := resp.Body.Close(); err != nil {
|
||||
logrus.WithError(err).Error("closing response body (leaked fd)")
|
||||
}
|
||||
}()
|
||||
|
||||
var respData struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
|
@ -112,37 +122,18 @@ func (t *twitchServiceHandler) getAccessToken() (string, error) {
|
|||
}
|
||||
|
||||
t.accessToken = respData.AccessToken
|
||||
t.accessTokenExpiry = time.Now().Add(time.Duration(time.Duration(respData.ExpiresIn)) * time.Second)
|
||||
t.accessTokenExpiry = time.Now().Add(time.Duration(respData.ExpiresIn) * time.Second)
|
||||
|
||||
return t.accessToken, nil
|
||||
}
|
||||
|
||||
func (t *twitchServiceHandler) getIDForUser(login string) (string, error) {
|
||||
var respData struct {
|
||||
Data []struct {
|
||||
ID string `json:"id"`
|
||||
Login string `json:"login"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
if err := t.doTwitchRequest(http.MethodGet, fmt.Sprintf("https://api.twitch.tv/helix/users?login=%s", login), nil, &respData); err != nil {
|
||||
return "", errors.Wrap(err, "requesting user list")
|
||||
}
|
||||
|
||||
if len(respData.Data) != 1 {
|
||||
return "", errors.New("unexpected number of users returned")
|
||||
}
|
||||
|
||||
return respData.Data[0].ID, nil
|
||||
}
|
||||
|
||||
func (t *twitchServiceHandler) doTwitchRequest(method, url string, body io.Reader, out interface{}) error {
|
||||
at, err := t.getAccessToken()
|
||||
func (t *twitchServiceHandler) doTwitchRequest(ctx context.Context, method, reqURL string, body io.Reader, out any) error {
|
||||
at, err := t.getAccessToken(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "getting access token")
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(method, url, body)
|
||||
req, err := http.NewRequestWithContext(ctx, method, reqURL, body)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "creating request")
|
||||
}
|
||||
|
@ -153,7 +144,11 @@ func (t *twitchServiceHandler) doTwitchRequest(method, url string, body io.Reade
|
|||
if err != nil {
|
||||
return errors.Wrap(err, "executing request")
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
defer func() {
|
||||
if err := resp.Body.Close(); err != nil {
|
||||
logrus.WithError(err).Error("closing response body (leaked fd)")
|
||||
}
|
||||
}()
|
||||
|
||||
if err = json.NewDecoder(resp.Body).Decode(out); err != nil {
|
||||
return errors.Wrap(err, "reading response")
|
||||
|
|
Loading…
Reference in a new issue