summaryrefslogtreecommitdiffstats
path: root/vendor/github.com/Philipp15b/go-steam/tradeoffer
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/Philipp15b/go-steam/tradeoffer')
-rw-r--r--vendor/github.com/Philipp15b/go-steam/tradeoffer/client.go452
-rw-r--r--vendor/github.com/Philipp15b/go-steam/tradeoffer/error.go19
-rw-r--r--vendor/github.com/Philipp15b/go-steam/tradeoffer/escrow.go47
-rw-r--r--vendor/github.com/Philipp15b/go-steam/tradeoffer/receipt.go35
-rw-r--r--vendor/github.com/Philipp15b/go-steam/tradeoffer/tradeoffer.go118
5 files changed, 671 insertions, 0 deletions
diff --git a/vendor/github.com/Philipp15b/go-steam/tradeoffer/client.go b/vendor/github.com/Philipp15b/go-steam/tradeoffer/client.go
new file mode 100644
index 00000000..7bf171c3
--- /dev/null
+++ b/vendor/github.com/Philipp15b/go-steam/tradeoffer/client.go
@@ -0,0 +1,452 @@
+package tradeoffer
+
+import (
+ "encoding/json"
+ "fmt"
+ "github.com/Philipp15b/go-steam/community"
+ "github.com/Philipp15b/go-steam/economy/inventory"
+ "github.com/Philipp15b/go-steam/netutil"
+ "github.com/Philipp15b/go-steam/steamid"
+ "io/ioutil"
+ "net/http"
+ "strconv"
+ "time"
+)
+
+type APIKey string
+
+const apiUrl = "https://api.steampowered.com/IEconService/%s/v%d"
+
+type Client struct {
+ client *http.Client
+ key APIKey
+ sessionId string
+}
+
+func NewClient(key APIKey, sessionId, steamLogin, steamLoginSecure string) *Client {
+ c := &Client{
+ new(http.Client),
+ key,
+ sessionId,
+ }
+ community.SetCookies(c.client, sessionId, steamLogin, steamLoginSecure)
+ return c
+}
+
+func (c *Client) GetOffer(offerId uint64) (*TradeOfferResult, error) {
+ resp, err := c.client.Get(fmt.Sprintf(apiUrl, "GetTradeOffer", 1) + "?" + netutil.ToUrlValues(map[string]string{
+ "key": string(c.key),
+ "tradeofferid": strconv.FormatUint(offerId, 10),
+ "language": "en_us",
+ }).Encode())
+ if err != nil {
+ return nil, err
+ }
+ defer resp.Body.Close()
+ t := new(struct {
+ Response *TradeOfferResult
+ })
+ if err = json.NewDecoder(resp.Body).Decode(t); err != nil {
+ return nil, err
+ }
+ if t.Response == nil || t.Response.Offer == nil {
+ return nil, newSteamErrorf("steam returned empty offer result\n")
+ }
+
+ return t.Response, nil
+}
+
+func (c *Client) GetOffers(getSent bool, getReceived bool, getDescriptions bool, activeOnly bool, historicalOnly bool, timeHistoricalCutoff *uint32) (*TradeOffersResult, error) {
+ if !getSent && !getReceived {
+ return nil, fmt.Errorf("getSent and getReceived can't be both false\n")
+ }
+
+ params := map[string]string{
+ "key": string(c.key),
+ }
+ if getSent {
+ params["get_sent_offers"] = "1"
+ }
+ if getReceived {
+ params["get_received_offers"] = "1"
+ }
+ if getDescriptions {
+ params["get_descriptions"] = "1"
+ params["language"] = "en_us"
+ }
+ if activeOnly {
+ params["active_only"] = "1"
+ }
+ if historicalOnly {
+ params["historical_only"] = "1"
+ }
+ if timeHistoricalCutoff != nil {
+ params["time_historical_cutoff"] = strconv.FormatUint(uint64(*timeHistoricalCutoff), 10)
+ }
+ resp, err := c.client.Get(fmt.Sprintf(apiUrl, "GetTradeOffers", 1) + "?" + netutil.ToUrlValues(params).Encode())
+ if err != nil {
+ return nil, err
+ }
+ defer resp.Body.Close()
+ t := new(struct {
+ Response *TradeOffersResult
+ })
+ if err = json.NewDecoder(resp.Body).Decode(t); err != nil {
+ return nil, err
+ }
+ if t.Response == nil {
+ return nil, newSteamErrorf("steam returned empty offers result\n")
+ }
+ return t.Response, nil
+}
+
+// action() is used by Decline() and Cancel()
+// Steam only return success and error fields for malformed requests,
+// hence client shall use GetOffer() to check action result
+// It is also possible to implement Decline/Cancel using steamcommunity,
+// which have more predictable responses
+func (c *Client) action(method string, version uint, offerId uint64) error {
+ resp, err := c.client.Do(netutil.NewPostForm(fmt.Sprintf(apiUrl, method, version), netutil.ToUrlValues(map[string]string{
+ "key": string(c.key),
+ "tradeofferid": strconv.FormatUint(offerId, 10),
+ })))
+ if err != nil {
+ return err
+ }
+ defer resp.Body.Close()
+ if resp.StatusCode != 200 {
+ return fmt.Errorf(method+" error: status code %d", resp.StatusCode)
+ }
+ return nil
+}
+
+func (c *Client) Decline(offerId uint64) error {
+ return c.action("DeclineTradeOffer", 1, offerId)
+}
+
+func (c *Client) Cancel(offerId uint64) error {
+ return c.action("CancelTradeOffer", 1, offerId)
+}
+
+// Accept received trade offer
+// It is best to confirm that offer was actually accepted
+// by calling GetOffer after Accept and checking offer state
+func (c *Client) Accept(offerId uint64) error {
+ baseurl := fmt.Sprintf("https://steamcommunity.com/tradeoffer/%d/", offerId)
+ req := netutil.NewPostForm(baseurl+"accept", netutil.ToUrlValues(map[string]string{
+ "sessionid": c.sessionId,
+ "serverid": "1",
+ "tradeofferid": strconv.FormatUint(offerId, 10),
+ }))
+ req.Header.Add("Referer", baseurl)
+
+ resp, err := c.client.Do(req)
+ if err != nil {
+ return err
+ }
+ defer resp.Body.Close()
+ t := new(struct {
+ StrError string `json:"strError"`
+ })
+ if err = json.NewDecoder(resp.Body).Decode(t); err != nil {
+ return err
+ }
+ if t.StrError != "" {
+ return newSteamErrorf("accept error: %v\n", t.StrError)
+ }
+ if resp.StatusCode != 200 {
+ return fmt.Errorf("accept error: status code %d", resp.StatusCode)
+ }
+ return nil
+}
+
+type TradeItem struct {
+ AppId uint32 `json:"appid"`
+ ContextId uint64 `json:"contextid,string"`
+ Amount uint64 `json:"amount"`
+ AssetId uint64 `json:"assetid,string,omitempty"`
+ CurrencyId uint64 `json:"currencyid,string,omitempty"`
+}
+
+// Sends a new trade offer to the given Steam user. You can optionally specify an access token if you've got one.
+// In addition, `counteredOfferId` can be non-nil, indicating the trade offer this is a counter for.
+// On success returns trade offer id
+func (c *Client) Create(other steamid.SteamId, accessToken *string, myItems, theirItems []TradeItem, counteredOfferId *uint64, message string) (uint64, error) {
+ // Create new trade offer status
+ to := map[string]interface{}{
+ "newversion": true,
+ "version": 3,
+ "me": map[string]interface{}{
+ "assets": myItems,
+ "currency": make([]struct{}, 0),
+ "ready": false,
+ },
+ "them": map[string]interface{}{
+ "assets": theirItems,
+ "currency": make([]struct{}, 0),
+ "ready": false,
+ },
+ }
+
+ jto, err := json.Marshal(to)
+ if err != nil {
+ panic(err)
+ }
+
+ // Create url parameters for request
+ data := map[string]string{
+ "sessionid": c.sessionId,
+ "serverid": "1",
+ "partner": other.ToString(),
+ "tradeoffermessage": message,
+ "json_tradeoffer": string(jto),
+ }
+
+ var referer string
+ if counteredOfferId != nil {
+ referer = fmt.Sprintf("https://steamcommunity.com/tradeoffer/%d/", *counteredOfferId)
+ data["tradeofferid_countered"] = strconv.FormatUint(*counteredOfferId, 10)
+ } else {
+ // Add token for non-friend offers
+ if accessToken != nil {
+ params := map[string]string{
+ "trade_offer_access_token": *accessToken,
+ }
+ paramsJson, err := json.Marshal(params)
+ if err != nil {
+ panic(err)
+ }
+
+ data["trade_offer_create_params"] = string(paramsJson)
+
+ referer = "https://steamcommunity.com/tradeoffer/new/?partner=" + strconv.FormatUint(uint64(other.GetAccountId()), 10) + "&token=" + *accessToken
+ } else {
+
+ referer = "https://steamcommunity.com/tradeoffer/new/?partner=" + strconv.FormatUint(uint64(other.GetAccountId()), 10)
+ }
+ }
+
+ // Create request
+ req := netutil.NewPostForm("https://steamcommunity.com/tradeoffer/new/send", netutil.ToUrlValues(data))
+ req.Header.Add("Referer", referer)
+
+ // Send request
+ resp, err := c.client.Do(req)
+ if err != nil {
+ return 0, err
+ }
+ defer resp.Body.Close()
+ t := new(struct {
+ StrError string `json:"strError"`
+ TradeOfferId uint64 `json:"tradeofferid,string"`
+ })
+ if err = json.NewDecoder(resp.Body).Decode(t); err != nil {
+ return 0, err
+ }
+ // strError code descriptions:
+ // 15 invalide trade access token
+ // 16 timeout
+ // 20 wrong contextid
+ // 25 can't send more offers until some is accepted/cancelled...
+ // 26 object is not in our inventory
+ // error code names are in internal/steamlang/enums.go EResult_name
+ if t.StrError != "" {
+ return 0, newSteamErrorf("create error: %v\n", t.StrError)
+ }
+ if resp.StatusCode != 200 {
+ return 0, fmt.Errorf("create error: status code %d", resp.StatusCode)
+ }
+ if t.TradeOfferId == 0 {
+ return 0, newSteamErrorf("create error: steam returned 0 for trade offer id")
+ }
+ return t.TradeOfferId, nil
+}
+
+func (c *Client) GetOwnInventory(contextId uint64, appId uint32) (*inventory.Inventory, error) {
+ return inventory.GetOwnInventory(c.client, contextId, appId)
+}
+
+func (c *Client) GetPartnerInventory(other steamid.SteamId, contextId uint64, appId uint32, offerId *uint64) (*inventory.Inventory, error) {
+ return inventory.GetFullInventory(func() (*inventory.PartialInventory, error) {
+ return c.getPartialPartnerInventory(other, contextId, appId, offerId, nil)
+ }, func(start uint) (*inventory.PartialInventory, error) {
+ return c.getPartialPartnerInventory(other, contextId, appId, offerId, &start)
+ })
+}
+
+func (c *Client) getPartialPartnerInventory(other steamid.SteamId, contextId uint64, appId uint32, offerId *uint64, start *uint) (*inventory.PartialInventory, error) {
+ data := map[string]string{
+ "sessionid": c.sessionId,
+ "partner": other.ToString(),
+ "contextid": strconv.FormatUint(contextId, 10),
+ "appid": strconv.FormatUint(uint64(appId), 10),
+ }
+ if start != nil {
+ data["start"] = strconv.FormatUint(uint64(*start), 10)
+ }
+
+ baseUrl := "https://steamcommunity.com/tradeoffer/%v/"
+ if offerId != nil {
+ baseUrl = fmt.Sprintf(baseUrl, *offerId)
+ } else {
+ baseUrl = fmt.Sprintf(baseUrl, "new")
+ }
+
+ req, err := http.NewRequest("GET", baseUrl+"partnerinventory/?"+netutil.ToUrlValues(data).Encode(), nil)
+ if err != nil {
+ panic(err)
+ }
+ req.Header.Add("Referer", baseUrl+"?partner="+strconv.FormatUint(uint64(other.GetAccountId()), 10))
+
+ return inventory.DoInventoryRequest(c.client, req)
+}
+
+// Can be used to verify accepted tradeoffer and find out received asset ids
+func (c *Client) GetTradeReceipt(tradeId uint64) ([]*TradeReceiptItem, error) {
+ url := fmt.Sprintf("https://steamcommunity.com/trade/%d/receipt", tradeId)
+ resp, err := c.client.Get(url)
+ if err != nil {
+ return nil, err
+ }
+ defer resp.Body.Close()
+ respBody, err := ioutil.ReadAll(resp.Body)
+ if err != nil {
+ return nil, err
+ }
+ items, err := parseTradeReceipt(respBody)
+ if err != nil {
+ return nil, newSteamErrorf("failed to parse trade receipt: %v", err)
+ }
+ return items, nil
+}
+
+// Get duration of escrow in days. Call this before sending a trade offer
+func (c *Client) GetPartnerEscrowDuration(other steamid.SteamId, accessToken *string) (*EscrowDuration, error) {
+ data := map[string]string{
+ "partner": strconv.FormatUint(uint64(other.GetAccountId()), 10),
+ }
+ if accessToken != nil {
+ data["token"] = *accessToken
+ }
+ return c.getEscrowDuration("https://steamcommunity.com/tradeoffer/new/?" + netutil.ToUrlValues(data).Encode())
+}
+
+// Get duration of escrow in days. Call this after receiving a trade offer
+func (c *Client) GetOfferEscrowDuration(offerId uint64) (*EscrowDuration, error) {
+ return c.getEscrowDuration("http://steamcommunity.com/tradeoffer/" + strconv.FormatUint(offerId, 10))
+}
+
+func (c *Client) getEscrowDuration(queryUrl string) (*EscrowDuration, error) {
+ resp, err := c.client.Get(queryUrl)
+ if err != nil {
+ return nil, fmt.Errorf("failed to retrieve escrow duration: %v", err)
+ }
+ defer resp.Body.Close()
+ respBody, err := ioutil.ReadAll(resp.Body)
+ if err != nil {
+ return nil, err
+ }
+ escrowDuration, err := parseEscrowDuration(respBody)
+ if err != nil {
+ return nil, newSteamErrorf("failed to parse escrow duration: %v", err)
+ }
+ return escrowDuration, nil
+}
+
+func (c *Client) GetOfferWithRetry(offerId uint64, retryCount int, retryDelay time.Duration) (*TradeOfferResult, error) {
+ var res *TradeOfferResult
+ return res, withRetry(
+ func() (err error) {
+ res, err = c.GetOffer(offerId)
+ return err
+ }, retryCount, retryDelay)
+}
+
+func (c *Client) GetOffersWithRetry(getSent bool, getReceived bool, getDescriptions bool, activeOnly bool, historicalOnly bool, timeHistoricalCutoff *uint32, retryCount int, retryDelay time.Duration) (*TradeOffersResult, error) {
+ var res *TradeOffersResult
+ return res, withRetry(
+ func() (err error) {
+ res, err = c.GetOffers(getSent, getReceived, getDescriptions, activeOnly, historicalOnly, timeHistoricalCutoff)
+ return err
+ }, retryCount, retryDelay)
+}
+
+func (c *Client) DeclineWithRetry(offerId uint64, retryCount int, retryDelay time.Duration) error {
+ return withRetry(
+ func() error {
+ return c.Decline(offerId)
+ }, retryCount, retryDelay)
+}
+
+func (c *Client) CancelWithRetry(offerId uint64, retryCount int, retryDelay time.Duration) error {
+ return withRetry(
+ func() error {
+ return c.Cancel(offerId)
+ }, retryCount, retryDelay)
+}
+
+func (c *Client) AcceptWithRetry(offerId uint64, retryCount int, retryDelay time.Duration) error {
+ return withRetry(
+ func() error {
+ return c.Accept(offerId)
+ }, retryCount, retryDelay)
+}
+
+func (c *Client) CreateWithRetry(other steamid.SteamId, accessToken *string, myItems, theirItems []TradeItem, counteredOfferId *uint64, message string, retryCount int, retryDelay time.Duration) (uint64, error) {
+ var res uint64
+ return res, withRetry(
+ func() (err error) {
+ res, err = c.Create(other, accessToken, myItems, theirItems, counteredOfferId, message)
+ return err
+ }, retryCount, retryDelay)
+}
+
+func (c *Client) GetOwnInventoryWithRetry(contextId uint64, appId uint32, retryCount int, retryDelay time.Duration) (*inventory.Inventory, error) {
+ var res *inventory.Inventory
+ return res, withRetry(
+ func() (err error) {
+ res, err = c.GetOwnInventory(contextId, appId)
+ return err
+ }, retryCount, retryDelay)
+}
+
+func (c *Client) GetPartnerInventoryWithRetry(other steamid.SteamId, contextId uint64, appId uint32, offerId *uint64, retryCount int, retryDelay time.Duration) (*inventory.Inventory, error) {
+ var res *inventory.Inventory
+ return res, withRetry(
+ func() (err error) {
+ res, err = c.GetPartnerInventory(other, contextId, appId, offerId)
+ return err
+ }, retryCount, retryDelay)
+}
+
+func (c *Client) GetTradeReceiptWithRetry(tradeId uint64, retryCount int, retryDelay time.Duration) ([]*TradeReceiptItem, error) {
+ var res []*TradeReceiptItem
+ return res, withRetry(
+ func() (err error) {
+ res, err = c.GetTradeReceipt(tradeId)
+ return err
+ }, retryCount, retryDelay)
+}
+
+func withRetry(f func() error, retryCount int, retryDelay time.Duration) error {
+ if retryCount <= 0 {
+ panic("retry count must be more than 0")
+ }
+ i := 0
+ for {
+ i++
+ if err := f(); err != nil {
+ // If we got steam error do not retry
+ if _, ok := err.(*SteamError); ok {
+ return err
+ }
+ if i == retryCount {
+ return err
+ }
+ time.Sleep(retryDelay)
+ continue
+ }
+ break
+ }
+ return nil
+}
diff --git a/vendor/github.com/Philipp15b/go-steam/tradeoffer/error.go b/vendor/github.com/Philipp15b/go-steam/tradeoffer/error.go
new file mode 100644
index 00000000..439ee69c
--- /dev/null
+++ b/vendor/github.com/Philipp15b/go-steam/tradeoffer/error.go
@@ -0,0 +1,19 @@
+package tradeoffer
+
+import (
+ "fmt"
+)
+
+// SteamError can be returned by Create, Accept, Decline and Cancel methods.
+// It means we got response from steam, but it was in unknown format
+// or request was declined.
+type SteamError struct {
+ msg string
+}
+
+func (e *SteamError) Error() string {
+ return e.msg
+}
+func newSteamErrorf(format string, a ...interface{}) *SteamError {
+ return &SteamError{fmt.Sprintf(format, a...)}
+}
diff --git a/vendor/github.com/Philipp15b/go-steam/tradeoffer/escrow.go b/vendor/github.com/Philipp15b/go-steam/tradeoffer/escrow.go
new file mode 100644
index 00000000..07806716
--- /dev/null
+++ b/vendor/github.com/Philipp15b/go-steam/tradeoffer/escrow.go
@@ -0,0 +1,47 @@
+package tradeoffer
+
+import (
+ "errors"
+ "fmt"
+ "regexp"
+ "strconv"
+)
+
+type EscrowDuration struct {
+ DaysMyEscrow uint32
+ DaysTheirEscrow uint32
+}
+
+func parseEscrowDuration(data []byte) (*EscrowDuration, error) {
+ // TODO: why we are using case insensitive matching?
+ myRegex := regexp.MustCompile("(?i)g_daysMyEscrow[\\s=]+(\\d+);")
+ theirRegex := regexp.MustCompile("(?i)g_daysTheirEscrow[\\s=]+(\\d+);")
+
+ myM := myRegex.FindSubmatch(data)
+ theirM := theirRegex.FindSubmatch(data)
+
+ if myM == nil || theirM == nil {
+ // check if access token is valid
+ notFriendsRegex := regexp.MustCompile(">You are not friends with this user<")
+ notFriendsM := notFriendsRegex.FindSubmatch(data)
+ if notFriendsM == nil {
+ return nil, errors.New("regexp does not match")
+ } else {
+ return nil, errors.New("you are not friends with this user")
+ }
+ }
+
+ myEscrow, err := strconv.ParseUint(string(myM[1]), 10, 32)
+ if err != nil {
+ return nil, fmt.Errorf("failed to parse my duration into uint: %v", err)
+ }
+ theirEscrow, err := strconv.ParseUint(string(theirM[1]), 10, 32)
+ if err != nil {
+ return nil, fmt.Errorf("failed to parse their duration into uint: %v", err)
+ }
+
+ return &EscrowDuration{
+ DaysMyEscrow: uint32(myEscrow),
+ DaysTheirEscrow: uint32(theirEscrow),
+ }, nil
+}
diff --git a/vendor/github.com/Philipp15b/go-steam/tradeoffer/receipt.go b/vendor/github.com/Philipp15b/go-steam/tradeoffer/receipt.go
new file mode 100644
index 00000000..3f4b556d
--- /dev/null
+++ b/vendor/github.com/Philipp15b/go-steam/tradeoffer/receipt.go
@@ -0,0 +1,35 @@
+package tradeoffer
+
+import (
+ "encoding/json"
+ "fmt"
+ "github.com/Philipp15b/go-steam/economy/inventory"
+ "regexp"
+)
+
+type TradeReceiptItem struct {
+ AssetId uint64 `json:"id,string"`
+ AppId uint32
+ ContextId uint64
+ Owner uint64 `json:",string"`
+ Pos uint32
+ inventory.Description
+}
+
+func parseTradeReceipt(data []byte) ([]*TradeReceiptItem, error) {
+ reg := regexp.MustCompile("oItem =\\s+(.+?});")
+ itemMatches := reg.FindAllSubmatch(data, -1)
+ if itemMatches == nil {
+ return nil, fmt.Errorf("items not found\n")
+ }
+ items := make([]*TradeReceiptItem, 0, len(itemMatches))
+ for _, m := range itemMatches {
+ item := new(TradeReceiptItem)
+ err := json.Unmarshal(m[1], &item)
+ if err != nil {
+ return nil, err
+ }
+ items = append(items, item)
+ }
+ return items, nil
+}
diff --git a/vendor/github.com/Philipp15b/go-steam/tradeoffer/tradeoffer.go b/vendor/github.com/Philipp15b/go-steam/tradeoffer/tradeoffer.go
new file mode 100644
index 00000000..1cf3aaa3
--- /dev/null
+++ b/vendor/github.com/Philipp15b/go-steam/tradeoffer/tradeoffer.go
@@ -0,0 +1,118 @@
+/*
+Implements methods to interact with the official Trade Offer API.
+
+See: https://developer.valvesoftware.com/wiki/Steam_Web_API/IEconService
+*/
+package tradeoffer
+
+import (
+ "encoding/json"
+ "github.com/Philipp15b/go-steam/economy/inventory"
+ "github.com/Philipp15b/go-steam/steamid"
+)
+
+type TradeOfferState uint
+
+const (
+ TradeOfferState_Invalid TradeOfferState = 1 // Invalid
+ TradeOfferState_Active = 2 // This trade offer has been sent, neither party has acted on it yet.
+ TradeOfferState_Accepted = 3 // The trade offer was accepted by the recipient and items were exchanged.
+ TradeOfferState_Countered = 4 // The recipient made a counter offer
+ TradeOfferState_Expired = 5 // The trade offer was not accepted before the expiration date
+ TradeOfferState_Canceled = 6 // The sender cancelled the offer
+ TradeOfferState_Declined = 7 // The recipient declined the offer
+ TradeOfferState_InvalidItems = 8 // Some of the items in the offer are no longer available (indicated by the missing flag in the output)
+ TradeOfferState_CreatedNeedsConfirmation = 9 // The offer hasn't been sent yet and is awaiting email/mobile confirmation. The offer is only visible to the sender.
+ TradeOfferState_CanceledBySecondFactor = 10 // 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.
+ TradeOfferState_InEscrow = 11 // 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.
+)
+
+type TradeOfferConfirmationMethod uint
+
+const (
+ TradeOfferConfirmationMethod_Invalid TradeOfferConfirmationMethod = 0
+ TradeOfferConfirmationMethod_Email = 1
+ TradeOfferConfirmationMethod_MobileApp = 2
+)
+
+type Asset struct {
+ AppId uint32 `json:",string"`
+ ContextId uint64 `json:",string"`
+ AssetId uint64 `json:",string"`
+ CurrencyId uint64 `json:",string"`
+ ClassId uint64 `json:",string"`
+ InstanceId uint64 `json:",string"`
+ Amount uint64 `json:",string"`
+ Missing bool
+}
+
+type TradeOffer struct {
+ TradeOfferId uint64 `json:",string"`
+ TradeId uint64 `json:",string"`
+ OtherAccountId uint32 `json:"accountid_other"`
+ OtherSteamId steamid.SteamId `json:"-"`
+ Message string `json:"message"`
+ ExpirationTime uint32 `json:"expiraton_time"`
+ State TradeOfferState `json:"trade_offer_state"`
+ ToGive []*Asset `json:"items_to_give"`
+ ToReceive []*Asset `json:"items_to_receive"`
+ IsOurOffer bool `json:"is_our_offer"`
+ TimeCreated uint32 `json:"time_created"`
+ TimeUpdated uint32 `json:"time_updated"`
+ EscrowEndDate uint32 `json:"escrow_end_date"`
+ ConfirmationMethod TradeOfferConfirmationMethod `json:"confirmation_method"`
+}
+
+func (t *TradeOffer) UnmarshalJSON(data []byte) error {
+ type Alias TradeOffer
+ aux := struct {
+ *Alias
+ }{
+ Alias: (*Alias)(t),
+ }
+ if err := json.Unmarshal(data, &aux); err != nil {
+ return err
+ }
+ if t.OtherAccountId == 0 {
+ t.OtherSteamId = steamid.SteamId(0)
+ return nil
+ }
+ t.OtherSteamId = steamid.SteamId(uint64(t.OtherAccountId) + 76561197960265728)
+ return nil
+}
+
+type TradeOffersResult struct {
+ Sent []*TradeOffer `json:"trade_offers_sent"`
+ Received []*TradeOffer `json:"trade_offers_received"`
+ Descriptions []*Description
+}
+
+type TradeOfferResult struct {
+ Offer *TradeOffer
+ Descriptions []*Description
+}
+type Description struct {
+ AppId uint32 `json:"appid"`
+ ClassId uint64 `json:"classid,string"`
+ InstanceId uint64 `json:"instanceid,string"`
+
+ IconUrl string `json:"icon_url"`
+ IconUrlLarge string `json:"icon_url_large"`
+
+ Name string
+ MarketName string `json:"market_name"`
+ MarketHashName string `json:"market_hash_name"`
+
+ // Colors in hex, for example `B2B2B2`
+ NameColor string `json:"name_color"`
+ BackgroundColor string `json:"background_color"`
+
+ Type string
+
+ Tradable bool `json:"tradable"`
+ Commodity bool `json:"commodity"`
+ MarketTradableRestriction uint32 `json:"market_tradable_restriction"`
+
+ Descriptions inventory.DescriptionLines `json:"descriptions"`
+ Actions []*inventory.Action `json:"actions"`
+}