-
Notifications
You must be signed in to change notification settings - Fork 135
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add
gsbot
utilities package and an example bot
- Loading branch information
1 parent
c6388c5
commit 8e21755
Showing
3 changed files
with
266 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,208 @@ | ||
// 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 | ||
} | ||
|
||
// 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, | ||
}) | ||
} | ||
|
||
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() | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
// A simple example that uses the modules from the gsbot package and go-steam to log on | ||
// to the Steam network. | ||
// | ||
// The command expects log on data, optionally with an auth code: | ||
// | ||
// gsbot [username] [password] | ||
// gsbot [username] [password] [authcode] | ||
package main | ||
|
||
import ( | ||
"fmt" | ||
"os" | ||
|
||
"github.com/Philipp15b/go-steam" | ||
"github.com/Philipp15b/go-steam/gsbot" | ||
"github.com/Philipp15b/go-steam/protocol/steamlang" | ||
) | ||
|
||
func main() { | ||
if len(os.Args) >= 3 { | ||
fmt.Println("gsbot example\nusage: \n\tgsbot [username] [password] [authcode]") | ||
return | ||
} | ||
authcode := "" | ||
if len(os.Args) > 3 { | ||
authcode = os.Args[3] | ||
} | ||
|
||
bot := gsbot.Default() | ||
client := bot.Client | ||
auth := gsbot.NewAuth(bot, &gsbot.LogOnDetails{ | ||
os.Args[1], | ||
os.Args[2], | ||
authcode, | ||
}, "sentry.bin") | ||
debug, err := gsbot.NewDebug(bot, "debug") | ||
if err != nil { | ||
panic(err) | ||
} | ||
client.RegisterPacketHandler(debug) | ||
serverList := gsbot.NewServerList(bot, "serverlist.json") | ||
serverList.Connect() | ||
|
||
for event := range client.Events() { | ||
auth.HandleEvent(event) | ||
debug.HandleEvent(event) | ||
serverList.HandleEvent(event) | ||
|
||
switch e := event.(type) { | ||
case error: | ||
fmt.Printf("Error: %v", e) | ||
case *steam.LoggedOnEvent: | ||
client.Social.SetPersonaState(steamlang.EPersonaState_Online) | ||
} | ||
} | ||
} |