Initial commit.

feature/event-api
Icedream 2016-06-13 00:39:00 +02:00
commit 055b2a4227
21 changed files with 704 additions and 0 deletions

24
.gitignore vendored Normal file
View File

@ -0,0 +1,24 @@
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
# Folders
_obj
_test
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe
*.test
*.prof

17
.travis.yml Normal file
View File

@ -0,0 +1,17 @@
language: go
go:
- 1.3.3
- 1.4.3
- 1.5.4
- 1.6.2
- release
- tip
install:
- export GOPATH="$HOME/gopath"
- mkdir -p "$GOPATH/src/github.com/icedream"
- mv "$TRAVIS_BUILD_DIR" "$GOPATH/src/github.com/icedream"
- go get -v -t -d github.com/icedream/go-footballdata/...
script:
- go test -v github.com/icedream/go-footballdata/...

47
README.md Normal file
View File

@ -0,0 +1,47 @@
# Football-Data API for Golang
[![Build Status](https://travis-ci.org/icedream/go-footballdata.svg?branch=master)](https://travis-ci.org/icedream/go-footballdata)
[![GoDoc](https://godoc.org/github.com/icedream/go-footballdata?status.svg)](https://godoc.org/github.com/icedream/go-footballdata)
This library provides functionality to communicate with the API provided by http://football-api.org. This way programs can use data provided by the API in order to show information about football/soccer games from various seasons for free.
## How to use this library?
Before you use this library please [register for a free API key](http://api.football-data.org/register) in order to increase your usage limits. The library also works without an API key.
You can install this library by running:
go get github.com/icedream/go-footballdata
Afterwards you can use this library like this:
```go
package main
import (
"fmt"
"net/http"
"github.com/icedream/go-footballdata"
)
func main() {
// Create client
client := footballdata.NewClient(http.DefaultClient)
// Tell it to use our API token
client.AuthToken = "<insert your api token here>"
// Get list of seasons...
seasons, err := client.SoccerSeasons().Do()
if err != nil {
panic(err)
}
// ...and print them
for _, season := range seasons {
fmt.Println(season.Id, season.Caption)
}
}
```

16
api_methods.go Normal file
View File

@ -0,0 +1,16 @@
package footballdata
import (
"encoding/json"
"net/url"
)
type request struct {
c *Client
p string
v url.Values
}
func (r request) doJson(method string) (*json.Decoder, ResponseMeta, error) {
return r.c.doJson(method, r.p, r.v)
}

124
api_types.go Normal file
View File

@ -0,0 +1,124 @@
package footballdata
import "time"
type FixtureStatus string
const (
FixtureStatus_Timed FixtureStatus = "TIMED"
FixtureStatus_InPlay FixtureStatus = "IN_PLAY"
FixtureStatus_Finished FixtureStatus = "FINISHED"
)
// Describes the venue.
type Venue string
const (
// The home venue.
Venue_Home Venue = "home"
// The away venue.
Venue_Away Venue = "away"
)
// Contains the list of soccer seasons returned by the API.
type SoccerSeasonList []SoccerSeason
// Contains information about a soccer season.
type SoccerSeason struct {
Id uint64
Caption string
League string
Year string
CurrentMatchday uint16
NumberOfMatchdays uint16
NumberOfTeams uint16
NumberOfGames uint16
LastUpdates time.Time
}
// Contains the fixture and the head to head information delivered by the API
// for a wanted fixture.
type FixtureResponse struct {
Fixture Fixture
Head2Head Head2Head
}
// Contains head to head information.
type Head2Head struct {
Count uint64
TimeFrameStart time.Time
TimeFrameEnd time.Time
HomeTeamWins uint64
AwayTeamWins uint64
Draws uint64
LastHomeWinHomeTeam *Fixture
LastWinHomeTeam *Fixture
LastAwayWinAwayTeam *Fixture
Fixtures []Fixture
}
// Contains information about a fixture.
type Fixture struct {
Date time.Time
Status FixtureStatus
Matchday uint16
HomeTeamName string
AwayTeamName string
Result FixtureResult
//HomeTeamId uint64
//AwayTeamId uint64
}
// Contains a list of fixtures.
type FixtureList struct {
Count uint64
Fixtures []Fixture
}
// Contains information about a list of fixtures as returned by the API.
type FixturesResponse struct {
FixtureList
TimeFrameStart time.Time
TimeFrameEnd time.Time
}
// Contains information about a fixture's results.
type FixtureResult struct {
GoalsHomeTeam uint16
GoalsAwayTeam uint16
}
// Contains a list of teams.
type TeamList struct {
Count uint64
Teams []Team
}
// Contains information about a team.
type Team struct {
Id uint64
Name string
ShortName string
SquadMarketValue string
CrestUrl string
}
// Contains a list of players.
type PlayerList struct {
Count uint64
Players []Player
}
// Contains information about a player.
type Player struct {
Id uint64
Name string
Position string
JerseyNumber uint8
DateOfBirth time.Time
Nationality string
ContractUntil time.Time
MarketValue string
}

22
api_url.go Normal file
View File

@ -0,0 +1,22 @@
package footballdata
import "net/url"
const (
baseUrl = "http://api.football-data.org/v1/"
)
func resolveRelativeUrl(path string, values url.Values) *url.URL {
if values == nil {
values = url.Values{}
}
u, err := url.Parse(baseUrl)
if err != nil {
panic(err)
}
ru := &url.URL{Path: path, RawQuery: values.Encode()}
return u.ResolveReference(ru)
}

65
client.go Normal file
View File

@ -0,0 +1,65 @@
package footballdata
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
)
/*
Provides a high-level client to talk to the API that football-data.org offers.
To create an instance please use NewClient(h).
*/
type Client struct {
h *http.Client
// Insert an API token here if you have one. It will be sent across with all requests.
AuthToken string
}
// Creates a new Client instance that wraps around the given HTTP client.
func NewClient(h *http.Client) *Client {
return &Client{h: h}
}
func (c *Client) req(path string, pathValues ...interface{}) request {
return request{c, fmt.Sprintf(path, pathValues...), url.Values{}}
}
// Executes an HTTP request with given parameters and on success returns the response wrapped in a JSON decoder.
func (c *Client) doJson(method string, path string, values url.Values) (j *json.Decoder, meta ResponseMeta, err error) {
// Create request
req := &http.Request{
Method: method,
URL: resolveRelativeUrl(path, values),
}
// Set request headers
if len(c.AuthToken) > 0 {
req.Header.Set("X-Auth-Token", c.AuthToken)
}
req.Header.Set("X-Response-Control", "minified")
req.Header.Set("User-Agent", "go-footballdata/0.0")
// Execute request
resp, err := c.h.Do(req)
if err != nil {
return
}
// Save metadata from headers
meta = responseMetaFromHeaders(resp.Header)
// Download to buffer to allow passing back a fully prepared decoder
defer resp.Body.Close()
buf := new(bytes.Buffer)
io.Copy(buf, resp.Body)
// Wrap JSON decoder around buffered data
j = json.NewDecoder(buf)
return
}

27
example_test.go Normal file
View File

@ -0,0 +1,27 @@
package footballdata_test
import (
"fmt"
"net/http"
"github.com/icedream/go-footballdata"
)
func Example() {
// Create client
client := footballdata.NewClient(http.DefaultClient)
// Tell it to use our API token
client.AuthToken = "<insert your api token here>"
// Get list of seasons...
seasons, err := client.SoccerSeasons().Do()
if err != nil {
panic(err)
}
// ...and print them
for _, season := range seasons {
fmt.Println(season.Id, season.Caption)
}
}

27
req_fixture.go Normal file
View File

@ -0,0 +1,27 @@
package footballdata
import "fmt"
type FixtureRequest struct{ request }
// Modifies the request to specify the number of former games to be analyzed (normally 10).
func (r FixtureRequest) Head2Head(num uint16) FixtureRequest {
r.v.Set("head2head", fmt.Sprintf("%i", num))
return r
}
// Executes the request.
func (r FixtureRequest) Do() (s Fixture, err error) {
d, _, err := r.doJson("GET")
if err != nil {
return
}
err = d.Decode(&s)
return
}
// Prepares a request to fetch the fixtures of a soccer season.
func (c *Client) Fixture(id uint64) FixtureRequest {
return FixtureRequest{c.req("fixture/%i", id)}
}

36
req_fixtures.go Normal file
View File

@ -0,0 +1,36 @@
package footballdata
import (
"strings"
"time"
)
type FixturesRequest struct{ request }
// Modifies the request to specify a specific time frame.
func (r FixturesRequest) TimeFrame(timeframe time.Duration) FixturesRequest {
r.v.Set("timeFrame", durationToTimeFrame(timeframe))
return r
}
// Modifies the request to specify a list of leagues by their code.
func (r FixturesRequest) League(leagueCodes ...string) FixturesRequest {
r.v.Set("league", strings.Join(leagueCodes, ","))
return r
}
// Executes the request.
func (r FixturesRequest) Do() (s FixturesResponse, err error) {
d, _, err := r.doJson("GET")
if err != nil {
return
}
err = d.Decode(&s)
return
}
// Prepares a request to fetch the fixtures of a soccer season.
func (c *Client) Fixtures() FixturesRequest {
return FixturesRequest{c.req("fixtures")}
}

19
req_soccerseason.go Normal file
View File

@ -0,0 +1,19 @@
package footballdata
type SoccerSeasonRequest struct{ request }
// Executes the request.
func (r SoccerSeasonRequest) Do() (s SoccerSeason, err error) {
d, _, err := r.doJson("GET")
if err != nil {
return
}
err = d.Decode(&s)
return
}
// Prepares a request to fetch the complete list of soccer seasons.
func (c *Client) SoccerSeason(id uint64) SoccerSeasonRequest {
return SoccerSeasonRequest{c.req("soccerseasons/%i", id)}
}

View File

@ -0,0 +1,36 @@
package footballdata
import (
"fmt"
"time"
)
type SoccerSeasonFixturesRequest struct{ request }
// Modifies the request to specify a match day.
func (r SoccerSeasonFixturesRequest) Matchday(matchday uint16) SoccerSeasonFixturesRequest {
r.v.Set("matchday", fmt.Sprintf("%i", matchday))
return r
}
// Modifies the request to specify a specific time frame.
func (r SoccerSeasonFixturesRequest) TimeFrame(timeframe time.Duration) SoccerSeasonFixturesRequest {
r.v.Set("timeFrame", durationToTimeFrame(timeframe))
return r
}
// Executes the request.
func (r SoccerSeasonFixturesRequest) Do() (s SoccerSeason, err error) {
d, _, err := r.doJson("GET")
if err != nil {
return
}
err = d.Decode(&s)
return
}
// Prepares a request to fetch the fixtures of a soccer season.
func (c *Client) FixturesOfSoccerSeason(soccerSeasonId uint64) SoccerSeasonFixturesRequest {
return SoccerSeasonFixturesRequest{c.req("soccerseasons/%i/fixtures", soccerSeasonId)}
}

View File

@ -0,0 +1,27 @@
package footballdata
import "fmt"
type SoccerSeasonLeagueTableRequest struct{ request }
// Modifies the request to specify a match day.
func (r SoccerSeasonLeagueTableRequest) Matchday(matchday uint16) SoccerSeasonLeagueTableRequest {
r.v.Set("matchday", fmt.Sprintf("%i", matchday))
return r
}
// Executes the request.
func (r SoccerSeasonLeagueTableRequest) Do() (s SoccerSeason, err error) {
d, _, err := r.doJson("GET")
if err != nil {
return
}
err = d.Decode(&s)
return
}
// Prepares a new request to fetch the league table of a given soccer season.
func (c *Client) LeagueTableOfSoccerSeason(soccerSeasonId uint64) SoccerSeasonLeagueTableRequest {
return SoccerSeasonLeagueTableRequest{c.req("soccerseasons/%i/leagueTable", soccerSeasonId)}
}

19
req_soccerseason_teams.go Normal file
View File

@ -0,0 +1,19 @@
package footballdata
type SoccerSeasonTeamsRequest struct{ request }
// Executes the request.
func (r SoccerSeasonTeamsRequest) Do() (s TeamList, err error) {
d, _, err := r.doJson("GET")
if err != nil {
return
}
err = d.Decode(&s)
return
}
// Prepares a new request to fetch the league table of a given soccer season.
func (c *Client) TeamsOfSoccerSeason(soccerSeasonId uint64) SoccerSeasonTeamsRequest {
return SoccerSeasonTeamsRequest{c.req("soccerseasons/%i/leagueTable", soccerSeasonId)}
}

27
req_soccerseasons.go Normal file
View File

@ -0,0 +1,27 @@
package footballdata
import "fmt"
type SoccerSeasonsRequest struct{ request }
// Modifies the request to specify a season.
func (r SoccerSeasonsRequest) Season(num uint32) SoccerSeasonsRequest {
r.v.Set("season", fmt.Sprintf("%i", num))
return r
}
// Executes the request.
func (r SoccerSeasonsRequest) Do() (s SoccerSeasonList, err error) {
d, _, err := r.doJson("GET")
if err != nil {
return
}
err = d.Decode(&s)
return
}
// Prepares a request to fetch the complete list of soccer seasons.
func (c *Client) SoccerSeasons() SoccerSeasonsRequest {
return SoccerSeasonsRequest{c.req("soccerseasons")}
}

26
req_team.go Normal file
View File

@ -0,0 +1,26 @@
package footballdata
type TeamRequest struct {
request
id uint64
}
// Executes the request.
func (r TeamRequest) Do() (s Team, err error) {
d, _, err := r.doJson("GET")
if err != nil {
return
}
err = d.Decode(&s)
if err != nil {
// Fill up data not returned by the server
s.Id = r.id
}
return
}
// Prepares a request to fetch a team's information.
func (c *Client) Team(id uint64) TeamRequest {
return TeamRequest{c.req("teams/%i", id), id}
}

42
req_team_fixtures.go Normal file
View File

@ -0,0 +1,42 @@
package footballdata
import (
"fmt"
"time"
)
type TeamFixturesRequest struct{ request }
// Modifies the request to specify a specific time frame.
func (r TeamFixturesRequest) TimeFrame(timeframe time.Duration) TeamFixturesRequest {
r.v.Set("timeFrame", durationToTimeFrame(timeframe))
return r
}
// Modifies the request to specify a list of leagues by their code.
func (r TeamFixturesRequest) Season(season uint64) TeamFixturesRequest {
r.v.Set("season", fmt.Sprintf("%i", season))
return r
}
// Modifies the request to specify a venue.
func (r TeamFixturesRequest) Venue(venue Venue) TeamFixturesRequest {
r.v.Set("venue", string(venue))
return r
}
// Executes the request.
func (r TeamFixturesRequest) Do() (s FixturesResponse, err error) {
d, _, err := r.doJson("GET")
if err != nil {
return
}
err = d.Decode(&s)
return
}
// Prepares a request to fetch the fixtures of a soccer season.
func (c *Client) FixturesOfTeam(id uint64) TeamFixturesRequest {
return TeamFixturesRequest{c.req("teams/%i/fixtures", id)}
}

19
req_team_players.go Normal file
View File

@ -0,0 +1,19 @@
package footballdata
type TeamPlayersRequest struct{ request }
// Executes the request.
func (r TeamPlayersRequest) Do() (s PlayerList, err error) {
d, _, err := r.doJson("GET")
if err != nil {
return
}
err = d.Decode(&s)
return
}
// Prepares a request to fetch a team's players.
func (c *Client) PlayersOfTeam(id uint64) TeamPlayersRequest {
return TeamPlayersRequest{c.req("teams/%i/players", id)}
}

38
response_meta.go Normal file
View File

@ -0,0 +1,38 @@
package footballdata
import (
"net/http"
"strconv"
)
// Contains additional information returned by the Football-Data API in the HTTP headers.
// This includes the currently authenticated user and information about the rate limitation.
type ResponseMeta struct {
// Indicates the recognized user or returns "anonymous" if not authenticated.
AuthenticatedClient string
// Defines the seconds left to reset your request counter.
RequestCounterReset uint64
// Indicates the requests left.
RequestsAvailable uint64
}
func responseMetaFromHeaders(h http.Header) (r ResponseMeta) {
if v := h.Get("X-Authenticated-Client"); v != "" {
r.AuthenticatedClient = v
}
if v := h.Get("X-RequestCounter-Reset"); v != "" {
i, err := strconv.ParseUint(v, 10, 64)
if err != nil {
r.RequestCounterReset = i
}
}
if v := h.Get("X-Requests-Available"); v != "" {
i, err := strconv.ParseUint(v, 10, 64)
if err != nil {
r.RequestsAvailable = i
}
}
return
}

22
util.go Normal file
View File

@ -0,0 +1,22 @@
package footballdata
import (
"fmt"
"time"
)
const (
day = 24 * time.Hour
week = 7 * day
)
func durationToTimeFrame(d time.Duration) (r string) {
if d < 0 {
r = "p"
d = -d
} else if d > 0 {
r = "n"
}
r += fmt.Sprint(d.Hours() / 24)
return
}

24
util_test.go Normal file
View File

@ -0,0 +1,24 @@
package footballdata
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func Test_durationToTimeFrame_Zero(t *testing.T) {
assert.Equal(t, "0", durationToTimeFrame(0))
}
func Test_durationToTimeFrame_Negative7Days(t *testing.T) {
assert.Equal(t, "p7", durationToTimeFrame(-7*day))
}
func Test_durationToTimeFrame_Positive7Days(t *testing.T) {
assert.Equal(t, "n7", durationToTimeFrame(7*day))
}
func Test_durationToTimeFrame_PositiveHalfDay(t *testing.T) {
assert.Equal(t, "n0.5", durationToTimeFrame(12*time.Hour))
}