summaryrefslogtreecommitdiffstats
path: root/vendor/github.com/Philipp15b/go-steam/gsbot/gsbot.go
blob: ddcd1af6b9651a77391746b1b0afb9c6f6f96139 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
// The GsBot package contains some useful utilites for working with the
// steam package. It implements authentication with sentries, server lists and
// logging messages and events.
//
// Every module is optional and requires an instance of the GsBot struct.
// Should a module have a `HandlePacket` method, you must register it with the
// steam.Client with `RegisterPacketHandler`. Any module with a `HandleEvent`
// method must be integrated into your event loop and should be called for each
// event you receive.
package gsbot

import (
	"encoding/hex"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"math/rand"
	"net"
	"os"
	"path"
	"reflect"
	"time"

	"github.com/Philipp15b/go-steam"
	"github.com/Philipp15b/go-steam/netutil"
	"github.com/Philipp15b/go-steam/protocol"
	"github.com/davecgh/go-spew/spew"
)

// Base structure holding common data among GsBot modules.
type GsBot struct {
	Client *steam.Client
	Log    *log.Logger
}

// Creates a new GsBot with a new steam.Client where logs are written to stdout.
func Default() *GsBot {
	return &GsBot{
		steam.NewClient(),
		log.New(os.Stdout, "", 0),
	}
}

// This module handles authentication. It logs on automatically after a ConnectedEvent
// and saves the sentry data to a file which is also used for logon if available.
// If you're logging on for the first time Steam may require an authcode. You can then
// connect again with the new logon details.
type Auth struct {
	bot             *GsBot
	details         *LogOnDetails
	sentryPath      string
	machineAuthHash []byte
}

func NewAuth(bot *GsBot, details *LogOnDetails, sentryPath string) *Auth {
	return &Auth{
		bot:        bot,
		details:    details,
		sentryPath: sentryPath,
	}
}

type LogOnDetails struct {
	Username      string
	Password      string
	AuthCode      string
	TwoFactorCode string
}

// This is called automatically after every ConnectedEvent, but must be called once again manually
// with an authcode if Steam requires it when logging on for the first time.
func (a *Auth) LogOn(details *LogOnDetails) {
	a.details = details
	sentry, err := ioutil.ReadFile(a.sentryPath)
	if err != nil {
		a.bot.Log.Printf("Error loading sentry file from path %v - This is normal if you're logging in for the first time.\n", a.sentryPath)
	}
	a.bot.Client.Auth.LogOn(&steam.LogOnDetails{
		Username:       details.Username,
		Password:       details.Password,
		SentryFileHash: sentry,
		AuthCode:       details.AuthCode,
		TwoFactorCode:  details.TwoFactorCode,
	})
}

func (a *Auth) HandleEvent(event interface{}) {
	switch e := event.(type) {
	case *steam.ConnectedEvent:
		a.LogOn(a.details)
	case *steam.LoggedOnEvent:
		a.bot.Log.Printf("Logged on (%v) with SteamId %v and account flags %v", e.Result, e.ClientSteamId, e.AccountFlags)
	case *steam.MachineAuthUpdateEvent:
		a.machineAuthHash = e.Hash
		err := ioutil.WriteFile(a.sentryPath, e.Hash, 0666)
		if err != nil {
			panic(err)
		}
	}
}

// This module saves the server list from ClientCMListEvent and uses
// it when you call `Connect()`.
type ServerList struct {
	bot      *GsBot
	listPath string
}

func NewServerList(bot *GsBot, listPath string) *ServerList {
	return &ServerList{
		bot,
		listPath,
	}
}

func (s *ServerList) HandleEvent(event interface{}) {
	switch e := event.(type) {
	case *steam.ClientCMListEvent:
		d, err := json.Marshal(e.Addresses)
		if err != nil {
			panic(err)
		}
		err = ioutil.WriteFile(s.listPath, d, 0666)
		if err != nil {
			panic(err)
		}
	}
}

func (s *ServerList) Connect() (bool, error) {
	return s.ConnectBind(nil)
}

func (s *ServerList) ConnectBind(laddr *net.TCPAddr) (bool, error) {
	d, err := ioutil.ReadFile(s.listPath)
	if err != nil {
		s.bot.Log.Println("Connecting to random server.")
		s.bot.Client.Connect()
		return false, nil
	}
	var addrs []*netutil.PortAddr
	err = json.Unmarshal(d, &addrs)
	if err != nil {
		return false, err
	}
	raddr := addrs[rand.Intn(len(addrs))]
	s.bot.Log.Printf("Connecting to %v from server list\n", raddr)
	s.bot.Client.ConnectToBind(raddr, laddr)
	return true, nil
}

// This module logs incoming packets and events to a directory.
type Debug struct {
	packetId, eventId uint64
	bot               *GsBot
	base              string
}

func NewDebug(bot *GsBot, base string) (*Debug, error) {
	base = path.Join(base, fmt.Sprint(time.Now().Unix()))
	err := os.MkdirAll(path.Join(base, "events"), 0700)
	if err != nil {
		return nil, err
	}
	err = os.MkdirAll(path.Join(base, "packets"), 0700)
	if err != nil {
		return nil, err
	}
	return &Debug{
		0, 0,
		bot,
		base,
	}, nil
}

func (d *Debug) HandlePacket(packet *protocol.Packet) {
	d.packetId++
	name := path.Join(d.base, "packets", fmt.Sprintf("%d_%d_%s", time.Now().Unix(), d.packetId, packet.EMsg))

	text := packet.String() + "\n\n" + hex.Dump(packet.Data)
	err := ioutil.WriteFile(name+".txt", []byte(text), 0666)
	if err != nil {
		panic(err)
	}

	err = ioutil.WriteFile(name+".bin", packet.Data, 0666)
	if err != nil {
		panic(err)
	}
}

func (d *Debug) HandleEvent(event interface{}) {
	d.eventId++
	name := fmt.Sprintf("%d_%d_%s.txt", time.Now().Unix(), d.eventId, name(event))
	err := ioutil.WriteFile(path.Join(d.base, "events", name), []byte(spew.Sdump(event)), 0666)
	if err != nil {
		panic(err)
	}
}

func name(obj interface{}) string {
	val := reflect.ValueOf(obj)
	ind := reflect.Indirect(val)
	if ind.IsValid() {
		return ind.Type().Name()
	} else {
		return val.Type().Name()
	}
}