Skip to content

Commit 7e081a7

Browse files
committed
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.
1 parent d4ae0bd commit 7e081a7

File tree

4 files changed

+186
-5
lines changed

4 files changed

+186
-5
lines changed

.travis.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,4 @@ install:
1212

1313
script:
1414
- go test -v -race ./...
15-
- gometalinter.v1 --cyclo-over=16 ./...
15+
- gometalinter.v1 --cyclo-over=17 ./...

mockserver_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ var (
3838
"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",
3939
"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",
4040
"channellist": "cid=499 pid=0 channel_order=0 channel_name=Default\\sChannel total_clients=1 channel_needed_subscribe_power=0",
41+
"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`,
4142
"clientlist": "clid=5 cid=7 client_database_id=40 client_nickname=ScP client_type=0 client_away=1 client_away_message=not\\shere",
4243
"clientdblist": "cldbid=7 client_unique_identifier=DZhdQU58qyooEK4Fr8Ly738hEmc= client_nickname=MuhChy client_created=1259147468 client_lastconnected=1259421233",
4344
"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",

server_cmds.go

Lines changed: 112 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -263,16 +263,16 @@ func (s *ServerMethods) Stop(id int) error {
263263

264264
// Group represents a virtual server group.
265265
type Group struct {
266-
ID int `ms:"sgid"`
266+
ID int `ms:"sgid"`
267267
Name string
268268
Type int
269269
IconID int
270270
Saved bool `ms:"savedb"`
271271
SortID int
272272
NameMode int
273-
ModifyPower int `ms:"n_modifyp"`
274-
MemberAddPower int `ms:"n_member_addp"`
275-
MemberRemovePower int `ms:"n_member_addp"`
273+
ModifyPower int `ms:"n_modifyp"`
274+
MemberAddPower int `ms:"n_member_addp"`
275+
MemberRemovePower int `ms:"n_member_addp"`
276276
}
277277

278278
// GroupList returns a list of available groups for the selected server.
@@ -349,6 +349,114 @@ type OnlineClient struct {
349349
AwayMessage string `ms:"client_away_message"`
350350
}
351351

352+
// DetailedOnlineClient extends OnlineClient with all information from the
353+
// clientinfo server query command about a client online on a virtual server.
354+
type DetailedOnlineClient struct {
355+
OnlineClient `ms:",squash"`
356+
357+
// Empty or a string of 32 hexadecimal characters. Indicates whether the
358+
// client has set an avatar or not.
359+
// What platform the client is running on. For example "Windows".
360+
Base64HashClientUID string `ms:"client_base64HashClientUID"`
361+
// For example "83.123.45.6". According to the manual this is always IPv4.
362+
ConnectionClientIP string `ms:"connection_client_ip"`
363+
// For example "DE" for Germany.
364+
Country string `ms:"client_country"`
365+
// Takes the form of "/channelID"
366+
DefaultChannel string `ms:"client_default_channel"`
367+
DefaultToken string `ms:"client_default_token"`
368+
Description string `ms:"client_description"`
369+
LoginName string `ms:"client_login_name"`
370+
NicknamePhonetic string `ms:"client_nickname_phonetic"`
371+
SecurityHash string `ms:"client_security_hash"`
372+
TalkRequestMsg string `ms:"client_talk_request_msg"`
373+
UniqueIdentifier string `ms:"client_unique_identifier"`
374+
FlagAvatar string `ms:"client_flag_avatar"`
375+
Platform string `ms:"client_platform"`
376+
// Which version of the Teamspeak client application this client uses.
377+
Version string `ms:"client_version"`
378+
VersionSign string `ms:"client_version_sign"`
379+
380+
// UTC timestamp at which the client has been created.
381+
Created int64 `ms:"client_created"`
382+
// Milliseconds since the client connected to the server.
383+
ConnectionConnectedTime int64 `ms:"connection_connected_time"`
384+
ConnectionBytesReceivedTotal int64 `ms:"connection_bytes_received_total"`
385+
ConnectionBytesSentTotal int64 `ms:"connection_bytes_sent_total"`
386+
ConnectionPacketsReceivedTotal int64 `ms:"connection_packets_received_total"`
387+
ConnectionPacketsSentTotal int64 `ms:"connection_packets_sent_total"`
388+
// Milliseconds since the client did something, for example sending
389+
// a message, muting themselves or talking.
390+
IdleTime int64 `ms:"client_idle_time"`
391+
// UTC timestamp at which the client has been online for the last time.
392+
// Not when the connection was established but when it was closed.
393+
LastConnected int64 `ms:"client_lastconnected"`
394+
MonthBytesDownloaded int64 `ms:"client_month_bytes_downloaded"`
395+
MonthBytesUploaded int64 `ms:"client_month_bytes_uploaded"`
396+
TotalBytesDownloaded int64 `ms:"client_total_bytes_downloaded"`
397+
TotalBytesUploaded int64 `ms:"client_total_bytes_uploaded"`
398+
399+
// Current bandwidth used for outgoing file transfers (Bytes/s)
400+
ConnectionFiletransferBandwidthSent int `ms:"connection_filetransfer_bandwidth_sent"`
401+
// Current bandwidth used for incoming file transfers (Bytes/s)
402+
ConnectionFiletransferBandwidthReceived int `ms:"connection_filetransfer_bandwidth_received"`
403+
// Average bandwidth used for outgoing data in the last second (Bytes/s)
404+
ConnectionBandwidthSentLastSecondTotal int `ms:"connection_bandwidth_sent_last_second_total"`
405+
// Average bandwidth used for outgoing data in the last minute (Bytes/s)
406+
ConnectionBandwidthSentLastMinuteTotal int `ms:"connection_bandwidth_sent_last_minute_total"`
407+
// Average bandwidth used for incoming data in the last second (Bytes/s)
408+
ConnectionBandwidthReceivedLastSecondTotal int `ms:"connection_bandwidth_received_last_second_total"`
409+
// Average bandwidth used for incoming data in the last minute (Bytes/s)
410+
ConnectionBandwidthReceivedLastMinuteTotal int `ms:"connection_bandwidth_received_last_minute_total"`
411+
ChannelGroupID int `ms:"client_channel_group_id"`
412+
ChannelGroupInheritedChannelID int `ms:"client_channel_group_inherited_channel_id"`
413+
NeededServerqueryViewPower int `ms:"client_needed_serverquery_view_power"`
414+
Servergroups int `ms:"client_servergroups"`
415+
TalkPower int `ms:"client_talk_power"`
416+
// How often the client has connected to the server.
417+
Totalconnections int `ms:"client_totalconnections"`
418+
419+
// CRC32 checksum of the client icon
420+
IconID uint32 `ms:"client_icon_id"`
421+
422+
// False if the client has their microphone disabled, for example
423+
// because they unplugged it. Do not confuse this with InputMuted.
424+
InputHardware bool `ms:"client_input_hardware"`
425+
// True if the client has their microphone muted
426+
InputMuted bool `ms:"client_input_muted"`
427+
IsChannelCommander bool `ms:"client_is_channel_commander"`
428+
IsPrioritySpeaker bool `ms:"client_is_priority_speaker"`
429+
IsRecording bool `ms:"client_is_recording"`
430+
// False if the client has their speakers disabled, for example
431+
// because they are unplugged. Do not confuse this with OutputMuted.
432+
OutputHardware bool `ms:"client_output_hardware"`
433+
// True if the client has their speakers muted
434+
OutputMuted bool `ms:"client_output_muted"`
435+
OutputOnlyMuted bool `ms:"client_outputonly_muted"`
436+
TalkRequest bool `ms:"client_talk_request"`
437+
438+
// Indicates whether the client is able to talk or not.
439+
//TODO: This is always 0, even if my talk power is high enough?
440+
IsTalker bool `ms:"client_is_talker"`
441+
442+
//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).
443+
//Badges string `ms:"client_badges"`
444+
445+
//TODO: I never managed to receive any value for this field
446+
//MetaData interface{} `ms:"client_meta_data"`
447+
}
448+
449+
// ClientInfo returns detailed information about a single online client.
450+
func (s *ServerMethods) ClientInfo(clientID int) (*DetailedOnlineClient, error) {
451+
var client DetailedOnlineClient
452+
if _, err := s.ExecCmd(NewCmd("clientinfo").WithArgs(NewArg("clid", clientID)).WithResponse(&client)); err != nil {
453+
return nil, err
454+
}
455+
456+
client.ID = clientID // the clientinfo command does not include the clid in the result set
457+
return &client, nil
458+
}
459+
352460
// ClientList returns a list of online clients.
353461
func (s *ServerMethods) ClientList() ([]*OnlineClient, error) {
354462
var clients []*OnlineClient

server_cmds_test.go

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,77 @@ func TestCmdsServer(t *testing.T) {
251251
assert.Equal(t, expected, channels)
252252
}
253253

254+
clientinfo := func(t *testing.T) {
255+
client, err := c.Server.ClientInfo(8)
256+
if !assert.NoError(t, err) {
257+
return
258+
}
259+
260+
expected := &DetailedOnlineClient{
261+
OnlineClient: OnlineClient{
262+
ID: 8,
263+
ChannelID: 20,
264+
DatabaseID: 8,
265+
Nickname: "Rabe85",
266+
Type: 0,
267+
Away: false,
268+
AwayMessage: "",
269+
},
270+
IdleTime: 28122,
271+
UniqueIdentifier: "P5H2hrN6+gpQI4n/dXp3p17vtY0=",
272+
Version: "3.0.0-alpha24 [Build: 8785] (UI: 8785)",
273+
Platform: "Windows",
274+
InputMuted: false,
275+
OutputMuted: false,
276+
OutputOnlyMuted: false,
277+
InputHardware: true,
278+
OutputHardware: true,
279+
DefaultChannel: "/20",
280+
IsRecording: false,
281+
VersionSign: "+/BWvaeokGg4YkO1v3ouZB5vtIIgUZ5bM5cRfxBstfnHUdro2ja+5b+3sFUzEy8/vvEISXVD6U95blTb638MCQ==",
282+
SecurityHash: "",
283+
LoginName: "",
284+
ChannelGroupID: 8,
285+
Servergroups: 6,
286+
Created: 1503431624,
287+
LastConnected: 1530383977,
288+
Totalconnections: 138,
289+
FlagAvatar: "dd213abf2a94396ece544b22c4e56821",
290+
TalkPower: 75,
291+
TalkRequest: false,
292+
TalkRequestMsg: "",
293+
Description: "",
294+
IsTalker: false,
295+
MonthBytesUploaded: 0,
296+
MonthBytesDownloaded: 0,
297+
TotalBytesUploaded: 0,
298+
TotalBytesDownloaded: 3014720,
299+
IsPrioritySpeaker: true,
300+
NicknamePhonetic: "rabeh",
301+
NeededServerqueryViewPower: 75,
302+
DefaultToken: "",
303+
IconID: 0,
304+
IsChannelCommander: true,
305+
Country: "DE",
306+
ChannelGroupInheritedChannelID: 20,
307+
Base64HashClientUID: "kdohhblmninnfhaecihcijemaigdnkdhgjllefed",
308+
ConnectionFiletransferBandwidthSent: 0,
309+
ConnectionFiletransferBandwidthReceived: 0,
310+
ConnectionPacketsSentTotal: 46880,
311+
ConnectionBytesSentTotal: 6426774,
312+
ConnectionPacketsReceivedTotal: 14098,
313+
ConnectionBytesReceivedTotal: 1644574,
314+
ConnectionBandwidthSentLastSecondTotal: 81,
315+
ConnectionBandwidthSentLastMinuteTotal: 92,
316+
ConnectionBandwidthReceivedLastSecondTotal: 83,
317+
ConnectionBandwidthReceivedLastMinuteTotal: 97,
318+
ConnectionConnectedTime: 2084247,
319+
ConnectionClientIP: "83.123.45.6",
320+
}
321+
322+
assert.Equal(t, expected, client)
323+
}
324+
254325
clientlist := func(t *testing.T) {
255326
clients, err := c.Server.ClientList()
256327
if !assert.NoError(t, err) {
@@ -309,6 +380,7 @@ func TestCmdsServer(t *testing.T) {
309380
{"serverrequestconnectioninfo", serverrequestconnectioninfo},
310381
{"instanceinfo", instanceinfo},
311382
{"channellist", channellist},
383+
{"clientinfo", clientinfo},
312384
{"clientlist", clientlist},
313385
{"clientdblist", clientdblist},
314386
}

0 commit comments

Comments
 (0)