Skip to content

Commit c9624d1

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 c9624d1

File tree

3 files changed

+204
-4
lines changed

3 files changed

+204
-4
lines changed

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: 131 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,133 @@ type OnlineClient struct {
349349
AwayMessage string `ms:"client_away_message"`
350350
}
351351

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