Skip to content
Open
2 changes: 1 addition & 1 deletion _examples/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ func main() {
url := ctftime.GetUrl("events", nil)
fmt.Printf("[==>] Requesting %s ...\n", url)
events := ctftime.GetAPIData("events", nil)
for idx, event := range events.([]ctftime.Event) {
for idx, event := range events.(ctftime.Events) {
fmt.Printf("[event%d]\n", idx)
fmt.Printf("%#v\n", event)
}
Expand Down
42 changes: 42 additions & 0 deletions _examples/top10.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package main

import (
"fmt"

"github.com/g0tiu5a/ctftime"
)

func GetTop10() {
url := ctftime.GetUrl("top10", nil)
fmt.Printf("[==>] Requesting %s ...\n", url)

top10s := ctftime.GetAPIData("top10", nil)
for key, top10 := range top10s.(ctftime.Top10s) {
fmt.Printf("[%s]", key)
for idx, team := range top10 {
fmt.Printf(" [%d] %#v\n", idx, team)
}
fmt.Printf("\n")
}
}

func Get2017Top10() {
ctx := ctftime.APIContext{
"year": "2017",
}
url := ctftime.GetUrl("top10", ctx)
fmt.Printf("[==>] Requesting %s ...\n", url)

top10 := ctftime.GetAPIData("top10", ctx)
for idx, team := range top10.(ctftime.Top10) {
fmt.Printf(" [%d] %#v\n", idx, team)
}
}

func main() {
fmt.Printf("[*] Trying All year's top10 api ...\n")
GetTop10()

fmt.Printf("[*] Trying 2017's top10 api ...\n")
Get2017Top10()
}
15 changes: 5 additions & 10 deletions api.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ type apiClient interface {
GetAPIData() interface{}
}

type apiContext map[string]interface{}
type apiClientFactory func(ctx apiContext) apiClient
type APIContext map[string]interface{}
type apiClientFactory func(ctx APIContext) apiClient

var apiClientFactories = make(map[string]apiClientFactory)

Expand All @@ -27,12 +27,7 @@ func registerAPIClient(name string, factory apiClientFactory) {
apiClientFactories[name] = factory
}

// この関数はパッケージがimportされた時に呼び出されます
func init() {
registerAPIClient("events", newEventsAPIClient)
}

func newAPIClient(name string, ctx apiContext) apiClient {
func newAPIClient(name string, ctx APIContext) apiClient {
clientFactory, ok := apiClientFactories[name]
if !ok {
log.Panicf("Invalid API Client name!")
Expand All @@ -41,12 +36,12 @@ func newAPIClient(name string, ctx apiContext) apiClient {
return clientFactory(ctx)
}

func GetUrl(name string, ctx apiContext) string {
func GetUrl(name string, ctx APIContext) string {
client := newAPIClient(name, ctx)
return client.GetUrl()
}

func GetAPIData(name string, ctx map[string]interface{}) interface{} {
func GetAPIData(name string, ctx APIContext) interface{} {
client := newAPIClient(name, ctx)
return client.GetAPIData()
}
2 changes: 2 additions & 0 deletions event.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,5 @@ type Duration struct {
Hours int `json:"hours"`
Days int `json:"days"`
}

type Events []Event
10 changes: 7 additions & 3 deletions events.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,19 @@ import (
)

type eventsAPIClient struct {
Ctx apiContext
Ctx APIContext
}

func newEventsAPIClient(ctx apiContext) apiClient {
func newEventsAPIClient(ctx APIContext) apiClient {
return &eventsAPIClient{
Ctx: ctx,
}
}

func init() {
registerAPIClient("events", newEventsAPIClient)
}

func (client *eventsAPIClient) GetUrl() string {
now := time.Now().Unix()

Expand All @@ -43,7 +47,7 @@ func (client *eventsAPIClient) GetAPIData() interface{} {
}
defer resp.Body.Close()

var events []Event
var events Events
httpResponseToStruct(resp, &events)
return events
}
10 changes: 10 additions & 0 deletions team.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package ctftime

type Team struct {
TeamId int64 `json:"team_id"`
TeamName string `json:"team_name"`
Points float64 `json:"points"`
}

type Top10 []Team
type Top10s map[string]Top10
1 change: 1 addition & 0 deletions test_data/top10_1.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"2016": [{"team_name": "dcua", "points": 1626.8398252895672, "team_id": 762}, {"team_name": "Dragon Sector", "points": 1437.0797589689191, "team_id": 3329}, {"team_name": "LC\u21afBC", "points": 1421.9162070743116, "team_id": 15726}, {"team_name": "Plaid Parliament of Pwning", "points": 1420.0849788230748, "team_id": 284}, {"team_name": "p4", "points": 1139.5346754638151, "team_id": 5152}, {"team_name": "217", "points": 1089.1912175681678, "team_id": 5160}, {"team_name": "TokyoWesterns", "points": 883.9267147647392, "team_id": 12599}, {"team_name": "Tasteless", "points": 875.5401997885435, "team_id": 604}, {"team_name": "0daysober", "points": 852.102032615904, "team_id": 760}, {"team_name": "Eat, Sleep, Pwn, Repeat", "points": 782.212810041931, "team_id": 15712}], "2017": [{"team_name": "Plaid Parliament of Pwning", "points": 701.2585566581962, "team_id": 284}, {"team_name": "217", "points": 570.1486229891746, "team_id": 5160}, {"team_name": "LC\u21afBC", "points": 469.20455737082784, "team_id": 15726}, {"team_name": "Bushwhackers", "points": 435.99265313767927, "team_id": 586}, {"team_name": "Dragon Sector", "points": 428.0948575325616, "team_id": 3329}, {"team_name": "Shellphish", "points": 426.1389494220905, "team_id": 285}, {"team_name": "binja", "points": 345.1303732845989, "team_id": 9083}, {"team_name": "dcua", "points": 344.43269610104136, "team_id": 762}, {"team_name": "p4", "points": 337.01080389588515, "team_id": 5152}, {"team_name": "Tasteless", "points": 330.3306567560065, "team_id": 604}], "2011": [], "2012": [{"team_name": "More Smoked Leet Chicken", "points": 1620.9290291782108, "team_id": 1005}, {"team_name": "Plaid Parliament of Pwning", "points": 1488.2189290148378, "team_id": 284}, {"team_name": "Eindbazen", "points": 1173.1869831197898, "team_id": 322}, {"team_name": "sutegoma2", "points": 739.9457040460449, "team_id": 280}, {"team_name": "LSE", "points": 680.8464679805236, "team_id": 757}, {"team_name": "GoN", "points": 582.0422811390673, "team_id": 1288}, {"team_name": "Hates Irony", "points": 481.28155543835965, "team_id": 279}, {"team_name": "CLGT", "points": 473.9239266941017, "team_id": 298}, {"team_name": "HackerDom", "points": 459.2769161002838, "team_id": 552}, {"team_name": "disekt", "points": 457.5268910229511, "team_id": 308}], "2013": [], "2014": [], "2015": [{"team_name": "Plaid Parliament of Pwning", "points": 1789.8838263769155, "team_id": 284}, {"team_name": "Dragon Sector", "points": 1184.774269385523, "team_id": 3329}, {"team_name": "0ops", "points": 1088.710549206889, "team_id": 4419}, {"team_name": "Shellphish", "points": 1019.3068651403331, "team_id": 285}, {"team_name": "!SpamAndHex", "points": 1015.4893823023731, "team_id": 5347}, {"team_name": "dcua", "points": 917.8869637081789, "team_id": 762}, {"team_name": "Samurai", "points": 786.9403043232309, "team_id": 1937}, {"team_name": "blue-lotus", "points": 783.0605525584488, "team_id": 1941}, {"team_name": "217", "points": 769.1900083740462, "team_id": 5160}, {"team_name": "Tasteless", "points": 766.7836623792566, "team_id": 604}]}
48 changes: 48 additions & 0 deletions top10.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package ctftime

import (
"log"
"net/http"
)

type top10APIClient struct {
Ctx APIContext
}

func newTop10APIClient(ctx APIContext) apiClient {
return &top10APIClient{
Ctx: ctx,
}
}

func init() {
registerAPIClient("top10", newTop10APIClient)
}

func (client *top10APIClient) GetUrl() string {
url := API_ENDPOINT + "/top/"
if year, ok := client.Ctx["year"]; ok {
url = url + year.(string) + "/"
}

return url
}

func (client *top10APIClient) GetAPIData() interface{} {
url := client.GetUrl()

resp, err := http.Get(url)
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()

var top10s Top10s
httpResponseToMap(resp, &top10s)
if year, ok := client.Ctx["year"]; ok {
var top10 Top10 = top10s[year.(string)]
return top10
} else {
return top10s
}
}
43 changes: 43 additions & 0 deletions top10_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package ctftime

import (
"bytes"
"encoding/json"
"io/ioutil"
"log"
"net/http"
"reflect"
"testing"
)

func TestGetTop10Data(t *testing.T) {
client := newAPIClient("top10", nil)

result := client.GetAPIData()

body, err := json.Marshal(result)
if err != nil {
log.Fatal(err)
}

dummy_resp := &http.Response{
StatusCode: 200,
ProtoMajor: 1,
ProtoMinor: 0,
Body: ioutil.NopCloser(bytes.NewReader(body)),
}

var dummy_top10 Top10s
httpResponseToMap(dummy_resp, &dummy_top10)
if len(dummy_top10["2017"]) != 10 {
t.Error("Invalid top10 of 2017 length!")
}

for _, team := range dummy_top10["2017"] {
valid := reflect.TypeOf(Team{})
actual := reflect.TypeOf(team)
if actual != valid {
t.Errorf("Invalid team type of %v! (should be %v).", actual, valid)
}
}
}
28 changes: 28 additions & 0 deletions util.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,17 @@ package ctftime

import (
"encoding/json"
"io"
"io/ioutil"
"log"
"net/http"
"path"
"strings"
)

/* Test */

// テストに使うjsonファイルのデータを読み込んでくる
func getTestData(fname string) []byte {
fpath := path.Join(test_dir, fname)

Expand All @@ -23,6 +26,7 @@ func getTestData(fname string) []byte {

/* HTTP */

// HTTPレスポンスのボディから構造体へ変換するための関数
func httpResponseToStruct(r *http.Response, v interface{}) {
defer r.Body.Close()
body, err := ioutil.ReadAll(r.Body)
Expand All @@ -44,3 +48,27 @@ func httpResponseToStruct(r *http.Response, v interface{}) {
}
}
}

// 年度をキーにしているなど、今後キーが変更される場合、mapで取れるようにするための関数
func httpResponseToMap(r *http.Response, v interface{}) {
defer r.Body.Close()
body, err := ioutil.ReadAll(r.Body)
if err != nil {
log.Fatal("[ReadAll ")
}

decoder := json.NewDecoder(strings.NewReader(string(body)))
err = decoder.Decode(&v)
if err != nil && err != io.EOF {
log.Fatal(err)
}

if valid, ok := v.(interface {
OK() error
}); ok {
err = valid.OK()
if err != nil {
log.Fatal("[Validation] ")
}
}
}
18 changes: 17 additions & 1 deletion util_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ func TestGetTestData(t *testing.T) {
}
}

func TestDecodeJsonResponse(t *testing.T) {
func TestHttpResponseToStruct(t *testing.T) {
buf := getTestData("event_1.json")

// Create HTTP Response
Expand All @@ -44,3 +44,19 @@ func TestDecodeJsonResponse(t *testing.T) {
var events []interface{}
httpResponseToStruct(response, &events)
}

func TestHttpResponseToMap(t *testing.T) {
buf := getTestData("top10_1.json")

response := &http.Response{
Status: "200 OK",
StatusCode: 200,
Proto: "HTTP/1.0",
ProtoMajor: 1,
ProtoMinor: 0,
Body: ioutil.NopCloser(bytes.NewReader(buf)),
}

var top10 Top10s
httpResponseToMap(response, &top10)
}