├── LICENSE ├── README.md ├── apps.go ├── bans.go ├── classinfo.go ├── classinfo_mock_test.go ├── classinfo_test.go ├── dota ├── dota.go ├── heroes.go ├── history.go └── match.go ├── friends.go ├── id.go ├── items.go ├── items_mock_test.go ├── items_test.go ├── prices.go ├── prices_mock_test.go ├── profiles.go ├── schema.go ├── servers.go ├── steamapi.go ├── tradeoffer.go ├── tradeoffer_mock_test.go └── tradeoffer_test.go /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Philipp Schröer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Steam API in Go 2 | 3 | This package provides a simple API to [the Steam API as documented in the TF2 Wiki](http://wiki.teamfortress.com/wiki/WebAPI). 4 | 5 | [You can find the documentation here.](http://godoc.org/github.com/Philipp15b/go-steamapi) 6 | 7 | It is licensed under the MIT license. 8 | -------------------------------------------------------------------------------- /apps.go: -------------------------------------------------------------------------------- 1 | package steamapi 2 | 3 | import ( 4 | "errors" 5 | "net/url" 6 | "strconv" 7 | ) 8 | 9 | type SteamApp struct { 10 | AppId uint64 11 | Name string 12 | } 13 | 14 | type appListJson struct { 15 | Applist struct { 16 | Apps []SteamApp 17 | } 18 | } 19 | 20 | type upToDateCheckJson struct { 21 | Response struct { 22 | Success bool 23 | UpToDate bool `json:"up_to_date"` 24 | Listable bool `json:"version_is_listable"` 25 | CurrentVersion uint `json:"required_version,omitempty"` 26 | Message string `json:"omitempty"` 27 | Error string `json:"omitempty"` 28 | } 29 | } 30 | 31 | func GetAppList() ([]SteamApp, error) { 32 | getAppList := NewSteamMethod("ISteamApps", "GetAppList", 2) 33 | 34 | var resp appListJson 35 | err := getAppList.Request(nil, &resp) 36 | if err != nil { 37 | return nil, err 38 | } 39 | return resp.Applist.Apps, nil 40 | } 41 | 42 | func IsAppUpToDate(app int, version uint) (bool, error) { 43 | upToDateCheck := NewSteamMethod("ISteamApps", "UpToDateCheck", 1) 44 | 45 | vals := url.Values{} 46 | vals.Add("appid", strconv.Itoa(app)) 47 | vals.Add("version", strconv.FormatUint(uint64(version), 10)) 48 | 49 | var resp upToDateCheckJson 50 | err := upToDateCheck.Request(vals, &resp) 51 | if err != nil { 52 | return false, err 53 | } 54 | if !resp.Response.Success { 55 | return false, errors.New(resp.Response.Error) 56 | } 57 | return resp.Response.UpToDate, nil 58 | } 59 | 60 | func GetCurrentAppVersion(app int) (uint, error) { 61 | upToDateCheck := NewSteamMethod("ISteamApps", "UpToDateCheck", 1) 62 | 63 | vals := url.Values{} 64 | vals.Add("appid", strconv.Itoa(app)) 65 | vals.Add("version", "1") 66 | 67 | var resp upToDateCheckJson 68 | err := upToDateCheck.Request(vals, &resp) 69 | if err != nil { 70 | return 0, err 71 | } 72 | if !resp.Response.Success { 73 | return 0, errors.New(resp.Response.Error) 74 | } 75 | return resp.Response.CurrentVersion, nil 76 | } 77 | -------------------------------------------------------------------------------- /bans.go: -------------------------------------------------------------------------------- 1 | package steamapi 2 | 3 | import ( 4 | "net/url" 5 | "strconv" 6 | "strings" 7 | ) 8 | 9 | type playerBansJSON struct { 10 | Players []PlayerBan 11 | } 12 | 13 | // PlayerBan contains all ban status for community, VAC and economy 14 | type PlayerBan struct { 15 | SteamID uint64 `json:"SteamId,string"` 16 | CommunityBanned bool 17 | VACBanned bool 18 | EconomyBan string 19 | NumberOfVACBans uint 20 | DaysSinceLastBan uint 21 | NumberOfGameBans uint 22 | } 23 | 24 | // GetPlayerBans takes a list of steamIDs and returns PlayerBan slice 25 | func GetPlayerBans(steamIDs []uint64, apiKey string) ([]PlayerBan, error) { 26 | var getPlayerBans = NewSteamMethod("ISteamUser", "GetPlayerBans", 1) 27 | strSteamIDs := make([]string, len(steamIDs)) 28 | for _, id := range steamIDs { 29 | strSteamIDs = append(strSteamIDs, strconv.FormatUint(id, 10)) 30 | } 31 | 32 | data := url.Values{} 33 | data.Add("key", apiKey) 34 | data.Add("steamids", strings.Join(strSteamIDs, ",")) 35 | 36 | var resp playerBansJSON 37 | err := getPlayerBans.Request(data, &resp) 38 | if err != nil { 39 | return nil, err 40 | } 41 | 42 | return resp.Players, nil 43 | } 44 | -------------------------------------------------------------------------------- /classinfo.go: -------------------------------------------------------------------------------- 1 | package steamapi 2 | 3 | import ( 4 | "encoding/json" 5 | "net/url" 6 | "strconv" 7 | ) 8 | 9 | // ClassInfo is the details of the specific classid 10 | type classInfoJSON struct { 11 | Result map[string]json.RawMessage `json:"result"` 12 | } 13 | 14 | // Info is the details about the class info 15 | type Info struct { 16 | ClassID string `json:"classid"` 17 | IconURL string `json:"icon_url"` 18 | MarketHashName string `json:"market_hash_name"` 19 | Tradable string 20 | Marketable string 21 | } 22 | 23 | // GetAssetClassInfo returns asset details 24 | func GetAssetClassInfo(appID, classID uint64, language, apiKey string) (*Info, error) { 25 | 26 | var getAssetClassInfo = NewSteamMethod("ISteamEconomy", "GetAssetClassInfo", 1) 27 | 28 | vals := url.Values{} 29 | vals.Add("key", apiKey) 30 | vals.Add("appid", strconv.FormatUint(appID, 10)) 31 | vals.Add("language", language) 32 | vals.Add("class_count", "1") 33 | vals.Add("classid0", strconv.FormatUint(classID, 10)) 34 | 35 | var resp classInfoJSON 36 | err := getAssetClassInfo.Request(vals, &resp) 37 | if err != nil { 38 | return nil, err 39 | } 40 | 41 | var info Info 42 | for _, object := range resp.Result { 43 | err := json.Unmarshal(object, &info) 44 | if err != nil { 45 | continue 46 | } 47 | } 48 | 49 | return &info, nil 50 | } 51 | -------------------------------------------------------------------------------- /classinfo_mock_test.go: -------------------------------------------------------------------------------- 1 | package steamapi 2 | 3 | // GetMockOKGetAssetClassInfo steam API gives class_id, market_hash_name... 4 | // http://api.steampowered.com/ISteamEconomy/GetAssetClassInfo/v0001?key=XXX&format=json&language=en&appid=XXX&class_count=2&classid0=123456789 5 | func GetMockOKGetAssetClassInfo() string { 6 | return ` 7 | { 8 | "result": { 9 | "123456789": { 10 | "icon_url": "W_I_5GLm4wPcv9jJQ7z7tz_l_0sEIYUhRfbF4arNQkgGQGKd3kMuVpMgCwRZrhSfeEqb1qNMeO7lDgsvJYj2VkHyNb-A-UWkTe9Xc8Rgd2sbj9_ugkgSUXffBrFHXNQrvM7K0Ay7XgXDLWdun9gFgPqagJWGCPPO6UywK3ID03w", 11 | "icon_url_large": "W_I_5GLm4wPcv9jJQ7z7tz_l_0sEIYUhRfbF4arNQkgGQGKd3kMuVpMgCwRZrhSfeEqb1qNMeO7lDgsvJYj2VkHyNb-A-UWkTe9Xc8RgBmMYzo69mB0TByTSDb8RDYMpupzD1APoW1HCcWFun4wGivufgpfQUqHSrESyJVJuk7o-hPMuyZ4", 12 | "icon_drag_url": "", 13 | "name": "Ye Olde Pipe", 14 | "market_hash_name": "Ye Olde Pipe", 15 | "market_name": "Ye Olde Pipe", 16 | "name_color": "D2D2D2", 17 | "background_color": "", 18 | "type": "Common Pipe", 19 | "tradable": "1", 20 | "marketable": "1", 21 | "commodity": "0", 22 | "market_tradable_restriction": "7", 23 | "market_marketable_restriction": "7", 24 | "fraudwarnings": "", 25 | "descriptions": { 26 | "0": { 27 | "type": "html", 28 | "value": "Used By: Kunkka", 29 | "app_data": "" 30 | }, 31 | "1": { 32 | "type": "html", 33 | "value": " ", 34 | "app_data": "" 35 | }, 36 | "2": { 37 | "type": "html", 38 | "value": "Armaments of Leviathan", 39 | "color": "9da1a9", 40 | "app_data": { 41 | "def_index": "20267", 42 | "is_itemset_name": "1" 43 | } 44 | }, 45 | "3": { 46 | "type": "html", 47 | "value": "Admiral's Foraged Cap", 48 | "color": "6c7075", 49 | "app_data": { 50 | "def_index": "5463" 51 | } 52 | }, 53 | "4": { 54 | "type": "html", 55 | "value": "Admiral's Stash", 56 | "color": "6c7075", 57 | "app_data": { 58 | "def_index": "5464" 59 | } 60 | }, 61 | "5": { 62 | "type": "html", 63 | "value": "Claddish Gauntlets", 64 | "color": "6c7075", 65 | "app_data": { 66 | "def_index": "5465" 67 | } 68 | }, 69 | "6": { 70 | "type": "html", 71 | "value": "Claddish Guard", 72 | "color": "6c7075", 73 | "app_data": { 74 | "def_index": "5466" 75 | } 76 | }, 77 | "7": { 78 | "type": "html", 79 | "value": "Claddish Hightops", 80 | "color": "6c7075", 81 | "app_data": { 82 | "def_index": "5467" 83 | } 84 | }, 85 | "8": { 86 | "type": "html", 87 | "value": "Neptunian Sabre", 88 | "color": "6c7075", 89 | "app_data": { 90 | "def_index": "5468" 91 | } 92 | }, 93 | "9": { 94 | "type": "html", 95 | "value": "Admiral's Salty Shawl", 96 | "color": "6c7075", 97 | "app_data": { 98 | "def_index": "5469" 99 | } 100 | }, 101 | "10": { 102 | "type": "html", 103 | "value": "Ye Olde Pipe", 104 | "color": "6c7075", 105 | "app_data": { 106 | "def_index": "5470" 107 | } 108 | }, 109 | "11": { 110 | "type": "html", 111 | "value": "An old pipe Kunkka plucked from a tidepool after being shipwrecked, the old seadog claims it brings him fortune and good luck, and thus never ever takes it from his scurvy-ridden mouth. \r\n\t", 112 | "app_data": "" 113 | } 114 | }, 115 | "tags": { 116 | "0": { 117 | "internal_name": "unique", 118 | "name": "Standard", 119 | "category": "Quality", 120 | "color": "D2D2D2", 121 | "category_name": "Quality" 122 | }, 123 | "1": { 124 | "internal_name": "Rarity_Common", 125 | "name": "Common", 126 | "category": "Rarity", 127 | "color": "b0c3d9", 128 | "category_name": "Rarity" 129 | }, 130 | "2": { 131 | "internal_name": "wearable", 132 | "name": "Wearable", 133 | "category": "Type", 134 | "category_name": "Type" 135 | }, 136 | "3": { 137 | "internal_name": "neck", 138 | "name": "Neck", 139 | "category": "Slot", 140 | "category_name": "Slot" 141 | }, 142 | "4": { 143 | "internal_name": "npc_dota_hero_kunkka", 144 | "name": "Kunkka", 145 | "category": "Hero", 146 | "category_name": "Hero" 147 | } 148 | }, 149 | "classid": "123456789" 150 | }, 151 | "success": true 152 | } 153 | } 154 | ` 155 | } 156 | -------------------------------------------------------------------------------- /classinfo_test.go: -------------------------------------------------------------------------------- 1 | package steamapi 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "net/http/httptest" 7 | "testing" 8 | ) 9 | 10 | func TestMockOkGetAssetClassInfo(t *testing.T) { 11 | 12 | ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 13 | w.Header().Set("Content-Type", "application/json") 14 | w.WriteHeader(http.StatusOK) 15 | fmt.Fprintf(w, GetMockOKGetAssetClassInfo()) 16 | })) 17 | defer ts.Close() 18 | 19 | expectedInfo := Info{ 20 | ClassID: "123456789", 21 | IconURL: "W_I_5GLm4wPcv9jJQ7z7tz_l_0sEIYUhRfbF4arNQkgGQGKd3kMuVpMgCwRZrhSfeEqb1qNMeO7lDgsvJYj2VkHyNb-A-UWkTe9Xc8Rgd2sbj9_ugkgSUXffBrFHXNQrvM7K0Ay7XgXDLWdun9gFgPqagJWGCPPO6UywK3ID03w", 22 | MarketHashName: "Ye Olde Pipe", 23 | Tradable: "1", 24 | Marketable: "1", 25 | } 26 | 27 | appID := uint64(2) 28 | classID := uint64(1234) 29 | language := "en" 30 | apiKey := "123" 31 | 32 | BaseSteamAPIURL = ts.URL 33 | infos, err := GetAssetClassInfo(appID, classID, language, apiKey) 34 | 35 | if err != nil { 36 | t.Errorf("GetAssetClassInfo failure: %v", err) 37 | } 38 | 39 | if *infos != expectedInfo { 40 | t.Errorf("GetAssetClassInfo(%v, %v, %v, %v, %v) == %#v, expected %#v", 41 | ts.URL, appID, classID, language, apiKey, infos, expectedInfo) 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /dota/dota.go: -------------------------------------------------------------------------------- 1 | package dota 2 | 3 | type DotaGameMode int 4 | 5 | const ( 6 | AnyMode = -1 7 | 8 | AllPick DotaGameMode = iota 9 | SingleDraft 10 | AllRandom 11 | RandomDraft 12 | CaptainsDraft 13 | CaptainsMode 14 | DeathMode 15 | Diretide 16 | ReverseCaptainsMode 17 | TheGreeviling 18 | TutorialGame 19 | MidOnly 20 | LeastPlayed 21 | NewPlayerPool 22 | CompendiumMatchmaking 23 | ) 24 | 25 | type DotaSkill uint 26 | 27 | const ( 28 | AnySkill DotaSkill = iota 29 | Normal 30 | High 31 | VeryHigh 32 | ) 33 | 34 | type DotaLeaverStatus uint 35 | 36 | const ( 37 | None DotaLeaverStatus = iota 38 | Disconnected 39 | DisconnectedTooLong 40 | Abandoned 41 | AFK 42 | NeverConnected 43 | NeverConnectedTooLong 44 | ) 45 | 46 | type DotaLobbyType int 47 | 48 | const ( 49 | Invalid DotaLobbyType = -1 50 | 51 | PublicMatchMaking DotaLobbyType = iota 52 | Practice 53 | Tournament 54 | Tutorial 55 | Coop 56 | TeamMatch 57 | SoloQueue 58 | ) 59 | 60 | type DotaPlayerSlot uint8 61 | 62 | func (d DotaPlayerSlot) IsDire() bool { 63 | if d&(1<<7) > 0 { 64 | return true 65 | } 66 | return false 67 | } 68 | 69 | func (d DotaPlayerSlot) GetPosition() (p uint) { 70 | p = uint(d & ((1 << 7) - 1)) 71 | return 72 | } 73 | 74 | type DotaTeam uint 75 | 76 | const ( 77 | Radiant DotaTeam = iota 78 | Dire 79 | ) 80 | 81 | // TODO: add methods that read information from bits 82 | type DotaTowerStatus uint16 83 | 84 | // TODO: add methods that read information from bits 85 | type DotaBarracksStatus uint16 86 | -------------------------------------------------------------------------------- /dota/heroes.go: -------------------------------------------------------------------------------- 1 | package dota 2 | 3 | type DotaHero uint 4 | 5 | // TODO: Add method to get name of hero (GetHero API call) 6 | -------------------------------------------------------------------------------- /dota/history.go: -------------------------------------------------------------------------------- 1 | package dota 2 | 3 | import ( 4 | "github.com/fasmat/go-steamapi" 5 | "net/url" 6 | "strconv" 7 | "time" 8 | ) 9 | 10 | type MatchFilter struct { 11 | PlayerName string 12 | HeroId DotaHero 13 | Skill DotaSkill 14 | DateMin time.Time 15 | DateMax time.Time 16 | MinPlayers uint 17 | AccountId uint32 18 | LeagueId uint32 19 | StartAtMatchId uint64 20 | MatchesRequested uint 21 | } 22 | 23 | type matchHistoryJson struct { 24 | Result HistoryResult 25 | } 26 | 27 | type HistoryResult struct { 28 | Status uint 29 | StatusDetail string `json:",omitempty"` 30 | NumResults uint `json:"num_results"` 31 | TotalResults uint `json:"total_results"` 32 | ResultsRemaining uint `json:"results_remaining"` 33 | Matches []Match 34 | } 35 | 36 | type Match struct { 37 | MatchId uint64 `json:"match_id"` 38 | MatchSequenceNo uint `json:"match_seq_num"` 39 | MatchStart uint `json:"start_time"` 40 | LobbyType DotaLobbyType `json:"lobby_type"` 41 | Players []PlayerSummary 42 | } 43 | 44 | type PlayerSummary struct { 45 | AccountId uint32 `json:"account_id"` 46 | PlayerSlot DotaPlayerSlot `json:"player_slot"` 47 | HeroId uint `json:"hero_id"` 48 | } 49 | 50 | func GetMatchHistory(filter MatchFilter, gameMode DotaGameMode, app int, apiKey string) ([]Match, error) { 51 | getMatchHistory := steamapi.NewSteamMethod("IDOTA2Match_"+strconv.Itoa(app), "GetMatchHistory", 1) 52 | 53 | vals := url.Values{} 54 | vals.Add("key", apiKey) 55 | 56 | // Add vals to url if any filter value was set 57 | if gameMode != AnyMode { 58 | vals.Add("game_mode", strconv.FormatUint(uint64(gameMode), 10)) 59 | } 60 | if len(filter.PlayerName) > 0 { 61 | vals.Add("player_name", filter.PlayerName) 62 | } 63 | if filter.HeroId != 0 { 64 | vals.Add("hero_id", strconv.FormatUint(uint64(filter.HeroId), 10)) 65 | } 66 | if filter.Skill != 0 { 67 | vals.Add("skill", strconv.FormatUint(uint64(filter.Skill), 10)) 68 | } 69 | if !filter.DateMin.IsZero() { 70 | vals.Add("date_min", strconv.FormatInt(filter.DateMin.Unix(), 10)) 71 | } 72 | if !filter.DateMax.IsZero() { 73 | vals.Add("date_min", strconv.FormatInt(filter.DateMax.Unix(), 10)) 74 | } 75 | if filter.MinPlayers != 0 { 76 | vals.Add("min_players", strconv.FormatUint(uint64(filter.MinPlayers), 10)) 77 | } 78 | if filter.AccountId != 0 { 79 | vals.Add("account_id", strconv.FormatUint(uint64(filter.AccountId), 10)) 80 | } 81 | if filter.LeagueId != 0 { 82 | vals.Add("league_id", strconv.FormatUint(uint64(filter.LeagueId), 10)) 83 | } 84 | if filter.StartAtMatchId != 0 { 85 | vals.Add("start_at_match_id", strconv.FormatUint(filter.StartAtMatchId, 10)) 86 | } 87 | if filter.MatchesRequested > 0 { 88 | vals.Add("matches_requested", strconv.FormatUint(uint64(filter.MatchesRequested), 10)) 89 | } 90 | 91 | var resp matchHistoryJson 92 | err := getMatchHistory.Request(vals, &resp) 93 | if err != nil { 94 | return nil, err 95 | } 96 | 97 | m := make([]Match, 0) 98 | m = append(m, resp.Result.Matches...) 99 | if filter.MatchesRequested > 0 { 100 | filter.MatchesRequested -= resp.Result.NumResults 101 | if filter.MatchesRequested == 0 { 102 | // Fetched as many as requested but less then available 103 | return m, nil 104 | } 105 | } 106 | 107 | if resp.Result.ResultsRemaining == 0 { 108 | // Fetched less as requested but all available 109 | return m, nil 110 | } 111 | 112 | // Fetched less as requested and there are still available 113 | // Start one match earlier than the earliest already available 114 | filter.StartAtMatchId = m[len(m)-1].MatchId - 1 115 | m2, err := GetMatchHistory(filter, gameMode, app, apiKey) 116 | if err != nil { 117 | return nil, err 118 | } 119 | m = append(m, m2...) 120 | return m, nil 121 | } 122 | -------------------------------------------------------------------------------- /dota/match.go: -------------------------------------------------------------------------------- 1 | package dota 2 | 3 | import ( 4 | "github.com/fasmat/go-steamapi" 5 | "net/url" 6 | "strconv" 7 | ) 8 | 9 | type matchResultJson struct { 10 | Result MatchResult 11 | } 12 | 13 | type MatchResult struct { 14 | Players []Player 15 | Season uint `json:"item_0,omitempty"` 16 | RadiantWin bool `json:"radiant_win"` 17 | Duration uint 18 | MatchStart uint `json:"start_time"` 19 | MatchId uint64 `json:"match_id"` 20 | MatchSequenceNo uint `json:"match_seq_num"` 21 | TowerStatusRadiant DotaTowerStatus `json:"tower_status_radiant"` 22 | TowerStatusDire DotaTowerStatus `json:"tower_status_dire"` 23 | BarracksStatusRadiant DotaBarracksStatus `json:"barracks_status_radiant"` 24 | BarracksStatusDire DotaBarracksStatus `json:"barracks_status_dire"` 25 | Cluster uint 26 | FirstBloodTime int `json:"first_blood_time"` 27 | LobbyType DotaLobbyType `json:"lobby_type"` 28 | HumanPlayers uint `json:"human_players"` 29 | LeagueId uint 30 | PositiveVotes uint `json:"positive_votes"` 31 | NegativeVotes uint `json:"negative_votes"` 32 | GameMode DotaGameMode `json:"game_mode"` 33 | PicksBans []PickBan `json:"picks_bans,omitempty"` 34 | } 35 | 36 | type Player struct { 37 | AccountId uint32 `json:"account_id"` 38 | PlayerSlot DotaPlayerSlot `json:"player_slot"` 39 | HeroId uint `json:"hero_id"` 40 | Item0 uint `json:"item_0"` 41 | Item1 uint `json:"item_1"` 42 | Item2 uint `json:"item_2"` 43 | Item3 uint `json:"item_3"` 44 | Item4 uint `json:"item_4"` 45 | Item5 uint `json:"item_5"` 46 | Kills uint 47 | Deaths uint 48 | Assists uint 49 | LeaverStatus DotaLeaverStatus `json:"leaver_status"` 50 | GoldRemaining uint `json:"gold"` 51 | LastHits uint `json:"last_hits"` 52 | Denies uint `json:"denies"` 53 | GPM uint `json:"gold_per_min"` 54 | XPM uint `json:"xp_per_min"` 55 | GoldSpent uint `json:"gold_spent"` 56 | HeroDamage uint `json:"hero_damage"` 57 | TowerDamage uint `json:"tower_damage"` 58 | HeroHealing uint `json:"hero_healing"` 59 | Level uint 60 | Abilities []Ability `json:"ability_upgrades"` 61 | Units []Unit `json:"additional_units,omitempty"` 62 | } 63 | 64 | type Ability struct { 65 | Id uint `json:"ability"` 66 | TimeUpgraded int `json:"time"` 67 | Level uint 68 | } 69 | 70 | type Unit struct { 71 | Name string `json:"unitname"` 72 | Item0 uint `json:"item_0"` 73 | Item1 uint `json:"item_1"` 74 | Item2 uint `json:"item_2"` 75 | Item3 uint `json:"item_3"` 76 | Item4 uint `json:"item_4"` 77 | Item5 uint `json:"item_5"` 78 | } 79 | 80 | type PickBan struct { 81 | IsPick bool `json:"is_pick"` 82 | HeroId uint `json:"hero_id"` 83 | Team DotaTeam 84 | Order uint 85 | } 86 | 87 | // Fetches statistics of a specific Match. 88 | func GetMatchDetails(matchid uint64, app int, apiKey string) (*MatchResult, error) { 89 | getMatchDetails := steamapi.NewSteamMethod("IDOTA2Match_"+strconv.Itoa(app), "GetMatchDetails", 1) 90 | 91 | vals := url.Values{} 92 | vals.Add("key", apiKey) 93 | vals.Add("match_id", strconv.FormatUint(matchid, 10)) 94 | 95 | var resp matchResultJson 96 | err := getMatchDetails.Request(vals, &resp) 97 | if err != nil { 98 | return nil, err 99 | } 100 | return &resp.Result, nil 101 | } 102 | -------------------------------------------------------------------------------- /friends.go: -------------------------------------------------------------------------------- 1 | package steamapi 2 | 3 | import ( 4 | "net/url" 5 | "strconv" 6 | ) 7 | 8 | // Relationship is a type of relationship 9 | type Relationship string 10 | 11 | const ( 12 | // All is a type of relationship 13 | All Relationship = "all" 14 | // Friend is a type of relationship 15 | Friend Relationship = "friend" 16 | ) 17 | 18 | // SteamFriend is a relationship between two steam users 19 | type SteamFriend struct { 20 | SteamID uint64 `json:",string"` 21 | Relationship Relationship 22 | FriendSince int64 `json:"friend_since"` 23 | } 24 | 25 | type playerFriendsListJSON struct { 26 | Friendslist *struct { 27 | Friends []SteamFriend 28 | } 29 | } 30 | 31 | // GetFriendsList Fetches the friends of the given steam id and returns the result. 32 | // 33 | // It returns nil if the profile is private or if there were no friends 34 | // found for the given relationship. In either one of both cases, no error 35 | // is returned. 36 | func GetFriendsList(steamID uint64, filter Relationship, apiKey string) ([]SteamFriend, error) { 37 | 38 | var getFriendsList = NewSteamMethod("ISteamUser", "GetFriendList", 1) 39 | 40 | data := url.Values{} 41 | data.Add("key", apiKey) 42 | data.Add("steamid", strconv.FormatUint(steamID, 10)) 43 | data.Add("relationship", string(filter)) 44 | 45 | var resp playerFriendsListJSON 46 | err := getFriendsList.Request(data, &resp) 47 | if err != nil { 48 | return nil, err 49 | } 50 | 51 | if resp.Friendslist == nil { 52 | return nil, nil 53 | } 54 | return resp.Friendslist.Friends, nil 55 | } 56 | -------------------------------------------------------------------------------- /id.go: -------------------------------------------------------------------------------- 1 | package steamapi 2 | 3 | import ( 4 | "errors" 5 | "regexp" 6 | "strconv" 7 | "strings" 8 | ) 9 | 10 | var ( 11 | ErrInvalidId = errors.New("Invalid Steam ID") 12 | ) 13 | 14 | type steamId struct { 15 | X uint32 16 | Y uint32 17 | Z uint32 18 | } 19 | 20 | func NewIdFrom32bit(i uint32) (id steamId) { 21 | id.Y = i % 2 22 | id.Z = i / 2 23 | return 24 | } 25 | 26 | func NewIdFrom64bit(i uint64) (id steamId) { 27 | i -= 0x0110000100000000 28 | id = NewIdFrom32bit(uint32(i)) 29 | return 30 | } 31 | 32 | func NewIdFromVanityUrl(vanityUrl, apiKey string) (id steamId, err error) { 33 | resp, err := ResolveVanityURL(vanityUrl, apiKey) 34 | if err != nil { 35 | return 36 | } 37 | 38 | id = NewIdFrom64bit(resp.SteamID) 39 | return 40 | } 41 | 42 | func NewIdFromString(s string) (id steamId, err error) { 43 | validid := regexp.MustCompile("STEAM_\\d:\\d:\\d{1,}") 44 | 45 | if !validid.MatchString(s) { 46 | err = ErrInvalidId 47 | return 48 | } 49 | 50 | tmp := strings.Split(s, ":") 51 | tmpX, _ := strconv.ParseUint(strings.Split(tmp[0], "_")[1], 10, 32) 52 | tmpY, _ := strconv.ParseUint(tmp[1], 10, 32) 53 | tmpZ, _ := strconv.ParseUint(tmp[2], 10, 32) 54 | 55 | id.X = uint32(tmpX) 56 | id.Y = uint32(tmpY) 57 | id.Z = uint32(tmpZ) 58 | return 59 | } 60 | 61 | func (id steamId) String() (s string) { 62 | s = "STEAM_" 63 | s += strconv.FormatUint(uint64(id.X), 10) + ":" 64 | s += strconv.FormatUint(uint64(id.Y), 10) + ":" 65 | s += strconv.FormatUint(uint64(id.Z), 10) 66 | return 67 | } 68 | 69 | func (id steamId) As32Bit() (i uint32) { 70 | i = id.Z*2 + id.Y 71 | return 72 | } 73 | 74 | func (id steamId) As64Bit() (i uint64) { 75 | i = uint64(id.As32Bit()) + 0x0110000100000000 76 | return 77 | } 78 | -------------------------------------------------------------------------------- /items.go: -------------------------------------------------------------------------------- 1 | package steamapi 2 | 3 | import ( 4 | "fmt" 5 | "net/url" 6 | "strconv" 7 | ) 8 | 9 | type playerItemsJSON struct { 10 | Result Inventory 11 | } 12 | 13 | // Inventory is the inventory of the user as represented in steam 14 | type Inventory struct { 15 | Status uint 16 | BackpackSlots int `json:"num_backpack_slots"` 17 | Items []Item 18 | } 19 | 20 | // Item in an inventory 21 | type Item struct { 22 | ID uint64 23 | OriginalID uint64 `json:"original_id"` 24 | Defindex uint32 25 | Level int 26 | Quantity int 27 | Origin int 28 | Untradeable bool `json:"flag_cannot_trade,omitempty"` 29 | Uncraftable bool `json:"flag_cannot_craft,omitempty"` 30 | InventoryToken uint32 `json:"inventory"` 31 | Quality int 32 | CustomName string `json:"custom_name,omitempty"` 33 | CustomDescription string `json:"custom_description,omitempty"` 34 | Attributes []Attribute `json:",omitempty"` 35 | Equipped []EquipInfo `json:",omitempty"` 36 | } 37 | 38 | // Position gets the position of the item in an inventory 39 | func (i *Item) Position() uint16 { 40 | return uint16(i.InventoryToken & 0xFFFF) 41 | } 42 | 43 | // Attribute is the attribute of an item 44 | type Attribute struct { 45 | Defindex uint32 46 | Value interface{} `json:"value"` 47 | FloatValue float64 `json:"float_value,omitempty"` 48 | AccountInfo *AccountInfo `json:",omitempty"` 49 | } 50 | 51 | // AccountInfo is id and name of user 52 | type AccountInfo struct { 53 | SteamID uint64 `json:",string"` 54 | PersonaName string 55 | } 56 | 57 | // EquipInfo class and slot of equipment 58 | type EquipInfo struct { 59 | Class int 60 | Slot int 61 | } 62 | 63 | // GetPlayerItems Fetches the player summaries for the given Steam Id. 64 | func GetPlayerItems(steamID uint64, appID uint64, apiKey string) (*Inventory, error) { 65 | 66 | getPlayerItems := NewSteamMethod("IEconItems_"+strconv.FormatUint(appID, 10), "GetPlayerItems", 1) 67 | 68 | vals := url.Values{} 69 | vals.Add("key", apiKey) 70 | vals.Add("steamid", strconv.FormatUint(steamID, 10)) 71 | 72 | var resp playerItemsJSON 73 | 74 | err := getPlayerItems.Request(vals, &resp) 75 | 76 | if err != nil { 77 | return nil, fmt.Errorf("steamapi GetPlayerItems: %v", err) 78 | } 79 | 80 | return &resp.Result, nil 81 | } 82 | -------------------------------------------------------------------------------- /items_mock_test.go: -------------------------------------------------------------------------------- 1 | package steamapi 2 | 3 | // GetMockOKGetPlayerItems steam API mock response gives you item_id, def_index... 4 | // https://api.steampowered.com/IEconItems_XXXX/GetPlayerItems/v1/?key=XXXXXX&format=json&steamid=XXXXX 5 | func GetMockOKGetPlayerItems() string { 6 | return ` 7 | { 8 | "result": { 9 | "status": 1, 10 | "num_backpack_slots": 840, 11 | "items": [ 12 | { 13 | "id": 1234567894, 14 | "original_id": 123456, 15 | "defindex": 5470, 16 | "level": 1, 17 | "quality": 4, 18 | "inventory": 19, 19 | "quantity": 1, 20 | "equipped": [ 21 | { 22 | "class": 23, 23 | "slot": 6 24 | } 25 | ] 26 | }, 27 | { 28 | "id": 1234567897, 29 | "original_id": 1234567897, 30 | "defindex": 5508, 31 | "level": 1, 32 | "quality": 4, 33 | "inventory": 25, 34 | "quantity": 1, 35 | "attributes": [ 36 | { 37 | "defindex": 8, 38 | "value": 1049511890, 39 | "float_value": 0.27789169549942017 40 | }, 41 | { 42 | "defindex": 147, 43 | "value": "models/weapons/stattrack.mdl" 44 | } 45 | ] 46 | } 47 | ] 48 | 49 | } 50 | } 51 | ` 52 | } 53 | -------------------------------------------------------------------------------- /items_test.go: -------------------------------------------------------------------------------- 1 | package steamapi 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "net/http" 7 | "net/http/httptest" 8 | "testing" 9 | ) 10 | 11 | func TestMockOkGetPlayerItems(t *testing.T) { 12 | 13 | ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 14 | w.Header().Set("Content-Type", "application/json") 15 | w.WriteHeader(http.StatusOK) 16 | fmt.Fprintf(w, GetMockOKGetPlayerItems()) 17 | })) 18 | defer ts.Close() 19 | BaseSteamAPIURL = ts.URL 20 | appID := uint64(2) 21 | steamID := uint64(1234) 22 | apiKey := "123" 23 | 24 | expectedPlayerItems := &Inventory{ 25 | Status: uint(1), 26 | BackpackSlots: 840, 27 | Items: []Item{ 28 | Item{ 29 | ID: uint64(1234567894), 30 | OriginalID: uint64(123456), 31 | Defindex: 5470, 32 | Level: 1, 33 | Quantity: 1, 34 | Origin: 0, 35 | Untradeable: false, 36 | Uncraftable: false, 37 | InventoryToken: uint32(19), 38 | Quality: 4, 39 | CustomName: "", 40 | CustomDescription: "", 41 | Attributes: []Attribute(nil), 42 | Equipped: []EquipInfo{ 43 | EquipInfo{ 44 | Class: 23, 45 | Slot: 6, 46 | }, 47 | }, 48 | }, 49 | Item{ 50 | ID: uint64(1234567897), 51 | OriginalID: uint64(1234567897), 52 | Defindex: 5508, 53 | Level: 1, 54 | Quantity: 1, 55 | Origin: 0, 56 | Untradeable: true, 57 | Uncraftable: false, 58 | InventoryToken: uint32(25), 59 | Quality: 4, 60 | CustomName: "", 61 | CustomDescription: "", 62 | Attributes: []Attribute{ 63 | Attribute{ 64 | Defindex: 8, 65 | Value: 1049511890, 66 | FloatValue: 0.27789169549942017, 67 | AccountInfo: (*AccountInfo)(nil), 68 | }, 69 | Attribute{ 70 | Defindex: 9, 71 | FloatValue: 0.2, 72 | AccountInfo: (*AccountInfo)(nil), 73 | }, 74 | }, 75 | }, 76 | }, 77 | } 78 | 79 | playerItems, err := GetPlayerItems(steamID, appID, apiKey) 80 | if err != nil { 81 | t.Errorf("GetPlayerItems failure: %v", err) 82 | return 83 | } 84 | 85 | // Is result marshallable? 86 | _, err = json.Marshal(playerItems) 87 | if err != nil { 88 | t.Errorf("GetPlayerItems result marshalling failure: %v", err) 89 | return 90 | } 91 | 92 | // Need a better test 93 | if playerItems.Status != expectedPlayerItems.Status || 94 | playerItems.BackpackSlots != expectedPlayerItems.BackpackSlots || 95 | len(playerItems.Items) != len(expectedPlayerItems.Items) || 96 | playerItems.Items[1].Attributes[0].FloatValue != 0.27789169549942017 { 97 | 98 | t.Errorf("GetPlayerItems(%v, %v, %v, %v) == %#v, expected %#v", 99 | ts.URL, steamID, appID, apiKey, playerItems, expectedPlayerItems) 100 | } 101 | 102 | } 103 | -------------------------------------------------------------------------------- /prices.go: -------------------------------------------------------------------------------- 1 | package steamapi 2 | 3 | import ( 4 | "errors" 5 | "net/url" 6 | "strconv" 7 | ) 8 | 9 | type storeJSON struct { 10 | Result struct { 11 | Success bool 12 | Assets []Asset 13 | } 14 | } 15 | 16 | // Asset is an item in the store. 17 | type Asset struct { 18 | Prices map[string]int 19 | Defindex int `json:"name,string"` 20 | Date string 21 | Tags []string 22 | TagIDs []int64 23 | } 24 | 25 | // HasTag return bool if the asset has a tag 26 | func (i *Asset) HasTag(tag string) bool { 27 | for _, t := range i.Tags { 28 | if t == tag { 29 | return true 30 | } 31 | } 32 | return false 33 | } 34 | 35 | // GetAssetPrices returns a list of assets with their prices 36 | func GetAssetPrices(appid uint64, language, currency, apiKey string) ([]Asset, error) { 37 | 38 | var getAssetPrices = NewSteamMethod("ISteamEconomy", "GetAssetPrices", 1) 39 | 40 | vals := url.Values{} 41 | vals.Add("key", apiKey) 42 | vals.Add("appid", strconv.FormatUint(appid, 10)) 43 | vals.Add("language", language) 44 | vals.Add("currency", currency) 45 | 46 | var resp storeJSON 47 | err := getAssetPrices.Request(vals, &resp) 48 | if err != nil { 49 | return nil, err 50 | } 51 | if !resp.Result.Success { 52 | return nil, errors.New("API call 'GetAssetPrices' did not succeed") 53 | } 54 | return resp.Result.Assets, nil 55 | } 56 | -------------------------------------------------------------------------------- /prices_mock_test.go: -------------------------------------------------------------------------------- 1 | package steamapi 2 | 3 | // GetMockOKGetAssetPrices gives def_index, class_id... 4 | // https://api.steampowered.com/ISteamEconomy/GetAssetPrices/v1/?key=XXXXX&format=json&appid=XXX¤cy=usd 5 | func GetMockOKGetAssetPrices() string { 6 | return ` 7 | { 8 | "result": { 9 | "success": true, 10 | "assets": [ 11 | { 12 | "prices": { 13 | "USD": 0 14 | }, 15 | "name": "4004", 16 | "date": "1/13/2014", 17 | "class": [ 18 | { 19 | "name": "def_index", 20 | "value": "4004" 21 | } 22 | ] 23 | , 24 | "classid": "57939754" 25 | }, 26 | { 27 | "prices": { 28 | "USD": 0 29 | }, 30 | "name": "4008", 31 | "date": "1/13/2014", 32 | "class": [ 33 | { 34 | "name": "def_index", 35 | "value": "4008" 36 | } 37 | ] 38 | , 39 | "classid": "57939594" 40 | }, 41 | { 42 | "prices": { 43 | "USD": 0 44 | }, 45 | "name": "4009", 46 | "date": "1/13/2014", 47 | "class": [ 48 | { 49 | "name": "def_index", 50 | "value": "4009" 51 | } 52 | ] 53 | , 54 | "classid": "57939591" 55 | }, 56 | { 57 | "prices": { 58 | "USD": 0 59 | }, 60 | "name": "4010", 61 | "date": "1/13/2014", 62 | "class": [ 63 | { 64 | "name": "def_index", 65 | "value": "4010" 66 | } 67 | ] 68 | , 69 | "classid": "57939593" 70 | }, 71 | { 72 | "prices": { 73 | "USD": 0 74 | }, 75 | "name": "4049", 76 | "date": "1/13/2014", 77 | "class": [ 78 | { 79 | "name": "def_index", 80 | "value": "4049" 81 | } 82 | ] 83 | , 84 | "classid": "93966736" 85 | }, 86 | { 87 | "prices": { 88 | "USD": 0 89 | }, 90 | "name": "4097", 91 | "date": "1/13/2014", 92 | "class": [ 93 | { 94 | "name": "def_index", 95 | "value": "4097" 96 | } 97 | ] 98 | , 99 | "classid": "147888890" 100 | }, 101 | { 102 | "prices": { 103 | "USD": 0 104 | }, 105 | "name": "4110", 106 | "date": "1/13/2014", 107 | "class": [ 108 | { 109 | "name": "def_index", 110 | "value": "4110" 111 | } 112 | ] 113 | , 114 | "classid": "57939654" 115 | }, 116 | { 117 | "prices": { 118 | "USD": 0 119 | }, 120 | "name": "20886", 121 | "date": "9/2/2015", 122 | "class": [ 123 | { 124 | "name": "def_index", 125 | "value": "20886" 126 | } 127 | ] 128 | , 129 | "classid": "1218050854" 130 | } 131 | ] 132 | } 133 | } 134 | ` 135 | } 136 | -------------------------------------------------------------------------------- /profiles.go: -------------------------------------------------------------------------------- 1 | package steamapi 2 | 3 | import ( 4 | "errors" 5 | "net/url" 6 | "strconv" 7 | "strings" 8 | ) 9 | 10 | // CommunityVisibilityState contains the visibility of the user 11 | type CommunityVisibilityState int 12 | 13 | const ( 14 | // Private community visibility state 15 | Private CommunityVisibilityState = 1 16 | // FriendsOnly community visibility state 17 | FriendsOnly CommunityVisibilityState = 2 18 | // Public community visibility state 19 | Public CommunityVisibilityState = 3 20 | ) 21 | 22 | // PersonaState is the visibility state 23 | type PersonaState int 24 | 25 | const ( 26 | // Offline persona state is also 27 | // used when the steam user has set his profile 28 | // to private. 29 | Offline PersonaState = iota 30 | 31 | // Online is online 32 | Online 33 | // Busy is busy 34 | Busy 35 | // Away is away 36 | Away 37 | // Snooze is sniooze 38 | Snooze 39 | // LookingToTrade is looking to trade 40 | LookingToTrade 41 | // LookingToPlay is looking ot play 42 | LookingToPlay 43 | ) 44 | 45 | // PlayerSummary gives an overall state of the user in steam community 46 | type PlayerSummary struct { 47 | SteamID uint64 `json:",string"` 48 | CommunityVisibilityState CommunityVisibilityState 49 | ProfileURL string 50 | 51 | ProfileState int // Set to 1 if the player has configured the profile. 52 | PersonaName string 53 | LastLogoff int64 54 | PersonaState PersonaState 55 | 56 | SmallAvatarURL string `json:"avatar"` // 32x32 57 | MediumAvatarURL string `json:"avatarmedium"` // 64x64 58 | LargeAvatarURL string `json:"avatarfull"` // 184x184 59 | 60 | TimeCreated int64 `json:",omitempty"` 61 | RealName string `json:",omitempty"` 62 | GameExtraInfo string `json:",omitempty"` 63 | 64 | PrimaryClanID uint64 `json:",string,omitempty"` 65 | GameID uint64 `json:",string,omitempty"` 66 | GameServerIp string `json:",omitempty"` 67 | } 68 | 69 | type playerSummaryJSON struct { 70 | Response struct { 71 | Players []PlayerSummary 72 | } 73 | } 74 | 75 | // GetPlayerSummaries Fetches the player summaries for the given Steam Ids. 76 | func GetPlayerSummaries(ids []uint64, apiKey string) ([]PlayerSummary, error) { 77 | var getPlayerSummaries = NewSteamMethod("ISteamUser", "GetPlayerSummaries", 2) 78 | strIds := make([]string, len(ids)) 79 | for _, id := range ids { 80 | strIds = append(strIds, strconv.FormatUint(id, 10)) 81 | } 82 | vals := url.Values{} 83 | vals.Add("key", apiKey) 84 | vals.Add("steamids", strings.Join(strIds, ",")) 85 | 86 | var resp playerSummaryJSON 87 | err := getPlayerSummaries.Request(vals, &resp) 88 | if err != nil { 89 | return nil, err 90 | } 91 | return resp.Response.Players, nil 92 | } 93 | 94 | // ResolveVanityURLResponse resolves the response from steam 95 | type ResolveVanityURLResponse struct { 96 | Success int 97 | SteamID uint64 `json:",omitempty,string"` 98 | Message string `json:",omitempty"` 99 | } 100 | 101 | // ResolveVanityURL should return a response 102 | func ResolveVanityURL(vanityURL string, apiKey string) (*ResolveVanityURLResponse, error) { 103 | var resolveVanityURL = NewSteamMethod("ISteamUser", "ResolveVanityURL", 1) 104 | data := url.Values{} 105 | data.Add("key", apiKey) 106 | data.Add("vanityURL", vanityURL) 107 | 108 | var resp struct { 109 | Response ResolveVanityURLResponse 110 | } 111 | err := resolveVanityURL.Request(data, &resp) 112 | if err != nil { 113 | return nil, err 114 | } 115 | if resp.Response.Success != 1 { 116 | err = errors.New(resp.Response.Message) 117 | } 118 | return &resp.Response, err 119 | } 120 | -------------------------------------------------------------------------------- /schema.go: -------------------------------------------------------------------------------- 1 | package steamapi 2 | 3 | import ( 4 | "net/url" 5 | "strconv" 6 | ) 7 | 8 | type schemaJSON struct { 9 | Result Schema 10 | } 11 | 12 | // Schema is a game schema 13 | type Schema struct { 14 | Status int 15 | FullSchemaURL string `json:"items_game_url"` 16 | Qualities map[string]int 17 | QualityNames map[string]string 18 | OriginNames []Origin 19 | Items []SchemaItem 20 | Attributes []SchemaAttribute 21 | ItemSets []ItemSet 22 | AttributeControlledAttachedParticles []ParticleEffect `json:"attribute_controlled_attached_particles"` 23 | ItemLevels []ItemRankSet `json:"item_levels"` 24 | KillEaterScoreTypes []KillEaterScoreType `json:"kill_eater_score_types"` 25 | } 26 | 27 | // Item finds an item by its defindex in a Schema. 28 | func (s *Schema) Item(defindex int) *SchemaItem { 29 | for _, item := range s.Items { 30 | if item.Defindex == defindex { 31 | return &item 32 | } 33 | } 34 | return nil 35 | } 36 | 37 | // SchemaItem is a schema description of an Item. 38 | type SchemaItem struct { 39 | Name string 40 | Defindex int 41 | ItemClass string `json:"item_class"` 42 | ItemTypeName string `json:"item_type_name"` 43 | ItemName string `json:"item_name"` 44 | Description string `json:"item_description,omitempty"` 45 | ProperName bool `json:"proper_name"` 46 | Slot string `json:"item_slot"` 47 | DefaultQuality int `json:"item_quality"` 48 | InventoryImage *string `json:"image_inventory"` // this is null for the "Free Trial Premium Upgrade" 49 | ImageURL string `json:"image_url"` 50 | ImageURLLarge string `json:"image_url_large"` 51 | DropType string `json:"drop_type,omitempty"` 52 | ItemSet string `json:"item_set,omitempty"` 53 | HolidayRestriction string `json:"holiday_restriction"` 54 | MinLevel int `json:"min_ilevel"` 55 | MaxLevel int `json:"max_ilevel"` 56 | CraftClass string `json:"craft_class,omitempty"` 57 | Capabilities map[string]bool `json:",omitempty"` 58 | UsedByClasses []string `json:"used_by_classes,omitempty"` 59 | ClassLoadoutSlots map[string]string `json:"per_class_loadout_slots,omitempty"` 60 | Styles []Style `json:",omitempty"` 61 | Attributes []SchemaItemAttribute `json:",omitempty"` 62 | } 63 | 64 | // Origin is an item origin as defined in the Schema. 65 | type Origin struct { 66 | ID int `json:"origin"` 67 | Name string 68 | } 69 | 70 | // Style is an item style 71 | type Style struct { 72 | Name string 73 | } 74 | 75 | // SchemaItemAttribute is the schema items attributes 76 | type SchemaItemAttribute struct { 77 | Name string 78 | Class string 79 | Value float64 80 | } 81 | 82 | // SchemaAttribute is a schema attribute 83 | type SchemaAttribute struct { 84 | Name string 85 | Defindex int 86 | Class string `json:"attribute_class"` 87 | MinValue float64 88 | MaxValue float64 89 | Description string `json:"description_string,omitempty"` 90 | DescriptionFormat string `json:"description_format,omitempty"` 91 | EffectType string `json:"effect_type"` 92 | Hidden bool 93 | StoredAsInteger bool `json:"stored_as_integer"` 94 | } 95 | 96 | // ItemSet is an item set 97 | type ItemSet struct { 98 | InternalName string `json:"item_set"` 99 | Name string 100 | StoreBundle string `json:"store_bundle,omitempty"` 101 | Items []string 102 | Attributes []SchemaItemAttribute `json:",omitempty"` 103 | } 104 | 105 | // ParticleEffect is the particle effect 106 | type ParticleEffect struct { 107 | System string 108 | ID int 109 | AttachToRootbone bool `json:"attach_to_rootbone"` 110 | Attachment string `json:",omitempty"` 111 | Name string 112 | } 113 | 114 | // ItemRankSet is the set of the possible ranks 115 | type ItemRankSet struct { 116 | Name string 117 | Levels []ItemRank 118 | } 119 | 120 | // ItemRank is the rank of the item 121 | type ItemRank struct { 122 | Level int 123 | RequiredScore int `json:"required_score"` 124 | Name string 125 | } 126 | 127 | // KillEaterScoreType is the type of kill eater score 128 | type KillEaterScoreType struct { 129 | ID int `json:"type"` 130 | Name string `json:"type_name"` 131 | } 132 | 133 | // GetSchema Fetches the Schema for the given game. 134 | func GetSchema(appID int, language, APIKey string) (*Schema, error) { 135 | getSchema := NewSteamMethod("IEconItems_"+strconv.Itoa(appID), "GetSchema", 1) 136 | 137 | vals := url.Values{} 138 | vals.Add("key", APIKey) 139 | vals.Add("language", language) 140 | 141 | var resp schemaJSON 142 | err := getSchema.Request(vals, &resp) 143 | if err != nil { 144 | return nil, err 145 | } 146 | return &resp.Result, nil 147 | } 148 | -------------------------------------------------------------------------------- /servers.go: -------------------------------------------------------------------------------- 1 | package steamapi 2 | 3 | import ( 4 | "net" 5 | "net/url" 6 | ) 7 | 8 | type RegionId int 9 | 10 | const ( 11 | World RegionId = 255 12 | 13 | USEast RegionId = iota 14 | USWest 15 | SouthAmerica 16 | Europe 17 | Asia 18 | Australia 19 | MiddleEast 20 | Africa 21 | ) 22 | 23 | type serverInfoJson struct { 24 | Response struct { 25 | Success bool 26 | Servers []ServerInfo 27 | } 28 | } 29 | 30 | type ServerInfo struct { 31 | Addr string 32 | 33 | // Seems to always be 65534 34 | // TODO: find out meaning of value 35 | Gmsindex uint32 36 | Message string 37 | AppId uint64 38 | GameDir string 39 | Region RegionId 40 | VACSecured bool `json:"secure"` 41 | LANOnly bool `json:"lan"` 42 | GamePort uint16 43 | SpecPort uint16 44 | } 45 | 46 | func GetServerInfo(ip net.IP) ([]ServerInfo, error) { 47 | getServerInfo := NewSteamMethod("ISteamApps", "GetServersAtAddress", 1) 48 | 49 | vals := url.Values{} 50 | vals.Add("addr", ip.String()) 51 | 52 | var resp serverInfoJson 53 | err := getServerInfo.Request(vals, &resp) 54 | if err != nil { 55 | return nil, err 56 | } 57 | return resp.Response.Servers, nil 58 | } 59 | -------------------------------------------------------------------------------- /steamapi.go: -------------------------------------------------------------------------------- 1 | // Package steamapi provides an interface to the 2 | // Steam Web API methods. 3 | package steamapi 4 | 5 | import ( 6 | "encoding/json" 7 | "fmt" 8 | "net/http" 9 | "net/url" 10 | "strconv" 11 | ) 12 | 13 | // BaseSteamAPIURLProduction is the steam url used to do requests in prod 14 | const BaseSteamAPIURLProduction = "https://api.steampowered.com" 15 | 16 | // BaseSteamAPIURL is the url used to do requests, defaulted to prod 17 | var BaseSteamAPIURL = BaseSteamAPIURLProduction 18 | 19 | // A SteamMethod represents a Steam Web API method. 20 | type SteamMethod string 21 | 22 | // NewSteamMethod creates a new SteamMethod. 23 | func NewSteamMethod(interf, method string, version int) SteamMethod { 24 | m := fmt.Sprintf("%v/%v/%v/v%v/", BaseSteamAPIURL, interf, method, strconv.Itoa(version)) 25 | return SteamMethod(m) 26 | } 27 | 28 | // Request makes a request to the Steam Web API with the given 29 | // url values and stores the result in v. 30 | // 31 | // Returns an error if the return status code was not 200. 32 | func (s SteamMethod) Request(data url.Values, v interface{}) error { 33 | url := string(s) 34 | if data != nil { 35 | url += "?" + data.Encode() 36 | } 37 | resp, err := http.Get(url) 38 | if err != nil { 39 | return err 40 | } 41 | defer resp.Body.Close() 42 | 43 | if resp.StatusCode != 200 { 44 | return fmt.Errorf("steamapi %s Status code %d", s, resp.StatusCode) 45 | } 46 | 47 | d := json.NewDecoder(resp.Body) 48 | 49 | return d.Decode(&v) 50 | } 51 | -------------------------------------------------------------------------------- /tradeoffer.go: -------------------------------------------------------------------------------- 1 | package steamapi 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io/ioutil" 7 | "net/http" 8 | "net/url" 9 | "strconv" 10 | ) 11 | 12 | // State represents the state of the tradeoffer, see constants 13 | type State uint 14 | 15 | const ( 16 | // ETradeOfferStateCreated /!\ non steam status, used to know the TO has been created 17 | ETradeOfferStateCreated State = 0 18 | // ETradeOfferStateInvalid Invalid 19 | ETradeOfferStateInvalid State = 1 20 | // ETradeOfferStateActive This trade offer has been sent, neither party has acted on it yet. 21 | ETradeOfferStateActive State = 2 22 | // ETradeOfferStateAccepted The trade offer was accepted by the recipient and items were exchanged. 23 | ETradeOfferStateAccepted State = 3 24 | // ETradeOfferStateCountered The recipient made a counter offer 25 | ETradeOfferStateCountered State = 4 26 | // ETradeOfferStateExpired The trade offer was not accepted before the expiration date 27 | ETradeOfferStateExpired State = 5 28 | // ETradeOfferStateCanceled The sender cancelled the offer 29 | ETradeOfferStateCanceled State = 6 30 | // ETradeOfferStateDeclined The recipient declined the offer 31 | ETradeOfferStateDeclined State = 7 32 | // ETradeOfferStateInvalidItems Some of the items in the offer are no longer available 33 | // (indicated by the missing flag in the output) 34 | ETradeOfferStateInvalidItems State = 8 35 | // ETradeOfferStateCreatedNeedsConfirmation The offer hasn't been sent yet and is awaiting email/mobile confirmation. The offer is only visible to the sender. 36 | ETradeOfferStateCreatedNeedsConfirmation State = 9 37 | // ETradeOfferStateCanceledBySecondFactor Either party canceled the offer via email/mobile. The offer is visible to both parties, even if the sender canceled it before it was sent. 38 | ETradeOfferStateCanceledBySecondFactor State = 10 39 | // ETradeOfferStateInEscrow The trade has been placed on hold. The items involved in the trade have all been removed from both parties' inventories and will be automatically delivered in the future. 40 | ETradeOfferStateInEscrow State = 11 41 | ) 42 | 43 | // ConfirmationMethod different methods in which a trade offer can be confirmed. 44 | type ConfirmationMethod int 45 | 46 | const ( 47 | // ETradeOfferConfirmationMethodInvalid Invalid 48 | ETradeOfferConfirmationMethodInvalid ConfirmationMethod = 0 49 | // ETradeOfferConfirmationMethodEmail An email was sent with details on how to confirm the trade offer 50 | ETradeOfferConfirmationMethodEmail ConfirmationMethod = 1 51 | // ETradeOfferConfirmationMethodMobileApp The trade offer may be confirmed via the mobile app 52 | ETradeOfferConfirmationMethodMobileApp ConfirmationMethod = 2 53 | ) 54 | 55 | // CEconAsset represents an asset in steam web api 56 | type CEconAsset struct { 57 | AppID uint `json:",string"` 58 | ContextID uint64 `json:",string"` 59 | AssetID uint64 `json:",string"` 60 | CurrencyID uint64 `json:",string"` 61 | ClassID uint64 `json:",string"` 62 | InstanceID uint64 `json:",string"` 63 | Amount uint64 `json:",string"` 64 | Missing bool 65 | MarketHashName string 66 | } 67 | 68 | // CEconTradeOffer represent the to from the steam API 69 | type CEconTradeOffer struct { 70 | TradeOfferID uint64 `json:",string"` 71 | OtherAccountID uint64 `json:"accountid_other"` 72 | Message string 73 | ExpirationTime uint32 `json:"expiration_time"` 74 | State State `json:"trade_offer_state"` 75 | ToGive []*CEconAsset `json:"items_to_give"` 76 | ToReceive []*CEconAsset `json:"items_to_receive"` 77 | IsOurs bool `json:"is_our_offer"` 78 | TimeCreated uint32 `json:"time_created"` 79 | TimeUpdated uint32 `json:"time_updated"` 80 | FromRealTimeTrade bool `json:"from_real_time_trade"` 81 | EscrowEndDate uint32 `json:"escrow_end_date"` 82 | ConfirmationMethod ConfirmationMethod `json:"confirmation_method"` 83 | TradeID uint64 `json:"tradeid,string"` 84 | } 85 | 86 | // CEconTradeOffers represent the list of different tradeoffers types 87 | type CEconTradeOffers struct { 88 | Sent []*CEconTradeOffer `json:"trade_offers_sent"` 89 | Received []*CEconTradeOffer `json:"trade_offers_received"` 90 | } 91 | 92 | type ieconGetTradeOffersResponse struct { 93 | Response struct { 94 | CEconTradeOffers 95 | } 96 | } 97 | 98 | // IEconGetTradeOffers retrieves a list of tradeoffers 99 | func IEconGetTradeOffers( 100 | apiKey string, 101 | getSentOffers bool, 102 | getReceivedOffers bool, 103 | getDescriptions bool, 104 | activeOnly bool, 105 | historicalOnly bool, 106 | timeHistoricalCutoff int64, 107 | ) (*CEconTradeOffers, error) { 108 | 109 | querystring := url.Values{} 110 | querystring.Add("key", apiKey) 111 | querystring.Add("get_sent_offers", boolToStr(getSentOffers)) 112 | querystring.Add("get_received_offers", boolToStr(getReceivedOffers)) 113 | querystring.Add("get_descriptions", boolToStr(getDescriptions)) 114 | querystring.Add("language", "en") 115 | querystring.Add("active_only", boolToStr(activeOnly)) 116 | querystring.Add("historical_only", boolToStr(historicalOnly)) 117 | querystring.Add("time_historical_cutoff", strconv.FormatInt(timeHistoricalCutoff, 10)) 118 | 119 | resp, err := http.Get(BaseSteamAPIURL + "/IEconService/GetTradeOffers/v0001?" + querystring.Encode()) 120 | 121 | if err != nil { 122 | return nil, fmt.Errorf("tradeoffer IEconGetTradeOffers http.Get: error %v", err) 123 | } 124 | 125 | if resp.StatusCode != http.StatusOK { 126 | return nil, fmt.Errorf("tradeoffer IEconGetTradeOffers http.Get: http status %v", resp.Status) 127 | } 128 | 129 | defer resp.Body.Close() 130 | 131 | tosResp := &ieconGetTradeOffersResponse{} 132 | err = json.NewDecoder(resp.Body).Decode(tosResp) 133 | 134 | if err != nil { 135 | return nil, fmt.Errorf("tradeoffer IEconGetTradeOffers Decode: error %v", err) 136 | } 137 | 138 | return &tosResp.Response.CEconTradeOffers, nil 139 | } 140 | 141 | type ieconGetTradeOfferResponse struct { 142 | Response struct { 143 | Offer CEconTradeOffer 144 | Descriptions []ItemDescription 145 | } 146 | } 147 | 148 | // ItemDescription represents the details about the items unique w classid instanceid 149 | type ItemDescription struct { 150 | AppID uint `json:"appid"` 151 | ClassID uint64 `json:"classid,string"` 152 | InstanceID uint64 `json:"instanceid,string"` 153 | MarketHashName string `json:"market_hash_name"` 154 | IconURL string `json:"icon_url"` 155 | NameColor string `json:"name_color"` 156 | Name string `json:"name"` 157 | } 158 | 159 | func findMarketHashName(itemD []ItemDescription, appID uint, classID, instanceID uint64) string { 160 | for _, description := range itemD { 161 | if description.AppID == appID && 162 | description.ClassID == classID && 163 | description.InstanceID == instanceID { 164 | return description.MarketHashName 165 | } 166 | } 167 | 168 | return "" 169 | } 170 | 171 | // IEconGetTradeOffer retrieves details about a specific tradeoffer 172 | func IEconGetTradeOffer(apiKey string, tradeOfferID uint64) ( 173 | *CEconTradeOffer, error, 174 | ) { 175 | 176 | querystring := url.Values{} 177 | querystring.Add("key", apiKey) 178 | querystring.Add("format", "json") 179 | querystring.Add("tradeofferid", strconv.FormatUint(tradeOfferID, 10)) 180 | querystring.Add("language", "en") 181 | 182 | resp, err := http.Get(BaseSteamAPIURL + "/IEconService/GetTradeOffer/v1?" + querystring.Encode()) 183 | if err != nil { 184 | return nil, fmt.Errorf("tradeoffer IEconGetTradeOffer http.Get: error %v", err) 185 | } 186 | if resp.StatusCode != http.StatusOK { 187 | body, errBody := ioutil.ReadAll(resp.Body) 188 | return nil, 189 | fmt.Errorf("tradeoffer IEconGetTradeOffer: steam responded with a status %d with the message: %s (%v)", 190 | resp.StatusCode, 191 | body, 192 | errBody, 193 | ) 194 | } 195 | 196 | defer resp.Body.Close() 197 | 198 | toResp := ieconGetTradeOfferResponse{} 199 | err = json.NewDecoder(resp.Body).Decode(&toResp) 200 | 201 | if err != nil { 202 | body, _ := ioutil.ReadAll(resp.Body) 203 | return nil, fmt.Errorf("tradeoffer IEconGetTradeOffer Decode(%s): error %v", body, err) 204 | } 205 | 206 | // If the state is 0, it means there is a mistake 207 | if toResp.Response.Offer.State == 0 { 208 | body, errBody := ioutil.ReadAll(resp.Body) 209 | return nil, 210 | fmt.Errorf("tradeoffer IEconGetTradeOffer: steam responded with a status %d with the message: %s (%v)", 211 | resp.StatusCode, 212 | body, 213 | errBody, 214 | ) 215 | } 216 | 217 | for giveIndex, asset := range toResp.Response.Offer.ToGive { 218 | toResp.Response.Offer.ToGive[giveIndex].MarketHashName = 219 | findMarketHashName(toResp.Response.Descriptions, asset.AppID, asset.ClassID, asset.InstanceID) 220 | } 221 | 222 | for receiveIndex, asset := range toResp.Response.Offer.ToReceive { 223 | toResp.Response.Offer.ToReceive[receiveIndex].MarketHashName = 224 | findMarketHashName(toResp.Response.Descriptions, asset.AppID, asset.ClassID, asset.InstanceID) 225 | } 226 | 227 | return &toResp.Response.Offer, nil 228 | } 229 | 230 | // IEconActionTradeOffer declines a TO created by someone else 231 | func IEconActionTradeOffer(action string, apiKey string, tradeOfferID uint64) error { 232 | 233 | if action != "Decline" && action != "Cancel" { 234 | return fmt.Errorf("tradeoffer IEconActionTradeOffer doesn't support %v action", action) 235 | } 236 | querystring := url.Values{} 237 | querystring.Add("key", apiKey) 238 | querystring.Add("tradeofferid", strconv.FormatUint(tradeOfferID, 10)) 239 | 240 | resp, err := http.Get( 241 | BaseSteamAPIURL + "/IEconService/" + action + "TradeOffer/v0001?" + querystring.Encode()) 242 | 243 | if err != nil { 244 | return fmt.Errorf("tradeoffer IEconGetTradeOffer http.Get: error %v", err) 245 | } 246 | if resp.StatusCode != http.StatusOK { 247 | body, errBody := ioutil.ReadAll(resp.Body) 248 | return fmt.Errorf("tradeoffer IEcon%sTradeOffer: steam responded with a status %d with the message: %s (%v)", 249 | action, 250 | resp.StatusCode, 251 | body, 252 | errBody, 253 | ) 254 | } 255 | 256 | err = resp.Body.Close() 257 | 258 | if err != nil { 259 | return fmt.Errorf("tradeoffer IEcon%sTradeOffer resp.Body.Close(): error %v", action, err) 260 | } 261 | 262 | return nil 263 | 264 | } 265 | 266 | // IEconCancelTradeOffer declines a TO created by someone else 267 | func IEconCancelTradeOffer(apiKey string, tradeOfferID uint64) error { 268 | 269 | resp, err := http.PostForm( 270 | BaseSteamAPIURL+"/IEconService/CancelTradeOffer/v1", 271 | url.Values{"key": {apiKey}, "tradeofferid": {strconv.FormatUint(tradeOfferID, 10)}}, 272 | ) 273 | if err != nil { 274 | return fmt.Errorf("tradeoffer IEconGetTradeOffer http.Get: error %v", err) 275 | } 276 | defer resp.Body.Close() 277 | 278 | if resp.StatusCode != http.StatusOK { 279 | body, errBody := ioutil.ReadAll(resp.Body) 280 | return fmt.Errorf("tradeoffer IEconCancelTradeOffer: steam responded with a status %d with the message: %s (%v)", 281 | resp.StatusCode, 282 | body, 283 | errBody, 284 | ) 285 | } 286 | 287 | return nil 288 | } 289 | 290 | func boolToStr(b bool) string { 291 | if b { 292 | return "1" 293 | } 294 | 295 | return "0" 296 | 297 | } 298 | -------------------------------------------------------------------------------- /tradeoffer_mock_test.go: -------------------------------------------------------------------------------- 1 | package steamapi 2 | 3 | // GetMockActiveStateIEconGetTradeOffer ... 4 | func GetMockActiveStateIEconGetTradeOffer() string { 5 | return ` 6 | { 7 | "response": { 8 | "offer": { 9 | "tradeofferid": "123456", 10 | "accountid_other": 1234, 11 | "message": "", 12 | "expiration_time": 1300000000, 13 | "trade_offer_state": 2, 14 | "items_to_receive": [ 15 | { 16 | "appid": "123", 17 | "contextid": "2", 18 | "assetid": "1234553", 19 | "classid": "888881", 20 | "instanceid": "0", 21 | "amount": "1", 22 | "missing": false 23 | } 24 | ], 25 | "is_our_offer": true, 26 | "time_created": 1300000000, 27 | "time_updated": 1300000000, 28 | "from_real_time_trade": false, 29 | "escrow_end_date": 1450075573, 30 | "confirmation_method": 0 31 | }, 32 | "descriptions": [ 33 | { 34 | "appid": 123, 35 | "classid": "888881", 36 | "instanceid": "0", 37 | "currency": false, 38 | "background_color": "", 39 | "icon_url": "454309584309539klfksdjflksdjf", 40 | "icon_url_large": "09089dklngflndfmd", 41 | "descriptions": [ 42 | { 43 | "type": "html", 44 | "value": "ttttt" 45 | }, 46 | { 47 | "type": "html", 48 | "value": " " 49 | }, 50 | { 51 | "type": "html", 52 | "value": "ttttt." 53 | }, 54 | { 55 | "type": "html", 56 | "value": " " 57 | }, 58 | { 59 | "type": "html", 60 | "value": "test", 61 | "color": "EEEE" 62 | }, 63 | { 64 | "type": "html", 65 | "value": " " 66 | } 67 | ], 68 | "tradable": true, 69 | "actions": [ 70 | { 71 | "link": "lnk", 72 | "name": "" 73 | } 74 | ], 75 | "name": "testtest", 76 | "name_color": "DDDD", 77 | "type": "test", 78 | "market_name": "testmkt", 79 | "market_hash_name": "testmkt", 80 | "market_actions": [ 81 | { 82 | "link": "lnk", 83 | "name": "" 84 | } 85 | ], 86 | "commodity": false, 87 | "market_tradable_restriction": 0 88 | } 89 | ] 90 | } 91 | }` 92 | } 93 | 94 | // GetMockIEconGetTradeOffers .. 95 | func GetMockIEconGetTradeOffers() string { 96 | return ` 97 | { 98 | "response": { 99 | "trade_offers_sent": [ 100 | { 101 | "tradeofferid": "123456", 102 | "accountid_other": 1234, 103 | "message": "", 104 | "expiration_time": 1300000000, 105 | "trade_offer_state": 2, 106 | "items_to_receive": [ 107 | { 108 | "appid": "123", 109 | "contextid": "2", 110 | "assetid": "1234553", 111 | "classid": "888881", 112 | "instanceid": "0", 113 | "amount": "1", 114 | "missing": false 115 | } 116 | ], 117 | "is_our_offer": true, 118 | "time_created": 1300000000, 119 | "time_updated": 1300000000, 120 | "from_real_time_trade": false, 121 | "escrow_end_date": 1450075573, 122 | "confirmation_method": 0 123 | }, 124 | { 125 | "tradeofferid": "123457", 126 | "accountid_other": 1234, 127 | "message": "", 128 | "expiration_time": 1300000000, 129 | "trade_offer_state": 2, 130 | "items_to_receive": [ 131 | { 132 | "appid": "123", 133 | "contextid": "2", 134 | "assetid": "1234553", 135 | "classid": "888881", 136 | "instanceid": "0", 137 | "amount": "1", 138 | "missing": false 139 | } 140 | ], 141 | "is_our_offer": true, 142 | "time_created": 1300000000, 143 | "time_updated": 1300000000, 144 | "from_real_time_trade": false, 145 | "escrow_end_date": 1450075573, 146 | "confirmation_method": 0 147 | } 148 | ], 149 | "trade_offers_received": [ 150 | { 151 | "tradeofferid": "123458", 152 | "accountid_other": 12345, 153 | "message": "", 154 | "expiration_time": 1300000000, 155 | "trade_offer_state": 2, 156 | "items_to_receive": [ 157 | { 158 | "appid": "123", 159 | "contextid": "2", 160 | "assetid": "1234553", 161 | "classid": "888881", 162 | "instanceid": "0", 163 | "amount": "1", 164 | "missing": false 165 | } 166 | ], 167 | "is_our_offer": true, 168 | "time_created": 1300000000, 169 | "time_updated": 1300000000, 170 | "from_real_time_trade": false, 171 | "escrow_end_date": 1450075573, 172 | "confirmation_method": 0 173 | } 174 | ], 175 | "descriptions": [ 176 | { 177 | "appid": 123, 178 | "classid": "888881", 179 | "instanceid": "0", 180 | "currency": false, 181 | "background_color": "", 182 | "icon_url": "454309584309539klfksdjflksdjf", 183 | "icon_url_large": "09089dklngflndfmd", 184 | "descriptions": [ 185 | { 186 | "type": "html", 187 | "value": "ttttt" 188 | }, 189 | { 190 | "type": "html", 191 | "value": " " 192 | }, 193 | { 194 | "type": "html", 195 | "value": "ttttt." 196 | }, 197 | { 198 | "type": "html", 199 | "value": " " 200 | }, 201 | { 202 | "type": "html", 203 | "value": "test", 204 | "color": "EEEE" 205 | }, 206 | { 207 | "type": "html", 208 | "value": " " 209 | } 210 | ], 211 | "tradable": true, 212 | "actions": [ 213 | { 214 | "link": "lnk", 215 | "name": "" 216 | } 217 | ], 218 | "name": "testtest", 219 | "name_color": "DDDD", 220 | "type": "test", 221 | "market_name": "testmkt", 222 | "market_hash_name": "testmkt", 223 | "market_actions": [ 224 | { 225 | "link": "lnk", 226 | "name": "" 227 | } 228 | ], 229 | "commodity": false, 230 | "market_tradable_restriction": 0 231 | } 232 | ] 233 | } 234 | }` 235 | } 236 | 237 | func GetMockIEconCancelTradeOffer() string { 238 | return `{ 239 | "response": {} 240 | }` 241 | } 242 | -------------------------------------------------------------------------------- /tradeoffer_test.go: -------------------------------------------------------------------------------- 1 | package steamapi 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "net/http/httptest" 7 | "testing" 8 | ) 9 | 10 | func TestIEconGetTradeOffer(t *testing.T) { 11 | ts := httptest.NewServer( 12 | http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 13 | w.Header().Set("Content-Type", "application/json") 14 | w.WriteHeader(http.StatusOK) 15 | fmt.Fprintf(w, GetMockActiveStateIEconGetTradeOffer()) 16 | }), 17 | ) 18 | BaseSteamAPIURL = ts.URL 19 | 20 | defer ts.Close() 21 | 22 | expectedItem := CEconAsset{ 23 | AppID: 123, 24 | ContextID: 2, 25 | AssetID: 1234553, 26 | CurrencyID: 0, 27 | ClassID: 888881, 28 | InstanceID: 0, 29 | Amount: 1, 30 | Missing: false, 31 | MarketHashName: "testmkt", 32 | } 33 | 34 | expectedCETO := CEconTradeOffer{ 35 | TradeOfferID: 123456, 36 | OtherAccountID: 1234, 37 | Message: "", 38 | ExpirationTime: 1300000000, 39 | State: 2, 40 | ToGive: []*CEconAsset{}, 41 | ToReceive: []*CEconAsset{&expectedItem}, 42 | IsOurs: true, 43 | TimeCreated: 1300000000, 44 | TimeUpdated: 1300000000, 45 | FromRealTimeTrade: false, 46 | EscrowEndDate: 1450075573, 47 | ConfirmationMethod: ETradeOfferConfirmationMethodInvalid, 48 | } 49 | 50 | TOgot, err := IEconGetTradeOffer("123", 1) 51 | 52 | if err != nil { 53 | t.Errorf("IEconGetTradeOffer unexpected err %v", err) 54 | return 55 | } 56 | 57 | if TOgot.TradeOfferID != expectedCETO.TradeOfferID || 58 | TOgot.OtherAccountID != expectedCETO.OtherAccountID || 59 | TOgot.Message != expectedCETO.Message || 60 | TOgot.ExpirationTime != expectedCETO.ExpirationTime || 61 | TOgot.State != expectedCETO.State || 62 | TOgot.IsOurs != expectedCETO.IsOurs || 63 | TOgot.TimeCreated != expectedCETO.TimeCreated || 64 | TOgot.TimeUpdated != expectedCETO.TimeUpdated || 65 | len(TOgot.ToGive) != len(expectedCETO.ToGive) || 66 | TOgot.FromRealTimeTrade != expectedCETO.FromRealTimeTrade || 67 | TOgot.ConfirmationMethod != expectedCETO.ConfirmationMethod || 68 | TOgot.EscrowEndDate != expectedCETO.EscrowEndDate || 69 | len(TOgot.ToReceive) != len(expectedCETO.ToReceive) { 70 | t.Errorf("IEconGetTradeOffer expected %v, got %v", expectedCETO, TOgot) 71 | } 72 | 73 | if *TOgot.ToReceive[0] != *expectedCETO.ToReceive[0] { 74 | t.Errorf("IEconGetTradeOffer expected %v, got %v", expectedCETO, TOgot) 75 | } 76 | } 77 | 78 | func TestIEconGetTradeOffers(t *testing.T) { 79 | ts := httptest.NewServer( 80 | http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 81 | w.Header().Set("Content-Type", "application/json") 82 | w.WriteHeader(http.StatusOK) 83 | fmt.Fprintf(w, GetMockIEconGetTradeOffers()) 84 | }), 85 | ) 86 | BaseSteamAPIURL = ts.URL 87 | defer ts.Close() 88 | 89 | expectedItem := CEconAsset{ 90 | AppID: 123, 91 | ContextID: 2, 92 | AssetID: 1234553, 93 | CurrencyID: 0, 94 | ClassID: 888881, 95 | InstanceID: 0, 96 | Amount: 1, 97 | Missing: false, 98 | MarketHashName: "testmkt", 99 | } 100 | 101 | expectedCETOsSent := []*CEconTradeOffer{ 102 | &CEconTradeOffer{ 103 | TradeOfferID: 123456, 104 | OtherAccountID: 1234, 105 | Message: "", 106 | ExpirationTime: 1300000000, 107 | State: 2, 108 | ToGive: []*CEconAsset{}, 109 | ToReceive: []*CEconAsset{&expectedItem}, 110 | IsOurs: true, 111 | TimeCreated: 1300000000, 112 | TimeUpdated: 1300000000, 113 | FromRealTimeTrade: false, 114 | EscrowEndDate: 1450075573, 115 | ConfirmationMethod: ETradeOfferConfirmationMethodInvalid, 116 | }, 117 | &CEconTradeOffer{ 118 | TradeOfferID: 123457, 119 | OtherAccountID: 1234, 120 | Message: "", 121 | ExpirationTime: 1300000000, 122 | State: 2, 123 | ToGive: []*CEconAsset{}, 124 | ToReceive: []*CEconAsset{&expectedItem}, 125 | IsOurs: true, 126 | TimeCreated: 1300000000, 127 | TimeUpdated: 1300000000, 128 | FromRealTimeTrade: false, 129 | EscrowEndDate: 1450075573, 130 | ConfirmationMethod: ETradeOfferConfirmationMethodInvalid, 131 | }, 132 | } 133 | 134 | expectedCETOsReceived := []*CEconTradeOffer{ 135 | &CEconTradeOffer{ 136 | TradeOfferID: 123458, 137 | OtherAccountID: 12345, 138 | Message: "", 139 | ExpirationTime: 1300000000, 140 | State: 2, 141 | ToGive: []*CEconAsset{}, 142 | ToReceive: []*CEconAsset{&expectedItem}, 143 | IsOurs: true, 144 | TimeCreated: 1300000000, 145 | TimeUpdated: 1300000000, 146 | FromRealTimeTrade: false, 147 | EscrowEndDate: 1450075573, 148 | ConfirmationMethod: ETradeOfferConfirmationMethodInvalid, 149 | }, 150 | } 151 | 152 | TOsGot, err := IEconGetTradeOffers("123", true, true, true, false, false, 1) 153 | 154 | if err != nil { 155 | t.Errorf("IEconGetTradeOffers unexpected err %v", err) 156 | return 157 | } 158 | 159 | if len(TOsGot.Sent) != len(expectedCETOsSent) || 160 | len(TOsGot.Received) != len(expectedCETOsReceived) { 161 | t.Errorf("IEconGetTradeOffers expected %d offers sent, %d received "+ 162 | "got %d offers sent, %d received ", 163 | len(expectedCETOsSent), len(expectedCETOsReceived), 164 | len(TOsGot.Sent), len(TOsGot.Received), 165 | ) 166 | } 167 | 168 | if TOsGot.Received[0].OtherAccountID != expectedCETOsReceived[0].OtherAccountID { 169 | t.Errorf("IEconGetTradeOffers expected accountid %d, got %d", 170 | expectedCETOsReceived[0].OtherAccountID, TOsGot.Received[0].OtherAccountID) 171 | } 172 | } 173 | 174 | func TestIEconCancelTradeOffer(t *testing.T) { 175 | ts := httptest.NewServer( 176 | http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 177 | w.Header().Set("Content-Type", "application/json") 178 | w.WriteHeader(http.StatusOK) 179 | fmt.Fprintf(w, GetMockIEconCancelTradeOffer()) 180 | }), 181 | ) 182 | 183 | defer ts.Close() 184 | BaseSteamAPIURL = ts.URL 185 | 186 | err := IEconCancelTradeOffer("123", 1) 187 | if err != nil { 188 | t.Errorf("IEconCancelTradeOffer returns an error %s, whereas it shouldn't", err) 189 | } 190 | } 191 | 192 | func TestWrongAPIKeyIEconCancelTradeOffer(t *testing.T) { 193 | ts := httptest.NewServer( 194 | http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 195 | w.Header().Set("Content-Type", "application/json") 196 | w.WriteHeader(http.StatusForbidden) 197 | }), 198 | ) 199 | BaseSteamAPIURL = ts.URL 200 | defer ts.Close() 201 | err := IEconCancelTradeOffer("123", 1) 202 | if err == nil { 203 | t.Errorf("IEconCancelTradeOffer returns no error, whereas it should") 204 | } 205 | } 206 | --------------------------------------------------------------------------------