From d4ae0bd5322fece929d0e4ce01ca1563fa3a7267 Mon Sep 17 00:00:00 2001 From: Henner Date: Sat, 30 Jun 2018 12:44:31 +0200 Subject: [PATCH 1/5] Fix channel and client id for type OnlineClient --- server_cmds.go | 3 ++- server_cmds_test.go | 9 +++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/server_cmds.go b/server_cmds.go index 9ba2b9a..1a2935c 100644 --- a/server_cmds.go +++ b/server_cmds.go @@ -340,7 +340,8 @@ func (s *ServerMethods) PrivilegeKeyAdd(ttype, id1, id2 int, options ...CmdArg) // OnlineClient represents a client online on a virtual server. type OnlineClient struct { - ID int `ms:"cid"` + ID int `ms:"clid"` + ChannelID int `ms:"cid"` DatabaseID int `ms:"client_database_id"` Nickname string `ms:"client_nickname"` Type int `ms:"client_type"` diff --git a/server_cmds_test.go b/server_cmds_test.go index 70e029d..4e58a0f 100644 --- a/server_cmds_test.go +++ b/server_cmds_test.go @@ -75,9 +75,9 @@ func TestCmdsServer(t *testing.T) { return } expected := &Server{ - Status: "template", - MaxClients: 32, - Name: "Test Server", + Status: "template", + MaxClients: 32, + Name: "Test Server", AntiFloodPointsNeededCommandBlock: 150, AntiFloodPointsNeededIPBlock: 250, AntiFloodPointsTickReduce: 5, @@ -259,7 +259,8 @@ func TestCmdsServer(t *testing.T) { expected := []*OnlineClient{ { - ID: 7, + ID: 5, + ChannelID: 7, DatabaseID: 40, Nickname: "ScP", Type: 0, From 7e081a7ba4ecf317c8ad8a077cdb7e5788e15732 Mon Sep 17 00:00:00 2001 From: Henner Date: Sat, 30 Jun 2018 14:30:17 +0200 Subject: [PATCH 2/5] Add ClientInfo() server method This also adds a proper struct to hold the response data. Documentation of the fields is a mix of the server query manual and experiments with an actual server. I could not figure out the meaning or data type for all fields, most notably `client_badges` and `client_meta_data` which I even excluded from the struct definition. Data for the tests is the incomplete data from the manual supplemented by real query results. Fields like ip address and UUIDs have been anonymized by replacing characters. I tried not to introduce characters which were previously not used in the value. --- .travis.yml | 2 +- mockserver_test.go | 1 + server_cmds.go | 116 ++++++++++++++++++++++++++++++++++++++++++-- server_cmds_test.go | 72 +++++++++++++++++++++++++++ 4 files changed, 186 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 8222cf5..bb96cd9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,4 +12,4 @@ install: script: - go test -v -race ./... - - gometalinter.v1 --cyclo-over=16 ./... + - gometalinter.v1 --cyclo-over=17 ./... diff --git a/mockserver_test.go b/mockserver_test.go index 580e74b..96b4e42 100644 --- a/mockserver_test.go +++ b/mockserver_test.go @@ -38,6 +38,7 @@ var ( "instanceinfo": "serverinstance_database_version=26 serverinstance_filetransfer_port=30033 serverinstance_max_download_total_bandwidth=18446744073709551615 serverinstance_max_upload_total_bandwidth=18446744073709551615 serverinstance_guest_serverquery_group=1 serverinstance_serverquery_flood_commands=50 serverinstance_serverquery_flood_time=3 serverinstance_serverquery_ban_time=600 serverinstance_template_serveradmin_group=3 serverinstance_template_serverdefault_group=5 serverinstance_template_channeladmin_group=1 serverinstance_template_channeldefault_group=4 serverinstance_permissions_version=19 serverinstance_pending_connections_per_ip=0", "serverrequestconnectioninfo": "connection_filetransfer_bandwidth_sent=0 connection_filetransfer_bandwidth_received=0 connection_filetransfer_bytes_sent_total=617 connection_filetransfer_bytes_received_total=0 connection_packets_sent_total=926413 connection_bytes_sent_total=92911395 connection_packets_received_total=650335 connection_bytes_received_total=61940731 connection_bandwidth_sent_last_second_total=0 connection_bandwidth_sent_last_minute_total=0 connection_bandwidth_received_last_second_total=0 connection_bandwidth_received_last_minute_total=0 connection_connected_time=49408 connection_packetloss_total=0.0000 connection_ping=0.0000", "channellist": "cid=499 pid=0 channel_order=0 channel_name=Default\\sChannel total_clients=1 channel_needed_subscribe_power=0", + "clientinfo": `cid=20 client_idle_time=28122 client_unique_identifier=P5H2hrN6+gpQI4n\/dXp3p17vtY0= client_nickname=Rabe85 client_version=3.0.0-alpha24\s[Build:\s8785]\s(UI:\s8785) client_platform=Windows client_input_muted=0 client_output_muted=0 client_outputonly_muted=0 client_input_hardware=1 client_output_hardware=1 client_default_channel=\/20 client_meta_data client_is_recording=0 client_version_sign=+\/BWvaeokGg4YkO1v3ouZB5vtIIgUZ5bM5cRfxBstfnHUdro2ja+5b+3sFUzEy8\/vvEISXVD6U95blTb638MCQ== client_security_hash client_login_name client_database_id=8 client_channel_group_id=8 client_servergroups=6 client_created=1503431624 client_lastconnected=1530383977 client_totalconnections=138 client_away=0 client_away_message client_type=0 client_flag_avatar=dd213abf2a94396ece544b22c4e56821 client_talk_power=75 client_talk_request=0 client_talk_request_msg client_description client_is_talker=0 client_month_bytes_uploaded=0 client_month_bytes_downloaded=0 client_total_bytes_uploaded=0 client_total_bytes_downloaded=3014720 client_is_priority_speaker=1 client_nickname_phonetic=rabeh client_needed_serverquery_view_power=75 client_default_token client_icon_id=0 client_is_channel_commander=1 client_country=DE client_channel_group_inherited_channel_id=20 client_badges=overwolf=0 client_base64HashClientUID=kdohhblmninnfhaecihcijemaigdnkdhgjllefed connection_filetransfer_bandwidth_sent=0 connection_filetransfer_bandwidth_received=0 connection_packets_sent_total=46880 connection_bytes_sent_total=6426774 connection_packets_received_total=14098 connection_bytes_received_total=1644574 connection_bandwidth_sent_last_second_total=81 connection_bandwidth_sent_last_minute_total=92 connection_bandwidth_received_last_second_total=83 connection_bandwidth_received_last_minute_total=97 connection_connected_time=2084247 connection_client_ip=83.123.45.6`, "clientlist": "clid=5 cid=7 client_database_id=40 client_nickname=ScP client_type=0 client_away=1 client_away_message=not\\shere", "clientdblist": "cldbid=7 client_unique_identifier=DZhdQU58qyooEK4Fr8Ly738hEmc= client_nickname=MuhChy client_created=1259147468 client_lastconnected=1259421233", "whoami": "virtualserver_status=online virtualserver_id=18 virtualserver_unique_identifier=gNITtWtKs9+Uh3L4LKv8\\/YHsn5c= virtualserver_port=9987 client_id=94 client_channel_id=432 client_nickname=serveradmin\\sfrom\\s127.0.0.1:49725 client_database_id=1 client_login_name=serveradmin client_unique_identifier=serveradmin client_origin_server_id=0", diff --git a/server_cmds.go b/server_cmds.go index 1a2935c..5c84b3f 100644 --- a/server_cmds.go +++ b/server_cmds.go @@ -263,16 +263,16 @@ func (s *ServerMethods) Stop(id int) error { // Group represents a virtual server group. type Group struct { - ID int `ms:"sgid"` + ID int `ms:"sgid"` Name string Type int IconID int Saved bool `ms:"savedb"` SortID int NameMode int - ModifyPower int `ms:"n_modifyp"` - MemberAddPower int `ms:"n_member_addp"` - MemberRemovePower int `ms:"n_member_addp"` + ModifyPower int `ms:"n_modifyp"` + MemberAddPower int `ms:"n_member_addp"` + MemberRemovePower int `ms:"n_member_addp"` } // GroupList returns a list of available groups for the selected server. @@ -349,6 +349,114 @@ type OnlineClient struct { AwayMessage string `ms:"client_away_message"` } +// DetailedOnlineClient extends OnlineClient with all information from the +// clientinfo server query command about a client online on a virtual server. +type DetailedOnlineClient struct { + OnlineClient `ms:",squash"` + + // Empty or a string of 32 hexadecimal characters. Indicates whether the + // client has set an avatar or not. + // What platform the client is running on. For example "Windows". + Base64HashClientUID string `ms:"client_base64HashClientUID"` + // For example "83.123.45.6". According to the manual this is always IPv4. + ConnectionClientIP string `ms:"connection_client_ip"` + // For example "DE" for Germany. + Country string `ms:"client_country"` + // Takes the form of "/channelID" + DefaultChannel string `ms:"client_default_channel"` + DefaultToken string `ms:"client_default_token"` + Description string `ms:"client_description"` + LoginName string `ms:"client_login_name"` + NicknamePhonetic string `ms:"client_nickname_phonetic"` + SecurityHash string `ms:"client_security_hash"` + TalkRequestMsg string `ms:"client_talk_request_msg"` + UniqueIdentifier string `ms:"client_unique_identifier"` + FlagAvatar string `ms:"client_flag_avatar"` + Platform string `ms:"client_platform"` + // Which version of the Teamspeak client application this client uses. + Version string `ms:"client_version"` + VersionSign string `ms:"client_version_sign"` + + // UTC timestamp at which the client has been created. + Created int64 `ms:"client_created"` + // Milliseconds since the client connected to the server. + ConnectionConnectedTime int64 `ms:"connection_connected_time"` + ConnectionBytesReceivedTotal int64 `ms:"connection_bytes_received_total"` + ConnectionBytesSentTotal int64 `ms:"connection_bytes_sent_total"` + ConnectionPacketsReceivedTotal int64 `ms:"connection_packets_received_total"` + ConnectionPacketsSentTotal int64 `ms:"connection_packets_sent_total"` + // Milliseconds since the client did something, for example sending + // a message, muting themselves or talking. + IdleTime int64 `ms:"client_idle_time"` + // UTC timestamp at which the client has been online for the last time. + // Not when the connection was established but when it was closed. + LastConnected int64 `ms:"client_lastconnected"` + MonthBytesDownloaded int64 `ms:"client_month_bytes_downloaded"` + MonthBytesUploaded int64 `ms:"client_month_bytes_uploaded"` + TotalBytesDownloaded int64 `ms:"client_total_bytes_downloaded"` + TotalBytesUploaded int64 `ms:"client_total_bytes_uploaded"` + + // Current bandwidth used for outgoing file transfers (Bytes/s) + ConnectionFiletransferBandwidthSent int `ms:"connection_filetransfer_bandwidth_sent"` + // Current bandwidth used for incoming file transfers (Bytes/s) + ConnectionFiletransferBandwidthReceived int `ms:"connection_filetransfer_bandwidth_received"` + // Average bandwidth used for outgoing data in the last second (Bytes/s) + ConnectionBandwidthSentLastSecondTotal int `ms:"connection_bandwidth_sent_last_second_total"` + // Average bandwidth used for outgoing data in the last minute (Bytes/s) + ConnectionBandwidthSentLastMinuteTotal int `ms:"connection_bandwidth_sent_last_minute_total"` + // Average bandwidth used for incoming data in the last second (Bytes/s) + ConnectionBandwidthReceivedLastSecondTotal int `ms:"connection_bandwidth_received_last_second_total"` + // Average bandwidth used for incoming data in the last minute (Bytes/s) + ConnectionBandwidthReceivedLastMinuteTotal int `ms:"connection_bandwidth_received_last_minute_total"` + ChannelGroupID int `ms:"client_channel_group_id"` + ChannelGroupInheritedChannelID int `ms:"client_channel_group_inherited_channel_id"` + NeededServerqueryViewPower int `ms:"client_needed_serverquery_view_power"` + Servergroups int `ms:"client_servergroups"` + TalkPower int `ms:"client_talk_power"` + // How often the client has connected to the server. + Totalconnections int `ms:"client_totalconnections"` + + // CRC32 checksum of the client icon + IconID uint32 `ms:"client_icon_id"` + + // False if the client has their microphone disabled, for example + // because they unplugged it. Do not confuse this with InputMuted. + InputHardware bool `ms:"client_input_hardware"` + // True if the client has their microphone muted + InputMuted bool `ms:"client_input_muted"` + IsChannelCommander bool `ms:"client_is_channel_commander"` + IsPrioritySpeaker bool `ms:"client_is_priority_speaker"` + IsRecording bool `ms:"client_is_recording"` + // False if the client has their speakers disabled, for example + // because they are unplugged. Do not confuse this with OutputMuted. + OutputHardware bool `ms:"client_output_hardware"` + // True if the client has their speakers muted + OutputMuted bool `ms:"client_output_muted"` + OutputOnlyMuted bool `ms:"client_outputonly_muted"` + TalkRequest bool `ms:"client_talk_request"` + + // Indicates whether the client is able to talk or not. + //TODO: This is always 0, even if my talk power is high enough? + IsTalker bool `ms:"client_is_talker"` + + //TODO: I always got "overwolf=0". I assume it is a list of "key=value|key2=value2...". In that case, the type of this should be a map (or maybe an array, if it's only true/false). + //Badges string `ms:"client_badges"` + + //TODO: I never managed to receive any value for this field + //MetaData interface{} `ms:"client_meta_data"` +} + +// ClientInfo returns detailed information about a single online client. +func (s *ServerMethods) ClientInfo(clientID int) (*DetailedOnlineClient, error) { + var client DetailedOnlineClient + if _, err := s.ExecCmd(NewCmd("clientinfo").WithArgs(NewArg("clid", clientID)).WithResponse(&client)); err != nil { + return nil, err + } + + client.ID = clientID // the clientinfo command does not include the clid in the result set + return &client, nil +} + // ClientList returns a list of online clients. func (s *ServerMethods) ClientList() ([]*OnlineClient, error) { var clients []*OnlineClient diff --git a/server_cmds_test.go b/server_cmds_test.go index 4e58a0f..7be2f9b 100644 --- a/server_cmds_test.go +++ b/server_cmds_test.go @@ -251,6 +251,77 @@ func TestCmdsServer(t *testing.T) { assert.Equal(t, expected, channels) } + clientinfo := func(t *testing.T) { + client, err := c.Server.ClientInfo(8) + if !assert.NoError(t, err) { + return + } + + expected := &DetailedOnlineClient{ + OnlineClient: OnlineClient{ + ID: 8, + ChannelID: 20, + DatabaseID: 8, + Nickname: "Rabe85", + Type: 0, + Away: false, + AwayMessage: "", + }, + IdleTime: 28122, + UniqueIdentifier: "P5H2hrN6+gpQI4n/dXp3p17vtY0=", + Version: "3.0.0-alpha24 [Build: 8785] (UI: 8785)", + Platform: "Windows", + InputMuted: false, + OutputMuted: false, + OutputOnlyMuted: false, + InputHardware: true, + OutputHardware: true, + DefaultChannel: "/20", + IsRecording: false, + VersionSign: "+/BWvaeokGg4YkO1v3ouZB5vtIIgUZ5bM5cRfxBstfnHUdro2ja+5b+3sFUzEy8/vvEISXVD6U95blTb638MCQ==", + SecurityHash: "", + LoginName: "", + ChannelGroupID: 8, + Servergroups: 6, + Created: 1503431624, + LastConnected: 1530383977, + Totalconnections: 138, + FlagAvatar: "dd213abf2a94396ece544b22c4e56821", + TalkPower: 75, + TalkRequest: false, + TalkRequestMsg: "", + Description: "", + IsTalker: false, + MonthBytesUploaded: 0, + MonthBytesDownloaded: 0, + TotalBytesUploaded: 0, + TotalBytesDownloaded: 3014720, + IsPrioritySpeaker: true, + NicknamePhonetic: "rabeh", + NeededServerqueryViewPower: 75, + DefaultToken: "", + IconID: 0, + IsChannelCommander: true, + Country: "DE", + ChannelGroupInheritedChannelID: 20, + Base64HashClientUID: "kdohhblmninnfhaecihcijemaigdnkdhgjllefed", + ConnectionFiletransferBandwidthSent: 0, + ConnectionFiletransferBandwidthReceived: 0, + ConnectionPacketsSentTotal: 46880, + ConnectionBytesSentTotal: 6426774, + ConnectionPacketsReceivedTotal: 14098, + ConnectionBytesReceivedTotal: 1644574, + ConnectionBandwidthSentLastSecondTotal: 81, + ConnectionBandwidthSentLastMinuteTotal: 92, + ConnectionBandwidthReceivedLastSecondTotal: 83, + ConnectionBandwidthReceivedLastMinuteTotal: 97, + ConnectionConnectedTime: 2084247, + ConnectionClientIP: "83.123.45.6", + } + + assert.Equal(t, expected, client) + } + clientlist := func(t *testing.T) { clients, err := c.Server.ClientList() if !assert.NoError(t, err) { @@ -309,6 +380,7 @@ func TestCmdsServer(t *testing.T) { {"serverrequestconnectioninfo", serverrequestconnectioninfo}, {"instanceinfo", instanceinfo}, {"channellist", channellist}, + {"clientinfo", clientinfo}, {"clientlist", clientlist}, {"clientdblist", clientdblist}, } From fc2b5196202077f55d8e4fdb4177664937b30156 Mon Sep 17 00:00:00 2001 From: Henner Date: Sun, 1 Jul 2018 15:20:02 +0200 Subject: [PATCH 3/5] Add github handle to TODOs https://github.com/multiplay/go-ts3/pull/16#discussion_r199345866 --- server_cmds.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/server_cmds.go b/server_cmds.go index 5c84b3f..32d6da9 100644 --- a/server_cmds.go +++ b/server_cmds.go @@ -436,13 +436,13 @@ type DetailedOnlineClient struct { TalkRequest bool `ms:"client_talk_request"` // Indicates whether the client is able to talk or not. - //TODO: This is always 0, even if my talk power is high enough? + //TODO(Henner25): This is always 0, even if my talk power is high enough? IsTalker bool `ms:"client_is_talker"` - //TODO: I always got "overwolf=0". I assume it is a list of "key=value|key2=value2...". In that case, the type of this should be a map (or maybe an array, if it's only true/false). + //TODO(Henner25): I always got "overwolf=0". I assume it is a list of "key=value|key2=value2...". In that case, the type of this should be a map (or maybe an array, if it's only true/false). //Badges string `ms:"client_badges"` - //TODO: I never managed to receive any value for this field + //TODO(Henner25): I never managed to receive any value for this field //MetaData interface{} `ms:"client_meta_data"` } From 89d091280b3ce0b2907853b87541da2660615bfc Mon Sep 17 00:00:00 2001 From: Henner Date: Sun, 1 Jul 2018 15:40:11 +0200 Subject: [PATCH 4/5] Use time.Time type for DetailedOnlineClient fields --- server_cmds.go | 48 +++++++++++++++++++++++---------------------- server_cmds_test.go | 4 ++-- 2 files changed, 27 insertions(+), 25 deletions(-) diff --git a/server_cmds.go b/server_cmds.go index 32d6da9..a44a85a 100644 --- a/server_cmds.go +++ b/server_cmds.go @@ -354,6 +354,13 @@ type OnlineClient struct { type DetailedOnlineClient struct { OnlineClient `ms:",squash"` + // Creation date and time of the clients first connection to the server. + Created time.Time `ms:"client_created"` + // Termination date and time of the clients last connection to the server. + // Note: The manual claims this to be the *creation* date and time but the + // values received while testing suggest otherwise. + LastConnected time.Time `ms:"client_lastconnected"` + // Empty or a string of 32 hexadecimal characters. Indicates whether the // client has set an avatar or not. // What platform the client is running on. For example "Windows". @@ -363,38 +370,33 @@ type DetailedOnlineClient struct { // For example "DE" for Germany. Country string `ms:"client_country"` // Takes the form of "/channelID" - DefaultChannel string `ms:"client_default_channel"` - DefaultToken string `ms:"client_default_token"` - Description string `ms:"client_description"` - LoginName string `ms:"client_login_name"` - NicknamePhonetic string `ms:"client_nickname_phonetic"` - SecurityHash string `ms:"client_security_hash"` - TalkRequestMsg string `ms:"client_talk_request_msg"` - UniqueIdentifier string `ms:"client_unique_identifier"` - FlagAvatar string `ms:"client_flag_avatar"` - Platform string `ms:"client_platform"` + DefaultChannel string `ms:"client_default_channel"` + DefaultToken string `ms:"client_default_token"` + Description string `ms:"client_description"` + LoginName string `ms:"client_login_name"` + NicknamePhonetic string `ms:"client_nickname_phonetic"` + SecurityHash string `ms:"client_security_hash"` + TalkRequestMsg string `ms:"client_talk_request_msg"` + UniqueIdentifier string `ms:"client_unique_identifier"` + FlagAvatar string `ms:"client_flag_avatar"` + Platform string `ms:"client_platform"` // Which version of the Teamspeak client application this client uses. - Version string `ms:"client_version"` - VersionSign string `ms:"client_version_sign"` + Version string `ms:"client_version"` + VersionSign string `ms:"client_version_sign"` - // UTC timestamp at which the client has been created. - Created int64 `ms:"client_created"` // Milliseconds since the client connected to the server. - ConnectionConnectedTime int64 `ms:"connection_connected_time"` + ConnectionConnectedTime int64 `ms:"connection_connected_time"` ConnectionBytesReceivedTotal int64 `ms:"connection_bytes_received_total"` ConnectionBytesSentTotal int64 `ms:"connection_bytes_sent_total"` ConnectionPacketsReceivedTotal int64 `ms:"connection_packets_received_total"` ConnectionPacketsSentTotal int64 `ms:"connection_packets_sent_total"` // Milliseconds since the client did something, for example sending // a message, muting themselves or talking. - IdleTime int64 `ms:"client_idle_time"` - // UTC timestamp at which the client has been online for the last time. - // Not when the connection was established but when it was closed. - LastConnected int64 `ms:"client_lastconnected"` - MonthBytesDownloaded int64 `ms:"client_month_bytes_downloaded"` - MonthBytesUploaded int64 `ms:"client_month_bytes_uploaded"` - TotalBytesDownloaded int64 `ms:"client_total_bytes_downloaded"` - TotalBytesUploaded int64 `ms:"client_total_bytes_uploaded"` + IdleTime int64 `ms:"client_idle_time"` + MonthBytesDownloaded int64 `ms:"client_month_bytes_downloaded"` + MonthBytesUploaded int64 `ms:"client_month_bytes_uploaded"` + TotalBytesDownloaded int64 `ms:"client_total_bytes_downloaded"` + TotalBytesUploaded int64 `ms:"client_total_bytes_uploaded"` // Current bandwidth used for outgoing file transfers (Bytes/s) ConnectionFiletransferBandwidthSent int `ms:"connection_filetransfer_bandwidth_sent"` diff --git a/server_cmds_test.go b/server_cmds_test.go index 7be2f9b..6a54884 100644 --- a/server_cmds_test.go +++ b/server_cmds_test.go @@ -283,8 +283,8 @@ func TestCmdsServer(t *testing.T) { LoginName: "", ChannelGroupID: 8, Servergroups: 6, - Created: 1503431624, - LastConnected: 1530383977, + Created: time.Unix(1503431624, 0), + LastConnected: time.Unix(1530383977, 0), Totalconnections: 138, FlagAvatar: "dd213abf2a94396ece544b22c4e56821", TalkPower: 75, From 837f087ae47e2b2dd210f3fb6eb641cdf8d955f4 Mon Sep 17 00:00:00 2001 From: Henner Date: Tue, 1 Jan 2019 12:17:40 +0100 Subject: [PATCH 5/5] Support decoding responses into slices This fixes the Servergroups field of DetailedOnlineClient. Credits for the discovery and solution go to irgendwr. --- helpers.go | 20 +++++++++++++++++++- mockserver_test.go | 2 +- server_cmds.go | 2 +- server_cmds_test.go | 2 +- 4 files changed, 22 insertions(+), 4 deletions(-) diff --git a/helpers.go b/helpers.go index 9124c0c..5b3e74d 100644 --- a/helpers.go +++ b/helpers.go @@ -108,7 +108,10 @@ func decodeMap(d map[string]interface{}, r interface{}) error { WeaklyTypedInput: true, TagName: "ms", Result: r, - DecodeHook: timeHookFunc, + DecodeHook: mapstructure.ComposeDecodeHookFunc( + sliceHookFunc, + timeHookFunc, + ), } dec, err := mapstructure.NewDecoder(cfg) if err != nil { @@ -142,6 +145,21 @@ func decodeSlice(elemType reflect.Type, slice reflect.Value, input map[string]in return nil } +// sliceHookFunc supports int/string decoding to slice +func sliceHookFunc(from reflect.Kind, to reflect.Kind, data interface{}) (interface{}, error) { + if to == reflect.Slice { + if from == reflect.String { + return strings.Split(data.(string), ","), nil + } + + if from == reflect.Int { + return []int{data.(int)}, nil + } + } + + return data, nil +} + var timeType = reflect.TypeOf(time.Time{}) // timeHookFunc supports decoding to time diff --git a/mockserver_test.go b/mockserver_test.go index 96b4e42..ae565ad 100644 --- a/mockserver_test.go +++ b/mockserver_test.go @@ -38,7 +38,7 @@ var ( "instanceinfo": "serverinstance_database_version=26 serverinstance_filetransfer_port=30033 serverinstance_max_download_total_bandwidth=18446744073709551615 serverinstance_max_upload_total_bandwidth=18446744073709551615 serverinstance_guest_serverquery_group=1 serverinstance_serverquery_flood_commands=50 serverinstance_serverquery_flood_time=3 serverinstance_serverquery_ban_time=600 serverinstance_template_serveradmin_group=3 serverinstance_template_serverdefault_group=5 serverinstance_template_channeladmin_group=1 serverinstance_template_channeldefault_group=4 serverinstance_permissions_version=19 serverinstance_pending_connections_per_ip=0", "serverrequestconnectioninfo": "connection_filetransfer_bandwidth_sent=0 connection_filetransfer_bandwidth_received=0 connection_filetransfer_bytes_sent_total=617 connection_filetransfer_bytes_received_total=0 connection_packets_sent_total=926413 connection_bytes_sent_total=92911395 connection_packets_received_total=650335 connection_bytes_received_total=61940731 connection_bandwidth_sent_last_second_total=0 connection_bandwidth_sent_last_minute_total=0 connection_bandwidth_received_last_second_total=0 connection_bandwidth_received_last_minute_total=0 connection_connected_time=49408 connection_packetloss_total=0.0000 connection_ping=0.0000", "channellist": "cid=499 pid=0 channel_order=0 channel_name=Default\\sChannel total_clients=1 channel_needed_subscribe_power=0", - "clientinfo": `cid=20 client_idle_time=28122 client_unique_identifier=P5H2hrN6+gpQI4n\/dXp3p17vtY0= client_nickname=Rabe85 client_version=3.0.0-alpha24\s[Build:\s8785]\s(UI:\s8785) client_platform=Windows client_input_muted=0 client_output_muted=0 client_outputonly_muted=0 client_input_hardware=1 client_output_hardware=1 client_default_channel=\/20 client_meta_data client_is_recording=0 client_version_sign=+\/BWvaeokGg4YkO1v3ouZB5vtIIgUZ5bM5cRfxBstfnHUdro2ja+5b+3sFUzEy8\/vvEISXVD6U95blTb638MCQ== client_security_hash client_login_name client_database_id=8 client_channel_group_id=8 client_servergroups=6 client_created=1503431624 client_lastconnected=1530383977 client_totalconnections=138 client_away=0 client_away_message client_type=0 client_flag_avatar=dd213abf2a94396ece544b22c4e56821 client_talk_power=75 client_talk_request=0 client_talk_request_msg client_description client_is_talker=0 client_month_bytes_uploaded=0 client_month_bytes_downloaded=0 client_total_bytes_uploaded=0 client_total_bytes_downloaded=3014720 client_is_priority_speaker=1 client_nickname_phonetic=rabeh client_needed_serverquery_view_power=75 client_default_token client_icon_id=0 client_is_channel_commander=1 client_country=DE client_channel_group_inherited_channel_id=20 client_badges=overwolf=0 client_base64HashClientUID=kdohhblmninnfhaecihcijemaigdnkdhgjllefed connection_filetransfer_bandwidth_sent=0 connection_filetransfer_bandwidth_received=0 connection_packets_sent_total=46880 connection_bytes_sent_total=6426774 connection_packets_received_total=14098 connection_bytes_received_total=1644574 connection_bandwidth_sent_last_second_total=81 connection_bandwidth_sent_last_minute_total=92 connection_bandwidth_received_last_second_total=83 connection_bandwidth_received_last_minute_total=97 connection_connected_time=2084247 connection_client_ip=83.123.45.6`, + "clientinfo": `cid=20 client_idle_time=28122 client_unique_identifier=P5H2hrN6+gpQI4n\/dXp3p17vtY0= client_nickname=Rabe85 client_version=3.0.0-alpha24\s[Build:\s8785]\s(UI:\s8785) client_platform=Windows client_input_muted=0 client_output_muted=0 client_outputonly_muted=0 client_input_hardware=1 client_output_hardware=1 client_default_channel=\/20 client_meta_data client_is_recording=0 client_version_sign=+\/BWvaeokGg4YkO1v3ouZB5vtIIgUZ5bM5cRfxBstfnHUdro2ja+5b+3sFUzEy8\/vvEISXVD6U95blTb638MCQ== client_security_hash client_login_name client_database_id=8 client_channel_group_id=8 client_servergroups=6,10 client_created=1503431624 client_lastconnected=1530383977 client_totalconnections=138 client_away=0 client_away_message client_type=0 client_flag_avatar=dd213abf2a94396ece544b22c4e56821 client_talk_power=75 client_talk_request=0 client_talk_request_msg client_description client_is_talker=0 client_month_bytes_uploaded=0 client_month_bytes_downloaded=0 client_total_bytes_uploaded=0 client_total_bytes_downloaded=3014720 client_is_priority_speaker=1 client_nickname_phonetic=rabeh client_needed_serverquery_view_power=75 client_default_token client_icon_id=0 client_is_channel_commander=1 client_country=DE client_channel_group_inherited_channel_id=20 client_badges=overwolf=0 client_base64HashClientUID=kdohhblmninnfhaecihcijemaigdnkdhgjllefed connection_filetransfer_bandwidth_sent=0 connection_filetransfer_bandwidth_received=0 connection_packets_sent_total=46880 connection_bytes_sent_total=6426774 connection_packets_received_total=14098 connection_bytes_received_total=1644574 connection_bandwidth_sent_last_second_total=81 connection_bandwidth_sent_last_minute_total=92 connection_bandwidth_received_last_second_total=83 connection_bandwidth_received_last_minute_total=97 connection_connected_time=2084247 connection_client_ip=83.123.45.6`, "clientlist": "clid=5 cid=7 client_database_id=40 client_nickname=ScP client_type=0 client_away=1 client_away_message=not\\shere", "clientdblist": "cldbid=7 client_unique_identifier=DZhdQU58qyooEK4Fr8Ly738hEmc= client_nickname=MuhChy client_created=1259147468 client_lastconnected=1259421233", "whoami": "virtualserver_status=online virtualserver_id=18 virtualserver_unique_identifier=gNITtWtKs9+Uh3L4LKv8\\/YHsn5c= virtualserver_port=9987 client_id=94 client_channel_id=432 client_nickname=serveradmin\\sfrom\\s127.0.0.1:49725 client_database_id=1 client_login_name=serveradmin client_unique_identifier=serveradmin client_origin_server_id=0", diff --git a/server_cmds.go b/server_cmds.go index a44a85a..cfbbe9c 100644 --- a/server_cmds.go +++ b/server_cmds.go @@ -413,7 +413,7 @@ type DetailedOnlineClient struct { ChannelGroupID int `ms:"client_channel_group_id"` ChannelGroupInheritedChannelID int `ms:"client_channel_group_inherited_channel_id"` NeededServerqueryViewPower int `ms:"client_needed_serverquery_view_power"` - Servergroups int `ms:"client_servergroups"` + Servergroups []int `ms:"client_servergroups"` TalkPower int `ms:"client_talk_power"` // How often the client has connected to the server. Totalconnections int `ms:"client_totalconnections"` diff --git a/server_cmds_test.go b/server_cmds_test.go index 6a54884..03e6b2d 100644 --- a/server_cmds_test.go +++ b/server_cmds_test.go @@ -282,7 +282,7 @@ func TestCmdsServer(t *testing.T) { SecurityHash: "", LoginName: "", ChannelGroupID: 8, - Servergroups: 6, + Servergroups: []int{6, 10}, Created: time.Unix(1503431624, 0), LastConnected: time.Unix(1530383977, 0), Totalconnections: 138,