Compare commits

...

81 Commits

Author SHA1 Message Date
chteufleur f72f1653d0 Fix nil pointer on database.UpdateLine(). 2017-07-31 14:33:19 +02:00
chteufleur 8a52613171 Fixe concurrent map read and map write for XMPP remote roster request. 2017-07-01 16:59:05 +02:00
chteufleur 7ad36a0321 Fixe concurrent map read and map write for XMPP client connected. 2017-06-28 21:07:59 +02:00
chteufleur 458b96bffb Better concurrent map read/write management for friend steam id map. 2017-06-28 20:44:04 +02:00
chteufleur 4961ad8c9e Fixe concurrent map read and map write. 2017-06-28 19:44:57 +02:00
chteufleur 4a3fdd3da1 Modification because of un-golang fix names in lib go-xmpp. 2017-05-17 19:37:27 +02:00
Chteufleur 4f860c3f59 Fix presence type 'probe' to be manage (issue #27). 2017-03-14 20:17:11 +01:00
Chteufleur 82c8ed7cfa Add the gateway into roster (with roster manager). 2017-01-15 09:12:12 +01:00
Chteufleur 231a8acb5f Modifications because multi bodies support has been hadded into the library. 2016-11-13 20:29:24 +01:00
Chteufleur 5268350afb Fix nil pointer on chatstats notification. 2016-11-13 20:03:26 +01:00
Chteufleur cb49e1fbe9 Version 1.1 2016-11-09 22:07:20 +01:00
Chteufleur 17e46aba5e Fix govet issues. 2016-11-01 07:52:47 +01:00
Chteufleur 1d5fce9b06 gofmt -s on code. 2016-11-01 07:47:11 +01:00
Chteufleur e871a899f0 Correct some misspell and unreachable code. 2016-10-31 22:18:31 +01:00
Chteufleur 1743a46eea Update README with modification maded in the code. 2016-10-30 19:28:26 +01:00
Chteufleur 1d0a907b29 Fix non reset tempo for pause in chatstate notification. 2016-10-30 09:16:52 +01:00
chteufleur 195063bc75 (Re)Add chatstate notification. 2016-10-26 08:20:47 +02:00
chteufleur a4518d3f6b Fix local broken repo and commit all lost changes.
- XDG specification for data and file config
 - Better logger
 - Add Remote Roster Manager (compatible with Prosody)
 - Remove Chatstates notification (making bug, have to investigate)
 - Add an admin that have privilege
 - Add Ad-Hoc commands :
   - Uptime
   - Send message broadcast
 - Bugs fixe
2016-10-23 19:08:14 +02:00
Chteufleur 795d443878 Fix compilation error. 2016-09-15 09:36:22 +02:00
Chteufleur b8576883ad Add In-Band Registration for gateway authentication. 2016-09-15 09:32:11 +02:00
Chteufleur de65b55f7e Fix a typo 2016-09-13 21:57:57 +02:00
Chteufleur fdeb74e887 Fix breaking Ad-Hoc command made in last commit 2016-09-13 21:56:33 +02:00
Chteufleur db6d7326e6 Handle disco 2016-09-13 18:44:42 +02:00
Chteufleur d32b143bb3 Handle leave conversation in XMPP -> Steam way + send inactive on XMPP after 2 min inactivity 2016-09-04 15:42:12 +02:00
Chteufleur 1e63cc1759 Handle chatstates gone 2016-09-04 15:18:10 +02:00
Chteufleur 7e2db9f58b Add support chatstates notification (issue #23) 2016-09-04 14:47:48 +02:00
Chteufleur ba48518357 Add invitation to play (issue #15) 2016-09-03 22:20:42 +02:00
Chteufleur 679dc1a04c Do not forward steam presence received to XMPP if user isn't in steam's roster (fix #21) 2016-09-03 15:38:16 +02:00
Chteufleur d64fd5cb16 Re-organize to handle disco 2016-09-02 21:56:13 +02:00
Chteufleur f9d8cea66f Create sentries directory if not exist (fix #19) 2016-08-16 21:15:55 +02:00
chteufleur e0c08b5760 Merge branch 'master' of louiz/go-xmpp4steam into master 2016-08-12 17:09:02 +02:00
louiz’ 1013266a09 Do not send an empty line at the start of each steam message
We shouldn’t try to include the “Subject” part of the message: it’s always
empty, because this is only sent by MUC servers to signal the room’s
topic. This is not used in one-to-one conversations.

fix #20
2016-08-12 11:33:17 +02:00
Chteufleur 699c7cb5c5 Send Ad-Hoc command depending if user is already registered. 2016-08-11 19:56:03 +02:00
Chteufleur 149c5f9117 Fix nil pointer on database.GetLine() 2016-08-11 19:49:21 +02:00
chteufleur a6281fe093 Merge branch 'master' of louiz/go-xmpp4steam into master 2016-08-11 12:04:59 +02:00
louiz’ f9dc7d6b12 Accept action="next" in the ad-hoc command, to go to the next step
From http://xmpp.org/extensions/xep-0050.html#execute-multiple the default
action should be “next” (and that’s what poezio does). I’m not sure
action='execute' should even be accepted at all. But it remains for
compatibility with the old behaviour.
2016-08-11 11:54:33 +02:00
chteufleur 30ab74c5a0 Merge branch 'master' of louiz/go-xmpp4steam into master 2016-08-11 11:50:39 +02:00
louiz’ f83354ae74 Use a text-private ad-hoc field for the password 2016-08-11 11:45:09 +02:00
Chteufleur 60e3a7c215 Change import for steamlang that moved 2016-08-10 21:22:46 +02:00
chteufleur 723a36c3fe Version 1.0 2016-07-15 09:11:23 +02:00
Chteufleur ec8ff5ee92 Add debug command to allow/diallow debug messages 2016-06-19 14:14:19 +02:00
Chteufleur 659fcba63d go fmt 2016-06-17 23:27:53 +02:00
Chteufleur 271f431de2 Disable commands on Steam user + adaptation to library modifications 2016-06-17 23:25:22 +02:00
chteufleur 0ffd5de65c Change README to add SQLite library 2016-04-27 21:06:56 +02:00
chteufleur fcaa5a01bc Fix bag remove to SQLite
Still not perfect, but the problem comming from go-sqlite3 library
2016-04-27 21:02:12 +02:00
Chteufleur 7d9cf97445 Permit remove registration. Working but can be improve 2016-04-23 22:20:56 +02:00
Chteufleur 8692512b22 Modification README 2016-04-23 18:27:12 +02:00
Chteufleur 844eddae4f Send resource also for gateway JID 2016-04-23 16:35:23 +02:00
Chteufleur 7368dce789 Add resource for Steam JID 2016-04-23 11:23:58 +02:00
Chteufleur e064b71660 Answer to presence probe type 2016-04-23 10:04:44 +02:00
Chteufleur 3e3347123f Add an Ad-Hoc command to force Steam deconnexion 2016-04-21 23:33:59 +02:00
Chteufleur 43d9c6c5c6 Modification of the library go-xmpp 2016-04-20 23:02:35 +02:00
Chteufleur 66d99eeb94 Reply to information query
- vCard
 - Soft Version
2016-04-20 21:13:29 +02:00
Chteufleur 0b04382ca0 go fmt 2016-04-19 17:13:16 +02:00
Chteufleur ade4020625 Add multi connected client support for one account 2016-04-19 17:12:36 +02:00
Chteufleur dc5027d36d Fix XMPP EOF that brake the compenent 2016-04-19 16:10:01 +02:00
Chteufleur 5109d91e35 Add warn that Steam info are stored un-encrypted 2016-04-16 22:16:00 +02:00
Chteufleur 6d1fcc1333 Modification of the README to be in compliance with the new architecture 2016-04-16 12:35:18 +02:00
Chteufleur 9103fe6ecd Fix received auth code
Fix XMPP comp out when user registrered
Send offline presence when program exit
2016-04-16 11:39:36 +02:00
Chteufleur ce3b45b492 Version 0.3.1 2016-04-15 22:58:49 +02:00
Chteufleur f9d7c619d2 User can modify it's Steam informations 2016-04-15 22:47:29 +02:00
Chteufleur 683574a814 First working version for multi Steam and XMPP users 2016-04-15 08:53:33 +02:00
Chteufleur 11b43f35bf Merge branch 'master' into mutualisation 2016-04-12 19:10:06 +02:00
Chteufleur 7b2e4df008 Save Steam servers address only if it can be json mashal 2016-04-12 13:57:34 +02:00
Chteufleur 651cc738ad Add database management 2016-04-11 22:49:07 +02:00
Chteufleur 24f1a6d7a2 Bug fix on disconnect 2016-04-09 15:54:13 +02:00
Chteufleur e98a9df154 Version 0.2.0 2016-04-09 13:51:35 +02:00
Chteufleur 7f76a35240 go fmt 2016-04-09 13:50:19 +02:00
Chteufleur abfe6de004 Finalize steam user subscribtion 2016-04-09 13:47:31 +02:00
Chteufleur 387a0b7e5a Add Ad-Hoc command to get Steam Auth Code 2016-04-06 22:24:09 +02:00
Chteufleur ce4b966c9c Do not disconnect when an account disconnect while their is an other one connected 2016-02-29 23:02:19 +01:00
Chteufleur e11f5d3f98 Fix shutdown bug 2015-11-26 18:59:19 +01:00
Chteufleur c7d1d71c95 Modification of the README to go get repository 2015-11-07 09:29:45 +01:00
chteufleur ac2d6ea575 Merge branch 'master' of https://git.kingpenguin.tk/chteufleur/go-xmpp4steam 2015-10-28 15:08:37 +01:00
chteufleur 7f319f1389 Make it go getable 2015-10-28 15:07:32 +01:00
Chteufleur bd28e5ba6c Fix an steam connecting bug 2015-10-27 00:00:12 +01:00
Chteufleur 42c3787a0a XMPP debug in config file + stability 2015-10-26 23:37:29 +01:00
chteufleur 95a589c802 Add usefull XMPP type 2015-10-23 17:05:16 +02:00
chteufleur cec14b399f Send subscribe presence 2015-10-23 14:41:11 +02:00
Chteufleur c88585fc5a Fix disconection presence bug 2015-10-22 21:17:59 +02:00
Chteufleur d66c78055a Add playing game into XMPP presence. 2015-10-22 21:06:44 +02:00
13 changed files with 1789 additions and 423 deletions

View File

@ -9,32 +9,32 @@ go-xmpp4steam is a XMPP/Steam gateway.
* [go-xmpp](https://git.kingpenguin.tk/chteufleur/go-xmpp) for the XMPP part.
* [go-steam](https://github.com/Philipp15b/go-steam) for the steam part.
* [go-sqlite3](https://github.com/mattn/go-sqlite3) for the database part.
* [cfg](https://github.com/jimlawless/cfg) for the configuration file.
Go into your $GOPATH directory and execut those two line to get the 2 dependencies (cfg and go-steam).
### Build and run
You must first [install go environment](https://golang.org/doc/install) on your system.
Then, go into your $GOPATH directory and go get the source code (This will download the source code and the dependencies).
```sh
go get github.com/Philipp15b/go-steam
go get github.com/jimlawless/cfg
```
After that, go into ``src`` directory and get the go-xmpp sources dependence.
```sh
git clone https://git.kingpenguin.tk/chteufleur/go-xmpp
go get git.kingpenguin.tk/chteufleur/go-xmpp4steam.git
```
### Download sources
Then download and compile the go-xmpp4steam gateway.
```sh
git clone https://git.kingpenguin.tk/chteufleur/go-xmpp4steam.git
cd go-xmpp4steam
go build main.go
```
A binary file will be generated.
First, you need to go into directory ``$GOPATH/src/chteufleur/go-xmpp4steam.git``.
Then, you can run the project directly by using command ``go run main.go``.
Or, in order to build the project you can run the command ``go build main.go``.
It will generate a binary that you can run as any binary file.
### Configure
Configure the gateway by editing the ``xmpp4steam.cfg`` file.
The first time, let the variable ``steam_auth_code`` empty. After the first run of the gateway, Steam will send you a code that you have to give it in that variable. Then re-run the gateway and it should be OK.
Configure the gateway by editing the ``xmpp4steam.conf`` file in order to give all XMPP component information. This configuration file has to be placed following the [XDG specification](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html) (example ``/etc/xdg/http-auth/xmpp4steam.conf``).
An example of the config file can be found in [the repos](https://git.kingpenguin.tk/chteufleur/HTTPAuthentificationOverXMPP/src/master/xmpp4steam.conf).
### Utilization
To register, you have to send an Ad-Hoc command to the gateway in order to give your Steam login information.
When it done, send a presence to the gateway. It will try to connect to Steam, but should failed.
Steam should send you a code that you have to give to the gateway using Ad-Hoc command.
After giving the code to the gateway, send again a presence to it and it should be OK.
## Help
To get any help, please visit the XMPP conference room at ``go-xmpp4steam@muc.kingpenguin.tk`` with your prefered client, or [with your browser](https://jappix.kingpenguin.tk/?r=go-xmpp4steam@muc.kingpenguin.tk).
To get any help, please visit the XMPP conference room at [go-xmpp4steam@muc.kingpenguin.tk](xmpp://go-xmpp4steam@muc.kingpenguin.tk?join) with your prefered client, or [with your browser](https://jappix.kingpenguin.tk/?r=go-xmpp4steam@muc.kingpenguin.tk).

View File

@ -0,0 +1,98 @@
package configuration
import (
"git.kingpenguin.tk/chteufleur/go-xmpp4steam.git/database"
"git.kingpenguin.tk/chteufleur/go-xmpp4steam.git/gateway"
"git.kingpenguin.tk/chteufleur/go-xmpp4steam.git/logger"
"github.com/jimlawless/cfg"
"os"
"strings"
)
const (
XdgDirectoryName = "xmpp4steam"
configurationFilePath = "xmpp4steam/xmpp4steam.conf"
PathConfEnvVariable = "XDG_CONFIG_DIRS"
DefaultXdgConfigDirs = "/etc/xdg"
PathDataEnvVariable = "XDG_DATA_DIRS"
DefaultXdgDataDirs = "/usr/local/share/:/usr/share/"
PreferedPathDataDir = "/usr/local/share"
)
var (
MapConfig = make(map[string]string)
)
func Init() {
loadConfigFile()
dataPathDir := locateDataDirPath()
database.DatabaseFile = dataPathDir + "/" + database.DatabaseFileName
database.Init()
gateway.ServerAddrs = dataPathDir + "/" + gateway.ServerAddrs
gateway.SentryDirectory = dataPathDir + "/" + gateway.SentryDirectory
os.MkdirAll(gateway.SentryDirectory, 0700)
}
func loadConfigFile() bool {
ret := false
envVariable := os.Getenv(PathConfEnvVariable)
if envVariable == "" {
envVariable = DefaultXdgConfigDirs
}
for _, path := range strings.Split(envVariable, ":") {
logger.Debug.Println("Try to find configuration file into " + path)
configFile := path + "/" + configurationFilePath
if _, err := os.Stat(configFile); err == nil {
// The config file exist
if cfg.Load(configFile, MapConfig) == nil {
// And has been loaded successfully
logger.Info.Println("Find configuration file at " + configFile)
ret = true
break
}
}
}
return ret
}
func locateDataDirPath() string {
ret := ""
isDirFound := false
envVariable := os.Getenv(PathDataEnvVariable)
if envVariable == "" {
envVariable = DefaultXdgDataDirs
}
for _, path := range strings.Split(envVariable, ":") {
logger.Debug.Printf("Try to find data base directory into " + path)
dbDir := path + "/" + XdgDirectoryName
if fi, err := os.Stat(dbDir); err == nil && fi.IsDir() {
// The database file exist
logger.Info.Printf("Find data base directory at " + dbDir)
isDirFound = true
ret = dbDir
break
}
}
if !isDirFound {
if strings.Contains(envVariable, PreferedPathDataDir) {
ret = PreferedPathDataDir + "/" + XdgDirectoryName
} else {
ret = strings.Split(envVariable, ":")[0] + "/" + XdgDirectoryName
}
if os.MkdirAll(ret, 0700) == nil {
logger.Info.Printf("Creating new data base directory at " + ret)
} else {
logger.Error.Printf("Fail to create data base directory at " + ret)
os.Exit(1)
}
}
return ret
}

211
database/database.go Normal file
View File

@ -0,0 +1,211 @@
package database
import (
"database/sql"
"git.kingpenguin.tk/chteufleur/go-xmpp4steam.git/logger"
_ "github.com/mattn/go-sqlite3"
)
const (
DatabaseFileName = "go_xmpp4steam.db"
createDatabaseStmt = "create table if not exists users (jid text not null primary key, steamLogin text, steamPwd text, debug int);"
insertDatabaseStmt = "insert into users (jid, steamLogin, steamPwd, debug) values(?, ?, ?, ?)"
deleteDatabaseStmt = "delete from users where jid=?"
selectDatabaseStmt = "select jid, steamLogin, steamPwd, debug from users where jid=?"
selectAllDatabaseStmt = "select jid, steamLogin, steamPwd, debug from users"
updateDatabaseStmt = "update users set steamLogin=?, steamPwd=?, debug=? where jid=?"
)
type DatabaseLine struct {
Jid string
SteamLogin string
SteamPwd string
Debug bool
}
var (
db = new(sql.DB)
DatabaseFile = ""
)
func init() {
}
func Init() {
logger.Info.Printf("Init database (file %s)", DatabaseFile)
d, err := sql.Open("sqlite3", DatabaseFile)
if err != nil {
logger.Error.Printf("Error on openning database : %v", err)
}
db = d
_, err = db.Exec(createDatabaseStmt)
if err != nil {
logger.Error.Printf("Failed to create table : %v", err)
}
}
func Close() {
db.Close()
}
func (newLine *DatabaseLine) AddLine() bool {
logger.Info.Printf("Add new line %v", newLine)
isUserRegistred := getLine(newLine.Jid) != nil
if isUserRegistred {
return newLine.UpdateLine()
}
stmt, err := db.Prepare(insertDatabaseStmt)
if err != nil {
logger.Error.Printf("Error on insert jid %s : %v", newLine.Jid, err)
return false
}
defer stmt.Close()
debug := 0
if newLine.Debug {
debug = 1
}
_, err = stmt.Exec(newLine.Jid, newLine.SteamLogin, newLine.SteamPwd, debug)
if err != nil {
logger.Error.Printf("Error on creating SQL statement : %v", err)
return false
}
return true
}
func (newLine *DatabaseLine) UpdateLine() bool {
logger.Info.Printf("Update line %s", newLine.Jid)
stmt, err := db.Prepare(updateDatabaseStmt)
if err != nil {
logger.Error.Printf("Error on update : %v", err)
return false
}
defer stmt.Close()
debug := 0
if newLine.Debug {
debug = 1
}
if newLine.SteamPwd == "" {
oldLine := GetLine(newLine.Jid)
if oldLine != nil {
newLine.SteamPwd = oldLine.SteamPwd
}
}
_, err = stmt.Exec(newLine.SteamLogin, newLine.SteamPwd, debug, newLine.Jid)
if err != nil {
logger.Error.Printf("Error on updating SQL statement : %v", err)
return false
}
return true
}
func (dbUser *DatabaseLine) UpdateUser() bool {
isUserRegistred := GetLine(dbUser.Jid) != nil
var isSqlSuccess bool
if isUserRegistred {
isSqlSuccess = dbUser.UpdateLine()
} else {
isSqlSuccess = dbUser.AddLine()
}
return isSqlSuccess
}
func RemoveLine(jid string) bool {
// Update Steam login and password to blank before deleting,
// because it is not really deleted in the SQLite file.
line := new(DatabaseLine)
line.Jid = jid
line.UpdateLine()
logger.Info.Printf("Remove line %s", jid)
stmt, err := db.Prepare(deleteDatabaseStmt)
if err != nil {
logger.Error.Printf("Error on delete jid %s : %v", jid, err)
return false
}
defer stmt.Close()
res, err := stmt.Exec(jid)
if err != nil {
logger.Error.Printf("Error on delete SQL statement : %v", err)
return false
}
affect, err := res.RowsAffected()
if err != nil {
logger.Error.Printf("Error on delete SQL statement : %v", err)
return false
}
if affect == 0 {
logger.Debug.Printf("No line affected")
return false
}
return true
}
func GetLine(jid string) *DatabaseLine {
ret := getLine(jid)
if ret == nil || ret.SteamLogin == "" {
logger.Debug.Printf("Line empty")
return nil
}
return ret
}
func getLine(jid string) *DatabaseLine {
logger.Info.Printf("Get line %s", jid)
ret := new(DatabaseLine)
stmt, err := db.Prepare(selectDatabaseStmt)
if err != nil {
logger.Error.Printf("Error on select line : %v", err)
return nil
}
defer stmt.Close()
debug := 0
err = stmt.QueryRow(jid).Scan(&ret.Jid, &ret.SteamLogin, &ret.SteamPwd, &debug)
if err != nil {
logger.Error.Printf("Error on select scan : %v", err)
return nil
}
if debug == 1 {
ret.Debug = true
} else {
ret.Debug = false
}
return ret
}
func GetAllLines() []DatabaseLine {
logger.Info.Printf("Get all lines")
var ret []DatabaseLine
rows, err := db.Query(selectAllDatabaseStmt)
if err != nil {
logger.Error.Printf("Error on select query : %v", err)
}
defer rows.Close()
for rows.Next() {
user := new(DatabaseLine)
debug := 0
rows.Scan(&user.Jid, &user.SteamLogin, &user.SteamPwd, &debug)
if user.SteamLogin != "" {
if debug == 1 {
user.Debug = true
} else {
user.Debug = false
}
ret = append(ret, *user)
}
}
return ret
}

166
gateway/gateway.go Normal file
View File

@ -0,0 +1,166 @@
package gateway
import (
"sync"
"github.com/Philipp15b/go-steam"
)
const (
resource = "go-xmpp4steam"
)
var (
SentryDirectory = "sentries/"
XmppGroupUser = "Steam"
RemoteRosterRequestPermission = "remote-roster-request-permission"
RemoteRosterRequestRoster = "remote-roster-request-roster"
)
type GatewayInfo struct {
// Steam
SteamLogin string
SteamPassword string
SteamLoginInfo *steam.LogOnDetails
SteamClient *steam.Client
SentryFile string
friendSteamId *FriendSteam
SteamConnecting bool
Deleting bool
// XMPP
XMPP_JID_Client string
XMPP_Out chan interface{}
xmpp_Connected_Client *XmppConnectedClient
DebugMessage bool
xmpp_IQ_RemoteRoster_Request *XmppRemoteRosterRequest
AllowEditRoster bool
ChatstateNotificationData chan string
}
type FriendSteam struct {
steamId map[string]*StatusSteamFriend
sync.RWMutex
}
type XmppConnectedClient struct {
client map[string]bool
sync.RWMutex
}
type XmppRemoteRosterRequest struct {
request map[string]string
sync.RWMutex
}
type StatusSteamFriend struct {
XMPP_Status string
XMPP_Type string
SteamGameName string
SteamName string
}
func (g *GatewayInfo) Run() {
go g.SteamRun()
go g.chatstatesNotification()
}
func (g *GatewayInfo) SetSteamAuthCode(authCode string) {
g.SteamLoginInfo.AuthCode = authCode
}
func (g *GatewayInfo) Disconnect() {
go g.XMPP_Disconnect()
go g.SteamDisconnect()
g.SteamConnecting = false
}
func (g *GatewayInfo) Delete() {
g.Deleting = true
if g.AllowEditRoster {
g.removeAllUserFromRoster()
}
g.Disconnect()
}
func (s *GatewayInfo) CreateSteamIds() {
s.friendSteamId = &FriendSteam{steamId: make(map[string]*StatusSteamFriend)}
}
func (s *GatewayInfo) GetFriendSteamId(steamId string) *StatusSteamFriend {
s.friendSteamId.RLock()
defer s.friendSteamId.RUnlock()
return s.friendSteamId.steamId[steamId]
}
func (s *GatewayInfo) GetAllFriendSteamId() []string {
s.friendSteamId.RLock()
defer s.friendSteamId.RUnlock()
allSteamIds := make([]string, len(s.friendSteamId.steamId))
i := 0
for steamId := range s.friendSteamId.steamId {
allSteamIds[i] = steamId
i++
}
return allSteamIds
}
func (s *GatewayInfo) SetFriendSteamId(steamId string, status *StatusSteamFriend) {
s.friendSteamId.Lock()
s.friendSteamId.steamId[steamId] = status
s.friendSteamId.Unlock()
}
func (s *GatewayInfo) RemoveFriendSteamId(steamId string) {
s.friendSteamId.Lock()
delete(s.friendSteamId.steamId, steamId)
s.friendSteamId.Unlock()
}
func (s *GatewayInfo) CreateXmppConnectedClient() {
s.xmpp_Connected_Client = &XmppConnectedClient{client: make(map[string]bool)}
}
func (s *GatewayInfo) SetXmppConnectedClient(jid string) {
s.xmpp_Connected_Client.Lock()
s.xmpp_Connected_Client.client[jid] = true
s.xmpp_Connected_Client.Unlock()
}
func (s *GatewayInfo) RemoveXmppConnectedClient(jid string) {
s.xmpp_Connected_Client.Lock()
delete(s.xmpp_Connected_Client.client, jid)
s.xmpp_Connected_Client.Unlock()
}
func (s *GatewayInfo) GetLenXmppConnectedClient() int {
s.xmpp_Connected_Client.RLock()
defer s.xmpp_Connected_Client.RUnlock()
return len(s.xmpp_Connected_Client.client)
}
func (s *GatewayInfo) CreateXmppRemoteRosterRequest() {
s.xmpp_IQ_RemoteRoster_Request = &XmppRemoteRosterRequest{request: make(map[string]string)}
}
func (s *GatewayInfo) SetXmppRemoteRosterRequest(iqId, value string) {
s.xmpp_IQ_RemoteRoster_Request.Lock()
s.xmpp_IQ_RemoteRoster_Request.request[iqId] = value
s.xmpp_IQ_RemoteRoster_Request.Unlock()
}
func (s *GatewayInfo) RemoveXmppRemoteRosterRequest(iqId string) {
s.xmpp_IQ_RemoteRoster_Request.Lock()
delete(s.xmpp_IQ_RemoteRoster_Request.request, iqId)
s.xmpp_IQ_RemoteRoster_Request.Unlock()
}
func (s *GatewayInfo) GetXmppRemoteRosterRequest(iqId string) string {
s.xmpp_IQ_RemoteRoster_Request.RLock()
defer s.xmpp_IQ_RemoteRoster_Request.RUnlock()
return s.xmpp_IQ_RemoteRoster_Request.request[iqId]
}

270
gateway/steam.go Normal file
View File

@ -0,0 +1,270 @@
package gateway
import (
"git.kingpenguin.tk/chteufleur/go-xmpp4steam.git/logger"
"github.com/Philipp15b/go-steam"
"github.com/Philipp15b/go-steam/protocol/steamlang"
"github.com/Philipp15b/go-steam/steamid"
"fmt"
"io/ioutil"
"strconv"
"time"
)
const (
State_Offline = steamlang.EPersonaState_Offline
State_Online = steamlang.EPersonaState_Online
State_Busy = steamlang.EPersonaState_Busy
State_Away = steamlang.EPersonaState_Away
State_Snooze = steamlang.EPersonaState_Snooze
State_LookingToTrade = steamlang.EPersonaState_LookingToTrade
State_LookingToPlay = steamlang.EPersonaState_LookingToPlay
State_Max = steamlang.EPersonaState_Max
)
var (
ServerAddrs = "servers.addr"
)
func (g *GatewayInfo) SteamRun() {
if g.Deleting {
logger.Info.Printf("[%s] Deleting gateway", g.XMPP_JID_Client)
return
}
logger.Info.Printf("[%s] Running", g.XMPP_JID_Client)
steam.InitializeSteamDirectory()
g.setLoginInfos()
if g.SteamClient == nil {
g.SteamClient = steam.NewClient()
}
g.SteamConnecting = false
g.SteamClient.ConnectionTimeout = 10 * time.Second
g.mainSteam()
logger.Info.Printf("[%s] Reach main method's end", g.XMPP_JID_Client)
}
func (g *GatewayInfo) mainSteam() {
for event := range g.SteamClient.Events() {
switch e := event.(type) {
case *steam.ConnectedEvent:
// Connected on server
g.SteamConnecting = false
logger.Debug.Printf("[%s] Connected on Steam serveur", g.XMPP_JID_Client)
g.SteamClient.Auth.LogOn(g.SteamLoginInfo)
case *steam.MachineAuthUpdateEvent:
// Received sentry file
ioutil.WriteFile(g.SentryFile, e.Hash, 0666)
case *steam.LoggedOnEvent:
// Logged on
g.SendSteamPresence(steamlang.EPersonaState_Online)
g.SendXmppMessage(XmppJidComponent, "", "Connected on Steam network")
case *steam.LoggedOffEvent:
logger.Error.Printf("[%s] LoggedOffEvent: %v", g.XMPP_JID_Client, e)
g.SendXmppMessage(XmppJidComponent, "", fmt.Sprintf("Disconnected of Steam network (%v)", e))
g.SteamConnecting = false
case steam.FatalErrorEvent:
logger.Error.Printf("[%s] FatalError: %v", g.XMPP_JID_Client, e)
g.SendXmppMessage(XmppJidComponent, "", fmt.Sprintf("Steam Fatal Error : %v", e))
g.DisconnectAllSteamFriend()
g.SteamConnecting = false
case *steam.DisconnectedEvent:
logger.Info.Printf("[%s] Disconnected event", g.XMPP_JID_Client)
g.SendXmppMessage(XmppJidComponent, "", fmt.Sprintf("Steam Error : %v", e))
g.DisconnectAllSteamFriend()
g.SteamConnecting = false
case error:
logger.Error.Printf("[%s] error: %v", g.XMPP_JID_Client, e)
g.SendXmppMessage(XmppJidComponent, "", "Steam Error : "+e.Error())
case *steam.LogOnFailedEvent:
logger.Error.Printf("[%s] Login failed: %v", g.XMPP_JID_Client, e)
g.SendXmppMessage(XmppJidComponent, "", fmt.Sprintf("Login failed : %v", e.Result))
g.SteamConnecting = false
case *steam.ClientCMListEvent:
// Doing nothing with server list
case *steam.PersonaStateEvent:
logger.Debug.Printf("[%s] Received PersonaStateEvent: %v", g.XMPP_JID_Client, e)
// Presenc received
if _, ok := g.SteamClient.Social.Friends.GetCopy()[e.FriendId]; !ok {
// Is not in friend list
// Exepte for myself
if g.SteamClient.SteamId() != e.FriendId {
continue
}
}
steamId := e.FriendId.ToString()
name := e.Name
gameName := e.GameName
var status string
var tpye string
switch e.State {
case State_Offline:
status = Status_offline
tpye = Type_unavailable
case State_Online:
status = Status_online
tpye = Type_available
case State_Busy:
status = Status_do_not_disturb
tpye = Type_available
case State_Away:
status = Status_away
tpye = Type_available
case State_Snooze:
status = Status_extended_away
tpye = Type_available
}
steamFriendId := g.GetFriendSteamId(steamId)
if steamFriendId == nil {
// Send subscribsion
g.SendXmppPresence(status, Type_subscribe, "", steamId+"@"+XmppJidComponent, gameName, name)
g.SetFriendSteamId(steamId, &StatusSteamFriend{XMPP_Status: status, XMPP_Type: tpye})
} else {
steamFriendId.XMPP_Status = status
steamFriendId.XMPP_Type = tpye
steamFriendId.SteamGameName = gameName
steamFriendId.SteamName = name
}
g.SendXmppPresence(status, tpye, "", steamId+"@"+XmppJidComponent, gameName, name)
case *steam.ChatMsgEvent:
logger.Debug.Printf("[%s] Received ChatMsgEvent: %v", g.XMPP_JID_Client, e)
// Message received
from := e.ChatterId.ToString() + "@" + XmppJidComponent
if e.EntryType == steamlang.EChatEntryType_Typing {
g.SendXmppMessageComposing(from)
} else if e.EntryType == steamlang.EChatEntryType_LeftConversation {
g.SendXmppMessageLeaveConversation(from)
} else {
g.SendXmppMessage(from, "", e.Message)
}
case *steam.ChatInviteEvent:
logger.Debug.Printf("[%s] Received ChatInviteEvent: %v", g.XMPP_JID_Client, e)
// Invitation to play
if fromFriend, ok := g.SteamClient.Social.Friends.GetCopy()[e.FriendChatId]; ok {
messageToSend := fmt.Sprintf("Currently playing to « %s », would you like to join ?", fromFriend.GameName)
g.SendXmppMessage(e.FriendChatId.ToString()+"@"+XmppJidComponent, "", messageToSend)
}
default:
logger.Debug.Printf("[%s] Steam unmatch event (Type: %T): %v", g.XMPP_JID_Client, e, e)
g.SendXmppMessage(XmppJidComponent, "", fmt.Sprintf("Steam unmatch event (Type: %T): %v", e, e))
}
}
}
func (g *GatewayInfo) setLoginInfos() {
var sentryHash steam.SentryHash
sentryHash, err := ioutil.ReadFile(g.SentryFile)
g.SteamLoginInfo = new(steam.LogOnDetails)
g.SteamLoginInfo.Username = g.SteamLogin
g.SteamLoginInfo.Password = g.SteamPassword
if err == nil {
g.SteamLoginInfo.SentryFileHash = sentryHash
}
logger.Debug.Printf("Authentification of (%s, %s)", g.XMPP_JID_Client, g.SteamLoginInfo.Username)
}
func (g *GatewayInfo) IsSteamConnected() bool {
ret := g != nil
if ret {
ret = g.SteamClient != nil
if ret {
ret = g.SteamClient.Connected()
}
}
logger.Debug.Printf("[%s] Is Steam connected (Connected: %v)", g.XMPP_JID_Client, ret)
return ret
}
func (g *GatewayInfo) SteamConnect() {
if g.IsSteamConnected() {
logger.Debug.Printf("[%s] Try to connect, but already connected", g.XMPP_JID_Client)
return
}
if g.SteamConnecting {
logger.Debug.Printf("[%s] Try to connect, but currently connecting…", g.XMPP_JID_Client)
return
}
g.SteamConnecting = true
go func() {
logger.Info.Printf("[%s] Connecting...", g.XMPP_JID_Client)
g.SendXmppMessage(XmppJidComponent, "", "Connecting...")
addr := g.SteamClient.Connect()
logger.Info.Printf("[%s] Connected on %v", g.XMPP_JID_Client, addr)
g.SendXmppMessage(XmppJidComponent, "", fmt.Sprintf("Connected on %v", addr))
}()
}
func (g *GatewayInfo) SteamDisconnect() {
if !g.IsSteamConnected() {
logger.Debug.Printf("[%s] Try to disconnect, but already disconnected", g.XMPP_JID_Client)
return
}
logger.Info.Printf("[%s] Steam disconnect", g.XMPP_JID_Client)
g.XMPP_Disconnect()
g.DisconnectAllSteamFriend()
go g.SteamClient.Disconnect()
}
func (g *GatewayInfo) DisconnectAllSteamFriend() {
logger.Debug.Printf("[%s] Disconnect all Steam friend", g.XMPP_JID_Client)
for _, sid := range g.GetAllFriendSteamId() {
g.SendXmppPresence(Status_offline, Type_unavailable, "", sid+"@"+XmppJidComponent, "", "")
g.RemoveFriendSteamId(sid)
}
}
func (g *GatewayInfo) SendSteamMessage(steamId, message string) {
g.sendSteamMessage(steamId, message, steamlang.EChatEntryType_ChatMsg)
}
func (g *GatewayInfo) SendSteamMessageComposing(steamId string) {
g.sendSteamMessage(steamId, "", steamlang.EChatEntryType_Typing)
}
func (g *GatewayInfo) SendSteamMessageLeaveConversation(steamId string) {
g.sendSteamMessage(steamId, "", steamlang.EChatEntryType_LeftConversation)
}
func (g *GatewayInfo) sendSteamMessage(steamId, message string, chatEntryType steamlang.EChatEntryType) {
if !g.IsSteamConnected() {
logger.Debug.Printf("[%s] Try to send message, but disconnected", g.XMPP_JID_Client)
return
}
steamIdUint64, err := strconv.ParseUint(steamId, 10, 64)
if err == nil {
logger.Debug.Printf("[%s] Send message to %v", g.XMPP_JID_Client, steamIdUint64)
g.SteamClient.Social.SendMessage(steamid.SteamId(steamIdUint64), chatEntryType, message)
} else {
logger.Error.Printf("[%s] Failed to get SteamId from %s", g.XMPP_JID_Client, steamId)
}
}
func (g *GatewayInfo) SendSteamPresence(status steamlang.EPersonaState) {
if !g.IsSteamConnected() {
logger.Debug.Printf("[%s] Try to send presence, but disconnected", g.XMPP_JID_Client)
return
}
logger.Debug.Printf("[%s] Send presence (Status: %v)", g.XMPP_JID_Client, status)
g.SteamClient.Social.SetPersonaState(status)
}

371
gateway/xmpp.go Normal file
View File

@ -0,0 +1,371 @@
package gateway
import (
"git.kingpenguin.tk/chteufleur/go-xmpp.git/src/xmpp"
"git.kingpenguin.tk/chteufleur/go-xmpp4steam.git/logger"
"github.com/Philipp15b/go-steam/protocol/steamlang"
"strconv"
"strings"
"time"
)
const (
Status_online = ""
Status_offline = ""
Status_away = "away"
Status_chat = "chat"
Status_do_not_disturb = "dnd"
Status_extended_away = "xa"
Type_available = ""
Type_unavailable = "unavailable"
Type_subscribe = "subscribe"
Type_subscribed = "subscribed"
Type_unsubscribe = "unsubscribe"
Type_unsubscribed = "unsubscribed"
Type_probe = "probe"
Type_error = "error"
ActionConnexion = "action_xmpp_connexion"
ActionDeconnexion = "action_xmpp_deconnexion"
ActionMainMethodEnded = "action_xmpp_main_method_ended"
)
var (
XmppJidComponent = ""
iqId = uint64(0)
)
func NextIqId() string {
iqId += 1
ret := strconv.FormatUint(iqId, 10)
return ret
}
func (g *GatewayInfo) ReceivedXMPP_Presence(presence *xmpp.Presence) {
if presence.Type == Type_error {
return
}
transfertPresence := false
jid := strings.SplitN(presence.From, "/", 2)
steamJid := strings.SplitN(strings.SplitN(presence.To, "/", 2)[0], "@", 2)
if len(jid) == 2 {
// Resource exist —> client speaking
if presence.Type == Type_available {
g.SetXmppConnectedClient(presence.From)
} else if presence.Type == Type_unavailable {
g.RemoveXmppConnectedClient(presence.From)
}
}
if presence.Type == Type_probe {
steamFriendStatus := g.GetFriendSteamId(steamJid[0])
if steamFriendStatus != nil {
g.SendXmppPresence(steamFriendStatus.XMPP_Status, steamFriendStatus.XMPP_Type, "", steamJid[0]+"@"+XmppJidComponent, steamFriendStatus.SteamGameName, steamFriendStatus.SteamName)
}
} else if presence.Type == Type_subscribe {
// Send presence to tell that the JID has been added to roster
g.SendXmppPresence("", Type_subscribed, presence.From, presence.To, g.XMPP_JID_Client, "")
} else if presence.Type == Type_subscribed {
} else if presence.Type == Type_unsubscribe {
} else if presence.Type == Type_unsubscribed {
} else if presence.To == XmppJidComponent {
// Destination is gateway itself
if presence.Type == Type_unavailable {
// Disconnect
if g.GetLenXmppConnectedClient() <= 0 {
g.Disconnect()
}
} else {
if presence.Type == Type_available {
g.addUserIntoRoster(presence.To, "xmpp4steam gateway")
}
go g.SteamConnect()
transfertPresence = true
}
}
if transfertPresence {
// Transfert presence to Steam network
var steamStatus steamlang.EPersonaState
switch presence.Show {
case Status_online:
steamStatus = State_Online
case Status_away:
steamStatus = State_Away
case Status_chat:
steamStatus = State_Online
case Status_extended_away:
steamStatus = State_Snooze
case Status_do_not_disturb:
steamStatus = State_Busy
}
if g.IsSteamConnected() {
g.SendSteamPresence(steamStatus)
g.SendXmppPresence(presence.Show, presence.Type, "", "", presence.Status, "")
}
}
}
func (g *GatewayInfo) ReceivedXMPP_Message(message *xmpp.Message) {
steamID := strings.SplitN(message.To, "@", 2)[0]
if message.Composing != nil {
g.SendSteamMessageComposing(steamID)
} else if message.Paused != nil {
return
} else if message.Inactive != nil {
return
} else if message.Gone != nil {
g.SendSteamMessageLeaveConversation(steamID)
} else {
if message.Body != nil && len(message.Body) != 0 {
g.SendSteamMessage(steamID, message.Body[0].Value)
}
}
}
func (g *GatewayInfo) ReceivedXMPP_IQ(iq *xmpp.IQ) bool {
ret := false
remoteRosterRequestValue := g.GetXmppRemoteRosterRequest(iq.ID)
if remoteRosterRequestValue == RemoteRosterRequestPermission {
g.RemoveXmppRemoteRosterRequest(iq.ID)
if iq.Type == xmpp.IQTypeError && iq.Error.Condition() == xmpp.ErrorForbidden {
g.AllowEditRoster = false
logger.Info.Printf("Set allow roster edition to %v", g.AllowEditRoster)
} else if iq.Type == xmpp.IQTypeSet && iq.PayloadName().Space == xmpp.NSRemoteRosterManager {
remoteRosterQuery := &xmpp.RemoteRosterManagerQuery{}
iq.PayloadDecode(remoteRosterQuery)
if remoteRosterQuery.Type == xmpp.RemoteRosterManagerTypeAllowed {
g.AllowEditRoster = true
} else if remoteRosterQuery.Type == xmpp.RemoteRosterManagerTypeRejected {
g.AllowEditRoster = false
} else {
g.AllowEditRoster = false
}
logger.Info.Printf("Set allow roster edition to %v", g.AllowEditRoster)
g.SendXmppMessage(XmppJidComponent, "", "Set allow roster edition to "+strconv.FormatBool(g.AllowEditRoster))
} else {
logger.Info.Printf("Check roster edition authorisation by querying roster's user")
// Remote roster namespace may not be supported (like prosody), so we send a roster query
iqId := NextIqId()
g.SetXmppRemoteRosterRequest(iqId, RemoteRosterRequestRoster)
iqSend := &xmpp.IQ{ID: iqId, Type: xmpp.IQTypeGet, From: iq.To, To: iq.From}
iqSend.PayloadEncode(&xmpp.RosterQuery{})
g.XMPP_Out <- iqSend
}
ret = true
} else if remoteRosterRequestValue == RemoteRosterRequestRoster {
g.RemoveXmppRemoteRosterRequest(iq.ID)
if iq.Type == xmpp.IQTypeResult && iq.PayloadName().Space == xmpp.NSRoster {
g.AllowEditRoster = true
} else {
g.AllowEditRoster = false
}
logger.Info.Printf("Set allow roster edition to %v", g.AllowEditRoster)
g.SendXmppMessage(XmppJidComponent, "", "Set allow roster edition to "+strconv.FormatBool(g.AllowEditRoster))
ret = true
}
return ret
}
func (g *GatewayInfo) XMPP_Disconnect() {
g.SendXmppPresence(Status_offline, Type_unavailable, "", "", "", "")
}
func (g *GatewayInfo) SendXmppPresence(status, tpye, to, from, message, nick string) {
p := xmpp.Presence{}
if status != "" {
p.Show = status
}
if tpye != "" {
p.Type = tpye
}
if message != "" {
p.Status = message
}
if nick != "" {
p.Nick = nick
}
if to == "" {
p.To = g.XMPP_JID_Client
} else {
p.To = to
}
if from == "" {
p.From = XmppJidComponent + "/" + resource
} else {
p.From = from + "/" + resource
}
if tpye == Type_subscribe && g.AllowEditRoster {
g.addUserIntoRoster(from, nick)
} else {
logger.Info.Printf("Send presence %v", p)
g.XMPP_Out <- p
}
}
func (g *GatewayInfo) addUserIntoRoster(jid, nick string) {
iq := xmpp.IQ{To: g.XMPP_JID_Client, Type: xmpp.IQTypeSet, ID: NextIqId()}
query := &xmpp.RosterQuery{}
queryItem := &xmpp.RosterItem{JID: jid, Name: nick, Subscription: xmpp.RosterSubscriptionBoth}
queryItem.Groupes = append(queryItem.Groupes, XmppGroupUser)
query.Items = append(query.Items, *queryItem)
iq.PayloadEncode(query)
logger.Info.Printf("Add user into roster %v", iq)
g.XMPP_Out <- iq
}
func (g *GatewayInfo) removeAllUserFromRoster() {
// Friends
for steamId := range g.SteamClient.Social.Friends.GetCopy() {
iq := xmpp.IQ{To: g.XMPP_JID_Client, Type: xmpp.IQTypeSet, ID: NextIqId()}
query := &xmpp.RosterQuery{}
query.Items = append(query.Items, *&xmpp.RosterItem{JID: steamId.ToString() + "@" + XmppJidComponent, Subscription: xmpp.RosterSubscriptionRemove})
iq.PayloadEncode(query)
logger.Info.Printf("Remove steam user roster")
g.XMPP_Out <- iq
}
// Myself
iq := xmpp.IQ{To: g.XMPP_JID_Client, Type: xmpp.IQTypeSet, ID: NextIqId()}
query := &xmpp.RosterQuery{}
query.Items = append(query.Items, *&xmpp.RosterItem{JID: g.SteamClient.SteamId().ToString() + "@" + XmppJidComponent, Subscription: xmpp.RosterSubscriptionRemove})
iq.PayloadEncode(query)
logger.Info.Printf("Remove steam user roster")
g.XMPP_Out <- iq
}
func (g *GatewayInfo) SendXmppMessage(from, subject, message string) {
g.sendXmppMessage(from, subject, message, &xmpp.Active{})
g.ChatstateNotificationData <- from
g.ChatstateNotificationData <- "stop"
g.ChatstateNotificationData <- from
g.ChatstateNotificationData <- "inactive"
}
func (g *GatewayInfo) SendXmppMessageLeaveConversation(from string) {
g.sendXmppMessage(from, "", "", &xmpp.Gone{})
g.ChatstateNotificationData <- from
g.ChatstateNotificationData <- "stop"
}
func (g *GatewayInfo) SendXmppMessageComposing(from string) {
g.sendXmppMessage(from, "", "", &xmpp.Composing{})
g.ChatstateNotificationData <- from
g.ChatstateNotificationData <- "paused"
g.ChatstateNotificationData <- from
g.ChatstateNotificationData <- "inactive"
}
func (g *GatewayInfo) chatstatesNotification() {
inactiveTimers := make(map[string]*time.Timer)
pausedTimers := make(map[string]*time.Timer)
g.ChatstateNotificationData = make(chan string)
for {
jid := <-g.ChatstateNotificationData
chatstate := <-g.ChatstateNotificationData
timerInactive, okInactive := inactiveTimers[jid]
timerPaused, okPaused := pausedTimers[jid]
switch chatstate {
case "stop":
if okInactive {
if timerInactive != nil {
if !timerInactive.Stop() {
<-timerInactive.C
}
delete(inactiveTimers, jid)
}
}
if okPaused {
if timerPaused != nil {
if !timerPaused.Stop() {
<-timerPaused.C
}
delete(pausedTimers, jid)
}
}
case "paused":
if okInactive {
if timerPaused != nil {
if !timerPaused.Stop() {
<-timerPaused.C
}
timerPaused.Reset(20 * time.Second)
}
} else {
timerPaused = time.AfterFunc(20*time.Second, func() {
g.sendXmppMessage(jid, "", "", &xmpp.Paused{})
delete(pausedTimers, jid)
})
pausedTimers[jid] = timerPaused
}
case "inactive":
if okInactive {
if timerInactive != nil {
if !timerInactive.Stop() {
<-timerInactive.C
}
timerInactive.Reset(120 * time.Second)
}
} else {
timerInactive = time.AfterFunc(120*time.Second, func() {
g.sendXmppMessage(jid, "", "", &xmpp.Inactive{})
delete(inactiveTimers, jid)
})
inactiveTimers[jid] = timerInactive
}
}
}
}
func (g *GatewayInfo) sendXmppMessage(from, subject, message string, chatState interface{}) {
if from != XmppJidComponent || from == XmppJidComponent && g.DebugMessage {
m := xmpp.Message{To: g.XMPP_JID_Client, From: from, Type: "chat"}
mBody := xmpp.MessageBody{Value: message}
m.Body = append(m.Body, mBody)
if subject != "" {
m.Subject = subject
}
switch v := chatState.(type) {
case *xmpp.Active:
m.Active = v
case *xmpp.Composing:
m.Composing = v
case *xmpp.Paused:
m.Paused = v
case *xmpp.Inactive:
m.Inactive = v
case *xmpp.Gone:
m.Gone = v
default:
m.Active = &xmpp.Active{}
}
logger.Info.Printf("Send message %v", m)
g.XMPP_Out <- m
}
}

18
logger/logger.go Normal file
View File

@ -0,0 +1,18 @@
package logger
import (
"io"
"log"
)
var (
Info *log.Logger
Debug *log.Logger
Error *log.Logger
)
func Init(infoHandle io.Writer, warningHandle io.Writer, errorHandle io.Writer) {
Info = log.New(infoHandle, "INFO : ", log.Ldate|log.Ltime|log.Lshortfile)
Debug = log.New(warningHandle, "DEBUG : ", log.Ldate|log.Ltime|log.Lshortfile)
Error = log.New(errorHandle, "ERROR : ", log.Ldate|log.Ltime|log.Lshortfile)
}

214
main.go
View File

@ -1,202 +1,60 @@
package main
import (
"go-xmpp4steam/steam"
"go-xmpp4steam/xmpp"
"git.kingpenguin.tk/chteufleur/go-xmpp4steam.git/configuration"
"git.kingpenguin.tk/chteufleur/go-xmpp4steam.git/database"
"git.kingpenguin.tk/chteufleur/go-xmpp4steam.git/gateway"
"git.kingpenguin.tk/chteufleur/go-xmpp4steam.git/logger"
"git.kingpenguin.tk/chteufleur/go-xmpp4steam.git/xmpp"
"github.com/Philipp15b/go-steam/internal/steamlang"
"github.com/jimlawless/cfg"
"bufio"
"log"
"os"
"os/signal"
"strings"
"syscall"
"time"
)
const (
configurationFilePath = "xmpp4steam.cfg"
)
var (
mapConfig = make(map[string]string)
SetSteamId = make(map[string]struct{})
Version = "v1.1"
)
func init() {
xmpp.Version = "0.1.1"
err := cfg.Load(configurationFilePath, mapConfig)
if err != nil {
log.Fatal("Failed to load configuration file.", err)
}
logger.Init(os.Stdout, os.Stdout, os.Stderr)
configuration.Init()
logger.Info.Println("Running go-xmpp4steam " + Version)
xmpp.SoftVersion = Version
// XMPP config
xmpp.Addr = mapConfig["xmpp_server_address"] + ":" + mapConfig["xmpp_server_port"]
xmpp.JidStr = mapConfig["xmpp_hostname"]
xmpp.Secret = mapConfig["xmpp_secret"]
xmpp.PreferedJID = mapConfig["xmpp_authorized_jid"]
xmpp.Addr = configuration.MapConfig["xmpp_server_address"] + ":" + configuration.MapConfig["xmpp_server_port"]
xmpp.JidStr = configuration.MapConfig["xmpp_hostname"]
xmpp.Secret = configuration.MapConfig["xmpp_secret"]
xmpp.Debug = configuration.MapConfig["xmpp_debug"] == "true"
if configuration.MapConfig["xmpp_group"] != "" {
gateway.XmppGroupUser = configuration.MapConfig["xmpp_group"]
}
gateway.XmppJidComponent = xmpp.JidStr
// Steam config
steam.Username = mapConfig["steam_login"]
steam.Password = mapConfig["steam_password"]
steam.AuthCode = mapConfig["steam_auth_code"]
for _, admin := range strings.Split(configuration.MapConfig["xmpp_admins"], ";") {
xmpp.AdminUsers[admin] = true
}
}
func main() {
go gatewayXmppSteamAction()
go gatewaySteamXmppAction()
go xmpp.Run()
time.Sleep(1 * time.Second)
allDbUsers := database.GetAllLines()
for _, dbUser := range allDbUsers {
xmpp.AddNewUser(dbUser.Jid, dbUser.SteamLogin, dbUser.SteamPwd, dbUser.Debug)
}
go gatewayXmppSteamPresence()
go gatewayXmppSteamMessage()
sigchan := make(chan os.Signal, 1)
signal.Notify(sigchan, os.Interrupt)
signal.Notify(sigchan, syscall.SIGTERM)
signal.Notify(sigchan, os.Kill)
<-sigchan
go gatewaySteamXmppMessage()
go gatewaySteamXmppPresence()
go steam.Run()
xmpp.Run()
// inputStop()
steam.Disconnect()
xmpp.Disconnect()
logger.Info.Println("Exit main()")
time.Sleep(1 * time.Second)
}
// XMPP -> Steam gateways
func gatewayXmppSteamAction() {
for {
action := <-xmpp.ChanAction
switch action {
case xmpp.ActionConnexion:
if !steam.IsConnected() {
steam.Connect()
}
case xmpp.ActionDeconnexion:
if steam.IsConnected() {
steam.Disconnect()
}
}
}
}
func gatewayXmppSteamPresence() {
for {
status := <-xmpp.ChanPresence
var steamStatus steamlang.EPersonaState
switch status {
case xmpp.Status_online:
steamStatus = steam.State_Online
case xmpp.Status_away:
steamStatus = steam.State_Away
case xmpp.Status_chat:
case xmpp.Status_extended_away:
steamStatus = steam.State_Snooze
case xmpp.Status_do_not_disturb:
steamStatus = steam.State_Busy
}
steam.SendPresence(steamStatus)
}
}
func gatewayXmppSteamMessage() {
for {
steamId := <-xmpp.ChanMessage
message := <-xmpp.ChanMessage
steam.SendMessage(steamId, message)
}
}
// /XMPP -> Steam gateways
// Steam -> XMPP gateways
func gatewaySteamXmppAction() {
for {
action := <-steam.ChanAction
switch action {
case steam.ActionConnected:
xmpp.SendPresence(xmpp.CurrentStatus, xmpp.Type_available)
case steam.ActionDisconnected:
xmpp.Disconnect()
for sid, _ := range SetSteamId {
xmpp.SendPresenceFrom(xmpp.Status_offline, xmpp.Type_unavailable, sid+"@"+xmpp.JidStr)
delete(SetSteamId, sid)
}
case steam.ActionFatalError:
time.Sleep(2 * time.Second)
go steam.Run()
}
}
}
func gatewaySteamXmppMessage() {
for {
steamId := <-steam.ChanMessage
message := <-steam.ChanMessage
xmpp.SendMessage(steamId+"@"+xmpp.JidStr, message)
}
}
func gatewaySteamXmppPresence() {
for {
// name := steam.ChanPresence
steamId := <-steam.ChanPresence
stat := <-steam.ChanPresenceSteam
SetSteamId[steamId] = struct{}{}
var status string
var tpye string
switch stat {
case steam.State_Offline:
status = xmpp.Status_offline
tpye = xmpp.Type_unavailable
case steam.State_Online:
status = xmpp.Status_online
tpye = xmpp.Type_available
case steam.State_Busy:
status = xmpp.Status_do_not_disturb
tpye = xmpp.Type_available
case steam.State_Away:
status = xmpp.Status_away
tpye = xmpp.Type_available
case steam.State_Snooze:
status = xmpp.Status_extended_away
tpye = xmpp.Type_available
}
xmpp.SendPresenceFrom(status, tpye, steamId+"@"+xmpp.JidStr)
}
}
// /Steam -> XMPP gateways
func inputStop() {
for {
in := bufio.NewReader(os.Stdin)
line, err := in.ReadString('\n')
if err != nil {
continue
}
line = strings.TrimRight(line, "\n")
if line == "stop" {
return
}
}
}

View File

@ -1 +1 @@
{"Addresses":[{"IP":"162.254.196.40","Port":27020},{"IP":"162.254.196.43","Port":27021},{"IP":"162.254.196.40","Port":27019},{"IP":"162.254.196.40","Port":27017},{"IP":"146.66.152.10","Port":27017},{"IP":"162.254.196.40","Port":27021},{"IP":"162.254.196.40","Port":27018},{"IP":"162.254.196.41","Port":27018},{"IP":"162.254.196.43","Port":27018},{"IP":"162.254.196.41","Port":27020},{"IP":"162.254.196.41","Port":27021},{"IP":"162.254.196.42","Port":27017},{"IP":"162.254.196.41","Port":27017},{"IP":"162.254.196.42","Port":27021},{"IP":"162.254.197.42","Port":27017},{"IP":"146.66.152.11","Port":27020},{"IP":"162.254.196.43","Port":27019},{"IP":"162.254.196.42","Port":27018},{"IP":"162.254.196.42","Port":27019},{"IP":"162.254.196.41","Port":27019},{"IP":"162.254.197.41","Port":27021},{"IP":"162.254.197.41","Port":27020},{"IP":"162.254.197.40","Port":27019},{"IP":"162.254.197.40","Port":27021},{"IP":"162.254.197.42","Port":27018},{"IP":"162.254.197.41","Port":27019},{"IP":"146.66.152.11","Port":27018},{"IP":"146.66.152.10","Port":27018},{"IP":"162.254.196.43","Port":27017},{"IP":"162.254.196.43","Port":27020},{"IP":"146.66.152.10","Port":27020},{"IP":"162.254.197.42","Port":27021},{"IP":"162.254.197.42","Port":27019},{"IP":"162.254.197.41","Port":27017},{"IP":"162.254.197.42","Port":27020},{"IP":"162.254.196.42","Port":27020},{"IP":"162.254.197.40","Port":27017},{"IP":"162.254.197.40","Port":27018},{"IP":"146.66.152.10","Port":27019},{"IP":"146.66.152.11","Port":27017},{"IP":"146.66.152.11","Port":27019},{"IP":"162.254.197.41","Port":27018},{"IP":"162.254.197.40","Port":27020},{"IP":"185.25.180.14","Port":27019},{"IP":"146.66.155.8","Port":27018},{"IP":"185.25.180.15","Port":27020},{"IP":"185.25.180.15","Port":27018},{"IP":"155.133.242.8","Port":27018},{"IP":"155.133.242.8","Port":27019},{"IP":"155.133.242.9","Port":27020},{"IP":"185.25.182.10","Port":27018},{"IP":"185.25.182.10","Port":27017},{"IP":"185.25.180.14","Port":27018},{"IP":"185.25.180.15","Port":27017},{"IP":"155.133.242.8","Port":27017},{"IP":"185.25.180.14","Port":27017},{"IP":"146.66.155.8","Port":27017},{"IP":"185.25.182.10","Port":27019},{"IP":"185.25.182.10","Port":27020},{"IP":"155.133.242.9","Port":27019},{"IP":"185.25.180.15","Port":27019},{"IP":"185.25.180.14","Port":27020},{"IP":"146.66.155.8","Port":27019},{"IP":"146.66.155.8","Port":27020},{"IP":"155.133.242.9","Port":27017},{"IP":"155.133.242.8","Port":27020},{"IP":"155.133.242.9","Port":27018},{"IP":"208.78.164.9","Port":27019},{"IP":"208.78.164.11","Port":27018},{"IP":"208.78.164.9","Port":27017},{"IP":"208.78.164.12","Port":27019},{"IP":"208.78.164.13","Port":27017},{"IP":"208.78.164.14","Port":27017},{"IP":"208.78.164.12","Port":27017},{"IP":"208.78.164.12","Port":27018},{"IP":"208.78.164.9","Port":27018},{"IP":"208.78.164.14","Port":27019},{"IP":"208.78.164.13","Port":27018},{"IP":"208.78.164.10","Port":27019},{"IP":"208.78.164.10","Port":27018}]}
{"Addresses":[{"IP":"162.254.197.41","Port":27018},{"IP":"162.254.197.40","Port":27018},{"IP":"146.66.152.10","Port":27017},{"IP":"146.66.152.10","Port":27020},{"IP":"146.66.152.11","Port":27017},{"IP":"162.254.197.42","Port":27017},{"IP":"162.254.197.41","Port":27021},{"IP":"162.254.197.40","Port":27020},{"IP":"146.66.152.11","Port":27020},{"IP":"162.254.197.42","Port":27019},{"IP":"146.66.152.10","Port":27019},{"IP":"162.254.197.41","Port":27020},{"IP":"146.66.152.11","Port":27019},{"IP":"162.254.197.41","Port":27019},{"IP":"146.66.152.11","Port":27018},{"IP":"162.254.197.41","Port":27017},{"IP":"146.66.152.10","Port":27018},{"IP":"162.254.197.42","Port":27021},{"IP":"162.254.197.40","Port":27021},{"IP":"162.254.197.42","Port":27018},{"IP":"162.254.197.42","Port":27020},{"IP":"162.254.197.40","Port":27019},{"IP":"162.254.196.41","Port":27017},{"IP":"162.254.196.41","Port":27021},{"IP":"162.254.196.43","Port":27017},{"IP":"162.254.196.41","Port":27019},{"IP":"162.254.196.41","Port":27020},{"IP":"162.254.196.43","Port":27019},{"IP":"162.254.196.42","Port":27019},{"IP":"162.254.196.41","Port":27018},{"IP":"162.254.196.43","Port":27018},{"IP":"162.254.196.42","Port":27020},{"IP":"162.254.196.42","Port":27018},{"IP":"162.254.196.40","Port":27019},{"IP":"162.254.196.40","Port":27017},{"IP":"162.254.197.40","Port":27017},{"IP":"162.254.196.42","Port":27017},{"IP":"162.254.196.40","Port":27020},{"IP":"162.254.196.40","Port":27021},{"IP":"162.254.196.40","Port":27018},{"IP":"162.254.196.42","Port":27021},{"IP":"162.254.196.43","Port":27020},{"IP":"162.254.196.43","Port":27021},{"IP":"146.66.155.8","Port":27017},{"IP":"185.25.182.10","Port":27020},{"IP":"146.66.155.8","Port":27018},{"IP":"146.66.155.8","Port":27019},{"IP":"185.25.182.10","Port":27018},{"IP":"185.25.182.10","Port":27019},{"IP":"146.66.155.8","Port":27020},{"IP":"185.25.182.10","Port":27017},{"IP":"185.25.180.14","Port":27020},{"IP":"185.25.180.14","Port":27019},{"IP":"185.25.180.14","Port":27018},{"IP":"155.133.242.8","Port":27017},{"IP":"155.133.242.9","Port":27020},{"IP":"155.133.242.8","Port":27018},{"IP":"155.133.242.9","Port":27017},{"IP":"185.25.180.14","Port":27017},{"IP":"155.133.242.9","Port":27019},{"IP":"185.25.180.15","Port":27020},{"IP":"155.133.242.8","Port":27020},{"IP":"155.133.242.8","Port":27019},{"IP":"185.25.180.15","Port":27019},{"IP":"155.133.242.9","Port":27018},{"IP":"185.25.180.15","Port":27017},{"IP":"185.25.180.15","Port":27018},{"IP":"208.78.164.13","Port":27019},{"IP":"208.78.164.12","Port":27019},{"IP":"208.78.164.12","Port":27018},{"IP":"208.78.164.10","Port":27017},{"IP":"208.78.164.14","Port":27017},{"IP":"208.78.164.10","Port":27018},{"IP":"208.78.164.13","Port":27017},{"IP":"208.78.164.13","Port":27018},{"IP":"208.78.164.10","Port":27019},{"IP":"208.78.164.12","Port":27017},{"IP":"208.78.164.14","Port":27018},{"IP":"208.78.164.14","Port":27019},{"IP":"162.254.195.46","Port":27020}]}

View File

@ -1,164 +0,0 @@
package steam
import (
"github.com/Philipp15b/go-steam"
"github.com/Philipp15b/go-steam/internal/steamlang"
"github.com/Philipp15b/go-steam/steamid"
"encoding/json"
"io/ioutil"
"log"
"strconv"
"time"
)
const (
sentryFile = "sentry"
serverAddrs = "servers.addr"
State_Offline = steamlang.EPersonaState_Offline
State_Online = steamlang.EPersonaState_Online
State_Busy = steamlang.EPersonaState_Busy
State_Away = steamlang.EPersonaState_Away
State_Snooze = steamlang.EPersonaState_Snooze
State_LookingToTrade = steamlang.EPersonaState_LookingToTrade
State_LookingToPlay = steamlang.EPersonaState_LookingToPlay
State_Max = steamlang.EPersonaState_Max
ActionConnected = "steam_connected"
ActionDisconnected = "steam_disconnected"
ActionFatalError = "steam_fatal_error"
LogInfo = "\t[STEAM INFO]\t"
LogError = "\t[STEAM ERROR]\t"
LogDebug = "\t[STEAM DEBUG]\t"
)
var (
Username = ""
Password = ""
AuthCode = ""
myLoginInfo = new(steam.LogOnDetails)
client = steam.NewClient()
ChanPresence = make(chan string)
ChanPresenceSteam = make(chan steamlang.EPersonaState)
ChanMessage = make(chan string)
ChanAction = make(chan string)
)
func Run() {
log.Printf("%sRunning", LogInfo)
setLoginInfos()
client = steam.NewClient()
client.ConnectionTimeout = 10 * time.Second
mainSteam()
}
func mainSteam() {
for event := range client.Events() {
switch e := event.(type) {
case *steam.ConnectedEvent:
client.Auth.LogOn(myLoginInfo)
case *steam.MachineAuthUpdateEvent:
ioutil.WriteFile(sentryFile, e.Hash, 0666)
case *steam.LoggedOnEvent:
SendPresence(steamlang.EPersonaState_Online)
ChanAction <- ActionConnected
case steam.FatalErrorEvent:
log.Printf("%sFatalError: ", LogError, e)
ChanAction <- ActionFatalError
return
case error:
log.Printf("%s", LogError, e)
case *steam.ClientCMListEvent:
// Save servers addresses
b, err := json.Marshal(*e)
if err != nil {
log.Printf("%sFailed to json.Marshal() servers list", LogError)
}
ioutil.WriteFile(serverAddrs, b, 0666)
case *steam.PersonaStateEvent:
// ChanPresence <- e.Name
ChanPresence <- e.FriendId.ToString()
ChanPresenceSteam <- e.State
case *steam.ChatMsgEvent:
ChanMessage <- e.ChatterId.ToString()
ChanMessage <- e.Message
default:
log.Printf("%s", LogDebug, e)
}
}
}
func setLoginInfos() {
var sentryHash steam.SentryHash
sentryHash, err := ioutil.ReadFile(sentryFile)
myLoginInfo.Username = Username
myLoginInfo.Password = Password
if err == nil {
myLoginInfo.SentryFileHash = sentryHash
log.Printf("%sAuthentification by SentryFileHash", LogDebug)
} else if AuthCode != "" {
myLoginInfo.AuthCode = AuthCode
log.Printf("%sAuthentification by AuthCode", LogDebug)
} else {
log.Printf("%sFirst authentification", LogDebug)
}
}
func IsConnected() bool {
return client.Connected()
}
func Connect() {
if IsConnected() {
log.Printf("%sTry to connect, but already connected", LogDebug)
return
}
b, err := ioutil.ReadFile(serverAddrs)
if err == nil {
var toList steam.ClientCMListEvent
err := json.Unmarshal(b, &toList)
if err != nil {
log.Printf("%sFailed to json.Unmarshal() servers list", LogError)
} else {
log.Printf("%sConnecting...", LogInfo)
client.ConnectTo(toList.Addresses[0])
}
} else {
log.Printf("%sFailed to read servers list file", LogError)
client.Connect()
}
}
func Disconnect() {
log.Printf("%sSteam disconnect", LogInfo)
go client.Disconnect()
}
func SendMessage(steamId, message string) {
steamIdUint64, err := strconv.ParseUint(steamId, 10, 64)
if err == nil {
client.Social.SendMessage(steamid.SteamId(steamIdUint64), steamlang.EChatEntryType_ChatMsg, message)
} else {
log.Printf("%sFailed to get SteamId from %s", LogError, steamId)
}
}
func SendPresence(status steamlang.EPersonaState) {
client.Social.SetPersonaState(status)
}

344
xmpp/commands.go Normal file
View File

@ -0,0 +1,344 @@
package xmpp
import (
"git.kingpenguin.tk/chteufleur/go-xmpp.git/src/xmpp"
"git.kingpenguin.tk/chteufleur/go-xmpp4steam.git/database"
"git.kingpenguin.tk/chteufleur/go-xmpp4steam.git/logger"
"fmt"
"strings"
"time"
)
const (
CommandAuthcode = "steamAuthCodeCommand"
CommandGetIdentifiants = "steamGetIdentifiants"
CommandDisconnectSteam = "disconnectSteam"
CommandRemoveRegistration = "removeRegistration"
CommandToggleDebugMode = "toggleDebugMode"
CommandUptimeMode = "uptime"
CommandMessageBroadcast = "messageBroadcast"
)
var (
ChanAuthCode = make(chan string)
identityGateway = &xmpp.DiscoIdentity{Category: "gateway", Type: "steam", Name: "Steam Gateway"}
identityClients = &xmpp.DiscoIdentity{Category: "client", Type: "pc", Name: "Steam client"}
)
func execDiscoCommand(iq *xmpp.IQ) {
logger.Info.Printf("Ad-Hoc Command")
// Disco Ad-Hoc
reply := iq.Response(xmpp.IQTypeResult)
discoItem := &xmpp.DiscoItems{Node: xmpp.NodeAdHocCommand}
jidBareFrom := strings.SplitN(iq.From, "/", 2)[0]
jidBareTo := strings.SplitN(iq.To, "/", 2)[0]
dbUser := database.GetLine(jidBareFrom)
if jidBareTo == jid.Domain {
// Ad-Hoc command only on gateway
// Add available commands
if dbUser == nil {
discoI := &xmpp.DiscoItem{JID: jid.Domain, Node: CommandGetIdentifiants, Name: "Steam registration"}
discoItem.Item = append(discoItem.Item, *discoI)
} else {
// Add only if user is registered
discoI := &xmpp.DiscoItem{JID: jid.Domain, Node: CommandAuthcode, Name: "Add Steam Auth Code"}
discoItem.Item = append(discoItem.Item, *discoI)
discoI = &xmpp.DiscoItem{JID: jid.Domain, Node: CommandDisconnectSteam, Name: "Force Steam deconnexion"}
discoItem.Item = append(discoItem.Item, *discoI)
discoI = &xmpp.DiscoItem{JID: jid.Domain, Node: CommandRemoveRegistration, Name: "Remove registration"}
discoItem.Item = append(discoItem.Item, *discoI)
discoI = &xmpp.DiscoItem{JID: jid.Domain, Node: CommandToggleDebugMode, Name: "Toggle debug mode"}
discoItem.Item = append(discoItem.Item, *discoI)
discoI = &xmpp.DiscoItem{JID: jid.Domain, Node: CommandUptimeMode, Name: "Uptime"}
discoItem.Item = append(discoItem.Item, *discoI)
}
if AdminUsers[jidBareFrom] {
discoI := &xmpp.DiscoItem{JID: jid.Domain, Node: CommandMessageBroadcast, Name: "Broadcast a message"}
discoItem.Item = append(discoItem.Item, *discoI)
}
}
reply.PayloadEncode(discoItem)
comp.Out <- reply
}
func execDisco(iq *xmpp.IQ) {
logger.Info.Printf("Disco Feature")
jidBareTo := strings.SplitN(iq.To, "/", 2)[0]
discoInfoReceived := &xmpp.DiscoItems{}
iq.PayloadDecode(discoInfoReceived)
switch iq.PayloadName().Space {
case xmpp.NSDiscoInfo:
reply := iq.Response(xmpp.IQTypeResult)
discoInfo := &xmpp.DiscoInfo{}
if jidBareTo == jid.Domain {
// Only gateway
discoInfo.Identity = append(discoInfo.Identity, *identityGateway)
discoInfo.Feature = append(discoInfo.Feature, xmpp.DiscoFeature{Var: xmpp.NodeAdHocCommand})
discoInfo.Feature = append(discoInfo.Feature, xmpp.DiscoFeature{Var: xmpp.NSJabberClient})
discoInfo.Feature = append(discoInfo.Feature, xmpp.DiscoFeature{Var: xmpp.NSRegister})
discoInfo.Feature = append(discoInfo.Feature, xmpp.DiscoFeature{Var: xmpp.NSPing})
} else {
// Only steam users
discoInfo.Identity = append(discoInfo.Identity, *identityClients)
discoInfo.Feature = append(discoInfo.Feature, xmpp.DiscoFeature{Var: xmpp.NSChatStatesNotification})
}
// Both
discoInfo.Feature = append(discoInfo.Feature, xmpp.DiscoFeature{Var: xmpp.NSDiscoInfo})
discoInfo.Feature = append(discoInfo.Feature, xmpp.DiscoFeature{Var: xmpp.NSDiscoItems})
reply.PayloadEncode(discoInfo)
comp.Out <- reply
case xmpp.NSDiscoItems:
if discoInfoReceived.Node == xmpp.NodeAdHocCommand {
// Ad-Hoc command
execDiscoCommand(iq)
} else {
reply := iq.Response(xmpp.IQTypeResult)
discoItems := &xmpp.DiscoItems{}
reply.PayloadEncode(discoItems)
comp.Out <- reply
}
}
}
func execCommandAdHoc(iq *xmpp.IQ) {
adHoc := &xmpp.AdHocCommand{}
iq.PayloadDecode(adHoc)
jidBareFrom := strings.SplitN(iq.From, "/", 2)[0]
if adHoc.SessionID == "" && adHoc.Action == xmpp.ActionAdHocExecute {
// First step in the command
logger.Info.Printf("Ad-Hoc command (Node : %s). First step.", adHoc.Node)
reply := iq.Response(xmpp.IQTypeResult)
cmd := &xmpp.AdHocCommand{Node: adHoc.Node, Status: xmpp.StatusAdHocExecute, SessionID: xmpp.SessionID()}
if adHoc.Node == CommandAuthcode {
// Command Auth Code
cmdXForm := &xmpp.AdHocXForm{Type: xmpp.TypeAdHocForm, Title: "Steam Auth Code", Instructions: "Please provide the auth code that Steam sended to you."}
field := &xmpp.AdHocField{Var: "code", Label: "Auth Code", Type: xmpp.TypeAdHocFieldTextSingle}
cmdXForm.Fields = append(cmdXForm.Fields, *field)
cmd.XForm = *cmdXForm
} else if adHoc.Node == CommandGetIdentifiants {
// Command Auth Code
cmdXForm := getXFormRegistration("")
cmd.XForm = *cmdXForm
} else if adHoc.Node == CommandDisconnectSteam {
// Command steam deconnection
cmd.Status = xmpp.StatusAdHocCompleted
cmdXForm := &xmpp.AdHocXForm{Type: xmpp.TypeAdHocResult, Title: "Force Steam deconnexion"}
cmd.XForm = *cmdXForm
note := &xmpp.AdHocNote{Type: xmpp.TypeAdHocNoteInfo}
g := MapGatewayInfo[jidBareFrom]
if g != nil {
g.Disconnect()
note.Value = "Send deconnexion on Steam network"
} else {
note.Value = "Your are not registred."
}
cmd.Note = *note
} else if adHoc.Node == CommandRemoveRegistration {
// Command remove registration
cmd.Status = xmpp.StatusAdHocCompleted
cmdXForm := &xmpp.AdHocXForm{Type: xmpp.TypeAdHocResult, Title: "Remove registration"}
cmd.XForm = *cmdXForm
note := &xmpp.AdHocNote{Type: xmpp.TypeAdHocNoteInfo}
if RemoveUser(jidBareFrom) {
note.Value = "Remove registration success."
} else {
note.Value = "Failed to remove your registration."
}
cmd.Note = *note
} else if adHoc.Node == CommandToggleDebugMode {
// Command toggle debug mode
cmd.Status = xmpp.StatusAdHocCompleted
cmdXForm := &xmpp.AdHocXForm{Type: xmpp.TypeAdHocResult, Title: "Toggle debug mode"}
cmd.XForm = *cmdXForm
note := &xmpp.AdHocNote{Type: xmpp.TypeAdHocNoteInfo}
dbUser := database.GetLine(jidBareFrom)
if dbUser != nil {
dbUser.Debug = !dbUser.Debug
g := MapGatewayInfo[jidBareFrom]
ok := dbUser.UpdateLine()
if ok && g != nil {
g.DebugMessage = dbUser.Debug
if dbUser.Debug {
note.Value = "Debug activated."
} else {
note.Value = "Debug desactivated."
}
} else {
note.Value = "Failed to update your profile. :("
}
} else {
note.Value = "Your not registered."
}
cmd.Note = *note
} else if adHoc.Node == CommandUptimeMode {
// Command get uptime
cmd.Status = xmpp.StatusAdHocCompleted
cmdXForm := &xmpp.AdHocXForm{Type: xmpp.TypeAdHocResult, Title: "Uptime"}
cmd.XForm = *cmdXForm
deltaT := time.Since(startTime)
val := fmt.Sprintf("%dj %dh %dm %ds", int64(deltaT.Hours()/24), int64(deltaT.Hours())%24, int64(deltaT.Minutes())%60, int64(deltaT.Seconds())%60)
note := &xmpp.AdHocNote{Type: xmpp.TypeAdHocNoteInfo, Value: val}
cmd.Note = *note
} else if adHoc.Node == CommandMessageBroadcast && AdminUsers[jidBareFrom] {
// Command send broadcast message
cmdXForm := &xmpp.AdHocXForm{Type: xmpp.TypeAdHocForm, Title: "Broadcast a message", Instructions: "Message to broadcast to all user."}
field := &xmpp.AdHocField{Var: "message", Label: "Message", Type: xmpp.TypeAdHocFieldTextSingle}
cmdXForm.Fields = append(cmdXForm.Fields, *field)
cmd.XForm = *cmdXForm
}
reply.PayloadEncode(cmd)
comp.Out <- reply
} else if adHoc.Action == xmpp.ActionAdHocExecute || adHoc.Action == xmpp.ActionAdHocNext {
// Last step in the command
logger.Info.Printf("Ad-Hoc command (Node : %s). Last step.", adHoc.Node)
reply := iq.Response(xmpp.IQTypeResult)
cmd := &xmpp.AdHocCommand{Node: adHoc.Node, Status: xmpp.StatusAdHocCompleted, SessionID: adHoc.SessionID}
if adHoc.Node == CommandAuthcode && adHoc.XForm.Type == xmpp.TypeAdHocSubmit {
cmdXForm := &xmpp.AdHocXForm{Type: xmpp.TypeAdHocResult, Title: "Steam Auth Code"}
cmd.XForm = *cmdXForm
note := &xmpp.AdHocNote{Type: xmpp.TypeAdHocNoteInfo}
// Command Auth Code
authCode := ""
fields := adHoc.XForm.Fields
for _, field := range fields {
if field.Var == "code" {
authCode = field.Value
break
}
}
if authCode != "" {
// Succeeded
g := MapGatewayInfo[jidBareFrom]
if g != nil {
g.SetSteamAuthCode(authCode)
note.Value = "Command succeeded !"
} else {
note.Value = "Your are not registred. Please, register before sending Steam auth code."
}
} else {
// Failed
note.Value = "Error append while executing command"
}
cmd.Note = *note
} else if adHoc.Node == CommandGetIdentifiants {
cmdXForm := &xmpp.AdHocXForm{Type: xmpp.TypeAdHocResult, Title: "Steam Account Info"}
cmd.XForm = *cmdXForm
note := &xmpp.AdHocNote{Type: xmpp.TypeAdHocNoteInfo}
// Command Auth Code
dbUser := getUser(adHoc.XForm.Fields, iq)
if dbUser != nil {
if dbUser.UpdateUser() {
AddNewUser(dbUser.Jid, dbUser.SteamLogin, dbUser.SteamPwd, dbUser.Debug)
note.Value = "Command succeeded !"
} else {
note.Value = "Error append while executing command"
}
} else {
// Failed
note.Value = "Failed because Steam login or Steam password is empty."
}
cmd.Note = *note
} else if adHoc.Node == CommandMessageBroadcast && AdminUsers[jidBareFrom] {
cmdXForm := &xmpp.AdHocXForm{Type: xmpp.TypeAdHocResult, Title: "Broadcast a message"}
cmd.XForm = *cmdXForm
note := &xmpp.AdHocNote{Type: xmpp.TypeAdHocNoteInfo}
// Command Auth Code
message := ""
fields := adHoc.XForm.Fields
for _, field := range fields {
if field.Var == "message" {
message = field.Value
break
}
}
if message != "" {
// Succeeded
for userJID := range MapGatewayInfo {
SendMessage(userJID, "", message)
}
note.Value = "Message sended to all registered users"
} else {
// Failed
note.Value = "There is no message to send"
}
cmd.Note = *note
}
reply.PayloadEncode(cmd)
comp.Out <- reply
} else if adHoc.Action == xmpp.ActionAdHocCancel {
// command canceled
logger.Info.Printf("Ad-Hoc command (Node : %s). Command canceled.", adHoc.Node)
reply := iq.Response(xmpp.IQTypeResult)
cmd := &xmpp.AdHocCommand{Node: adHoc.Node, Status: xmpp.StatusAdHocCanceled, SessionID: adHoc.SessionID}
reply.PayloadEncode(cmd)
comp.Out <- reply
}
}
func getXFormRegistration(steamLogin string) *xmpp.AdHocXForm {
cmdXForm := &xmpp.AdHocXForm{Type: xmpp.TypeAdHocForm, Title: "Steam Account Info", Instructions: "Please provide your Steam login and password (Please, be aware that the given Steam account information will be saved into an un-encrypted SQLite database)."}
field := &xmpp.AdHocField{Var: "login", Label: "Steam Login", Type: xmpp.TypeAdHocFieldTextSingle}
field.Value = steamLogin
cmdXForm.Fields = append(cmdXForm.Fields, *field)
field = &xmpp.AdHocField{Var: "password", Label: "Steam Password", Type: xmpp.TypeAdHocFieldTextPrivate}
cmdXForm.Fields = append(cmdXForm.Fields, *field)
return cmdXForm
}
func getUser(fields []xmpp.AdHocField, iq *xmpp.IQ) *database.DatabaseLine {
// Command Auth Code
steamLogin := ""
steamPwd := ""
for _, field := range fields {
if field.Var == "login" {
steamLogin = field.Value
} else if field.Var == "password" {
steamPwd = field.Value
}
}
if steamLogin != "" {
// Succeeded
jidBareFrom := strings.SplitN(iq.From, "/", 2)[0]
dbUser := new(database.DatabaseLine)
dbUser.Jid = jidBareFrom
dbUser.SteamLogin = steamLogin
dbUser.SteamPwd = steamPwd
dbUser.Debug = false
return dbUser
} else {
return nil
}
}

View File

@ -1,30 +1,20 @@
package xmpp
import (
// "github.com/emgee/go-xmpp"
"go-xmpp"
"git.kingpenguin.tk/chteufleur/go-xmpp.git/src/xmpp"
"git.kingpenguin.tk/chteufleur/go-xmpp4steam.git/database"
"git.kingpenguin.tk/chteufleur/go-xmpp4steam.git/gateway"
"git.kingpenguin.tk/chteufleur/go-xmpp4steam.git/logger"
"log"
"os"
"strings"
"time"
)
const (
Status_online = ""
Status_offline = ""
Status_away = "away"
Status_chat = "chat"
Status_do_not_disturb = "dnd"
Status_extended_away = "xa"
Type_available = ""
Type_unavailable = "unavailable"
ActionConnexion = "action_xmpp_connexion"
ActionDeconnexion = "action_xmpp_deconnexion"
LogInfo = "\t[XMPP INFO]\t"
LogError = "\t[XMPP ERROR]\t"
LogDebug = "\t[XMPP DEBUG]\t"
ActionConnexion = "action_xmpp_connexion"
ActionDeconnexion = "action_xmpp_deconnexion"
ActionMainMethodEnded = "action_xmpp_main_method_ended"
)
var (
@ -32,84 +22,294 @@ var (
JidStr = ""
Secret = ""
PreferedJID = ""
SoftVersion = ""
jid xmpp.JID
stream = new(xmpp.Stream)
comp = new(xmpp.XMPP)
ChanPresence = make(chan string)
ChanMessage = make(chan string)
ChanAction = make(chan string)
ChanAction = make(chan string)
CurrentStatus = Status_offline
Debug = true
Version = ""
MapGatewayInfo = make(map[string]*gateway.GatewayInfo)
AdminUsers = make(map[string]bool)
startTime = time.Now()
)
func Run() {
log.Printf("%sRunning", LogInfo)
logger.Info.Printf("Running")
// Create stream and configure it as a component connection.
jid = must(xmpp.ParseJID(JidStr)).(xmpp.JID)
stream = must(xmpp.NewStream(Addr, &xmpp.StreamConfig{LogStanzas: true})).(*xmpp.Stream)
stream = must(xmpp.NewStream(Addr, &xmpp.StreamConfig{LogStanzas: Debug})).(*xmpp.Stream)
comp = must(xmpp.NewComponentXMPP(stream, jid, Secret)).(*xmpp.XMPP)
// SendPresence(Status_online, Type_available)
mainXMPP()
time.Sleep(1 * time.Second)
logger.Info.Printf("Reach XMPP Run method's end")
go Run()
}
func mainXMPP() {
defer logger.Info.Printf("Reach main method's end")
// Define xmpp out for all users
for _, u := range MapGatewayInfo {
u.XMPP_Out = comp.Out
}
for x := range comp.In {
switch v := x.(type) {
case *xmpp.Presence:
if strings.SplitN(v.From, "/", 2)[0] == PreferedJID && v.To == JidStr && v.Type != "probe" {
if v.Type == Type_unavailable {
log.Printf("%sPresence reçut unavailable", LogDebug)
Disconnect()
ChanAction <- ActionDeconnexion
} else {
log.Printf("%sPresence reçut", LogDebug)
CurrentStatus = v.Show
ChanAction <- ActionConnexion
jidBareFrom := strings.SplitN(v.From, "/", 2)[0]
jidBareTo := strings.SplitN(v.To, "/", 2)[0]
g := MapGatewayInfo[jidBareFrom]
if g != nil {
if jidBareTo == jid.Domain || v.Type == gateway.Type_probe {
// Forward only if presence is for component or is type probe, in order not to spam set presence on Steam
logger.Debug.Printf("Presence transferred to %s", jidBareFrom)
go g.ReceivedXMPP_Presence(v)
}
} else {
if v.Type != gateway.Type_error && v.Type != gateway.Type_probe {
SendPresence(gateway.Status_offline, gateway.Type_unavailable, jid.Domain, v.From, "Your are not registred", "")
}
ChanPresence <- v.Show
}
case *xmpp.Message:
steamID := strings.SplitN(v.To, "@", 2)[0]
ChanMessage <- steamID
ChanMessage <- v.Body
jidBareFrom := strings.SplitN(v.From, "/", 2)[0]
g := MapGatewayInfo[jidBareFrom]
if g != nil {
logger.Debug.Printf("Message transferred to %s", jidBareFrom)
go g.ReceivedXMPP_Message(v)
} else {
SendMessage(v.From, "", "Your are not registred. If you want to register, please, send an Ad-Hoc command.")
}
case *xmpp.IQ:
jidBareFrom := strings.SplitN(v.From, "/", 2)[0]
jidBareTo := strings.SplitN(v.To, "/", 2)[0]
g := MapGatewayInfo[jidBareFrom]
iqTreated := false
if g != nil {
logger.Debug.Printf("Iq transferred to %s", jidBareFrom)
iqTreated = g.ReceivedXMPP_IQ(v)
}
if !iqTreated {
switch v.PayloadName().Space {
case xmpp.NSDiscoInfo:
execDisco(v)
case xmpp.NSDiscoItems:
execDisco(v)
case xmpp.NodeAdHocCommand:
if jidBareTo == jid.Domain {
execCommandAdHoc(v)
} else {
sendNotSupportedFeature(v)
}
case xmpp.NSVCardTemp:
if jidBareTo == jid.Domain {
reply := v.Response(xmpp.IQTypeResult)
vcard := &xmpp.VCard{}
reply.PayloadEncode(vcard)
comp.Out <- reply
} else {
sendNotSupportedFeature(v)
}
case xmpp.NSJabberClient:
if jidBareTo == jid.Domain {
reply := v.Response(xmpp.IQTypeResult)
reply.PayloadEncode(&xmpp.SoftwareVersion{Name: "go-xmpp4steam", Version: SoftVersion})
comp.Out <- reply
} else {
sendNotSupportedFeature(v)
}
case xmpp.NSRegister:
if jidBareTo == jid.Domain {
treatmentNSRegister(v)
} else {
sendNotSupportedFeature(v)
}
case xmpp.NSRoster:
// Do nothing
case xmpp.NSPing:
if jidBareTo == jid.Domain {
treatmentNSPing(v)
} else {
sendNotSupportedFeature(v)
}
default:
sendNotSupportedFeature(v)
}
}
default:
log.Printf("%srecv: %v", LogDebug, x)
logger.Debug.Printf("recv: %v", x)
}
}
// Send deconnexion
SendPresence(Status_offline, Type_unavailable)
}
func must(v interface{}, err error) interface{} {
if err != nil {
log.Fatal(LogError, err)
logger.Debug.Printf("%v", err)
os.Exit(1)
}
return v
}
func treatmentNSRegister(iq *xmpp.IQ) {
reply := iq.Response(xmpp.IQTypeResult)
jidBareFrom := strings.SplitN(iq.From, "/", 2)[0]
registerQuery := &xmpp.RegisterQuery{}
if iq.Type == xmpp.IQTypeGet {
registerQuery.Instructions = "Please provide your Steam login and password (Please, be aware that the given Steam account information will be saved into an un-encrypted SQLite database)."
dbUser := database.GetLine(jidBareFrom)
if dbUser != nil {
// User already registered
registerQuery.Registered = &xmpp.RegisterRegistered{}
registerQuery.Username = dbUser.SteamLogin
registerQuery.XForm = *getXFormRegistration(dbUser.SteamLogin)
} else {
registerQuery.XForm = *getXFormRegistration("")
}
reply.PayloadEncode(registerQuery)
} else if iq.Type == xmpp.IQTypeSet {
iq.PayloadDecode(registerQuery)
if registerQuery.Remove != nil {
RemoveUser(jidBareFrom)
} else {
dbUser := getUser(registerQuery.XForm.Fields, iq)
if dbUser != nil {
if dbUser.UpdateUser() {
AddNewUser(dbUser.Jid, dbUser.SteamLogin, dbUser.SteamPwd, dbUser.Debug)
} else {
reply.Type = xmpp.IQTypeError
reply.Error = xmpp.NewErrorWithCode("406", "modify", xmpp.ErrorNotAcceptable, "")
}
} else {
reply.Type = xmpp.IQTypeError
reply.Error = xmpp.NewErrorWithCode("409", "cancel", xmpp.ErrorConflict, "")
}
}
}
comp.Out <- reply
}
func treatmentNSPing(iq *xmpp.IQ) {
reply := iq.Response(xmpp.IQTypeResult)
comp.Out <- reply
}
func sendNotSupportedFeature(iq *xmpp.IQ) {
if iq.Type != xmpp.IQTypeError && iq.Type != xmpp.IQTypeResult {
reply := iq.Response(xmpp.IQTypeError)
reply.PayloadEncode(xmpp.NewError("cancel", xmpp.ErrorFeatureNotImplemented, ""))
comp.Out <- reply
}
}
func Disconnect() {
SendPresence(Status_offline, Type_unavailable)
logger.Info.Printf("XMPP disconnect")
for _, u := range MapGatewayInfo {
u.SteamDisconnect()
}
comp.Close()
}
func SendPresence(status, tpye string) {
comp.Out <- xmpp.Presence{To: PreferedJID, From: jid.Domain, Show: status, Type: tpye, Status: "go-xmpp4steam v" + Version}
func SendPresence(status, tpye, from, to, message, nick string) {
p := xmpp.Presence{}
if status != "" {
p.Show = status
}
if tpye != "" {
p.Type = tpye
}
if message != "" {
p.Status = message
}
if nick != "" {
p.Nick = nick
}
if from == "" {
p.From = jid.Domain
} else {
p.From = from
}
if to != "" {
p.To = to
}
comp.Out <- p
}
func SendPresenceFrom(status, tpye, from string) {
comp.Out <- xmpp.Presence{To: PreferedJID, From: from, Show: status, Type: tpye}
func SendMessage(to, subject, message string) {
m := xmpp.Message{From: jid.Domain, To: to, Type: "chat"}
mBody := xmpp.MessageBody{Value: message}
m.Body = append(m.Body, mBody)
if subject != "" {
m.Subject = subject
}
logger.Info.Printf("Senp message %v", m)
comp.Out <- m
}
func SendMessage(from, message string) {
comp.Out <- xmpp.Message{To: PreferedJID, From: from, Body: message}
func AddNewUser(jidUser, steamLogin, steamPwd string, debugMessage bool) {
logger.Info.Printf("Add user %s to the map (debug mode set to %v)", jidUser, debugMessage)
g := new(gateway.GatewayInfo)
g.SteamLogin = steamLogin
g.SteamPassword = steamPwd
g.XMPP_JID_Client = jidUser
g.SentryFile = gateway.SentryDirectory + jidUser
g.CreateSteamIds()
g.Deleting = false
g.XMPP_Out = comp.Out
g.CreateXmppConnectedClient()
g.DebugMessage = debugMessage
g.CreateXmppRemoteRosterRequest()
g.AllowEditRoster = false
MapGatewayInfo[jidUser] = g
go g.Run()
logger.Info.Printf("Check roster edition by asking with remote roster manager namespace")
// Ask if remote roster is allow
iqId := gateway.NextIqId()
g.SetXmppRemoteRosterRequest(iqId, gateway.RemoteRosterRequestPermission)
iq := xmpp.IQ{To: jidUser, From: jid.Domain, Type: xmpp.IQTypeGet, ID: iqId}
// iq.PayloadEncode(&xmpp.RosterQuery{})
iq.PayloadEncode(&xmpp.RemoteRosterManagerQuery{Reason: "Manage contacts in the Steam contact list", Type: xmpp.RemoteRosterManagerTypeRequest})
comp.Out <- iq
}
func RemoveUser(jidBare string) bool {
ret := database.RemoveLine(jidBare)
if ret {
g := MapGatewayInfo[jidBare]
ret = g != nil
if ret {
g.Delete()
MapGatewayInfo[jidBare] = nil
}
}
return ret
}

View File

@ -3,10 +3,4 @@ xmpp_server_address=192.168.1.2
xmpp_server_port=5347
xmpp_hostname=xmppsteam.kingpenguin.tk
xmpp_secret=xmpp4steam_password
xmpp_authorized_jid=chteufleur@kingpenguin.tk
# Steam informations
steam_login=toto
steam_password=toto_password123$
# steam_auth_code must be blank the first time. Then Valve will send the auth code to give here.
steam_auth_code=CXD7J
xmpp_debug=true