// HomeHandler is the home of our website func HomeHandler(w http.ResponseWriter, r *http.Request) { // Try to refresh player count refreshPlayerCount() // Get leaderboards leaderboards, err := steam.GetLeaderboards() if err != nil { common.Err("SERVER", fmt.Sprintf("%v", err)) } // Prepare view var data IndexData // -- Paginate page := getPage(r) u, d, pagination := paginate.Paginate(len(leaderboards.List), page, LEADERBOARDSHOMECOUNT) data.Leaderboards = leaderboards.List[u:d] data.Page = pagination data.PlayerCount = playerCount // -- HTML rendering t, err := template.ParseFiles(layoutPath("default"), templatePath("index")) if err != nil { common.Err("SERVER", fmt.Sprintf("%v", err)) } else { t.Execute(w, data) } }
// PlayerHandler gives details for a given player func PlayerHandler(w http.ResponseWriter, r *http.Request) { // Get player object vars := mux.Vars(r) steamID, _ := vars["id"] // id should be safe thanks to routing players := steam.GetPlayersFromWeb(getAPIKey(), true, []string{steamID}) if len(players) < 0 { http.Redirect(w, r, "/", http.StatusNotFound) return } player, ok := players[steamID] if ok == false { http.Redirect(w, r, "/", http.StatusNotFound) return } // Get data for player var p PlayerData p.Player = player p.Ranks = steam.GetPlayerEntries(steamID) // HTML rendering t, err := template.ParseFiles(layoutPath("default"), templatePath("player")) if err != nil { common.Err("SERVER", fmt.Sprintf("%v", err)) } else { t.Execute(w, p) } }
// LeaderboardHandler displays information for a given leaderboard func LeaderboardHandler(w http.ResponseWriter, r *http.Request) { // Get leaderboard object vars := mux.Vars(r) ldbID, _ := strconv.Atoi(vars["id"]) l, err := steam.GetLeaderboard(ldbID) if err != nil { http.Redirect(w, r, "/", http.StatusNotFound) return } // Pagination page := getPage(r) u, d, pagination := paginate.Paginate(len(l.Entries), page, 100) data := LeaderboardData{ Entries: l.Entries[u:d], Page: pagination, Name: l.Name, Count: len(l.Entries), ID: l.UID, Daily: l.IsDaily(), LastUpdateDisplayable: l.LastUpdate.Format(time.Stamp), } // HTML rendering t, err := template.ParseFiles(layoutPath("default"), templatePath("leaderboard")) if err != nil { common.Err("SERVER", fmt.Sprintf("%v", err)) } else { t.Execute(w, data) } }
// GetLeaderboard returns a leaderboard object with the given Steam id func GetLeaderboard(steamID int) (common.Leaderboard, error) { l, err := common.GetLeaderboardFromDB(steamID) // Now load entries // -- Update first ttl := time.Duration(24) * time.Hour if l.IsDaily() { ttl = time.Duration(30) * time.Minute } if l.LastUpdate.Add(ttl).Before(time.Now()) { loadLeaderboardFromWeb(l) } // -- Read from DB entries, err := common.GetEntriesFromDB(l.UID) if err != nil { return l, err } // --------- // Additionnal datas for i := 0; i < len(entries); i++ { entry := &entries[i] // Metadata t, err := hex.DecodeString(entry.MetadataRaw) if err == nil { if len(t) > 0 { // Metadata are 32 bits integer = 4 * 8 bits bytes metadatas := []int{} for m := 0; m < len(t); m += 4 { var val int32 buf := bytes.NewBuffer(t[m : m+4]) binary.Read(buf, binary.LittleEndian, &val) metadatas = append(metadatas, int(val)) } specificFunctions.UseMetadata(entry, metadatas) } } else { common.Err("DATABASE", fmt.Sprintf("Error parsing metadata: %v", err)) } } l.Entries = entries return l, nil }
// GetPlayerEntries returns all entries for this player func GetPlayerEntries(playerID string) []common.PlayerRank { ranks, err := common.GetPlayerEntriesDB(playerID) if err != nil { common.Err("DATABASE", fmt.Sprintf("GetPlayerEntriesDB: %v", err)) } return ranks }
func loadLeaderboardsFromWeb() error { common.Info("LEADERBOARDS", "Reloading all leaderboards from web...") var ldbs common.LeaderboardsList // Parse new from URL url := fmt.Sprintf("http://steamcommunity.com/stats/%v/leaderboards/?xml=1", appID) // Get all leaderboards for the game if err := MakeAPICall(url, true, &ldbs); err != nil { common.Err("LEADERBOARDS", fmt.Sprintf("%s", err)) return err } // Exclude some leaderboards we don't want to show for i := 0; i < len(excludedLeaderboardIDs); i++ { for j := 0; j < len(ldbs.List); j++ { if ldbs.List[j].SteamID == excludedLeaderboardIDs[i] { // Remove the element ldbs.List = append(ldbs.List[:j], ldbs.List[j+1:]...) } } } // Parse dates for i := 0; i < len(ldbs.List); i++ { l := &ldbs.List[i] l.LastUpdate = time.Date(1988, time.April, 29, 3, 0, 0, 0, time.UTC) l.Date = specificFunctions.GetDate(l) } // Save to database for _, l := range ldbs.List { // Exists? existingLeaderboard, _ := common.GetLeaderboardFromDB(l.SteamID) if existingLeaderboard.Name == "" { common.InsertLeaderboardDB(l) } else { common.UpdateLeaderboardDB(l) } } lastLeaderboardRefresh = time.Now() return nil }
// loadLeaderboardFromWeb fetch data from Steam func loadLeaderboardFromWeb(l common.Leaderboard) { common.Info("LEADERBOARDS", fmt.Sprintf("Loading leaderboard %v from web...", l.SteamID)) resp, err := http.Get(l.URL) if err != nil { common.Err("LEADERBOARDS", fmt.Sprintf("%s", err)) return } defer resp.Body.Close() leaderboardBuffer, err := ioutil.ReadAll(resp.Body) if err != nil { common.Err("LEADERBOARDS", fmt.Sprintf("%s", err)) return } // Parse XML to get objects // LeaderboardEntriesRequest is the list of score entries. // It is only used for the marshaller requestType := struct { EntriesContainer struct { Entries []common.LeaderboardEntry `xml:"entry"` } `xml:"entries"` }{} xml.Unmarshal(leaderboardBuffer, &requestType) entries := requestType.EntriesContainer.Entries // Save to database // -- CLean all previous common.DeleteEntriesDB(l) // -- Save & get players var players map[string]common.Player players = make(map[string]common.Player) if len(entries) > 0 { var steamIDsToLoad []string for _, e := range entries { p, _ := common.GetPlayerWithSteamIDFromDB(e.SteamID) updatePlayer := true if p.UID != 0 { // Check last update if p.LastUpdate.Add(time.Duration(1) * time.Hour).After(time.Now()) { players[e.SteamID] = p updatePlayer = false } } if updatePlayer { steamIDsToLoad = append(steamIDsToLoad, e.SteamID) } } // Update = web to DB for steamID, p := range GetPlayersFromWeb(apiKey, false, steamIDsToLoad) { // Save player common.InsertOrUpdatePlayerDB(&p) players[steamID] = p } } // -- Insert all new ones for _, e := range requestType.EntriesContainer.Entries { // Set player e.Player = players[e.SteamID] e.PlayerUID = e.Player.UID // Save entry common.InsertEntryDB(l, e) } l.LastUpdate = time.Now() common.UpdateLeaderboardDB(l) common.Info("LEADERBOARDS", fmt.Sprintf("Loading leaderboard %v completed!", l.SteamID)) }
// Start the server. func Start() { common.Info("SERVER", "Loading...") steam.DebugRequests = false // Check API key // Request API Key: http://steamcommunity.com/dev/apikey err := godotenv.Load() if err != nil { common.Err("SERVER", fmt.Sprintf("Error loading .env file: %v ", err)) } else { steamAPIkey := getAPIKey() if steamAPIkey == "" { common.Warn("SERVER", "Missing Steam API key, some features will not work. Get one at http://steamcommunity.com/dev/apikey and add it to a .env file for key STEAM_API_KEY.") } else { common.Info("SERVER", "Steam key found!") } } // Configure common.ConfigureDB(config.DbFile) steam.Configure(getAPIKey(), STEREDENN, []int{1006063}, steredenn.Steredenn{}) // Get player count refreshPlayerCount() common.Info("SERVER", "Loading completed!") // Initial loading? if config.LoadAllOnStart { common.Info("SERVER", "Starting complete data reload...") leaderboards, _ := steam.GetLeaderboards() for _, l := range leaderboards.List { steam.GetLeaderboard(l.SteamID) } common.Info("SERVER", "Data reload completed!") } // Routing r := mux.NewRouter() r.HandleFunc("/", HomeHandler) r.HandleFunc("/{page:[0-9]+}", HomeHandler) r.HandleFunc("/leaderboard/{id:[0-9]+}", LeaderboardHandler) r.HandleFunc("/leaderboard/{id:[0-9]+}/{page:[0-9]+}", LeaderboardHandler) r.HandleFunc("/player/{id:[0-9]+}", PlayerHandler) // Use a custom file server in dev mode to serve static file. // Useless in production (nginx will handle that). if config.Dev { r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir(config.StaticFolder)))) } http.Handle("/", r) http.ListenAndServe(":3000", nil) }