mirror of
https://github.com/Alexander-D-Karpov/about.git
synced 2026-03-16 22:06:08 +03:00
776 lines
21 KiB
Go
776 lines
21 KiB
Go
package plugins
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"html/template"
|
|
"net/http"
|
|
"net/url"
|
|
"sort"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/Alexander-D-Karpov/about/internal/storage"
|
|
"github.com/Alexander-D-Karpov/about/internal/stream"
|
|
)
|
|
|
|
type SteamPlugin struct {
|
|
storage *storage.Storage
|
|
hub *stream.Hub
|
|
apiKey string
|
|
recentGames []SteamGame
|
|
topGames []SteamGame
|
|
playerSummary *SteamPlayerSummary
|
|
lastUpdate time.Time
|
|
}
|
|
|
|
type SteamGame struct {
|
|
Name string `json:"name"`
|
|
Playtime2w int `json:"playtime_2weeks"`
|
|
PlaytimeAll int `json:"playtime_forever"`
|
|
AppID int `json:"appid"`
|
|
ImgIconURL string `json:"img_icon_url"`
|
|
}
|
|
|
|
type SteamCurrentGame struct {
|
|
GameID string `json:"gameid"`
|
|
GameExtraInfo string `json:"gameextrainfo"`
|
|
GameServerIP string `json:"gameserverip"`
|
|
GameServerSteamID string `json:"gameserversteamid"`
|
|
}
|
|
|
|
type SteamPlayerSummary struct {
|
|
SteamID string `json:"steamid"`
|
|
CommunityVisibilityState int `json:"communityvisibilitystate"`
|
|
ProfileState int `json:"profilestate"`
|
|
PersonaName string `json:"personaname"`
|
|
ProfileURL string `json:"profileurl"`
|
|
Avatar string `json:"avatar"`
|
|
AvatarMedium string `json:"avatarmedium"`
|
|
AvatarFull string `json:"avatarfull"`
|
|
AvatarHash string `json:"avatarhash"`
|
|
LastLogoff int64 `json:"lastlogoff"`
|
|
PersonaState int `json:"personastate"`
|
|
RealName string `json:"realname"`
|
|
PrimaryClanID string `json:"primaryclanid"`
|
|
TimeCreated int64 `json:"timecreated"`
|
|
PersonaStateFlags int `json:"personastateflags"`
|
|
GameID string `json:"gameid,omitempty"`
|
|
GameExtraInfo string `json:"gameextrainfo,omitempty"`
|
|
GameServerIP string `json:"gameserverip,omitempty"`
|
|
GameServerSteamID string `json:"gameserversteamid,omitempty"`
|
|
}
|
|
|
|
type SteamResponse struct {
|
|
Response struct {
|
|
TotalCount int `json:"total_count"`
|
|
Games []SteamGame `json:"games"`
|
|
} `json:"response"`
|
|
}
|
|
|
|
type SteamOwnedGamesResponse struct {
|
|
Response struct {
|
|
GameCount int `json:"game_count"`
|
|
Games []SteamGame `json:"games"`
|
|
} `json:"response"`
|
|
}
|
|
|
|
type SteamPlayerSummaryResponse struct {
|
|
Response struct {
|
|
Players []SteamPlayerSummary `json:"players"`
|
|
} `json:"response"`
|
|
}
|
|
|
|
func NewSteamPlugin(storage *storage.Storage, hub *stream.Hub, apiKey string) *SteamPlugin {
|
|
return &SteamPlugin{
|
|
storage: storage,
|
|
hub: hub,
|
|
apiKey: apiKey,
|
|
}
|
|
}
|
|
|
|
func (p *SteamPlugin) Name() string {
|
|
return "steam"
|
|
}
|
|
|
|
func (p *SteamPlugin) Render(ctx context.Context) (string, error) {
|
|
config := p.storage.GetPluginConfig(p.Name())
|
|
settings := config.Settings
|
|
|
|
// Windows-style toggles
|
|
showSteam := p.getConfigBool(settings, "ui.showSteam", true)
|
|
if !showSteam {
|
|
return "", nil
|
|
}
|
|
sectionTitle := p.getConfigValue(settings, "ui.sectionTitle", "Gaming Activity")
|
|
|
|
if p.apiKey == "" {
|
|
return p.renderNoAPI(sectionTitle), nil
|
|
}
|
|
|
|
if p.playerSummary == nil && len(p.recentGames) == 0 && len(p.topGames) == 0 {
|
|
return p.renderLoading(sectionTitle), nil
|
|
}
|
|
|
|
tmpl := `
|
|
<section class="steam-section section plugin" data-w="2">
|
|
<header class="plugin-header">
|
|
<h3 class="plugin-title">{{.SectionTitle}}</h3>
|
|
</header>
|
|
|
|
<div class="plugin__inner">
|
|
{{if and .PlayerSummary .IsPlayingNow}}
|
|
<div class="current-game">
|
|
<div class="current-game-header">
|
|
<span class="status-indicator status-online"></span>
|
|
<span class="current-game-status">Currently Playing</span>
|
|
</div>
|
|
{{if .CurrentGameImage}}
|
|
<div class="current-game-cover">
|
|
<img src="{{.CurrentGameImage}}" alt="{{.CurrentGameName}}" class="game-cover-image" loading="lazy">
|
|
</div>
|
|
{{end}}
|
|
<div class="current-game-info">
|
|
<div class="current-game-name">{{.CurrentGameName}}</div>
|
|
<div class="current-game-actions">
|
|
{{if .CurrentGameStoreURL}}
|
|
<a href="{{.CurrentGameStoreURL}}" target="_blank" rel="noopener" class="btn btn-sm">
|
|
<!-- icon -->
|
|
View on Steam
|
|
</a>
|
|
{{else}}
|
|
<a href="https://store.steampowered.com/search/?term={{.CurrentGameNameEncoded}}" target="_blank" rel="noopener" class="btn btn-sm">
|
|
<!-- icon -->
|
|
Search on Steam
|
|
</a>
|
|
{{end}}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{{else if .PlayerSummary}}
|
|
<div class="player-status">
|
|
<div class="status-info">
|
|
<span class="status-indicator {{.PlayerStatusClass}}"></span>
|
|
<span class="status-text">{{.PlayerStatusText}}</span>
|
|
</div>
|
|
</div>
|
|
{{end}}
|
|
|
|
{{if .RecentGames}}
|
|
<div class="recent-games">
|
|
<h4>Recently Played Games</h4>
|
|
<div class="games-list">
|
|
{{range .RecentGames}}
|
|
<div class="game-item" data-app-id="{{.AppID}}">
|
|
{{if .Icon}}
|
|
<img src="{{.Icon}}" alt="{{.Name}}" class="game-icon" loading="lazy">
|
|
{{end}}
|
|
<div class="game-info">
|
|
<div class="game-name">{{.Name}}</div>
|
|
<div class="game-stats">
|
|
<a class="steam-stat-link" href="https://store.steampowered.com/app/{{.AppID}}/" target="_blank" rel="noopener">
|
|
<span class="game-playtime">{{.RecentHours}}h last 2 weeks</span>
|
|
</a>
|
|
<a class="steam-stat-link" href="https://store.steampowered.com/app/{{.AppID}}/" target="_blank" rel="noopener">
|
|
<span class="game-total">{{.TotalHours}}h total</span>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
<div class="game-actions">
|
|
<button class="btn btn-sm" onclick="window.open('https://store.steampowered.com/app/{{.AppID}}/', '_blank', 'noopener')">
|
|
View
|
|
</button>
|
|
</div>
|
|
</div>
|
|
{{end}}
|
|
</div>
|
|
</div>
|
|
{{end}}
|
|
|
|
{{if .TopGames}}
|
|
<div class="recent-games">
|
|
<h4>Top Games by Playtime</h4>
|
|
<div class="games-list">
|
|
{{range .TopGames}}
|
|
<div class="game-item" data-app-id="{{.AppID}}">
|
|
{{if .Icon}}
|
|
<img src="{{.Icon}}" alt="{{.Name}}" class="game-icon" loading="lazy">
|
|
{{end}}
|
|
<div class="game-info">
|
|
<div class="game-name">{{.Name}}</div>
|
|
<div class="game-stats">
|
|
<a class="steam-stat-link" href="https://store.steampowered.com/app/{{.AppID}}/" target="_blank" rel="noopener">
|
|
<span class="game-total">{{.TotalHours}}h total</span>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
<div class="game-actions">
|
|
<button class="btn btn-sm" onclick="window.open('https://store.steampowered.com/app/{{.AppID}}/', '_blank', 'noopener')">
|
|
View
|
|
</button>
|
|
</div>
|
|
</div>
|
|
{{end}}
|
|
</div>
|
|
</div>
|
|
{{end}}
|
|
|
|
{{if .PlayerSummary}}
|
|
<div class="steam-profile-link">
|
|
<a href="{{.PlayerSummary.ProfileURL}}" target="_blank" rel="noopener" class="view-profile-btn">
|
|
View Steam Profile
|
|
</a>
|
|
</div>
|
|
{{end}}
|
|
</div>
|
|
</section>`
|
|
|
|
type gameData struct {
|
|
Name string
|
|
Icon string
|
|
RecentHours string
|
|
RecentHoursNum float64
|
|
TotalHours string
|
|
AppID int
|
|
}
|
|
|
|
// Top games
|
|
var topGames []gameData
|
|
for i, game := range p.topGames {
|
|
if i >= 9 {
|
|
break
|
|
}
|
|
|
|
var icon string
|
|
if game.ImgIconURL != "" {
|
|
icon = fmt.Sprintf(
|
|
"https://media.steampowered.com/steamcommunity/public/images/apps/%d/%s.jpg",
|
|
game.AppID, game.ImgIconURL,
|
|
)
|
|
}
|
|
|
|
recentHoursNum := float64(game.Playtime2w) / 60.0
|
|
recentHours := fmt.Sprintf("%.1f", recentHoursNum)
|
|
totalHours := fmt.Sprintf("%.1f", float64(game.PlaytimeAll)/60.0)
|
|
|
|
topGames = append(topGames, gameData{
|
|
Name: game.Name,
|
|
Icon: icon,
|
|
RecentHours: recentHours,
|
|
RecentHoursNum: recentHoursNum,
|
|
TotalHours: totalHours,
|
|
AppID: game.AppID,
|
|
})
|
|
}
|
|
|
|
// Recent games
|
|
var recentGames []gameData
|
|
for i, game := range p.recentGames {
|
|
if i >= 3 {
|
|
break
|
|
}
|
|
|
|
var icon string
|
|
if game.ImgIconURL != "" {
|
|
icon = fmt.Sprintf(
|
|
"https://media.steampowered.com/steamcommunity/public/images/apps/%d/%s.jpg",
|
|
game.AppID, game.ImgIconURL,
|
|
)
|
|
}
|
|
|
|
recentHoursNum := float64(game.Playtime2w) / 60.0
|
|
recentHours := fmt.Sprintf("%.1f", recentHoursNum)
|
|
totalHours := fmt.Sprintf("%.1f", float64(game.PlaytimeAll)/60.0)
|
|
|
|
recentGames = append(recentGames, gameData{
|
|
Name: game.Name,
|
|
Icon: icon,
|
|
RecentHours: recentHours,
|
|
RecentHoursNum: recentHoursNum,
|
|
TotalHours: totalHours,
|
|
AppID: game.AppID,
|
|
})
|
|
}
|
|
|
|
isPlayingNow := p.playerSummary != nil && p.playerSummary.GameExtraInfo != ""
|
|
currentGameName := ""
|
|
currentGameNameEncoded := ""
|
|
currentGameImage := ""
|
|
currentGameID := ""
|
|
currentGameStoreURL := ""
|
|
currentGameRunURL := "" // optional
|
|
playerStatusClass := "status-offline"
|
|
playerStatusText := "Offline"
|
|
|
|
if p.playerSummary != nil {
|
|
if isPlayingNow {
|
|
currentGameName = p.playerSummary.GameExtraInfo
|
|
currentGameNameEncoded = url.QueryEscape(currentGameName)
|
|
|
|
currentGameID = p.playerSummary.GameID
|
|
if currentGameID != "" {
|
|
currentGameStoreURL = fmt.Sprintf("https://store.steampowered.com/app/%s/", currentGameID)
|
|
currentGameRunURL = fmt.Sprintf("steam://run/%s", currentGameID) // optional
|
|
|
|
currentGameImage = fmt.Sprintf(
|
|
"https://cdn.cloudflare.steamstatic.com/steam/apps/%s/header.jpg",
|
|
currentGameID,
|
|
)
|
|
}
|
|
}
|
|
|
|
switch p.playerSummary.PersonaState {
|
|
case 0:
|
|
playerStatusClass = "status-offline"
|
|
playerStatusText = "Offline"
|
|
case 1:
|
|
playerStatusClass = "status-online"
|
|
playerStatusText = "Online"
|
|
case 2:
|
|
playerStatusClass = "status-loading"
|
|
playerStatusText = "Busy"
|
|
case 3:
|
|
playerStatusClass = "status-loading"
|
|
playerStatusText = "Away"
|
|
case 4:
|
|
playerStatusClass = "status-loading"
|
|
playerStatusText = "Snooze"
|
|
case 5:
|
|
playerStatusClass = "status-loading"
|
|
playerStatusText = "Looking to trade"
|
|
case 6:
|
|
playerStatusClass = "status-loading"
|
|
playerStatusText = "Looking to play"
|
|
}
|
|
}
|
|
|
|
data := struct {
|
|
SectionTitle string
|
|
RecentGames []gameData
|
|
TopGames []gameData
|
|
PlayerSummary *SteamPlayerSummary
|
|
IsPlayingNow bool
|
|
CurrentGameName string
|
|
CurrentGameNameEncoded string
|
|
CurrentGameImage string
|
|
CurrentGameID string
|
|
PlayerStatusClass string
|
|
PlayerStatusText string
|
|
CurrentGameStoreURL string
|
|
CurrentGameRunURL string // optional
|
|
}{
|
|
SectionTitle: sectionTitle,
|
|
RecentGames: recentGames,
|
|
TopGames: topGames,
|
|
PlayerSummary: p.playerSummary,
|
|
IsPlayingNow: isPlayingNow,
|
|
CurrentGameName: currentGameName,
|
|
CurrentGameNameEncoded: currentGameNameEncoded,
|
|
CurrentGameImage: currentGameImage,
|
|
CurrentGameID: currentGameID,
|
|
PlayerStatusClass: playerStatusClass,
|
|
PlayerStatusText: playerStatusText,
|
|
CurrentGameStoreURL: currentGameStoreURL,
|
|
CurrentGameRunURL: currentGameRunURL, // optional
|
|
|
|
}
|
|
|
|
tmplParsed, err := template.New("steam").Parse(tmpl)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
var buf strings.Builder
|
|
err = tmplParsed.Execute(&buf, data)
|
|
return buf.String(), err
|
|
}
|
|
|
|
func (p *SteamPlugin) renderNoAPI(sectionTitle string) string {
|
|
return `<section class="steam-section section plugin" data-w="2">
|
|
<header class="plugin-header">
|
|
<h3 class="plugin-title">` + sectionTitle + `</h3>
|
|
</header>
|
|
<div class="plugin__inner">
|
|
<p class="text-muted">Steam API key not configured</p>
|
|
</div>
|
|
</section>`
|
|
}
|
|
|
|
func (p *SteamPlugin) renderLoading(sectionTitle string) string {
|
|
return `<section class="steam-section section plugin" data-w="2">
|
|
<header class="plugin-header">
|
|
<h3 class="plugin-title">` + sectionTitle + `</h3>
|
|
</header>
|
|
<div class="plugin__inner">
|
|
<div class="loading-indicator">
|
|
<div class="loading"></div>
|
|
<p class="text-muted">Loading Steam data...</p>
|
|
</div>
|
|
</div>
|
|
</section>`
|
|
}
|
|
|
|
func (p *SteamPlugin) UpdateData(ctx context.Context) error {
|
|
if p.apiKey == "" || time.Since(p.lastUpdate) < 4*time.Minute {
|
|
return nil
|
|
}
|
|
|
|
config := p.storage.GetPluginConfig(p.Name())
|
|
steamID, ok := config.Settings["steamid"].(string)
|
|
if !ok || steamID == "" {
|
|
return fmt.Errorf("steamid not configured")
|
|
}
|
|
|
|
if err := p.updatePlayerSummary(steamID); err != nil {
|
|
fmt.Printf("Warning: Failed to update Steam player summary: %v\n", err)
|
|
}
|
|
|
|
if err := p.updateRecentGames(steamID); err != nil {
|
|
fmt.Printf("Warning: Failed to update Steam recent games: %v\n", err)
|
|
}
|
|
|
|
if err := p.updateTopGames(steamID); err != nil {
|
|
fmt.Printf("Warning: Failed to update Steam top games: %v\n", err)
|
|
}
|
|
|
|
p.lastUpdate = time.Now()
|
|
return nil
|
|
}
|
|
|
|
func (p *SteamPlugin) updatePlayerSummary(steamID string) error {
|
|
url := fmt.Sprintf("http://api.steampowered.com/ISteamUser/GetPlayerSummaries/v0002/?key=%s&steamids=%s",
|
|
p.apiKey, steamID)
|
|
|
|
client := &http.Client{Timeout: 15 * time.Second}
|
|
req, err := http.NewRequest("GET", url, nil)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create request: %v", err)
|
|
}
|
|
|
|
req.Header.Set("User-Agent", "AboutPage/1.0 (aboutpage.akarpov.ru)")
|
|
req.Header.Set("Accept", "application/json")
|
|
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to fetch Steam player summary: %v", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
return fmt.Errorf("Steam API returned status %d", resp.StatusCode)
|
|
}
|
|
|
|
var response SteamPlayerSummaryResponse
|
|
decoder := json.NewDecoder(resp.Body)
|
|
if err := decoder.Decode(&response); err != nil {
|
|
return fmt.Errorf("failed to decode Steam player summary: %v", err)
|
|
}
|
|
|
|
if len(response.Response.Players) > 0 {
|
|
oldGameStatus := ""
|
|
oldPersonaState := 0
|
|
if p.playerSummary != nil {
|
|
oldGameStatus = p.playerSummary.GameExtraInfo
|
|
oldPersonaState = p.playerSummary.PersonaState
|
|
}
|
|
|
|
p.playerSummary = &response.Response.Players[0]
|
|
|
|
newGameStatus := p.playerSummary.GameExtraInfo
|
|
newPersonaState := p.playerSummary.PersonaState
|
|
|
|
if oldGameStatus != newGameStatus || oldPersonaState != newPersonaState {
|
|
gameImage := ""
|
|
if p.playerSummary.GameID != "" {
|
|
gameImage = fmt.Sprintf(
|
|
"https://cdn.cloudflare.steamstatic.com/steam/apps/%s/header.jpg",
|
|
p.playerSummary.GameID,
|
|
)
|
|
}
|
|
|
|
p.hub.Broadcast("steam_status_update", map[string]interface{}{
|
|
"isPlaying": newGameStatus != "",
|
|
"currentGame": newGameStatus,
|
|
"gameImage": gameImage,
|
|
"gameId": p.playerSummary.GameID,
|
|
"personaState": newPersonaState,
|
|
"personaName": p.playerSummary.PersonaName,
|
|
"timestamp": time.Now().Unix(),
|
|
})
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (p *SteamPlugin) updateRecentGames(steamID string) error {
|
|
url := fmt.Sprintf(
|
|
"http://api.steampowered.com/IPlayerService/GetRecentlyPlayedGames/v0001/?key=%s&steamid=%s&format=json&count=100",
|
|
p.apiKey, steamID,
|
|
)
|
|
|
|
client := &http.Client{Timeout: 15 * time.Second}
|
|
req, err := http.NewRequest("GET", url, nil)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create request: %v", err)
|
|
}
|
|
|
|
req.Header.Set("User-Agent", "AboutPage/1.0 (aboutpage.akarpov.ru)")
|
|
req.Header.Set("Accept", "application/json")
|
|
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to fetch Steam data: %v", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
return fmt.Errorf("steam API returned status %d", resp.StatusCode)
|
|
}
|
|
|
|
contentType := resp.Header.Get("Content-Type")
|
|
if !strings.Contains(contentType, "application/json") {
|
|
return fmt.Errorf("steam API returned non-JSON content: %s", contentType)
|
|
}
|
|
|
|
var response SteamResponse
|
|
decoder := json.NewDecoder(resp.Body)
|
|
if err := decoder.Decode(&response); err != nil {
|
|
return fmt.Errorf("failed to decode Steam data: %v", err)
|
|
}
|
|
|
|
games := response.Response.Games
|
|
|
|
sort.Slice(games, func(i, j int) bool {
|
|
return games[i].Playtime2w > games[j].Playtime2w
|
|
})
|
|
|
|
if len(games) > 10 {
|
|
games = games[:10]
|
|
}
|
|
|
|
oldCount := len(p.recentGames)
|
|
p.recentGames = games
|
|
|
|
if oldCount != len(p.recentGames) {
|
|
p.hub.Broadcast("steam_games_update", map[string]interface{}{
|
|
"games": len(p.recentGames),
|
|
})
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (p *SteamPlugin) updateTopGames(steamID string) error {
|
|
url := fmt.Sprintf(
|
|
"http://api.steampowered.com/IPlayerService/GetOwnedGames/v0001/?key=%s&steamid=%s&format=json&include_appinfo=1&include_played_free_games=1",
|
|
p.apiKey, steamID,
|
|
)
|
|
|
|
client := &http.Client{Timeout: 15 * time.Second}
|
|
req, err := http.NewRequest("GET", url, nil)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create request: %v", err)
|
|
}
|
|
|
|
req.Header.Set("User-Agent", "AboutPage/1.0 (aboutpage.akarpov.ru)")
|
|
req.Header.Set("Accept", "application/json")
|
|
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to fetch Steam data: %v", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
return fmt.Errorf("steam API returned status %d", resp.StatusCode)
|
|
}
|
|
|
|
contentType := resp.Header.Get("Content-Type")
|
|
if !strings.Contains(contentType, "application/json") {
|
|
return fmt.Errorf("steam API returned non-JSON content: %s", contentType)
|
|
}
|
|
|
|
var response SteamOwnedGamesResponse
|
|
decoder := json.NewDecoder(resp.Body)
|
|
if err := decoder.Decode(&response); err != nil {
|
|
return fmt.Errorf("failed to decode Steam data: %v", err)
|
|
}
|
|
|
|
games := response.Response.Games
|
|
|
|
sort.Slice(games, func(i, j int) bool {
|
|
return games[i].PlaytimeAll > games[j].PlaytimeAll
|
|
})
|
|
|
|
if len(games) > 10 {
|
|
games = games[:10]
|
|
}
|
|
|
|
if err := p.updateRecentPlaytime(steamID, games); err != nil {
|
|
fmt.Printf("Warning: Failed to update recent playtime: %v\n", err)
|
|
}
|
|
|
|
p.topGames = games
|
|
return nil
|
|
}
|
|
|
|
func (p *SteamPlugin) updateRecentPlaytime(steamID string, games []SteamGame) error {
|
|
url := fmt.Sprintf(
|
|
"http://api.steampowered.com/IPlayerService/GetRecentlyPlayedGames/v0001/?key=%s&steamid=%s&format=json&count=100",
|
|
p.apiKey, steamID,
|
|
)
|
|
|
|
client := &http.Client{Timeout: 15 * time.Second}
|
|
req, err := http.NewRequest("GET", url, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
req.Header.Set("User-Agent", "AboutPage/1.0 (aboutpage.akarpov.ru)")
|
|
req.Header.Set("Accept", "application/json")
|
|
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
return fmt.Errorf("steam API returned status %d", resp.StatusCode)
|
|
}
|
|
|
|
var response SteamResponse
|
|
if err := json.NewDecoder(resp.Body).Decode(&response); err != nil {
|
|
return err
|
|
}
|
|
|
|
recentMap := make(map[int]int)
|
|
for _, game := range response.Response.Games {
|
|
recentMap[game.AppID] = game.Playtime2w
|
|
}
|
|
|
|
for i := range games {
|
|
if recent, ok := recentMap[games[i].AppID]; ok {
|
|
games[i].Playtime2w = recent
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (p *SteamPlugin) GetSettings() map[string]interface{} {
|
|
config := p.storage.GetPluginConfig(p.Name())
|
|
return config.Settings
|
|
}
|
|
|
|
func (p *SteamPlugin) SetSettings(settings map[string]interface{}) error {
|
|
config := p.storage.GetPluginConfig(p.Name())
|
|
config.Settings = settings
|
|
return p.storage.SetPluginConfig(p.Name(), config)
|
|
}
|
|
|
|
func (p *SteamPlugin) RenderText(ctx context.Context) (string, error) {
|
|
if p.apiKey == "" {
|
|
return "Gaming: Steam API key not configured", nil
|
|
}
|
|
|
|
if p.playerSummary == nil {
|
|
return "Gaming: No Steam data available", nil
|
|
}
|
|
|
|
status := "Offline"
|
|
currentGame := ""
|
|
|
|
if p.playerSummary.GameExtraInfo != "" {
|
|
status = "Playing"
|
|
currentGame = fmt.Sprintf(" - %s", p.playerSummary.GameExtraInfo)
|
|
} else {
|
|
switch p.playerSummary.PersonaState {
|
|
case 1:
|
|
status = "Online"
|
|
case 2:
|
|
status = "Busy"
|
|
case 3:
|
|
status = "Away"
|
|
}
|
|
}
|
|
|
|
recentGamesCount := len(p.recentGames)
|
|
gamesInfo := ""
|
|
if recentGamesCount > 0 {
|
|
gamesInfo = fmt.Sprintf(", %d recent games", recentGamesCount)
|
|
}
|
|
|
|
return fmt.Sprintf("Gaming: %s%s%s", status, currentGame, gamesInfo), nil
|
|
}
|
|
|
|
func (p *SteamPlugin) getConfigValue(settings map[string]interface{}, key, defaultValue string) string {
|
|
keys := strings.Split(key, ".")
|
|
current := settings
|
|
|
|
for i, k := range keys {
|
|
if i == len(keys)-1 {
|
|
if value, ok := current[k].(string); ok {
|
|
return value
|
|
}
|
|
return defaultValue
|
|
}
|
|
if next, ok := current[k].(map[string]interface{}); ok {
|
|
current = next
|
|
} else {
|
|
return defaultValue
|
|
}
|
|
}
|
|
|
|
return defaultValue
|
|
}
|
|
|
|
func (p *SteamPlugin) getConfigBool(settings map[string]interface{}, key string, defaultValue bool) bool {
|
|
keys := strings.Split(key, ".")
|
|
current := settings
|
|
|
|
for i, k := range keys {
|
|
if i == len(keys)-1 {
|
|
if value, ok := current[k].(bool); ok {
|
|
return value
|
|
}
|
|
return defaultValue
|
|
}
|
|
if next, ok := current[k].(map[string]interface{}); ok {
|
|
current = next
|
|
} else {
|
|
return defaultValue
|
|
}
|
|
}
|
|
|
|
return defaultValue
|
|
}
|
|
|
|
func (p *SteamPlugin) GetMetrics() map[string]interface{} {
|
|
metrics := map[string]interface{}{
|
|
"is_online": 0,
|
|
"is_playing": 0,
|
|
"recent_games_count": len(p.recentGames),
|
|
"total_playtime_hours": 0.0,
|
|
}
|
|
|
|
if p.playerSummary != nil {
|
|
if p.playerSummary.PersonaState == 1 {
|
|
metrics["is_online"] = 1
|
|
}
|
|
if p.playerSummary.GameExtraInfo != "" {
|
|
metrics["is_playing"] = 1
|
|
}
|
|
}
|
|
|
|
var totalPlaytime int
|
|
for _, g := range p.topGames {
|
|
totalPlaytime += g.PlaytimeAll
|
|
}
|
|
metrics["total_playtime_hours"] = float64(totalPlaytime) / 60.0
|
|
|
|
return metrics
|
|
}
|