<commit_before>// Copyright 2014 Hajime Hoshi
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package ebiten

import (
	"github.com/hajimehoshi/ebiten/internal/opengl"
	"github.com/hajimehoshi/ebiten/internal/ui"
	"time"
)

// Run runs the game.
// f is a function which is called at every frame.
// The argument (*Image) is the render target that represents the screen.
//
// This function must be called from the main thread.
//
// The given function f is expected to be called 60 times a second,
// but this is not strictly guaranteed.
// If you need to care about time, you need to check current time every time f is called.
func Run(f func(*Image) error, width, height, scale int, title string) error {
	actualScale, err := ui.Start(width, height, scale, title)
	if err != nil {
		return err
	}
	defer ui.Terminate()

	var graphicsContext *graphicsContext
	ui.Use(func(c *opengl.Context) {
		graphicsContext, err = newGraphicsContext(c, width, height, actualScale)
	})
	if err != nil {
		return err
	}

	for {
		// To avoid busy loop when the window is inactive, wait 1/120 [sec] at least.
		ch := time.After(1 * time.Second / 120)
		ui.DoEvents()
		if ui.IsClosed() {
			return nil
		}
		ui.Use(func(*opengl.Context) {
			err = graphicsContext.preUpdate()
		})
		if err != nil {
			return err
		}
		if err := f(&Image{inner: graphicsContext.screen}); err != nil {
			return err
		}
		ui.Use(func(*opengl.Context) {
			err = graphicsContext.postUpdate()
			if err != nil {
				return
			}
			ui.SwapBuffers()
		})
		if err != nil {
			return err
		}
		<-ch
	}
}
<commit_msg>More precise game loop<commit_after>// Copyright 2014 Hajime Hoshi
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package ebiten

import (
	"github.com/hajimehoshi/ebiten/internal/opengl"
	"github.com/hajimehoshi/ebiten/internal/ui"
	"time"
)

var fps = 0

// Run runs the game.
// f is a function which is called at every frame.
// The argument (*Image) is the render target that represents the screen.
//
// This function must be called from the main thread.
//
// The given function f is expected to be called 60 times a second,
// but this is not strictly guaranteed.
// If you need to care about time, you need to check current time every time f is called.
func Run(f func(*Image) error, width, height, scale int, title string) error {
	actualScale, err := ui.Start(width, height, scale, title)
	if err != nil {
		return err
	}
	defer ui.Terminate()

	var graphicsContext *graphicsContext
	ui.Use(func(c *opengl.Context) {
		graphicsContext, err = newGraphicsContext(c, width, height, actualScale)
	})
	if err != nil {
		return err
	}

	frames := 0
	t0 := time.Now().UnixNano()
	tt0 := t0
	for {
		ui.DoEvents()
		if ui.IsClosed() {
			return nil
		}
		ui.Use(func(*opengl.Context) {
			err = graphicsContext.preUpdate()
		})
		if err != nil {
			return err
		}
		if err := f(&Image{inner: graphicsContext.screen}); err != nil {
			return err
		}
		ui.Use(func(*opengl.Context) {
			err = graphicsContext.postUpdate()
			if err != nil {
				return
			}
			ui.SwapBuffers()
		})
		if err != nil {
			return err
		}

		// Wait if the frame is too fast.
		now := time.Now().UnixNano()
		d := time.Duration(now - t0)
		if d < time.Second/90 {
			time.Sleep(time.Second/60 - d)
		}
		t0 = now

		// Calc the current FPS.
		frames++
		if time.Second <= time.Duration(now-tt0) {
			fps = frames
			tt0 = now
			frames = 0
		}
	}
}
<|endoftext|>


<commit_before>package kubernetes

import (
	"context"
	"fmt"
	"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
	"k8s.io/api/certificates/v1beta1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/util/retry"
	"log"
	"reflect"
	"time"
)

func resourceKubernetesCertificateSigningRequest() *schema.Resource {
	apiDoc := v1beta1.CertificateSigningRequest{}.SwaggerDoc()
	apiDocSpec := v1beta1.CertificateSigningRequestSpec{}.SwaggerDoc()
	apiDocStatus := v1beta1.CertificateSigningRequestStatus{}.SwaggerDoc()

	return &schema.Resource{
		CreateContext: resourceKubernetesCertificateSigningRequestCreate,
		ReadContext:   resourceKubernetesCertificateSigningRequestRead,
		DeleteContext: resourceKubernetesCertificateSigningRequestDelete,
		Importer: &schema.ResourceImporter{
			StateContext: schema.ImportStatePassthroughContext,
		},
		Timeouts: &schema.ResourceTimeout{
			Create: schema.DefaultTimeout(5 * time.Minute),
		},
		Schema: map[string]*schema.Schema{
			"auto_approve": {
				Type:        schema.TypeBool,
				Description: "Automatically approve the CertificateSigningRequest",
				Optional:    true,
				ForceNew:    true,
				Default:     true,
			},
			"certificate": {
				Type:        schema.TypeString,
				Description: apiDocStatus["certificate"],
				Computed:    true,
			},
			"metadata": metadataSchemaForceNew(metadataSchema("certificate signing request", true)),
			"spec": {
				ForceNew:    true,
				Type:        schema.TypeList,
				Description: apiDoc[""],
				Required:    true,
				MaxItems:    1,
				Elem: &schema.Resource{
					Schema: map[string]*schema.Schema{
						"request": {
							Type:        schema.TypeString,
							Description: apiDocSpec["request"],
							Required:    true,
							ForceNew:    true,
						},
						"signer_name": {
							Type: schema.TypeString,
							// no swagger doc available for signerName
							Description: "Requested signer for the request. It is a qualified name in the form: `scope-hostname.io/name`." +
								"If empty, it will be defaulted: 1. If it's a kubelet client certificate, it is assigned `kubernetes.io/kube-apiserver-client-kubelet`." +
								"2. If it's a kubelet serving certificate, it is assigned `kubernetes.io/kubelet-serving`." +
								"3. Otherwise, it is assigned `kubernetes.io/legacy-unknown`. Distribution of trust for signers happens out of band." +
								"You can select on this field using `spec.signerName`.",
							Optional: true,
							ForceNew: true,
						},
						"usages": {
							Type:        schema.TypeSet,
							Description: apiDocSpec["usages"],
							Set:         schema.HashString,
							Elem:        &schema.Schema{Type: schema.TypeString},
							Optional:    true,
							ForceNew:    true,
						},
					},
				},
			},
		},
	}
}

func resourceKubernetesCertificateSigningRequestCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
	conn, err := meta.(KubeClientsets).MainClientset()
	if err != nil {
		return diag.FromErr(err)
	}

	metadata := expandMetadata(d.Get("metadata").([]interface{}))
	spec, err := expandCertificateSigningRequestSpec(d.Get("spec").([]interface{}))
	if err != nil {
		return diag.FromErr(err)
	}

	csr := v1beta1.CertificateSigningRequest{
		ObjectMeta: metadata,
		Spec:       *spec,
	}
	log.Printf("[INFO] Creating new certificate signing request: %#v", csr)
	newCSR, createErr := conn.CertificatesV1beta1().CertificateSigningRequests().Create(ctx, &csr, metav1.CreateOptions{})
	if createErr != nil {
		return diag.Errorf("Failed to create certificate signing request: %s", err)
	}

	// Get the name, since it might have been randomly generated during create.
	csrName := newCSR.ObjectMeta.Name

	// Delete the remote CSR resource when this function exits, or when errors are encountered.
	defer conn.CertificatesV1beta1().CertificateSigningRequests().Delete(ctx, csrName, metav1.DeleteOptions{})

	if d.Get("auto_approve").(bool) {
		retryErr := retry.RetryOnConflict(retry.DefaultRetry, func() error {
			pendingCSR, getErr := conn.CertificatesV1beta1().CertificateSigningRequests().Get(ctx, csrName, metav1.GetOptions{})
			if getErr != nil {
				return getErr
			}
			approval := v1beta1.CertificateSigningRequestCondition{
				Type:    "Approved",
				Reason:  "TerraformAutoApprove",
				Message: "This CSR was approved by Terraform auto_approve.",
			}
			pendingCSR.Status.Certificate = []byte{}
			pendingCSR.Status.Conditions = append(pendingCSR.Status.Conditions, approval)
			_, updateErr := conn.CertificatesV1beta1().CertificateSigningRequests().UpdateApproval(ctx, pendingCSR, metav1.UpdateOptions{})
			return updateErr
		})
		if retryErr != nil {
			panic(diag.Errorf("CSR auto-approve update failed: %v", retryErr))
		}
		fmt.Println("CSR auto-approve update succeeded")
	}

	log.Printf("[DEBUG] Waiting for certificate to be issued")
	stateConf := &resource.StateChangeConf{
		Target:  []string{"Issued"},
		Pending: []string{"", "Approved"},
		Timeout: d.Timeout(schema.TimeoutCreate),
		Refresh: func() (interface{}, string, error) {
			out, refreshErr := conn.CertificatesV1beta1().CertificateSigningRequests().Get(ctx, csrName, metav1.GetOptions{})
			if refreshErr != nil {
				log.Printf("[ERROR] Received error: %v", refreshErr)
				return out, "Error", refreshErr
			}
			var csrStatus string
			emptyStatus := v1beta1.CertificateSigningRequestStatus{}
			emptyCSR := v1beta1.CertificateSigningRequest{}

			// If the CSR is empty, check again later.
			if reflect.DeepEqual(emptyCSR, out) {
				return out, csrStatus, nil
			}

			// If the status is empty, check again later.
			if reflect.DeepEqual(emptyStatus, out.Status) {
				return out, csrStatus, nil
			}

			// Check to see if a certificate has been issued, and update status accordingly,
			// since 'Issued' is not a state ever populated in the Status Conditions.
			for _, condition := range out.Status.Conditions {
				log.Printf("[DEBUG] Found Status.Condition.Type: %v", condition.Type)
				if string(condition.Type) == "Approved" {
					if string(out.Status.Certificate) != "" {
						log.Printf("[DEBUG] Found non-empty Certificate field in Status")
						csrStatus = "Issued"
					}
				}
			}
			log.Printf("[DEBUG] CertificateSigningRequest %s status received: %#v", csrName, csrStatus)
			return out, csrStatus, nil
		},
	}
	_, err = stateConf.WaitForState()
	if err != nil {
		return diag.FromErr(err)
	}
	log.Printf("[INFO] Certificate issued for request: %s", csrName)

	issued, err := conn.CertificatesV1beta1().CertificateSigningRequests().Get(ctx, csrName, metav1.GetOptions{})
	if err != nil {
		return diag.FromErr(err)
	}

	d.SetId(csrName)
	d.Set("certificate", string(issued.Status.Certificate))

	return resourceKubernetesCertificateSigningRequestRead(ctx, d, meta)
}

// resourceKubernetesCertificateSigningRequestRead does not return any data, because Read functions exist to
// sync the local state with the remote state. Since this data is local-only, there is nothing to read.
func resourceKubernetesCertificateSigningRequestRead(ctx context.Context, _ *schema.ResourceData, _ interface{}) diag.Diagnostics {
	return diag.Diagnostics{}
}

func resourceKubernetesCertificateSigningRequestDelete(ctx context.Context, d *schema.ResourceData, _ interface{}) diag.Diagnostics {
	d.SetId("")
	return diag.Diagnostics{}
}
<commit_msg>Create CSR returns the wrong error (#1206)<commit_after>package kubernetes

import (
	"context"
	"fmt"
	"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
	"k8s.io/api/certificates/v1beta1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/util/retry"
	"log"
	"reflect"
	"time"
)

func resourceKubernetesCertificateSigningRequest() *schema.Resource {
	apiDoc := v1beta1.CertificateSigningRequest{}.SwaggerDoc()
	apiDocSpec := v1beta1.CertificateSigningRequestSpec{}.SwaggerDoc()
	apiDocStatus := v1beta1.CertificateSigningRequestStatus{}.SwaggerDoc()

	return &schema.Resource{
		CreateContext: resourceKubernetesCertificateSigningRequestCreate,
		ReadContext:   resourceKubernetesCertificateSigningRequestRead,
		DeleteContext: resourceKubernetesCertificateSigningRequestDelete,
		Importer: &schema.ResourceImporter{
			StateContext: schema.ImportStatePassthroughContext,
		},
		Timeouts: &schema.ResourceTimeout{
			Create: schema.DefaultTimeout(5 * time.Minute),
		},
		Schema: map[string]*schema.Schema{
			"auto_approve": {
				Type:        schema.TypeBool,
				Description: "Automatically approve the CertificateSigningRequest",
				Optional:    true,
				ForceNew:    true,
				Default:     true,
			},
			"certificate": {
				Type:        schema.TypeString,
				Description: apiDocStatus["certificate"],
				Computed:    true,
			},
			"metadata": metadataSchemaForceNew(metadataSchema("certificate signing request", true)),
			"spec": {
				ForceNew:    true,
				Type:        schema.TypeList,
				Description: apiDoc[""],
				Required:    true,
				MaxItems:    1,
				Elem: &schema.Resource{
					Schema: map[string]*schema.Schema{
						"request": {
							Type:        schema.TypeString,
							Description: apiDocSpec["request"],
							Required:    true,
							ForceNew:    true,
						},
						"signer_name": {
							Type: schema.TypeString,
							// no swagger doc available for signerName
							Description: "Requested signer for the request. It is a qualified name in the form: `scope-hostname.io/name`." +
								"If empty, it will be defaulted: 1. If it's a kubelet client certificate, it is assigned `kubernetes.io/kube-apiserver-client-kubelet`." +
								"2. If it's a kubelet serving certificate, it is assigned `kubernetes.io/kubelet-serving`." +
								"3. Otherwise, it is assigned `kubernetes.io/legacy-unknown`. Distribution of trust for signers happens out of band." +
								"You can select on this field using `spec.signerName`.",
							Optional: true,
							ForceNew: true,
						},
						"usages": {
							Type:        schema.TypeSet,
							Description: apiDocSpec["usages"],
							Set:         schema.HashString,
							Elem:        &schema.Schema{Type: schema.TypeString},
							Optional:    true,
							ForceNew:    true,
						},
					},
				},
			},
		},
	}
}

func resourceKubernetesCertificateSigningRequestCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
	conn, err := meta.(KubeClientsets).MainClientset()
	if err != nil {
		return diag.FromErr(err)
	}

	metadata := expandMetadata(d.Get("metadata").([]interface{}))
	spec, err := expandCertificateSigningRequestSpec(d.Get("spec").([]interface{}))
	if err != nil {
		return diag.FromErr(err)
	}

	csr := v1beta1.CertificateSigningRequest{
		ObjectMeta: metadata,
		Spec:       *spec,
	}
	log.Printf("[INFO] Creating new certificate signing request: %#v", csr)
	newCSR, createErr := conn.CertificatesV1beta1().CertificateSigningRequests().Create(ctx, &csr, metav1.CreateOptions{})
	if createErr != nil {
		return diag.Errorf("Failed to create certificate signing request: %s", createErr)
	}

	// Get the name, since it might have been randomly generated during create.
	csrName := newCSR.ObjectMeta.Name

	// Delete the remote CSR resource when this function exits, or when errors are encountered.
	defer conn.CertificatesV1beta1().CertificateSigningRequests().Delete(ctx, csrName, metav1.DeleteOptions{})

	if d.Get("auto_approve").(bool) {
		retryErr := retry.RetryOnConflict(retry.DefaultRetry, func() error {
			pendingCSR, getErr := conn.CertificatesV1beta1().CertificateSigningRequests().Get(ctx, csrName, metav1.GetOptions{})
			if getErr != nil {
				return getErr
			}
			approval := v1beta1.CertificateSigningRequestCondition{
				Type:    "Approved",
				Reason:  "TerraformAutoApprove",
				Message: "This CSR was approved by Terraform auto_approve.",
			}
			pendingCSR.Status.Certificate = []byte{}
			pendingCSR.Status.Conditions = append(pendingCSR.Status.Conditions, approval)
			_, updateErr := conn.CertificatesV1beta1().CertificateSigningRequests().UpdateApproval(ctx, pendingCSR, metav1.UpdateOptions{})
			return updateErr
		})
		if retryErr != nil {
			panic(diag.Errorf("CSR auto-approve update failed: %v", retryErr))
		}
		fmt.Println("CSR auto-approve update succeeded")
	}

	log.Printf("[DEBUG] Waiting for certificate to be issued")
	stateConf := &resource.StateChangeConf{
		Target:  []string{"Issued"},
		Pending: []string{"", "Approved"},
		Timeout: d.Timeout(schema.TimeoutCreate),
		Refresh: func() (interface{}, string, error) {
			out, refreshErr := conn.CertificatesV1beta1().CertificateSigningRequests().Get(ctx, csrName, metav1.GetOptions{})
			if refreshErr != nil {
				log.Printf("[ERROR] Received error: %v", refreshErr)
				return out, "Error", refreshErr
			}
			var csrStatus string
			emptyStatus := v1beta1.CertificateSigningRequestStatus{}
			emptyCSR := v1beta1.CertificateSigningRequest{}

			// If the CSR is empty, check again later.
			if reflect.DeepEqual(emptyCSR, out) {
				return out, csrStatus, nil
			}

			// If the status is empty, check again later.
			if reflect.DeepEqual(emptyStatus, out.Status) {
				return out, csrStatus, nil
			}

			// Check to see if a certificate has been issued, and update status accordingly,
			// since 'Issued' is not a state ever populated in the Status Conditions.
			for _, condition := range out.Status.Conditions {
				log.Printf("[DEBUG] Found Status.Condition.Type: %v", condition.Type)
				if string(condition.Type) == "Approved" {
					if string(out.Status.Certificate) != "" {
						log.Printf("[DEBUG] Found non-empty Certificate field in Status")
						csrStatus = "Issued"
					}
				}
			}
			log.Printf("[DEBUG] CertificateSigningRequest %s status received: %#v", csrName, csrStatus)
			return out, csrStatus, nil
		},
	}
	_, err = stateConf.WaitForState()
	if err != nil {
		return diag.FromErr(err)
	}
	log.Printf("[INFO] Certificate issued for request: %s", csrName)

	issued, err := conn.CertificatesV1beta1().CertificateSigningRequests().Get(ctx, csrName, metav1.GetOptions{})
	if err != nil {
		return diag.FromErr(err)
	}

	d.SetId(csrName)
	d.Set("certificate", string(issued.Status.Certificate))

	return resourceKubernetesCertificateSigningRequestRead(ctx, d, meta)
}

// resourceKubernetesCertificateSigningRequestRead does not return any data, because Read functions exist to
// sync the local state with the remote state. Since this data is local-only, there is nothing to read.
func resourceKubernetesCertificateSigningRequestRead(ctx context.Context, _ *schema.ResourceData, _ interface{}) diag.Diagnostics {
	return diag.Diagnostics{}
}

func resourceKubernetesCertificateSigningRequestDelete(ctx context.Context, d *schema.ResourceData, _ interface{}) diag.Diagnostics {
	d.SetId("")
	return diag.Diagnostics{}
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"math/rand"
	"time"
)

// Tournament is the main container of data for this app.
type Tournament struct {
	Players     []Player
	Judges      []Judge
	Tryouts     []Match
	Semis       []Match
	Final       Match
	Opened      time.Time
	Started     time.Time
	Ended       time.Time
	length      int
	finalLength int
}

// NewTournament returns a completely new Tournament
func NewTournament() (*Tournament, error) {
	t := Tournament{
		Opened: time.Now(),
	}
	return &t, nil
}

// StartTournament will generate the tournament.
//
// This includes:
//  Generating Tryout matches
//  Setting Started date
//
// It will fail if there are not between 8 and 24 players.
func (t *Tournament) StartTournament() error {
	if len(t.Players) < 8 {
		return fmt.Errorf("Tournament needs at least 8 players, got %s", t.Players)
	}
	if len(t.Players) > 24 {
		return fmt.Errorf("Tournament can only host 24 players, got %s", t.Players)
	}

	// Generate tryouts and semis
	err := t.GenerateMatches()
	if err != nil {
		return err
	}

	// Populate the matches with players
	perr := t.PopulateMatches()
	if perr != nil {
		return perr
	}

	t.Started = time.Now()
	return nil
}

// GenerateMatches will generate all the matches for the semis
//
// The tournament model as it stands right now can handle matches sets of
// 2, 4 and 6 matches. If the amount of players do not match, add sets of
// two matches with runnerups.
func (t *Tournament) GenerateMatches() error {
	var tryouts int

	switch len(t.Players) {
	case 8:
		tryouts = 0
	case 9, 10, 11, 12, 13, 14, 15, 16:
		tryouts = 4
	default:
		tryouts = 6
	}

	for i := 0; i < tryouts; i++ {
		m := NewMatch()
		t.Tryouts = append(t.Tryouts, m)
	}

	// Right now there are only cases where we have two matches in the semis.
	t.Semis = []Match{NewMatch(), NewMatch()}
	t.Final = NewMatch()

	return nil
}

// PopulateMatches shuffles players into the matches
func (t *Tournament) PopulateMatches() error {
	// Make a copy of the players list and shuffle it
	slice := t.Players
	for i := range slice {
		j := rand.Intn(i + 1)
		slice[i], slice[j] = slice[j], slice[i]
	}

	// The case of eight players is different since they are to go directly
	// to the finals.
	if len(slice) == 8 {
		for x, p := range slice {
			t.Semis[x/4].AddPlayer(p)
		}
		return nil
	}

	// Fill the tryouts as much as possible!
	for x, p := range slice {
		t.Tryouts[x/4].AddPlayer(p)
	}

	return nil
}

func main() {
	fmt.Println("...and thus there was light.")
}
<commit_msg>Fix go vet errors<commit_after>package main

import (
	"fmt"
	"math/rand"
	"time"
)

// Tournament is the main container of data for this app.
type Tournament struct {
	Players     []Player
	Judges      []Judge
	Tryouts     []Match
	Semis       []Match
	Final       Match
	Opened      time.Time
	Started     time.Time
	Ended       time.Time
	length      int
	finalLength int
}

// NewTournament returns a completely new Tournament
func NewTournament() (*Tournament, error) {
	t := Tournament{
		Opened: time.Now(),
	}
	return &t, nil
}

// StartTournament will generate the tournament.
//
// This includes:
//  Generating Tryout matches
//  Setting Started date
//
// It will fail if there are not between 8 and 24 players.
func (t *Tournament) StartTournament() error {
	ps := len(t.Players)
	if ps < 8 {
		return fmt.Errorf("Tournament needs at least 8 players, got %d", ps)
	}
	if ps > 24 {
		return fmt.Errorf("Tournament can only host 24 players, got %d", ps)
	}

	// Generate tryouts and semis
	err := t.GenerateMatches()
	if err != nil {
		return err
	}

	// Populate the matches with players
	perr := t.PopulateMatches()
	if perr != nil {
		return perr
	}

	t.Started = time.Now()
	return nil
}

// GenerateMatches will generate all the matches for the semis
//
// The tournament model as it stands right now can handle matches sets of
// 2, 4 and 6 matches. If the amount of players do not match, add sets of
// two matches with runnerups.
func (t *Tournament) GenerateMatches() error {
	var tryouts int

	switch len(t.Players) {
	case 8:
		tryouts = 0
	case 9, 10, 11, 12, 13, 14, 15, 16:
		tryouts = 4
	default:
		tryouts = 6
	}

	for i := 0; i < tryouts; i++ {
		m := NewMatch()
		t.Tryouts = append(t.Tryouts, m)
	}

	// Right now there are only cases where we have two matches in the semis.
	t.Semis = []Match{NewMatch(), NewMatch()}
	t.Final = NewMatch()

	return nil
}

// PopulateMatches shuffles players into the matches
func (t *Tournament) PopulateMatches() error {
	// Make a copy of the players list and shuffle it
	slice := t.Players
	for i := range slice {
		j := rand.Intn(i + 1)
		slice[i], slice[j] = slice[j], slice[i]
	}

	// The case of eight players is different since they are to go directly
	// to the finals.
	if len(slice) == 8 {
		for x, p := range slice {
			t.Semis[x/4].AddPlayer(p)
		}
		return nil
	}

	// Fill the tryouts as much as possible!
	for x, p := range slice {
		t.Tryouts[x/4].AddPlayer(p)
	}

	return nil
}

func main() {
	fmt.Println("...and thus there was light.")
}
<|endoftext|>


<commit_before>// Package scp provides a simple interface to copying files over a
// go.crypto/ssh session.
package scp

import (
	"fmt"
	"io"
	"os"
	"path"

	"golang.org/x/crypto/ssh"
)

func Copy(size int64, mode os.FileMode, fileName string, contents io.Reader, destinationPath string, session *ssh.Session) error {
	return copy(size, mode, fileName, contents, destinationPath, session)
}

func CopyPath(filePath, destinationPath string, session *ssh.Session) error {
	f, err := os.Open(filePath)
	if err != nil {
		return err
	}
	defer f.Close()
	s, err := f.Stat()
	if err != nil {
		return err
	}
	return copy(s.Size(), s.Mode().Perm(), path.Base(filePath), f, destinationPath, session)
}

func copy(size int64, mode os.FileMode, fileName string, contents io.Reader, destination string, session *ssh.Session) error {
	defer session.Close()
	w, err := session.StdinPipe()

	if err != nil {
		return err
	}

	cmd := fmt.Sprintf("scp -t %s", destination)
	if err := session.Start(cmd); err != nil {
		return err
	}

	errors := make(chan error)

	go func() {
		errors <- session.Wait()
	}()

	fmt.Fprintf(w, "C%#o %d %s\n", mode, size, fileName)
	io.Copy(w, contents)
	fmt.Fprint(w, "\x00")
	w.Close()

	return <-errors
}
<commit_msg>Fixed: close writer if the start of the session returns an error'<commit_after>// Package scp provides a simple interface to copying files over a
// go.crypto/ssh session.
package scp

import (
	"fmt"
	"io"
	"os"
	"path"

	"golang.org/x/crypto/ssh"
)

func Copy(size int64, mode os.FileMode, fileName string, contents io.Reader, destinationPath string, session *ssh.Session) error {
	return copy(size, mode, fileName, contents, destinationPath, session)
}

func CopyPath(filePath, destinationPath string, session *ssh.Session) error {
	f, err := os.Open(filePath)
	if err != nil {
		return err
	}
	defer f.Close()
	s, err := f.Stat()
	if err != nil {
		return err
	}
	return copy(s.Size(), s.Mode().Perm(), path.Base(filePath), f, destinationPath, session)
}

func copy(size int64, mode os.FileMode, fileName string, contents io.Reader, destination string, session *ssh.Session) error {
	defer session.Close()
	w, err := session.StdinPipe()

	if err != nil {
		return err
	}

	cmd := fmt.Sprintf("scp -t %s", destination)
	if err := session.Start(cmd); err != nil {
		w.Close()
		return err
	}

	errors := make(chan error)

	go func() {
		errors <- session.Wait()
	}()

	fmt.Fprintf(w, "C%#o %d %s\n", mode, size, fileName)
	io.Copy(w, contents)
	fmt.Fprint(w, "\x00")
	w.Close()

	return <-errors
}
<|endoftext|>


<commit_before>package steam

import (
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"regexp"
	"strconv"
	"strings"
	"time"
)

const (
	TradeStateNone = iota
	TradeStateInvalid
	TradeStateActive
	TradeStateAccepted
	TradeStateCountered
	TradeStateExpired
	TradeStateCanceled
	TradeStateDeclined
	TradeStateInvalidItems
	TradeStateCreatedNeedsConfirmation
	TradeStatePendingConfirmation
	TradeStateEmailPending
	TradeStateCanceledByTwoFactor
	TradeStateCanceledConfirmation
	TradeStateEmailCanceled
	TradeStateInEscrow
)

const (
	TradeConfirmationNone = iota
	TradeConfirmationEmail
	TradeConfirmationMobileApp
	TradeConfirmationMobile
)

const (
	TradeFilterNone           = iota
	TradeFilterSentOffers     = 1 << 0
	TradeFilterRecvOffers     = 1 << 1
	TradeFilterActiveOnly     = 1 << 3
	TradeFilterHistoricalOnly = 1 << 4
)

var (
	// receiptExp matches JSON in the following form:
	//	oItem = {"id":"...",...}; (Javascript code)
	receiptExp    = regexp.MustCompile("oItem =\\s(.+?});")
	myEscrowExp   = regexp.MustCompile("var g_daysMyEscrow = (\\d+);")
	themEscrowExp = regexp.MustCompile("var g_daysTheirEscrow = (\\d+);")
	offerInfoExp  = regexp.MustCompile("token=([a-zA-Z0-9-_]+)")
	apiCallURL    = "https://api.steampowered.com/IEconService/"

	ErrReceiptMatch        = errors.New("unable to match items in trade receipt")
	ErrCannotAcceptActive  = errors.New("unable to accept a non-active trade")
	ErrCannotFindOfferInfo = errors.New("unable to match data from trade offer url")
)

type EconItem struct {
	AssetID    uint64 `json:"assetid,string,omitempty"`
	InstanceID uint64 `json:"instanceid,string,omitempty"`
	ClassID    uint64 `json:"classid,string,omitempty"`
	AppID      uint32 `json:"appid,string"`
	ContextID  uint64 `json:"contextid,string"`
	Amount     uint16 `json:"amount,string"`
	Name       string `json:"name,string"` // Will be used for item descriptions, do *not* remove
	Missing    bool   `json:"missing,omitempty"`
}

type TradeOffer struct {
	ID                 uint64      `json:"tradeofferid,string"`
	Partner            uint32      `json:"accountid_other"`
	ReceiptID          uint64      `json:"tradeid,string"`
	ReceiveItems       []*EconItem `json:"items_to_receive"`
	SendItems          []*EconItem `json:"items_to_give"`
	Message            string      `json:"message"`
	State              uint8       `json:"trade_offer_state"`
	ConfirmationMethod uint8       `json:"confirmation_method"`
	Created            int64       `json:"time_created"`
	Updated            int64       `json:"time_updated"`
	Expires            int64       `json:"expiration_time"`
	EscrowEndDate      int64       `json:"escrow_end_date"`
	RealTime           bool        `json:"from_real_time_trade"`
	IsOurOffer         bool        `json:"is_our_offer"`
}

type TradeOfferResponse struct {
	Offer          *TradeOffer   `json:"offer"`                 // GetTradeOffer
	SentOffers     []*TradeOffer `json:"trade_offers_sent"`     // GetTradeOffers
	ReceivedOffers []*TradeOffer `json:"trade_offers_received"` // GetTradeOffers
}

type APIResponse struct {
	Inner TradeOfferResponse `json:"response"`
}

func (session *Session) GetTradeOffer(id uint64) (*TradeOffer, error) {
	resp, err := session.client.Get(apiCallURL + "/GetTradeOffer/v1/?" + url.Values{
		"key":          {session.apiKey},
		"tradeofferid": {strconv.FormatUint(id, 10)},
	}.Encode())
	if resp != nil {
		defer resp.Body.Close()
	}

	if err != nil {
		return nil, err
	}

	var response APIResponse
	if err = json.NewDecoder(resp.Body).Decode(&response); err != nil {
		return nil, err
	}

	return response.Inner.Offer, nil
}

func testBit(bits uint32, bit uint32) bool {
	return (bits & bit) == bit
}

func (session *Session) GetTradeOffers(filter uint32, timeCutOff time.Time) ([]*TradeOffer, []*TradeOffer, error) {
	params := url.Values{
		"key": {session.apiKey},
	}
	if testBit(filter, TradeFilterSentOffers) {
		params.Set("get_sent_offers", "1")
	}

	if testBit(filter, TradeFilterRecvOffers) {
		params.Set("get_received_offers", "1")
	}

	if testBit(filter, TradeFilterActiveOnly) {
		params.Set("active_only", "1")
	}

	if testBit(filter, TradeFilterHistoricalOnly) {
		params.Set("historical_only", "1")
		params.Set("time_historical_cutoff", strconv.FormatInt(timeCutOff.Unix(), 10))
	}

	resp, err := session.client.Get(apiCallURL + "/GetTradeOffers/v1/?" + params.Encode())
	if resp != nil {
		defer resp.Body.Close()
	}

	if err != nil {
		return nil, nil, err
	}

	var response APIResponse
	if err := json.NewDecoder(resp.Body).Decode(&response); err != nil {
		return nil, nil, err
	}

	return response.Inner.SentOffers, response.Inner.ReceivedOffers, nil
}

func (session *Session) GetMyTradeToken() (string, error) {
	resp, err := session.client.Get("https://steamcommunity.com/my/tradeoffers/privacy")
	if resp != nil {
		defer resp.Body.Close()
	}

	if err != nil {
		return "", err
	}

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return "", err
	}

	m := offerInfoExp.FindStringSubmatch(string(body))
	if m == nil || len(m) < 2 {
		return "", ErrCannotFindOfferInfo
	}

	return string(m[1]), nil
}

func (session *Session) GetEscrowDuration(sid SteamID, token string) (int64, int64, error) {
	resp, err := session.client.Get("https://steamcommunity.com/tradeoffer/new/?" + url.Values{
		"partner": {strconv.FormatUint(uint64(sid.GetAccountID()), 10)},
		"token":   {token},
	}.Encode())
	if resp != nil {
		defer resp.Body.Close()
	}

	if err != nil {
		return 0, 0, err
	}

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return 0, 0, err
	}

	my := int64(0)
	m := myEscrowExp.FindStringSubmatch(string(body))
	if m != nil && len(m) == 2 {
		my, _ = strconv.ParseInt(m[1], 10, 32)
	}

	them := int64(0)
	m = themEscrowExp.FindStringSubmatch(string(body))
	if m != nil && len(m) == 2 {
		them, _ = strconv.ParseInt(m[1], 10, 32)
	}

	return my, them, nil
}

func (session *Session) SendTradeOffer(offer *TradeOffer, sid SteamID, token string) error {
	content := map[string]interface{}{
		"newversion": true,
		"version":    3,
		"me": map[string]interface{}{
			"assets":   offer.SendItems,
			"currency": make([]struct{}, 0),
			"ready":    false,
		},
		"them": map[string]interface{}{
			"assets":   offer.ReceiveItems,
			"currency": make([]struct{}, 0),
			"ready":    false,
		},
	}

	contentJSON, err := json.Marshal(content)
	if err != nil {
		return err
	}

	accessToken := map[string]string{
		"trade_offer_access_token": token,
	}
	params, err := json.Marshal(accessToken)
	if err != nil {
		return err
	}

	req, err := http.NewRequest(
		http.MethodPost,
		"https://steamcommunity.com/tradeoffer/new/send",
		strings.NewReader(url.Values{
			"sessionid":                 {session.sessionID},
			"serverid":                  {"1"},
			"partner":                   {sid.ToString()},
			"tradeoffermessage":         {offer.Message},
			"json_tradeoffer":           {string(contentJSON)},
			"trade_offer_create_params": {string(params)},
		}.Encode()),
	)
	if err != nil {
		return err
	}
	req.Header.Add("Referer", "https://steamcommunity.com/tradeoffer/new/?"+url.Values{
		"partner": {strconv.FormatUint(uint64(sid.GetAccountID()), 10)},
		"token":   {token},
	}.Encode())
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")

	resp, err := session.client.Do(req)
	if resp != nil {
		defer resp.Body.Close()
	}

	if err != nil {
		return err
	}

	type Response struct {
		ErrorMessage               string `json:"strError"`
		ID                         uint64 `json:"tradeofferid,string"`
		MobileConfirmationRequired bool   `json:"needs_mobile_confirmation"`
		EmailConfirmationRequired  bool   `json:"needs_email_confirmation"`
		EmailDomain                string `json:"email_domain"`
	}

	var response Response
	if err = json.NewDecoder(resp.Body).Decode(&response); err != nil {
		return err
	}

	if len(response.ErrorMessage) != 0 {
		return errors.New(response.ErrorMessage)
	}

	if response.ID == 0 {
		return errors.New("no OfferID included")
	}

	offer.ID = response.ID
	offer.Created = time.Now().Unix()
	offer.Updated = time.Now().Unix()

	// Just test mobile confirmation, email is deprecated
	if response.MobileConfirmationRequired {
		offer.ConfirmationMethod = TradeConfirmationMobileApp
		offer.State = TradeStateCreatedNeedsConfirmation
	} else {
		// set state to active
		offer.State = TradeStateActive
	}

	return nil
}

func (session *Session) GetTradeReceivedItems(receiptID uint64) ([]*InventoryItem, error) {
	resp, err := session.client.Get(fmt.Sprintf("https://steamcommunity.com/trade/%d/receipt", receiptID))
	if resp != nil {
		defer resp.Body.Close()
	}

	if err != nil {
		return nil, err
	}

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	m := receiptExp.FindAllSubmatch(body, -1)
	if m == nil {
		return nil, ErrReceiptMatch
	}

	items := []*InventoryItem{}
	for k := range m {
		item := &InventoryItem{}
		if err = json.Unmarshal(m[k][1], item); err != nil {
			return nil, err
		}
		items = append(items, item)
	}
	return items, nil
}

func (session *Session) DeclineTradeOffer(id uint64) error {
	resp, err := session.client.PostForm(apiCallURL+"/DeclineTradeOffer/v1/", url.Values{
		"key":          {session.apiKey},
		"tradeofferid": {strconv.FormatUint(id, 10)},
	})
	if resp != nil {
		defer resp.Body.Close()
	}

	if err != nil {
		return err
	}

	result := resp.Header.Get("x-eresult")
	if result != "1" {
		return fmt.Errorf("cannot decline trade: %s", result)
	}

	return nil
}

func (session *Session) CancelTradeOffer(id uint64) error {
	resp, err := session.client.PostForm(apiCallURL+"/CancelTradeOffer/v1/", url.Values{
		"key":          {session.apiKey},
		"tradeofferid": {strconv.FormatUint(id, 10)},
	})
	if resp != nil {
		defer resp.Body.Close()
	}

	if err != nil {
		return err
	}

	result := resp.Header.Get("x-eresult")
	if result != "1" {
		return fmt.Errorf("cannot cancel trade: %s", result)
	}

	return nil
}

func (session *Session) AcceptTradeOffer(offer *TradeOffer) error {
	if offer.State != TradeStateActive {
		return ErrCannotAcceptActive
	}

	postURL := fmt.Sprintf("https://steamcommunity.com/tradeoffer/%d", offer.ID)

	req, err := http.NewRequest(
		http.MethodPost,
		postURL,
		strings.NewReader(url.Values{
			"sessionid":    {session.sessionID},
			"serverid":     {"1"},
			"tradeofferid": {strconv.FormatUint(offer.ID, 10)},
		}.Encode()),
	)
	if err != nil {
		return err
	}

	req.Header.Add("Referer", postURL)
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")

	resp, err := session.client.Do(req)
	if resp != nil {
		defer resp.Body.Close()
	}

	if err != nil {
		return err
	}

	type Response struct {
		ErrorMessage string `json:"strError"`
	}

	var response Response
	if err = json.NewDecoder(resp.Body).Decode(&response); err != nil {
		return err
	}

	if len(response.ErrorMessage) != 0 {
		return errors.New(response.ErrorMessage)
	}

	return nil
}

func (offer *TradeOffer) Send(session *Session, sid SteamID, token string) error {
	return session.SendTradeOffer(offer, sid, token)
}

func (offer *TradeOffer) Accept(session *Session) error {
	return session.AcceptTradeOffer(offer)
}

func (offer *TradeOffer) Cancel(session *Session) error {
	if offer.IsOurOffer {
		return session.CancelTradeOffer(offer.ID)
	}

	return session.DeclineTradeOffer(offer.ID)
}
<commit_msg>tradeoffer: fix AcceptTradeOffer<commit_after>package steam

import (
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"regexp"
	"strconv"
	"strings"
	"time"
)

const (
	TradeStateNone = iota
	TradeStateInvalid
	TradeStateActive
	TradeStateAccepted
	TradeStateCountered
	TradeStateExpired
	TradeStateCanceled
	TradeStateDeclined
	TradeStateInvalidItems
	TradeStateCreatedNeedsConfirmation
	TradeStatePendingConfirmation
	TradeStateEmailPending
	TradeStateCanceledByTwoFactor
	TradeStateCanceledConfirmation
	TradeStateEmailCanceled
	TradeStateInEscrow
)

const (
	TradeConfirmationNone = iota
	TradeConfirmationEmail
	TradeConfirmationMobileApp
	TradeConfirmationMobile
)

const (
	TradeFilterNone           = iota
	TradeFilterSentOffers     = 1 << 0
	TradeFilterRecvOffers     = 1 << 1
	TradeFilterActiveOnly     = 1 << 3
	TradeFilterHistoricalOnly = 1 << 4
)

var (
	// receiptExp matches JSON in the following form:
	//	oItem = {"id":"...",...}; (Javascript code)
	receiptExp    = regexp.MustCompile("oItem =\\s(.+?});")
	myEscrowExp   = regexp.MustCompile("var g_daysMyEscrow = (\\d+);")
	themEscrowExp = regexp.MustCompile("var g_daysTheirEscrow = (\\d+);")
	offerInfoExp  = regexp.MustCompile("token=([a-zA-Z0-9-_]+)")
	apiCallURL    = "https://api.steampowered.com/IEconService/"

	ErrReceiptMatch        = errors.New("unable to match items in trade receipt")
	ErrCannotAcceptActive  = errors.New("unable to accept a non-active trade")
	ErrCannotFindOfferInfo = errors.New("unable to match data from trade offer url")
)

type EconItem struct {
	AssetID    uint64 `json:"assetid,string,omitempty"`
	InstanceID uint64 `json:"instanceid,string,omitempty"`
	ClassID    uint64 `json:"classid,string,omitempty"`
	AppID      uint32 `json:"appid,string"`
	ContextID  uint64 `json:"contextid,string"`
	Amount     uint16 `json:"amount,string"`
	Name       string `json:"name,string"` // Will be used for item descriptions, do *not* remove
	Missing    bool   `json:"missing,omitempty"`
}

type TradeOffer struct {
	ID                 uint64      `json:"tradeofferid,string"`
	Partner            uint32      `json:"accountid_other"`
	ReceiptID          uint64      `json:"tradeid,string"`
	ReceiveItems       []*EconItem `json:"items_to_receive"`
	SendItems          []*EconItem `json:"items_to_give"`
	Message            string      `json:"message"`
	State              uint8       `json:"trade_offer_state"`
	ConfirmationMethod uint8       `json:"confirmation_method"`
	Created            int64       `json:"time_created"`
	Updated            int64       `json:"time_updated"`
	Expires            int64       `json:"expiration_time"`
	EscrowEndDate      int64       `json:"escrow_end_date"`
	RealTime           bool        `json:"from_real_time_trade"`
	IsOurOffer         bool        `json:"is_our_offer"`
}

type TradeOfferResponse struct {
	Offer          *TradeOffer   `json:"offer"`                 // GetTradeOffer
	SentOffers     []*TradeOffer `json:"trade_offers_sent"`     // GetTradeOffers
	ReceivedOffers []*TradeOffer `json:"trade_offers_received"` // GetTradeOffers
}

type APIResponse struct {
	Inner TradeOfferResponse `json:"response"`
}

func (session *Session) GetTradeOffer(id uint64) (*TradeOffer, error) {
	resp, err := session.client.Get(apiCallURL + "/GetTradeOffer/v1/?" + url.Values{
		"key":          {session.apiKey},
		"tradeofferid": {strconv.FormatUint(id, 10)},
	}.Encode())
	if resp != nil {
		defer resp.Body.Close()
	}

	if err != nil {
		return nil, err
	}

	var response APIResponse
	if err = json.NewDecoder(resp.Body).Decode(&response); err != nil {
		return nil, err
	}

	return response.Inner.Offer, nil
}

func testBit(bits uint32, bit uint32) bool {
	return (bits & bit) == bit
}

func (session *Session) GetTradeOffers(filter uint32, timeCutOff time.Time) ([]*TradeOffer, []*TradeOffer, error) {
	params := url.Values{
		"key": {session.apiKey},
	}
	if testBit(filter, TradeFilterSentOffers) {
		params.Set("get_sent_offers", "1")
	}

	if testBit(filter, TradeFilterRecvOffers) {
		params.Set("get_received_offers", "1")
	}

	if testBit(filter, TradeFilterActiveOnly) {
		params.Set("active_only", "1")
	}

	if testBit(filter, TradeFilterHistoricalOnly) {
		params.Set("historical_only", "1")
		params.Set("time_historical_cutoff", strconv.FormatInt(timeCutOff.Unix(), 10))
	}

	resp, err := session.client.Get(apiCallURL + "/GetTradeOffers/v1/?" + params.Encode())
	if resp != nil {
		defer resp.Body.Close()
	}

	if err != nil {
		return nil, nil, err
	}

	var response APIResponse
	if err := json.NewDecoder(resp.Body).Decode(&response); err != nil {
		return nil, nil, err
	}

	return response.Inner.SentOffers, response.Inner.ReceivedOffers, nil
}

func (session *Session) GetMyTradeToken() (string, error) {
	resp, err := session.client.Get("https://steamcommunity.com/my/tradeoffers/privacy")
	if resp != nil {
		defer resp.Body.Close()
	}

	if err != nil {
		return "", err
	}

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return "", err
	}

	m := offerInfoExp.FindStringSubmatch(string(body))
	if m == nil || len(m) < 2 {
		return "", ErrCannotFindOfferInfo
	}

	return string(m[1]), nil
}

func (session *Session) GetEscrowDuration(sid SteamID, token string) (int64, int64, error) {
	resp, err := session.client.Get("https://steamcommunity.com/tradeoffer/new/?" + url.Values{
		"partner": {strconv.FormatUint(uint64(sid.GetAccountID()), 10)},
		"token":   {token},
	}.Encode())
	if resp != nil {
		defer resp.Body.Close()
	}

	if err != nil {
		return 0, 0, err
	}

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return 0, 0, err
	}

	my := int64(0)
	m := myEscrowExp.FindStringSubmatch(string(body))
	if m != nil && len(m) == 2 {
		my, _ = strconv.ParseInt(m[1], 10, 32)
	}

	them := int64(0)
	m = themEscrowExp.FindStringSubmatch(string(body))
	if m != nil && len(m) == 2 {
		them, _ = strconv.ParseInt(m[1], 10, 32)
	}

	return my, them, nil
}

func (session *Session) SendTradeOffer(offer *TradeOffer, sid SteamID, token string) error {
	content := map[string]interface{}{
		"newversion": true,
		"version":    3,
		"me": map[string]interface{}{
			"assets":   offer.SendItems,
			"currency": make([]struct{}, 0),
			"ready":    false,
		},
		"them": map[string]interface{}{
			"assets":   offer.ReceiveItems,
			"currency": make([]struct{}, 0),
			"ready":    false,
		},
	}

	contentJSON, err := json.Marshal(content)
	if err != nil {
		return err
	}

	accessToken := map[string]string{
		"trade_offer_access_token": token,
	}
	params, err := json.Marshal(accessToken)
	if err != nil {
		return err
	}

	req, err := http.NewRequest(
		http.MethodPost,
		"https://steamcommunity.com/tradeoffer/new/send",
		strings.NewReader(url.Values{
			"sessionid":                 {session.sessionID},
			"serverid":                  {"1"},
			"partner":                   {sid.ToString()},
			"tradeoffermessage":         {offer.Message},
			"json_tradeoffer":           {string(contentJSON)},
			"trade_offer_create_params": {string(params)},
		}.Encode()),
	)
	if err != nil {
		return err
	}
	req.Header.Add("Referer", "https://steamcommunity.com/tradeoffer/new/?"+url.Values{
		"partner": {strconv.FormatUint(uint64(sid.GetAccountID()), 10)},
		"token":   {token},
	}.Encode())
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")

	resp, err := session.client.Do(req)
	if resp != nil {
		defer resp.Body.Close()
	}

	if err != nil {
		return err
	}

	type Response struct {
		ErrorMessage               string `json:"strError"`
		ID                         uint64 `json:"tradeofferid,string"`
		MobileConfirmationRequired bool   `json:"needs_mobile_confirmation"`
		EmailConfirmationRequired  bool   `json:"needs_email_confirmation"`
		EmailDomain                string `json:"email_domain"`
	}

	var response Response
	if err = json.NewDecoder(resp.Body).Decode(&response); err != nil {
		return err
	}

	if len(response.ErrorMessage) != 0 {
		return errors.New(response.ErrorMessage)
	}

	if response.ID == 0 {
		return errors.New("no OfferID included")
	}

	offer.ID = response.ID
	offer.Created = time.Now().Unix()
	offer.Updated = time.Now().Unix()

	// Just test mobile confirmation, email is deprecated
	if response.MobileConfirmationRequired {
		offer.ConfirmationMethod = TradeConfirmationMobileApp
		offer.State = TradeStateCreatedNeedsConfirmation
	} else {
		// set state to active
		offer.State = TradeStateActive
	}

	return nil
}

func (session *Session) GetTradeReceivedItems(receiptID uint64) ([]*InventoryItem, error) {
	resp, err := session.client.Get(fmt.Sprintf("https://steamcommunity.com/trade/%d/receipt", receiptID))
	if resp != nil {
		defer resp.Body.Close()
	}

	if err != nil {
		return nil, err
	}

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	m := receiptExp.FindAllSubmatch(body, -1)
	if m == nil {
		return nil, ErrReceiptMatch
	}

	items := []*InventoryItem{}
	for k := range m {
		item := &InventoryItem{}
		if err = json.Unmarshal(m[k][1], item); err != nil {
			return nil, err
		}
		items = append(items, item)
	}
	return items, nil
}

func (session *Session) DeclineTradeOffer(id uint64) error {
	resp, err := session.client.PostForm(apiCallURL+"/DeclineTradeOffer/v1/", url.Values{
		"key":          {session.apiKey},
		"tradeofferid": {strconv.FormatUint(id, 10)},
	})
	if resp != nil {
		defer resp.Body.Close()
	}

	if err != nil {
		return err
	}

	result := resp.Header.Get("x-eresult")
	if result != "1" {
		return fmt.Errorf("cannot decline trade: %s", result)
	}

	return nil
}

func (session *Session) CancelTradeOffer(id uint64) error {
	resp, err := session.client.PostForm(apiCallURL+"/CancelTradeOffer/v1/", url.Values{
		"key":          {session.apiKey},
		"tradeofferid": {strconv.FormatUint(id, 10)},
	})
	if resp != nil {
		defer resp.Body.Close()
	}

	if err != nil {
		return err
	}

	result := resp.Header.Get("x-eresult")
	if result != "1" {
		return fmt.Errorf("cannot cancel trade: %s", result)
	}

	return nil
}

func (session *Session) AcceptTradeOffer(offer *TradeOffer) error {
	if offer.State != TradeStateActive {
		return ErrCannotAcceptActive
	}

	postURL := fmt.Sprintf("https://steamcommunity.com/tradeoffer/%d", offer.ID)

	req, err := http.NewRequest(
		http.MethodPost,
		postURL+"/accept",
		strings.NewReader(url.Values{
			"sessionid":    {session.sessionID},
			"serverid":     {"1"},
			"tradeofferid": {strconv.FormatUint(offer.ID, 10)},
		}.Encode()),
	)
	if err != nil {
		return err
	}

	req.Header.Add("Referer", postURL)
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")

	resp, err := session.client.Do(req)
	if resp != nil {
		defer resp.Body.Close()
	}

	if err != nil {
		return err
	}

	type Response struct {
		ErrorMessage string `json:"strError"`
	}

	var response Response
	if err = json.NewDecoder(resp.Body).Decode(&response); err != nil {
		return err
	}

	if len(response.ErrorMessage) != 0 {
		return errors.New(response.ErrorMessage)
	}

	return nil
}

func (offer *TradeOffer) Send(session *Session, sid SteamID, token string) error {
	return session.SendTradeOffer(offer, sid, token)
}

func (offer *TradeOffer) Accept(session *Session) error {
	return session.AcceptTradeOffer(offer)
}

func (offer *TradeOffer) Cancel(session *Session) error {
	if offer.IsOurOffer {
		return session.CancelTradeOffer(offer.ID)
	}

	return session.DeclineTradeOffer(offer.ID)
}
<|endoftext|>


<commit_before>package steam

import (
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"regexp"
	"strconv"
	"strings"
	"time"
)

const (
	TradeStateNone = iota
	TradeStateInvalid
	TradeStateActive
	TradeStateAccepted
	TradeStateCountered
	TradeStateExpired
	TradeStateCanceled
	TradeStateDeclined
	TradeStateInvalidItems
	TradeStateCreatedNeedsConfirmation
	TradeStatePendingConfirmation
	TradeStateEmailPending
	TradeStateCanceledByTwoFactor
	TradeStateCanceledConfirmation
	TradeStateEmailCanceled
	TradeStateInEscrow
)

const (
	TradeConfirmationNone = iota
	TradeConfirmationEmail
	TradeConfirmationMobileApp
	TradeConfirmationMobile
)

const (
	TradeFilterNone           = iota
	TradeFilterSentOffers     = 1 << 0
	TradeFilterRecvOffers     = 1 << 1
	TradeFilterActiveOnly     = 1 << 3
	TradeFilterHistoricalOnly = 1 << 4
)

var (
	// receiptExp matches JSON in the following form:
	//	oItem = {"id":"...",...}; (Javascript code)
	receiptExp = regexp.MustCompile("oItem =\\s(.+?});")
	apiCallURL = "https://api.steampowered.com/IEconService/"

	ErrReceiptMatch       = errors.New("unable to match items in trade receipt")
	ErrCannotAcceptActive = errors.New("unable to accept a non-active trade")
)

type EconItem struct {
	AssetID    uint64 `json:"assetid,string,omitempty"`
	InstanceID uint64 `json:"instanceid,string,omitempty"`
	ClassID    uint64 `json:"classid,string,omitempty"`
	AppID      uint32 `json:"appid,string"`
	ContextID  uint16 `json:"contextid,string"`
	Amount     uint16 `json:"amount,string"`
	Name       string `json:"name,string"` // Will be used for item descriptions, do nto remove
	Missing    bool   `json:"missing,omitempty"`
}

type TradeOffer struct {
	ID                 uint64      `json:"tradeofferid,string"`
	Partner            uint32      `json:"accountid_other"`
	ReceiptID          uint64      `json:"tradeid,string"`
	ReceiveItems       []*EconItem `json:"items_to_receive"`
	SendItems          []*EconItem `json:"items_to_give"`
	Message            string      `json:"message"`
	State              uint8       `json:"trade_offer_state"`
	ConfirmationMethod uint8       `json:"confirmation_method"`
	Created            uint64      `json:"time_created"`
	Updated            uint64      `json:"time_updated"`
	Expires            uint64      `json:"expiration_time"`
	EscrowEndDate      uint64      `json:"escrow_end_date"`
	RealTime           bool        `json:"from_real_time_trade"`
	IsOurOffer         bool        `json:"is_our_offer"`
}

type TradeOfferResponse struct {
	Offer          *TradeOffer   `json:"offer"`                 // GetTradeOffer
	SentOffers     []*TradeOffer `json:"trade_offers_sent"`     // GetTradeOffers
	ReceivedOffers []*TradeOffer `json:"trade_offers_received"` // GetTradeOffers
}

type APIResponse struct {
	Inner TradeOfferResponse `json:"response"`
}

func (community *Community) GetTradeOffer(id uint64) (*TradeOffer, error) {
	resp, err := community.client.Get(fmt.Sprintf("%s/GetTradeOffer/v1/?key=%s&tradeofferid=%d", apiCallURL, community.apiKey, id))
	if resp != nil {
		defer resp.Body.Close()
	}

	if err != nil {
		return nil, err
	}

	var response APIResponse
	if err = json.NewDecoder(resp.Body).Decode(&response); err != nil {
		return nil, err
	}

	return response.Inner.Offer, nil
}

func testBit(bits uint32, bit uint32) bool {
	return (bits & bit) == bit
}

func (community *Community) GetTradeOffers(filter uint32, timeCutOff time.Time) ([]*TradeOffer, []*TradeOffer, error) {
	values := "key=" + community.apiKey
	if testBit(filter, TradeFilterSentOffers) {
		values += "&get_sent_offers=1"
	}

	if testBit(filter, TradeFilterRecvOffers) {
		values += "&get_received_offers=1"
	}

	if testBit(filter, TradeFilterActiveOnly) {
		values += "&active_only=1"
	}

	if testBit(filter, TradeFilterHistoricalOnly) {
		values += "&historical_only=1&time_historical_cutoff=" + strconv.FormatInt(timeCutOff.Unix(), 10)
	}

	resp, err := community.client.Get(fmt.Sprintf("%s/GetTradeOffers/v1/?%s", apiCallURL, values))
	if resp != nil {
		defer resp.Body.Close()
	}

	if err != nil {
		return nil, nil, err
	}

	var response APIResponse
	if err := json.NewDecoder(resp.Body).Decode(&response); err != nil {
		return nil, nil, err
	}

	return response.Inner.SentOffers, response.Inner.ReceivedOffers, nil
}

func (community *Community) SendTradeOffer(offer *TradeOffer, sid SteamID, token string) error {
	content := map[string]interface{}{
		"newversion": true,
		"version":    3,
		"me": map[string]interface{}{
			"assets":   offer.SendItems,
			"currency": make([]struct{}, 0),
			"ready":    false,
		},
		"them": map[string]interface{}{
			"assets":   offer.ReceiveItems,
			"currency": make([]struct{}, 0),
			"ready":    false,
		},
	}

	contentJSON, err := json.Marshal(content)
	if err != nil {
		return err
	}

	accessToken := map[string]string{
		"trade_offer_access_token": token,
	}
	params, err := json.Marshal(accessToken)
	if err != nil {
		return err
	}

	body := url.Values{
		"sessionid":                 {community.sessionID},
		"serverid":                  {"1"},
		"partner":                   {sid.ToString()},
		"tradeoffermessage":         {offer.Message},
		"json_tradeoffer":           {string(contentJSON)},
		"trade_offer_create_params": {string(params)},
	}

	req, err := http.NewRequest(http.MethodPost, "https://steamcommunity.com/tradeoffer/new/send", strings.NewReader(body.Encode()))
	if err != nil {
		return err
	}
	req.Header.Add("Referer", fmt.Sprintf("https://steamcommunity.com/tradeoffer/new/?partner=%d&token=%s", sid.GetAccountID(), token))
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")

	resp, err := community.client.Do(req)
	if resp != nil {
		defer resp.Body.Close()
	}

	if err != nil {
		return err
	}

	type Response struct {
		ErrorMessage               string `json:"strError"`
		ID                         uint64 `json:"tradeofferid,string"`
		MobileConfirmationRequired bool   `json:"needs_mobile_confirmation"`
		EmailConfirmationRequired  bool   `json:"needs_email_confirmation"`
		EmailDomain                string `json:"email_domain"`
	}

	var response Response
	if err = json.NewDecoder(resp.Body).Decode(&response); err != nil {
		return err
	}

	if len(response.ErrorMessage) != 0 {
		return errors.New(response.ErrorMessage)
	}

	if response.ID == 0 {
		return errors.New("no OfferID included")
	}

	offer.ID = response.ID

	// Just test mobile confirmation, email is deprecated
	if response.MobileConfirmationRequired {
		offer.ConfirmationMethod = TradeConfirmationMobileApp
		offer.State = TradeStateCreatedNeedsConfirmation
	} else {
		// set state to active
		offer.State = TradeStateActive
	}

	return nil
}

func (community *Community) GetTradeReceivedItems(receiptID uint64) ([]*InventoryItem, error) {
	resp, err := community.client.Get(fmt.Sprintf("https://steamcommunity.com/trade/%d/receipt", receiptID))
	if resp != nil {
		defer resp.Body.Close()
	}

	if err != nil {
		return nil, err
	}

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	m := receiptExp.FindAllSubmatch(body, -1)
	if m == nil {
		return nil, ErrReceiptMatch
	}

	items := []*InventoryItem{}
	for k := range m {
		item := &InventoryItem{}
		if err = json.Unmarshal(m[k][1], item); err != nil {
			return nil, err
		}
		items = append(items, item)
	}
	return items, nil
}

func (community *Community) DeclineTradeOffer(id uint64) error {
	values := url.Values{}
	values.Set("key", community.apiKey)
	values.Set("tradeofferid", strconv.FormatUint(id, 10))

	resp, err := community.client.PostForm(apiCallURL+"/DeclineTradeOffer/v1/", values)
	if resp != nil {
		defer resp.Body.Close()
	}

	if err != nil {
		return err
	}

	result := resp.Header.Get("x-eresult")
	if result != "1" {
		return fmt.Errorf("cannot decline trade: %s", result)
	}

	return nil
}

func (community *Community) CancelTradeOffer(id uint64) error {
	values := url.Values{}
	values.Set("key", community.apiKey)
	values.Set("tradeofferid", strconv.FormatUint(id, 10))

	resp, err := community.client.PostForm(apiCallURL+"/CancelTradeOffer/v1/", values)
	if resp != nil {
		defer resp.Body.Close()
	}

	if err != nil {
		return err
	}

	result := resp.Header.Get("x-eresult")
	if result != "1" {
		return fmt.Errorf("cannot cancel trade: %s", result)
	}

	return nil
}

func (community *Community) AcceptTradeOffer(offer *TradeOffer) error {
	if offer.State != TradeStateActive {
		return ErrCannotAcceptActive
	}

	body := url.Values{
		"sessionid":    {community.sessionID},
		"serverid":     {"1"},
		"tradeofferid": {strconv.FormatUint(offer.ID, 10)},
	}

	url := fmt.Sprintf("https://steamcommunity.com/tradeoffer/%d", offer.ID)
	req, err := http.NewRequest(http.MethodPost, url, strings.NewReader(body.Encode()))
	if err != nil {
		return err
	}
	req.Header.Add("Referer", url)
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")

	resp, err := community.client.Do(req)
	if resp != nil {
		defer resp.Body.Close()
	}

	if err != nil {
		return err
	}

	type Response struct {
		ErrorMessage string `json:"strError"`
	}

	var response Response
	if err = json.NewDecoder(resp.Body).Decode(&response); err != nil {
		return err
	}

	if len(response.ErrorMessage) != 0 {
		return errors.New(response.ErrorMessage)
	}

	return nil
}

func (offer *TradeOffer) Accept(community *Community) error {
	return community.AcceptTradeOffer(offer)
}

func (offer *TradeOffer) Cancel(community *Community) error {
	if offer.IsOurOffer {
		return community.CancelTradeOffer(offer.ID)
	}

	return community.DeclineTradeOffer(offer.ID)
}
<commit_msg>tradeoffer: typo<commit_after>package steam

import (
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"regexp"
	"strconv"
	"strings"
	"time"
)

const (
	TradeStateNone = iota
	TradeStateInvalid
	TradeStateActive
	TradeStateAccepted
	TradeStateCountered
	TradeStateExpired
	TradeStateCanceled
	TradeStateDeclined
	TradeStateInvalidItems
	TradeStateCreatedNeedsConfirmation
	TradeStatePendingConfirmation
	TradeStateEmailPending
	TradeStateCanceledByTwoFactor
	TradeStateCanceledConfirmation
	TradeStateEmailCanceled
	TradeStateInEscrow
)

const (
	TradeConfirmationNone = iota
	TradeConfirmationEmail
	TradeConfirmationMobileApp
	TradeConfirmationMobile
)

const (
	TradeFilterNone           = iota
	TradeFilterSentOffers     = 1 << 0
	TradeFilterRecvOffers     = 1 << 1
	TradeFilterActiveOnly     = 1 << 3
	TradeFilterHistoricalOnly = 1 << 4
)

var (
	// receiptExp matches JSON in the following form:
	//	oItem = {"id":"...",...}; (Javascript code)
	receiptExp = regexp.MustCompile("oItem =\\s(.+?});")
	apiCallURL = "https://api.steampowered.com/IEconService/"

	ErrReceiptMatch       = errors.New("unable to match items in trade receipt")
	ErrCannotAcceptActive = errors.New("unable to accept a non-active trade")
)

type EconItem struct {
	AssetID    uint64 `json:"assetid,string,omitempty"`
	InstanceID uint64 `json:"instanceid,string,omitempty"`
	ClassID    uint64 `json:"classid,string,omitempty"`
	AppID      uint32 `json:"appid,string"`
	ContextID  uint16 `json:"contextid,string"`
	Amount     uint16 `json:"amount,string"`
	Name       string `json:"name,string"` // Will be used for item descriptions, do *not* remove
	Missing    bool   `json:"missing,omitempty"`
}

type TradeOffer struct {
	ID                 uint64      `json:"tradeofferid,string"`
	Partner            uint32      `json:"accountid_other"`
	ReceiptID          uint64      `json:"tradeid,string"`
	ReceiveItems       []*EconItem `json:"items_to_receive"`
	SendItems          []*EconItem `json:"items_to_give"`
	Message            string      `json:"message"`
	State              uint8       `json:"trade_offer_state"`
	ConfirmationMethod uint8       `json:"confirmation_method"`
	Created            uint64      `json:"time_created"`
	Updated            uint64      `json:"time_updated"`
	Expires            uint64      `json:"expiration_time"`
	EscrowEndDate      uint64      `json:"escrow_end_date"`
	RealTime           bool        `json:"from_real_time_trade"`
	IsOurOffer         bool        `json:"is_our_offer"`
}

type TradeOfferResponse struct {
	Offer          *TradeOffer   `json:"offer"`                 // GetTradeOffer
	SentOffers     []*TradeOffer `json:"trade_offers_sent"`     // GetTradeOffers
	ReceivedOffers []*TradeOffer `json:"trade_offers_received"` // GetTradeOffers
}

type APIResponse struct {
	Inner TradeOfferResponse `json:"response"`
}

func (community *Community) GetTradeOffer(id uint64) (*TradeOffer, error) {
	resp, err := community.client.Get(fmt.Sprintf("%s/GetTradeOffer/v1/?key=%s&tradeofferid=%d", apiCallURL, community.apiKey, id))
	if resp != nil {
		defer resp.Body.Close()
	}

	if err != nil {
		return nil, err
	}

	var response APIResponse
	if err = json.NewDecoder(resp.Body).Decode(&response); err != nil {
		return nil, err
	}

	return response.Inner.Offer, nil
}

func testBit(bits uint32, bit uint32) bool {
	return (bits & bit) == bit
}

func (community *Community) GetTradeOffers(filter uint32, timeCutOff time.Time) ([]*TradeOffer, []*TradeOffer, error) {
	values := "key=" + community.apiKey
	if testBit(filter, TradeFilterSentOffers) {
		values += "&get_sent_offers=1"
	}

	if testBit(filter, TradeFilterRecvOffers) {
		values += "&get_received_offers=1"
	}

	if testBit(filter, TradeFilterActiveOnly) {
		values += "&active_only=1"
	}

	if testBit(filter, TradeFilterHistoricalOnly) {
		values += "&historical_only=1&time_historical_cutoff=" + strconv.FormatInt(timeCutOff.Unix(), 10)
	}

	resp, err := community.client.Get(fmt.Sprintf("%s/GetTradeOffers/v1/?%s", apiCallURL, values))
	if resp != nil {
		defer resp.Body.Close()
	}

	if err != nil {
		return nil, nil, err
	}

	var response APIResponse
	if err := json.NewDecoder(resp.Body).Decode(&response); err != nil {
		return nil, nil, err
	}

	return response.Inner.SentOffers, response.Inner.ReceivedOffers, nil
}

func (community *Community) SendTradeOffer(offer *TradeOffer, sid SteamID, token string) error {
	content := map[string]interface{}{
		"newversion": true,
		"version":    3,
		"me": map[string]interface{}{
			"assets":   offer.SendItems,
			"currency": make([]struct{}, 0),
			"ready":    false,
		},
		"them": map[string]interface{}{
			"assets":   offer.ReceiveItems,
			"currency": make([]struct{}, 0),
			"ready":    false,
		},
	}

	contentJSON, err := json.Marshal(content)
	if err != nil {
		return err
	}

	accessToken := map[string]string{
		"trade_offer_access_token": token,
	}
	params, err := json.Marshal(accessToken)
	if err != nil {
		return err
	}

	body := url.Values{
		"sessionid":                 {community.sessionID},
		"serverid":                  {"1"},
		"partner":                   {sid.ToString()},
		"tradeoffermessage":         {offer.Message},
		"json_tradeoffer":           {string(contentJSON)},
		"trade_offer_create_params": {string(params)},
	}

	req, err := http.NewRequest(http.MethodPost, "https://steamcommunity.com/tradeoffer/new/send", strings.NewReader(body.Encode()))
	if err != nil {
		return err
	}
	req.Header.Add("Referer", fmt.Sprintf("https://steamcommunity.com/tradeoffer/new/?partner=%d&token=%s", sid.GetAccountID(), token))
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")

	resp, err := community.client.Do(req)
	if resp != nil {
		defer resp.Body.Close()
	}

	if err != nil {
		return err
	}

	type Response struct {
		ErrorMessage               string `json:"strError"`
		ID                         uint64 `json:"tradeofferid,string"`
		MobileConfirmationRequired bool   `json:"needs_mobile_confirmation"`
		EmailConfirmationRequired  bool   `json:"needs_email_confirmation"`
		EmailDomain                string `json:"email_domain"`
	}

	var response Response
	if err = json.NewDecoder(resp.Body).Decode(&response); err != nil {
		return err
	}

	if len(response.ErrorMessage) != 0 {
		return errors.New(response.ErrorMessage)
	}

	if response.ID == 0 {
		return errors.New("no OfferID included")
	}

	offer.ID = response.ID

	// Just test mobile confirmation, email is deprecated
	if response.MobileConfirmationRequired {
		offer.ConfirmationMethod = TradeConfirmationMobileApp
		offer.State = TradeStateCreatedNeedsConfirmation
	} else {
		// set state to active
		offer.State = TradeStateActive
	}

	return nil
}

func (community *Community) GetTradeReceivedItems(receiptID uint64) ([]*InventoryItem, error) {
	resp, err := community.client.Get(fmt.Sprintf("https://steamcommunity.com/trade/%d/receipt", receiptID))
	if resp != nil {
		defer resp.Body.Close()
	}

	if err != nil {
		return nil, err
	}

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	m := receiptExp.FindAllSubmatch(body, -1)
	if m == nil {
		return nil, ErrReceiptMatch
	}

	items := []*InventoryItem{}
	for k := range m {
		item := &InventoryItem{}
		if err = json.Unmarshal(m[k][1], item); err != nil {
			return nil, err
		}
		items = append(items, item)
	}
	return items, nil
}

func (community *Community) DeclineTradeOffer(id uint64) error {
	values := url.Values{}
	values.Set("key", community.apiKey)
	values.Set("tradeofferid", strconv.FormatUint(id, 10))

	resp, err := community.client.PostForm(apiCallURL+"/DeclineTradeOffer/v1/", values)
	if resp != nil {
		defer resp.Body.Close()
	}

	if err != nil {
		return err
	}

	result := resp.Header.Get("x-eresult")
	if result != "1" {
		return fmt.Errorf("cannot decline trade: %s", result)
	}

	return nil
}

func (community *Community) CancelTradeOffer(id uint64) error {
	values := url.Values{}
	values.Set("key", community.apiKey)
	values.Set("tradeofferid", strconv.FormatUint(id, 10))

	resp, err := community.client.PostForm(apiCallURL+"/CancelTradeOffer/v1/", values)
	if resp != nil {
		defer resp.Body.Close()
	}

	if err != nil {
		return err
	}

	result := resp.Header.Get("x-eresult")
	if result != "1" {
		return fmt.Errorf("cannot cancel trade: %s", result)
	}

	return nil
}

func (community *Community) AcceptTradeOffer(offer *TradeOffer) error {
	if offer.State != TradeStateActive {
		return ErrCannotAcceptActive
	}

	body := url.Values{
		"sessionid":    {community.sessionID},
		"serverid":     {"1"},
		"tradeofferid": {strconv.FormatUint(offer.ID, 10)},
	}

	url := fmt.Sprintf("https://steamcommunity.com/tradeoffer/%d", offer.ID)
	req, err := http.NewRequest(http.MethodPost, url, strings.NewReader(body.Encode()))
	if err != nil {
		return err
	}
	req.Header.Add("Referer", url)
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")

	resp, err := community.client.Do(req)
	if resp != nil {
		defer resp.Body.Close()
	}

	if err != nil {
		return err
	}

	type Response struct {
		ErrorMessage string `json:"strError"`
	}

	var response Response
	if err = json.NewDecoder(resp.Body).Decode(&response); err != nil {
		return err
	}

	if len(response.ErrorMessage) != 0 {
		return errors.New(response.ErrorMessage)
	}

	return nil
}

func (offer *TradeOffer) Accept(community *Community) error {
	return community.AcceptTradeOffer(offer)
}

func (offer *TradeOffer) Cancel(community *Community) error {
	if offer.IsOurOffer {
		return community.CancelTradeOffer(offer.ID)
	}

	return community.DeclineTradeOffer(offer.ID)
}
<|endoftext|>


<commit_before>package steamapi

import (
	"encoding/json"
	"fmt"
	"net/http"
	"net/url"
	"strconv"
)

// State represents the state of the tradeoffer, see constants
type State uint

const (
	// ETradeOfferStateCreated /!\ non steam status, used to know the TO has been created
	ETradeOfferStateCreated State = iota
	// ETradeOfferStateInvalid Invalid
	ETradeOfferStateInvalid
	// ETradeOfferStateActive This trade offer has been sent, neither party has acted on it yet.
	ETradeOfferStateActive
	// ETradeOfferStateAccepted The trade offer was accepted by the recipient and items were exchanged.
	ETradeOfferStateAccepted
	// ETradeOfferStateCountered The recipient made a counter offer
	ETradeOfferStateCountered
	// ETradeOfferStateExpired The trade offer was not accepted before the expiration date
	ETradeOfferStateExpired
	// ETradeOfferStateCanceled The sender cancelled the offer
	ETradeOfferStateCanceled
	// ETradeOfferStateDeclined The recipient declined the offer
	ETradeOfferStateDeclined
	// ETradeOfferStateInvalidItems Some of the items in the offer are no longer available
	// (indicated by the missing flag in the output)
	ETradeOfferStateInvalidItems
	// ETradeOfferStateEmailPending The offer hasn't been sent yet and is awaiting email confirmation
	ETradeOfferStateEmailPending
	// ETradeOfferStateEmailCanceled The receiver cancelled the offer via email
	ETradeOfferStateEmailCanceled
)

// CEconAsset represents an asset in steam web api
type CEconAsset struct {
	AppID          uint   `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
	MarketHashName string
}

// CEconTradeOffer represent the to from the steam API
type CEconTradeOffer struct {
	TradeOfferID   uint64 `json:",string"`
	OtherAccountID uint64 `json:"accountid_other"`
	Message        string
	ExpirationTime uint32        `json:"expiration_time"`
	State          State         `json:"trade_offer_state"`
	ToGive         []*CEconAsset `json:"items_to_give"`
	ToReceive      []*CEconAsset `json:"items_to_receive"`
	IsOurs         bool          `json:"is_our_offer"`
	TimeCreated    uint32        `json:"time_created"`
	TimeUpdated    uint32        `json:"time_updated"`
	TradeID        uint64        `json:"tradeid"`
}

// CEconTradeOffers represent the list of different tradeoffers types
type CEconTradeOffers struct {
	Sent     []*CEconTradeOffer `json:"trade_offers_sent"`
	Received []*CEconTradeOffer `json:"trade_offers_received"`
}

type ieconGetTradeOffersResponse struct {
	Response struct {
		CEconTradeOffers
	}
}

// IEconGetTradeOffers retrieves a list of tradeoffers
func IEconGetTradeOffers(
	baseSteamAPIURL string,
	apiKey string,
	getSentOffers bool,
	getReceivedOffers bool,
	getDescriptions bool,
	activeOnly bool,
	historicalOnly bool,
	timeHistoricalCutoff int64,
) (*CEconTradeOffers, error) {

	querystring := url.Values{}
	querystring.Add("key", apiKey)
	querystring.Add("get_sent_offers", boolToStr(getSentOffers))
	querystring.Add("get_received_offers", boolToStr(getReceivedOffers))
	querystring.Add("get_descriptions", boolToStr(getDescriptions))
	querystring.Add("language", "en")
	querystring.Add("active_only", boolToStr(activeOnly))
	querystring.Add("historical_only", boolToStr(historicalOnly))
	querystring.Add("time_historical_cutoff", strconv.FormatInt(timeHistoricalCutoff, 10))

	resp, err := http.Get(baseSteamAPIURL + "/IEconService/GetTradeOffers/v0001?" + querystring.Encode())

	if err != nil {
		return nil, fmt.Errorf("tradeoffer IEconGetTradeOffers http.Get: error %v", err)
	}

	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("tradeoffer IEconGetTradeOffers http.Get: http status %v", resp.Status)
	}

	defer resp.Body.Close()

	tosResp := &ieconGetTradeOffersResponse{}
	err = json.NewDecoder(resp.Body).Decode(tosResp)

	if err != nil {
		return nil, fmt.Errorf("tradeoffer IEconGetTradeOffers Decode: error %v", err)
	}

	return &tosResp.Response.CEconTradeOffers, nil
}

type ieconGetTradeOfferResponse struct {
	Response struct {
		Offer        CEconTradeOffer
		Descriptions []ItemDescription
	}
}

// ItemDescription represents the details about the items unique w classid instanceid
type ItemDescription struct {
	AppID          uint   `json:"appid"`
	ClassID        uint64 `json:"classid,string"`
	InstanceID     uint64 `json:"instanceid,string"`
	MarketHashName string `json:"market_hash_name"`
	IconURL        string `json:"icon_url"`
	NameColor      string `json:"name_color"`
	Name           string `json:"name"`
}

func findMarketHashName(itemD []ItemDescription, appID uint, classID, instanceID uint64) string {
	for _, description := range itemD {
		if description.AppID == appID &&
			description.ClassID == classID &&
			description.InstanceID == instanceID {
			return description.MarketHashName
		}
	}

	return ""
}

// IEconGetTradeOffer retrieves details about a specific tradeoffer
func IEconGetTradeOffer(baseSteamAPIURL string, apiKey string, steamID uint64, tradeOfferID uint64) (
	*CEconTradeOffer, error,
) {

	querystring := url.Values{}
	querystring.Add("key", apiKey)
	querystring.Add("steamid", strconv.FormatUint(steamID, 10))
	querystring.Add("format", "json")
	querystring.Add("tradeofferid", strconv.FormatUint(tradeOfferID, 10))
	querystring.Add("language", "en")

	resp, err := http.Get(baseSteamAPIURL + "/IEconService/GetTradeOffer/v1?" + querystring.Encode())

	if err != nil {
		return nil, fmt.Errorf("tradeoffer IEconGetTradeOffer http.Get: error %v", err)
	}

	defer resp.Body.Close()

	toResp := ieconGetTradeOfferResponse{}
	err = json.NewDecoder(resp.Body).Decode(&toResp)

	if err != nil {
		return nil, fmt.Errorf("tradeoffer IEconGetTradeOffer Decode: error %v", err)
	}

	for giveIndex, asset := range toResp.Response.Offer.ToGive {
		toResp.Response.Offer.ToGive[giveIndex].MarketHashName =
			findMarketHashName(toResp.Response.Descriptions, asset.AppID, asset.ClassID, asset.InstanceID)
	}

	for receiveIndex, asset := range toResp.Response.Offer.ToReceive {
		toResp.Response.Offer.ToReceive[receiveIndex].MarketHashName =
			findMarketHashName(toResp.Response.Descriptions, asset.AppID, asset.ClassID, asset.InstanceID)
	}

	return &toResp.Response.Offer, nil
}

// IEconActionTradeOffer declines a TO created by someone else
func IEconActionTradeOffer(baseSteamAPIURL string, action string, apiKey string, tradeOfferID uint64) error {

	if action != "Decline" && action != "Cancel" {
		return fmt.Errorf("tradeoffer IEconActionTradeOffer doesn't support %v action", action)
	}
	querystring := url.Values{}
	querystring.Add("key", apiKey)
	querystring.Add("tradeofferid", strconv.FormatUint(tradeOfferID, 10))

	resp, err := http.Get(
		baseSteamAPIURL + "/IEconService/" + action + "TradeOffer/v0001?" + querystring.Encode())

	if resp.StatusCode != 200 || err != nil {
		return fmt.Errorf("tradeoffer IEcon%sTradeOffer http.Get: %v error %v", action, resp.StatusCode, err)
	}

	err = resp.Body.Close()

	if err != nil {
		return fmt.Errorf("tradeoffer IEcon%sTradeOffer resp.Body.Close(): error %v", action, err)
	}

	return nil

}

func boolToStr(b bool) string {
	if b {
		return "1"
	}

	return "0"

}
<commit_msg>tradeid is a string<commit_after>package steamapi

import (
	"encoding/json"
	"fmt"
	"net/http"
	"net/url"
	"strconv"
)

// State represents the state of the tradeoffer, see constants
type State uint

const (
	// ETradeOfferStateCreated /!\ non steam status, used to know the TO has been created
	ETradeOfferStateCreated State = iota
	// ETradeOfferStateInvalid Invalid
	ETradeOfferStateInvalid
	// ETradeOfferStateActive This trade offer has been sent, neither party has acted on it yet.
	ETradeOfferStateActive
	// ETradeOfferStateAccepted The trade offer was accepted by the recipient and items were exchanged.
	ETradeOfferStateAccepted
	// ETradeOfferStateCountered The recipient made a counter offer
	ETradeOfferStateCountered
	// ETradeOfferStateExpired The trade offer was not accepted before the expiration date
	ETradeOfferStateExpired
	// ETradeOfferStateCanceled The sender cancelled the offer
	ETradeOfferStateCanceled
	// ETradeOfferStateDeclined The recipient declined the offer
	ETradeOfferStateDeclined
	// ETradeOfferStateInvalidItems Some of the items in the offer are no longer available
	// (indicated by the missing flag in the output)
	ETradeOfferStateInvalidItems
	// ETradeOfferStateEmailPending The offer hasn't been sent yet and is awaiting email confirmation
	ETradeOfferStateEmailPending
	// ETradeOfferStateEmailCanceled The receiver cancelled the offer via email
	ETradeOfferStateEmailCanceled
)

// CEconAsset represents an asset in steam web api
type CEconAsset struct {
	AppID          uint   `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
	MarketHashName string
}

// CEconTradeOffer represent the to from the steam API
type CEconTradeOffer struct {
	TradeOfferID   uint64 `json:",string"`
	OtherAccountID uint64 `json:"accountid_other"`
	Message        string
	ExpirationTime uint32        `json:"expiration_time"`
	State          State         `json:"trade_offer_state"`
	ToGive         []*CEconAsset `json:"items_to_give"`
	ToReceive      []*CEconAsset `json:"items_to_receive"`
	IsOurs         bool          `json:"is_our_offer"`
	TimeCreated    uint32        `json:"time_created"`
	TimeUpdated    uint32        `json:"time_updated"`
	TradeID        uint64        `json:"tradeid,string"`
}

// CEconTradeOffers represent the list of different tradeoffers types
type CEconTradeOffers struct {
	Sent     []*CEconTradeOffer `json:"trade_offers_sent"`
	Received []*CEconTradeOffer `json:"trade_offers_received"`
}

type ieconGetTradeOffersResponse struct {
	Response struct {
		CEconTradeOffers
	}
}

// IEconGetTradeOffers retrieves a list of tradeoffers
func IEconGetTradeOffers(
	baseSteamAPIURL string,
	apiKey string,
	getSentOffers bool,
	getReceivedOffers bool,
	getDescriptions bool,
	activeOnly bool,
	historicalOnly bool,
	timeHistoricalCutoff int64,
) (*CEconTradeOffers, error) {

	querystring := url.Values{}
	querystring.Add("key", apiKey)
	querystring.Add("get_sent_offers", boolToStr(getSentOffers))
	querystring.Add("get_received_offers", boolToStr(getReceivedOffers))
	querystring.Add("get_descriptions", boolToStr(getDescriptions))
	querystring.Add("language", "en")
	querystring.Add("active_only", boolToStr(activeOnly))
	querystring.Add("historical_only", boolToStr(historicalOnly))
	querystring.Add("time_historical_cutoff", strconv.FormatInt(timeHistoricalCutoff, 10))

	resp, err := http.Get(baseSteamAPIURL + "/IEconService/GetTradeOffers/v0001?" + querystring.Encode())

	if err != nil {
		return nil, fmt.Errorf("tradeoffer IEconGetTradeOffers http.Get: error %v", err)
	}

	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("tradeoffer IEconGetTradeOffers http.Get: http status %v", resp.Status)
	}

	defer resp.Body.Close()

	tosResp := &ieconGetTradeOffersResponse{}
	err = json.NewDecoder(resp.Body).Decode(tosResp)

	if err != nil {
		return nil, fmt.Errorf("tradeoffer IEconGetTradeOffers Decode: error %v", err)
	}

	return &tosResp.Response.CEconTradeOffers, nil
}

type ieconGetTradeOfferResponse struct {
	Response struct {
		Offer        CEconTradeOffer
		Descriptions []ItemDescription
	}
}

// ItemDescription represents the details about the items unique w classid instanceid
type ItemDescription struct {
	AppID          uint   `json:"appid"`
	ClassID        uint64 `json:"classid,string"`
	InstanceID     uint64 `json:"instanceid,string"`
	MarketHashName string `json:"market_hash_name"`
	IconURL        string `json:"icon_url"`
	NameColor      string `json:"name_color"`
	Name           string `json:"name"`
}

func findMarketHashName(itemD []ItemDescription, appID uint, classID, instanceID uint64) string {
	for _, description := range itemD {
		if description.AppID == appID &&
			description.ClassID == classID &&
			description.InstanceID == instanceID {
			return description.MarketHashName
		}
	}

	return ""
}

// IEconGetTradeOffer retrieves details about a specific tradeoffer
func IEconGetTradeOffer(baseSteamAPIURL string, apiKey string, steamID uint64, tradeOfferID uint64) (
	*CEconTradeOffer, error,
) {

	querystring := url.Values{}
	querystring.Add("key", apiKey)
	querystring.Add("steamid", strconv.FormatUint(steamID, 10))
	querystring.Add("format", "json")
	querystring.Add("tradeofferid", strconv.FormatUint(tradeOfferID, 10))
	querystring.Add("language", "en")

	resp, err := http.Get(baseSteamAPIURL + "/IEconService/GetTradeOffer/v1?" + querystring.Encode())

	if err != nil {
		return nil, fmt.Errorf("tradeoffer IEconGetTradeOffer http.Get: error %v", err)
	}

	defer resp.Body.Close()

	toResp := ieconGetTradeOfferResponse{}
	err = json.NewDecoder(resp.Body).Decode(&toResp)

	if err != nil {
		return nil, fmt.Errorf("tradeoffer IEconGetTradeOffer Decode: error %v", err)
	}

	for giveIndex, asset := range toResp.Response.Offer.ToGive {
		toResp.Response.Offer.ToGive[giveIndex].MarketHashName =
			findMarketHashName(toResp.Response.Descriptions, asset.AppID, asset.ClassID, asset.InstanceID)
	}

	for receiveIndex, asset := range toResp.Response.Offer.ToReceive {
		toResp.Response.Offer.ToReceive[receiveIndex].MarketHashName =
			findMarketHashName(toResp.Response.Descriptions, asset.AppID, asset.ClassID, asset.InstanceID)
	}

	return &toResp.Response.Offer, nil
}

// IEconActionTradeOffer declines a TO created by someone else
func IEconActionTradeOffer(baseSteamAPIURL string, action string, apiKey string, tradeOfferID uint64) error {

	if action != "Decline" && action != "Cancel" {
		return fmt.Errorf("tradeoffer IEconActionTradeOffer doesn't support %v action", action)
	}
	querystring := url.Values{}
	querystring.Add("key", apiKey)
	querystring.Add("tradeofferid", strconv.FormatUint(tradeOfferID, 10))

	resp, err := http.Get(
		baseSteamAPIURL + "/IEconService/" + action + "TradeOffer/v0001?" + querystring.Encode())

	if resp.StatusCode != 200 || err != nil {
		return fmt.Errorf("tradeoffer IEcon%sTradeOffer http.Get: %v error %v", action, resp.StatusCode, err)
	}

	err = resp.Body.Close()

	if err != nil {
		return fmt.Errorf("tradeoffer IEcon%sTradeOffer resp.Body.Close(): error %v", action, err)
	}

	return nil

}

func boolToStr(b bool) string {
	if b {
		return "1"
	}

	return "0"

}
<|endoftext|>