diff --git a/TODO.md b/TODO.md index 269cd39..114f373 100644 --- a/TODO.md +++ b/TODO.md @@ -35,6 +35,7 @@ Cheesy Arena To-Do List * Automatic download of recent accomplishments (needs better TBA API) ###Development tasks +* Change to use the new FMS API for team data * Generate more schedules and find an automated way to evaluate them * Clean up sponsor carousel JavaScript and make it load new slides asynchronously without needing a reload of the audience display page * Refactor websockets to reduce code repetition between displays with similar functions diff --git a/db/migrations/20150401132914_AddTBAColumns.sql b/db/migrations/20150401132914_AddTBAColumns.sql new file mode 100644 index 0000000..89d6c6a --- /dev/null +++ b/db/migrations/20150401132914_AddTBAColumns.sql @@ -0,0 +1,3 @@ +-- +goose Up +ALTER TABLE event_settings ADD COLUMN tbadownloadenabled BOOLEAN; +ALTER TABLE event_settings ADD COLUMN tbaawardsdownloadenabled BOOLEAN; \ No newline at end of file diff --git a/event_settings.go b/event_settings.go index 7af9ca7..2809ae4 100644 --- a/event_settings.go +++ b/event_settings.go @@ -6,26 +6,28 @@ package main type EventSettings struct { - Id int - Name string - Code string - DisplayBackgroundColor string - NumElimAlliances int - SelectionRound2Order string - SelectionRound3Order string - TeamInfoDownloadEnabled bool - RedGoalLightsAddress string - BlueGoalLightsAddress string - TbaPublishingEnabled bool - TbaEventCode string - TbaSecretId string - TbaSecret string - NetworkSecurityEnabled bool - ApAddress string - ApUsername string - ApPassword string - SwitchAddress string - SwitchPassword string + Id int + Name string + Code string + DisplayBackgroundColor string + NumElimAlliances int + SelectionRound2Order string + SelectionRound3Order string + TBADownloadEnabled bool + TBAAwardsDownloadEnabled bool + AllianceDisplayHotGoals bool + RedGoalLightsAddress string + BlueGoalLightsAddress string + TbaPublishingEnabled bool + TbaEventCode string + TbaSecretId string + TbaSecret string + NetworkSecurityEnabled bool + ApAddress string + ApUsername string + ApPassword string + SwitchAddress string + SwitchPassword string BandwidthMonitoringEnabled bool } @@ -42,7 +44,8 @@ func (database *Database) GetEventSettings() (*EventSettings, error) { eventSettings.NumElimAlliances = 8 eventSettings.SelectionRound2Order = "L" eventSettings.SelectionRound3Order = "" - eventSettings.TeamInfoDownloadEnabled = true + eventSettings.TBADownloadEnabled = true + eventSettings.TBAAwardsDownloadEnabled = true err = database.eventSettingsMap.Insert(eventSettings) if err != nil { return nil, err diff --git a/event_settings_test.go b/event_settings_test.go index d1162fb..65f89c8 100644 --- a/event_settings_test.go +++ b/event_settings_test.go @@ -19,7 +19,7 @@ func TestEventSettingsReadWrite(t *testing.T) { assert.Nil(t, err) assert.Equal(t, EventSettings{Id: 0, Name: "Untitled Event", Code: "UE", DisplayBackgroundColor: "#00ff00", NumElimAlliances: 8, SelectionRound2Order: "L", SelectionRound3Order: "", - TeamInfoDownloadEnabled: true}, *eventSettings) + TBADownloadEnabled: true, TBAAwardsDownloadEnabled: true}, *eventSettings) eventSettings.Name = "Chezy Champs" eventSettings.Code = "cc" diff --git a/setup_settings.go b/setup_settings.go index 13410cc..fa29e97 100644 --- a/setup_settings.go +++ b/setup_settings.go @@ -42,7 +42,8 @@ func SettingsPostHandler(w http.ResponseWriter, r *http.Request) { eventSettings.NumElimAlliances = numAlliances eventSettings.SelectionRound2Order = r.PostFormValue("selectionRound2Order") eventSettings.SelectionRound3Order = r.PostFormValue("selectionRound3Order") - eventSettings.TeamInfoDownloadEnabled = r.PostFormValue("teamInfoDownloadEnabled") == "on" + eventSettings.TBADownloadEnabled = r.PostFormValue("TBADownloadEnabled") == "on" + eventSettings.TBAAwardsDownloadEnabled = r.PostFormValue("TBAAwardsDownloadEnabled") == "on" eventSettings.RedGoalLightsAddress = r.PostFormValue("redGoalLightsAddress") eventSettings.BlueGoalLightsAddress = r.PostFormValue("blueGoalLightsAddress") eventSettings.TbaPublishingEnabled = r.PostFormValue("tbaPublishingEnabled") == "on" diff --git a/setup_teams.go b/setup_teams.go index 0ce2cff..fb84b55 100644 --- a/setup_teams.go +++ b/setup_teams.go @@ -6,24 +6,20 @@ package main import ( - "encoding/csv" + "bytes" "fmt" "github.com/dchest/uniuri" "github.com/gorilla/mux" - "html" "html/template" - "io" - "io/ioutil" "net/http" - "regexp" "strconv" "strings" + "time" ) const wpaKeyLength = 8 -var officialTeamInfoUrl = "https://my.usfirst.org/frc/scoring/index.lasso?page=teamlist" -var officialTeamInfo map[int][]string +var officialTeamInfoUrl = "http://www.thebluealliance.com/api/v2/team/" // Shows the team list. func TeamsGetHandler(w http.ResponseWriter, r *http.Request) { @@ -235,52 +231,42 @@ func canModifyTeamList() bool { // Returns the data for the given team number. func getOfficialTeamInfo(teamId int) (*Team, error) { - if officialTeamInfo == nil && eventSettings.TeamInfoDownloadEnabled { - // Download all team info from the FIRST website if it is not cached. - resp, err := http.Get(officialTeamInfoUrl) - if err != nil { - return nil, err - } - defer resp.Body.Close() - body, err := ioutil.ReadAll(resp.Body) - if err != nil { - return nil, err - } - re := regexp.MustCompile("(?s).*
(.*).*") - teamsCsv := re.FindStringSubmatch(string(body))[1] - - // Parse the tab-separated data. - reader := csv.NewReader(strings.NewReader(teamsCsv)) - reader.Comma = '\t' - reader.FieldsPerRecord = -1 - officialTeamInfo = make(map[int][]string) - reader.Read() // Ignore header line. - for { - fields, err := reader.Read() - if err == io.EOF { - break - } else if err != nil { - return nil, err - } - teamNumber, err := strconv.Atoi(fields[1]) - if err != nil { - return nil, err - } - officialTeamInfo[teamNumber] = fields - } - } - - teamData, ok := officialTeamInfo[teamId] + // Create the team variable that stores the result var team Team - if ok { - rookieYear, _ := strconv.Atoi(teamData[8]) - team = Team{Id: teamId, Name: html.UnescapeString(teamData[2]), Nickname: html.UnescapeString(teamData[7]), - City: html.UnescapeString(teamData[4]), StateProv: html.UnescapeString(teamData[5]), - Country: html.UnescapeString(teamData[6]), RookieYear: rookieYear, - RobotName: html.UnescapeString(teamData[9])} + + // If team info download is enabled, download the current teams data (caching isn't easy with the new paging system in the api) + if eventSettings.TBADownloadEnabled { + var tbaTeam *TbaTeam = getTeamFromTba(teamId) + + // Check if the result is valid. If a team is not found, just return a basic team + if tbaTeam == nil { + team = Team{Id: teamId} + return &team, nil + } + + var recentAwards []TbaAward + if eventSettings.TBAAwardsDownloadEnabled { + recentAwards = getTeamAwardsFromTba(teamId) + } + + var accomplishmentsBuffer bytes.Buffer + + // Generate accomplishments string + for _, award := range recentAwards { + if time.Now().Year()-award.Year <= 2 { + accomplishmentsBuffer.WriteString(fmt.Sprint(award.Year, " - ", award.Name, "\n")) + } + } + + // Use those variables to make a team object + team = Team{Id: teamId, Name: tbaTeam.Name, Nickname: tbaTeam.Nickname, + City: tbaTeam.Locality, StateProv: tbaTeam.Reigon, + Country: tbaTeam.Country, RookieYear: tbaTeam.RookieYear, Accomplishments: accomplishmentsBuffer.String()} } else { - // If no team data exists, just fill in the team number. + // If team grab is disabled, just use the team number team = Team{Id: teamId} } + + // Return the team object return &team, nil } diff --git a/tba.go b/tba.go index dbacc86..380bc6d 100644 --- a/tba.go +++ b/tba.go @@ -17,6 +17,8 @@ import ( var tbaBaseUrl = "http://www.thebluealliance.com" +// MODELS + type TbaMatch struct { CompLevel string `json:"comp_level"` MatchNumber int `json:"match_number"` @@ -48,6 +50,63 @@ type TbaRanking struct { Played int `json:"played"` } +type TbaTeam struct { + Website string `json:"website"` + Name string `json:"name"` + Locality string `json:"locality"` + RookieYear int `json:"rookie_year"` + Reigon string `json:"region"` + TeamNumber int `json:"team_number"` + Location string `json:"location"` + Key string `json:"key"` + Country string `json:"country_name"` + Nickname string `json:"nickname"` +} + +type TbaAward struct { + Name string `json:"name"` + EventKey string `json:"event_key"` + Year int `json:"year"` + AwardType int `json:"award_type"` +} + +// DATA RETRIEVAL +func getTeamFromTba(teamNumber int) *TbaTeam { + url := fmt.Sprint("/api/v2/team/", string(getTbaTeam(teamNumber))) + resp, _ := getTbaRequest(url) + + // Get the response and handle errors + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil + } + + var teamData TbaTeam + json.Unmarshal(body, &teamData) + + return &teamData +} + +func getTeamAwardsFromTba(teamNumber int) []TbaAward { + url := fmt.Sprint("/api/v2/team/", string(getTbaTeam(teamNumber)), "/history/awards") + resp, _ := getTbaRequest(url) + + // Get the response and handle errors + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil + } + + var awardData []TbaAward + json.Unmarshal(body, &awardData) + + return awardData +} + +// PUBLISHING + // Uploads the event team list to The Blue Alliance. func PublishTeams() error { teams, err := db.GetAllTeams() @@ -153,7 +212,7 @@ func PublishRankings() error { } // Build a JSON object of TBA-format rankings. - breakdowns := []string{"QA", "Coopertition", "Auto", "Container", "Tote", "Litter"} + breakdowns := []string{"QS", "Assist", "Auto", "T&C", "G&F", "wins", "losses", "ties"} tbaRankings := make([]TbaRanking, len(rankings)) for i, ranking := range rankings { tbaRankings[i] = TbaRanking{getTbaTeam(ranking.TeamId), ranking.Rank, ranking.QualificationAverage, @@ -213,6 +272,8 @@ func getTbaTeam(team int) string { return fmt.Sprintf("frc%d", team) } +// HELPERS + // Signs the request and sends it to the TBA API. func postTbaRequest(resource string, body []byte) (*http.Response, error) { path := fmt.Sprintf("/api/trusted/v1/event/%s/%s/update", eventSettings.TbaEventCode, resource) @@ -227,3 +288,15 @@ func postTbaRequest(resource string, body []byte) (*http.Response, error) { request.Header.Add("X-TBA-Auth-Sig", signature) return client.Do(request) } + +// Sends a GET request to the TBA API +func getTbaRequest(path string) (*http.Response, error) { + // Make an HTTP GET request with the TBA auth headers + client := &http.Client{} + req, err := http.NewRequest("GET", fmt.Sprint(tbaBaseUrl, path), nil) + if err != nil { + return nil, err + } + req.Header.Set("X-TBA-App-Id", "cheesy-arena:cheesy-fms:v0.1") + return client.Do(req) +} diff --git a/templates/base.html b/templates/base.html index be4eb75..752f70e 100644 --- a/templates/base.html +++ b/templates/base.html @@ -17,14 +17,22 @@ +