diff --git a/bridge/bridge.go b/bridge/bridge.go index fa00ea44..2cf56c0b 100644 --- a/bridge/bridge.go +++ b/bridge/bridge.go @@ -10,6 +10,7 @@ import ( "github.com/42wim/matterbridge/bridge/mattermost" "github.com/42wim/matterbridge/bridge/rocketchat" "github.com/42wim/matterbridge/bridge/slack" + "github.com/42wim/matterbridge/bridge/steam" "github.com/42wim/matterbridge/bridge/telegram" "github.com/42wim/matterbridge/bridge/xmpp" log "github.com/Sirupsen/logrus" @@ -75,6 +76,9 @@ func New(cfg *config.Config, bridge *config.Bridge, c chan config.Message) *Brid case "matrix": b.Config = cfg.Matrix[name] b.Bridger = bmatrix.New(cfg.Matrix[name], bridge.Account, c) + case "steam": + b.Config = cfg.Steam[name] + b.Bridger = bsteam.New(cfg.Steam[name], bridge.Account, c) case "api": b.Config = cfg.Api[name] b.Bridger = api.New(cfg.Api[name], bridge.Account, c) diff --git a/bridge/config/config.go b/bridge/config/config.go index 79715b53..7f27115b 100644 --- a/bridge/config/config.go +++ b/bridge/config/config.go @@ -39,6 +39,7 @@ type ChannelInfo struct { } type Protocol struct { + AuthCode string // steam BindAddress string // mattermost, slack Buffer int // api EditSuffix string // mattermost, slack, discord, telegram, gitter @@ -109,6 +110,7 @@ type Config struct { Mattermost map[string]Protocol Matrix map[string]Protocol Slack map[string]Protocol + Steam map[string]Protocol Gitter map[string]Protocol Xmpp map[string]Protocol Discord map[string]Protocol diff --git a/bridge/steam/steam.go b/bridge/steam/steam.go new file mode 100644 index 00000000..491612d1 --- /dev/null +++ b/bridge/steam/steam.go @@ -0,0 +1,158 @@ +package bsteam + +import ( + "fmt" + "github.com/42wim/matterbridge/bridge/config" + "github.com/Philipp15b/go-steam" + "github.com/Philipp15b/go-steam/protocol/steamlang" + "github.com/Philipp15b/go-steam/steamid" + log "github.com/Sirupsen/logrus" + //"io/ioutil" + "strconv" + "sync" + "time" +) + +type Bsteam struct { + c *steam.Client + connected chan struct{} + Config *config.Protocol + Remote chan config.Message + Account string + userMap map[steamid.SteamId]string + sync.RWMutex +} + +var flog *log.Entry +var protocol = "steam" + +func init() { + flog = log.WithFields(log.Fields{"module": protocol}) +} + +func New(cfg config.Protocol, account string, c chan config.Message) *Bsteam { + b := &Bsteam{} + b.Config = &cfg + b.Remote = c + b.Account = account + b.userMap = make(map[steamid.SteamId]string) + b.connected = make(chan struct{}) + return b +} + +func (b *Bsteam) Connect() error { + flog.Info("Connecting") + b.c = steam.NewClient() + go b.handleEvents() + go b.c.Connect() + select { + case <-b.connected: + flog.Info("Connection succeeded") + case <-time.After(time.Second * 30): + return fmt.Errorf("connection timed out") + } + return nil +} + +func (b *Bsteam) Disconnect() error { + b.c.Disconnect() + return nil + +} + +func (b *Bsteam) JoinChannel(channel string) error { + id, err := steamid.NewId(channel) + if err != nil { + return err + } + b.c.Social.JoinChat(id) + return nil +} + +func (b *Bsteam) Send(msg config.Message) error { + id, err := steamid.NewId(msg.Channel) + if err != nil { + return err + } + b.c.Social.SendMessage(id, steamlang.EChatEntryType_ChatMsg, msg.Username+msg.Text) + return nil +} + +func (b *Bsteam) getNick(id steamid.SteamId) string { + b.RLock() + defer b.RUnlock() + if name, ok := b.userMap[id]; ok { + return name + } + return "unknown" +} + +func (b *Bsteam) handleEvents() { + myLoginInfo := new(steam.LogOnDetails) + myLoginInfo.Username = b.Config.Login + myLoginInfo.Password = b.Config.Password + myLoginInfo.AuthCode = b.Config.AuthCode + // Attempt to read existing auth hash to avoid steam guard. + // Maybe works + //myLoginInfo.SentryFileHash, _ = ioutil.ReadFile("sentry") + for event := range b.c.Events() { + //flog.Info(event) + switch e := event.(type) { + case *steam.ChatMsgEvent: + flog.Debugf("Receiving ChatMsgEvent: %#v", e) + flog.Debugf("Sending message from %s on %s to gateway", b.getNick(e.ChatterId), b.Account) + msg := config.Message{Username: b.getNick(e.ChatterId), Text: e.Message, Channel: strconv.FormatInt(int64(e.ChatRoomId), 10), Account: b.Account, UserID: strconv.FormatInt(int64(e.ChatterId), 10)} + b.Remote <- msg + case *steam.PersonaStateEvent: + flog.Debugf("PersonaStateEvent: %#v\n", e) + b.Lock() + b.userMap[e.FriendId] = e.Name + b.Unlock() + case *steam.ConnectedEvent: + b.c.Auth.LogOn(myLoginInfo) + case *steam.MachineAuthUpdateEvent: + /* + flog.Info("authupdate", e) + flog.Info("hash", e.Hash) + ioutil.WriteFile("sentry", e.Hash, 0666) + */ + case *steam.LogOnFailedEvent: + flog.Info("Logon failed", e) + switch e.Result { + case steamlang.EResult_AccountLogonDeniedNeedTwoFactorCode: + { + flog.Info("Steam guard isn't letting me in! Enter 2FA code:") + var code string + fmt.Scanf("%s", &code) + myLoginInfo.TwoFactorCode = code + } + case steamlang.EResult_AccountLogonDenied: + { + flog.Info("Steam guard isn't letting me in! Enter auth code:") + var code string + fmt.Scanf("%s", &code) + myLoginInfo.AuthCode = code + } + default: + log.Errorf("LogOnFailedEvent: ", e.Result) + // TODO: Handle EResult_InvalidLoginAuthCode + return + } + case *steam.LoggedOnEvent: + flog.Debugf("LoggedOnEvent: %#v", e) + b.connected <- struct{}{} + flog.Debugf("setting online") + b.c.Social.SetPersonaState(steamlang.EPersonaState_Online) + case *steam.DisconnectedEvent: + flog.Info("Disconnected") + flog.Info("Attempting to reconnect...") + b.c.Connect() + case steam.FatalErrorEvent: + flog.Error(e) + case error: + flog.Error(e) + default: + flog.Debugf("unknown event %#v", e) + } + } +} diff --git a/matterbridge.toml.sample b/matterbridge.toml.sample index ecc921b1..12034930 100644 --- a/matterbridge.toml.sample +++ b/matterbridge.toml.sample @@ -656,6 +656,55 @@ RemoteNickFormat="[{PROTOCOL}] <{NICK}> " #OPTIONAL (default false) ShowJoinPart=false +################################################################### +#steam section +################################################################### +[steam] +#You can configure multiple servers "[steam.name]" or "[steam.name2]" +#In this example we use [steam.gamechat] +#REQUIRED + +[matrix.gamechat] +#login/pass of your bot. +#Use a dedicated user for this and not your own account! +#REQUIRED +Login="yourlogin" +Password="yourpass" + +#steamguard mail authcode (not the 2FA code) +#OPTIONAL +Authcode="ABCE12" + +#Whether to prefix messages from other bridges to matrix with the sender's nick. +#Useful if username overrides for incoming webhooks isn't enabled on the +#matrix server. If you set PrefixMessagesWithNick to true, each message +#from bridge to matrix will by default be prefixed by the RemoteNickFormat setting. i +#OPTIONAL (default false) +PrefixMessagesWithNick=false + +#Nicks you want to ignore. +#Messages from those users will not be sent to other bridges. +#OPTIONAL +IgnoreNicks="spammer1 spammer2" + +#Messages you want to ignore. +#Messages matching these regexp will be ignored and not sent to other bridges +#See https://regex-golang.appspot.com/assets/html/index.html for more regex info +#OPTIONAL (example below ignores messages starting with ~~ or messages containing badword +IgnoreMessages="^~~ badword" + +#RemoteNickFormat defines how remote users appear on this bridge +#The string "{NICK}" (case sensitive) will be replaced by the actual nick / username. +#The string "{BRIDGE}" (case sensitive) will be replaced by the sending bridge +#The string "{PROTOCOL}" (case sensitive) will be replaced by the protocol used by the bridge +#OPTIONAL (default empty) +RemoteNickFormat="[{PROTOCOL}] <{NICK}> " + +#Enable to show users joins/parts from other bridges +#Only works hiding/show messages from irc and mattermost bridge for now +#OPTIONAL (default false) +ShowJoinPart=false + ################################################################### #API @@ -744,6 +793,9 @@ enable=true #rocketchat - #channel (# is required (also needed for private channels!) #matrix - #channel:server (eg #yourchannel:matrix.org) # - encrypted rooms are not supported in matrix + #steam - chatid (a large number). + # The number in the URL when you click "enter chat room" in the browser + # #REQUIRED channel="#testing"