001package com.github.theholywaffle.teamspeak3;
002
003/*
004 * #%L
005 * TeamSpeak 3 Java API
006 * %%
007 * Copyright (C) 2014 Bert De Geyter
008 * %%
009 * Permission is hereby granted, free of charge, to any person obtaining a copy
010 * of this software and associated documentation files (the "Software"), to deal
011 * in the Software without restriction, including without limitation the rights
012 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
013 * copies of the Software, and to permit persons to whom the Software is
014 * furnished to do so, subject to the following conditions:
015 * 
016 * The above copyright notice and this permission notice shall be included in
017 * all copies or substantial portions of the Software.
018 * 
019 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
020 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
021 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
022 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
023 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
024 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
025 * THE SOFTWARE.
026 * #L%
027 */
028
029import com.github.theholywaffle.teamspeak3.api.*;
030import com.github.theholywaffle.teamspeak3.api.event.TS3EventType;
031import com.github.theholywaffle.teamspeak3.api.event.TS3Listener;
032import com.github.theholywaffle.teamspeak3.api.exception.TS3CommandFailedException;
033import com.github.theholywaffle.teamspeak3.api.wrapper.*;
034import com.github.theholywaffle.teamspeak3.commands.*;
035
036import java.util.ArrayList;
037import java.util.Collection;
038import java.util.Collections;
039import java.util.List;
040import java.util.Locale;
041import java.util.Map;
042import java.util.concurrent.TimeUnit;
043import java.util.regex.Pattern;
044
045/**
046 * Asynchronous version of {@link TS3Api} to interact with the {@link TS3Query}.
047 * <p>
048 * This class is used to easily interact with a {@link TS3Query}. It constructs commands,
049 * sends them to the TeamSpeak3 server, processes the response and returns the result.
050 * </p><p>
051 * All methods in this class are asynchronous (so they won't block) and
052 * will return a {@link CommandFuture} of the corresponding return type in {@link TS3Api}.
053 * If a command fails, no exception will be thrown directly. It will however be rethrown in
054 * {@link CommandFuture#get()} and {@link CommandFuture#get(long, TimeUnit)}.
055 * Usually, the thrown exception is a {@link TS3CommandFailedException}, which will get you
056 * access to the {@link QueryError} from which more information about the error can be obtained.
057 * </p><p>
058 * Also note that while these methods are asynchronous, the commands will still be sent through a
059 * synchronous command pipeline. That means if an asynchronous method is called immediately
060 * followed by a synchronous method, the synchronous method will first have to wait until the
061 * asynchronous method completed until it its command is sent.
062 * </p><p>
063 * You won't be able to execute most commands while you're not logged in due to missing permissions.
064 * Make sure to either pass your login credentials to the {@link TS3Config} object when
065 * creating the {@code TS3Query} or to call {@link #login(String, String)} to log in.
066 * </p><p>
067 * After that, most commands also require you to select a {@linkplain VirtualServer virtual server}.
068 * To do so, call either {@link #selectVirtualServerByPort(int)} or {@link #selectVirtualServerById(int)}.
069 * </p>
070 *
071 * @see TS3Api The synchronous version of the API
072 */
073public class TS3ApiAsync {
074
075        /**
076         * The TS3 query to which this API sends its commands.
077         */
078        private final TS3Query query;
079
080        /**
081         * Creates a new asynchronous API object for the given {@code TS3Query}.
082         * <p>
083         * <b>Usually, this constructor should not be called.</b> Use {@link TS3Query#getAsyncApi()} instead.
084         * </p>
085         *
086         * @param query
087         *              the TS3Query to call
088         */
089        public TS3ApiAsync(TS3Query query) {
090                this.query = query;
091        }
092
093        /**
094         * Adds a new ban entry. At least one of the parameters {@code ip}, {@code name} or {@code uid} needs to be not null.
095         * Returns the ID of the newly created ban.
096         *
097         * @param ip
098         *              a RegEx pattern to match a client's IP against, can be null
099         * @param name
100         *              a RegEx pattern to match a client's name against, can be null
101         * @param uid
102         *              the unique identifier of a client, can be null
103         * @param timeInSeconds
104         *              the duration of the ban in seconds. 0 equals a permanent ban
105         * @param reason
106         *              the reason for the ban, can be null
107         *
108         * @return the ID of the newly created ban entry
109         *
110         * @querycommands 1
111         * @see Pattern RegEx Pattern
112         * @see Client#getId()
113         * @see Client#getUniqueIdentifier()
114         * @see ClientInfo#getIp()
115         */
116        public CommandFuture<Integer> addBan(String ip, String name, String uid, long timeInSeconds, String reason) {
117                if (ip == null && name == null && uid == null) {
118                        throw new IllegalArgumentException("Either IP, Name or UID must be set");
119                }
120
121                final CBanAdd add = new CBanAdd(ip, name, uid, timeInSeconds, reason);
122                return executeAndReturnIntProperty(add, "banid");
123        }
124
125        /**
126         * Adds a specified permission to a client in a specific channel.
127         *
128         * @param channelId
129         *              the ID of the channel wherein the permission should be granted
130         * @param clientDBId
131         *              the database ID of the client to add a permission to
132         * @param permName
133         *              the name of the permission to grant
134         * @param permValue
135         *              the numeric value of the permission (or for boolean permissions: 1 = true, 0 = false)
136         *
137         * @return whether the command succeeded or not
138         *
139         * @querycommands 1
140         * @see Channel#getId()
141         * @see Client#getDatabaseId()
142         * @see Permission
143         */
144        public CommandFuture<Boolean> addChannelClientPermission(int channelId, int clientDBId, String permName, int permValue) {
145                final CChannelClientAddPerm add = new CChannelClientAddPerm(channelId, clientDBId, permName, permValue);
146                return executeAndReturnError(add);
147        }
148
149        /**
150         * Creates a new channel group for clients using a given name and returns its ID.
151         * <p>
152         * To create channel group templates or ones for server queries,
153         * use {@link #addChannelGroup(String, PermissionGroupDatabaseType)}.
154         * </p>
155         *
156         * @param name
157         *              the name of the new channel group
158         *
159         * @return the ID of the newly created channel group
160         *
161         * @querycommands 1
162         * @see ChannelGroup
163         */
164        public CommandFuture<Integer> addChannelGroup(String name) {
165                return addChannelGroup(name, null);
166        }
167
168        /**
169         * Creates a new channel group using a given name and returns its ID.
170         *
171         * @param name
172         *              the name of the new channel group
173         * @param type
174         *              the desired type of channel group
175         *
176         * @return the ID of the newly created channel group
177         *
178         * @querycommands 1
179         * @see ChannelGroup
180         */
181        public CommandFuture<Integer> addChannelGroup(String name, PermissionGroupDatabaseType type) {
182                final CChannelGroupAdd add = new CChannelGroupAdd(name, type);
183                return executeAndReturnIntProperty(add, "cgid");
184        }
185
186        /**
187         * Adds a specified permission to a channel group.
188         *
189         * @param groupId
190         *              the ID of the channel group to grant the permission
191         * @param permName
192         *              the name of the permission to be granted
193         * @param permValue
194         *              the numeric value of the permission (or for boolean permissions: 1 = true, 0 = false)
195         *
196         * @return whether the command succeeded or not
197         *
198         * @querycommands 1
199         * @see ChannelGroup#getId()
200         * @see Permission
201         */
202        public CommandFuture<Boolean> addChannelGroupPermission(int groupId, String permName, int permValue) {
203                final CChannelGroupAddPerm add = new CChannelGroupAddPerm(groupId, permName, permValue);
204                return executeAndReturnError(add);
205        }
206
207        /**
208         * Adds a specified permission to a channel.
209         *
210         * @param channelId
211         *              the ID of the channel wherein the permission should be granted
212         * @param permName
213         *              the name of the permission to grant
214         * @param permValue
215         *              the numeric value of the permission (or for boolean permissions: 1 = true, 0 = false)
216         *
217         * @return whether the command succeeded or not
218         *
219         * @querycommands 1
220         * @see Channel#getId()
221         * @see Permission
222         */
223        public CommandFuture<Boolean> addChannelPermission(int channelId, String permName, int permValue) {
224                final CChannelAddPerm perm = new CChannelAddPerm(channelId, permName, permValue);
225                return executeAndReturnError(perm);
226        }
227
228        /**
229         * Adds a specified permission to a channel.
230         *
231         * @param clientDBId
232         *              the database ID of the client to grant the permission
233         * @param permName
234         *              the name of the permission to grant
235         * @param value
236         *              the numeric value of the permission (or for boolean permissions: 1 = true, 0 = false)
237         * @param skipped
238         *              if set to {@code true}, the permission will not be overridden by channel group permissions
239         *
240         * @return whether the command succeeded or not
241         *
242         * @querycommands 1
243         * @see Client#getDatabaseId()
244         * @see Permission
245         */
246        public CommandFuture<Boolean> addClientPermission(int clientDBId, String permName, int value, boolean skipped) {
247                final CClientAddPerm add = new CClientAddPerm(clientDBId, permName, value, skipped);
248                return executeAndReturnError(add);
249        }
250
251        /**
252         * Adds a client to the specified server group.
253         * <p>
254         * Please note that a client cannot be added to default groups or template groups.
255         * </p>
256         *
257         * @param groupId
258         *              the ID of the server group to add the client to
259         * @param clientDatabaseId
260         *              the database ID of the client to add
261         *
262         * @return whether the command succeeded or not
263         *
264         * @querycommands 1
265         * @see ServerGroup#getId()
266         * @see Client#getDatabaseId()
267         */
268        public CommandFuture<Boolean> addClientToServerGroup(int groupId, int clientDatabaseId) {
269                final CServerGroupAddClient add = new CServerGroupAddClient(groupId, clientDatabaseId);
270                return executeAndReturnError(add);
271        }
272
273        /**
274         * Submits a complaint about the specified client.
275         * The length of the message is limited to 200 UTF-8 bytes and BB codes in it will be ignored.
276         *
277         * @param clientDBId
278         *              the database ID of the client
279         * @param message
280         *              the message of the complaint, may not contain BB codes
281         *
282         * @return whether the command succeeded or not
283         *
284         * @querycommands 1
285         * @see Client#getDatabaseId()
286         * @see Complaint#getMessage()
287         */
288        public CommandFuture<Boolean> addComplaint(int clientDBId, String message) {
289                final CComplainAdd add = new CComplainAdd(clientDBId, message);
290                return executeAndReturnError(add);
291        }
292
293        /**
294         * Adds a specified permission to all server groups of the type specified by {@code type} on all virtual servers.
295         *
296         * @param type
297         *              the kind of server group this permission should be added to
298         * @param permName
299         *              the name of the permission to be granted
300         * @param value
301         *              the numeric value of the permission (or for boolean permissions: 1 = true, 0 = false)
302         * @param negated
303         *              if set to true, the lowest permission value will be selected instead of the highest
304         * @param skipped
305         *              if set to true, this permission will not be overridden by client or channel group permissions
306         *
307         * @return whether the command succeeded or not
308         *
309         * @querycommands 1
310         * @see ServerGroupType
311         * @see Permission
312         */
313        public CommandFuture<Boolean> addPermissionToAllServerGroups(ServerGroupType type, String permName, int value, boolean negated, boolean skipped) {
314                final CServerGroupAutoAddPerm add = new CServerGroupAutoAddPerm(type, permName, value, negated, skipped);
315                return executeAndReturnError(add);
316        }
317
318        /**
319         * Create a new privilege key that allows one client to join a server or channel group.
320         * <ul>
321         * <li>If {@code type} is set to {@linkplain PrivilegeKeyType#SERVER_GROUP SERVER_GROUP},
322         * {@code groupId} is used as a server group ID and {@code channelId} is ignored.</li>
323         * <li>If {@code type} is set to {@linkplain PrivilegeKeyType#CHANNEL_GROUP CHANNEL_GROUP},
324         * {@code groupId} is used as a channel group ID and {@code channelId} is used as the channel in which the group should be set.</li>
325         * </ul>
326         *
327         * @param type
328         *              the type of token that should be created
329         * @param groupId
330         *              the ID of the server or channel group
331         * @param channelId
332         *              the ID of the channel, in case the token is channel group token
333         * @param description
334         *              the description for the token, can be null
335         *
336         * @return the created token for a client to use
337         *
338         * @querycommands 1
339         * @see PrivilegeKeyType
340         * @see #addPrivilegeKeyServerGroup(int, String)
341         * @see #addPrivilegeKeyChannelGroup(int, int, String)
342         */
343        public CommandFuture<String> addPrivilegeKey(PrivilegeKeyType type, int groupId, int channelId, String description) {
344                final CPrivilegeKeyAdd add = new CPrivilegeKeyAdd(type, groupId, channelId, description);
345                return executeAndReturnStringProperty(add, "token");
346        }
347
348        /**
349         * Creates a new privilege key for a channel group.
350         *
351         * @param channelGroupId
352         *              the ID of the channel group
353         * @param channelId
354         *              the ID of the channel in which the channel group should be set
355         * @param description
356         *              the description for the token, can be null
357         *
358         * @return the created token for a client to use
359         *
360         * @querycommands 1
361         * @see ChannelGroup#getId()
362         * @see Channel#getId()
363         * @see #addPrivilegeKey(PrivilegeKeyType, int, int, String)
364         * @see #addPrivilegeKeyServerGroup(int, String)
365         */
366        public CommandFuture<String> addPrivilegeKeyChannelGroup(int channelGroupId, int channelId, String description) {
367                return addPrivilegeKey(PrivilegeKeyType.CHANNEL_GROUP, channelGroupId, channelId, description);
368        }
369
370        /**
371         * Creates a new privilege key for a server group.
372         *
373         * @param serverGroupId
374         *              the ID of the server group
375         * @param description
376         *              the description for the token, can be null
377         *
378         * @return the created token for a client to use
379         *
380         * @querycommands 1
381         * @see ServerGroup#getId()
382         * @see #addPrivilegeKey(PrivilegeKeyType, int, int, String)
383         * @see #addPrivilegeKeyChannelGroup(int, int, String)
384         */
385        public CommandFuture<String> addPrivilegeKeyServerGroup(int serverGroupId, String description) {
386                return addPrivilegeKey(PrivilegeKeyType.SERVER_GROUP, serverGroupId, 0, description);
387        }
388
389        /**
390         * Creates a new server group for clients using a given name and returns its ID.
391         * <p>
392         * To create server group templates or ones for server queries,
393         * use {@link #addServerGroup(String, PermissionGroupDatabaseType)}.
394         * </p>
395         *
396         * @param name
397         *              the name of the new server group
398         *
399         * @return the ID of the newly created server group
400         *
401         * @querycommands 1
402         * @see ServerGroup
403         */
404        public CommandFuture<Integer> addServerGroup(String name) {
405                return addServerGroup(name, PermissionGroupDatabaseType.REGULAR);
406        }
407
408        /**
409         * Creates a new server group using a given name and returns its ID.
410         *
411         * @param name
412         *              the name of the new server group
413         * @param type
414         *              the desired type of server group
415         *
416         * @return the ID of the newly created server group
417         *
418         * @querycommands 1
419         * @see ServerGroup
420         * @see PermissionGroupDatabaseType
421         */
422        public CommandFuture<Integer> addServerGroup(String name, PermissionGroupDatabaseType type) {
423                final CServerGroupAdd add = new CServerGroupAdd(name, type);
424                return executeAndReturnIntProperty(add, "sgid");
425        }
426
427        /**
428         * Adds a specified permission to a server group.
429         *
430         * @param groupId
431         *              the ID of the channel group to which the permission should be added
432         * @param permName
433         *              the name of the permission to add
434         * @param value
435         *              the numeric value of the permission (or for boolean permissions: 1 = true, 0 = false)
436         * @param negated
437         *              if set to true, the lowest permission value will be selected instead of the highest
438         * @param skipped
439         *              if set to true, this permission will not be overridden by client or channel group permissions
440         *
441         * @return whether the command succeeded or not
442         *
443         * @querycommands 1
444         * @see ServerGroup#getId()
445         * @see Permission
446         */
447        public CommandFuture<Boolean> addServerGroupPermission(int groupId, String permName, int value, boolean negated, boolean skipped) {
448                final CServerGroupAddPerm add = new CServerGroupAddPerm(groupId, permName, value, negated, skipped);
449                return executeAndReturnError(add);
450        }
451
452        /**
453         * Adds one or more {@link TS3Listener}s to the event manager of the query.
454         * These listeners will be notified when the TS3 server fires an event.
455         * <p>
456         * Note that for the TS3 server to fire events, you must first also register
457         * the event types you want to listen to.
458         * </p>
459         *
460         * @param listeners
461         *              one or more listeners to register
462         *
463         * @see #registerAllEvents()
464         * @see #registerEvent(TS3EventType, int)
465         * @see TS3Listener
466         * @see TS3EventType
467         */
468        public void addTS3Listeners(TS3Listener... listeners) {
469                query.getEventManager().addListeners(listeners);
470        }
471
472        /**
473         * Bans a client with a given client ID for a given time.
474         * <p>
475         * Please note that this will create two separate ban rules,
476         * one for the targeted client's IP address and their unique identifier.
477         * </p><p>
478         * <i>Exception:</i> If the banned client connects via a loopback address
479         * (i.e. {@code 127.0.0.1} or {@code localhost}), no IP ban is created
480         * and the returned array will only have 1 entry.
481         * </p>
482         *
483         * @param clientId
484         *              the ID of the client
485         * @param timeInSeconds
486         *              the duration of the ban in seconds. 0 equals a permanent ban
487         *
488         * @return an array containing the IDs of the first and the second ban entry
489         *
490         * @querycommands 1
491         * @see Client#getId()
492         * @see #addBan(String, String, String, long, String)
493         */
494        public CommandFuture<int[]> banClient(int clientId, long timeInSeconds) {
495                return banClient(clientId, timeInSeconds, null);
496        }
497
498        /**
499         * Bans a client with a given client ID for a given time for the specified reason.
500         * <p>
501         * Please note that this will create two separate ban rules,
502         * one for the targeted client's IP address and their unique identifier.
503         * </p><p>
504         * <i>Exception:</i> If the banned client connects via a loopback address
505         * (i.e. {@code 127.0.0.1} or {@code localhost}), no IP ban is created
506         * and the returned array will only have 1 entry.
507         * </p>
508         *
509         * @param clientId
510         *              the ID of the client
511         * @param timeInSeconds
512         *              the duration of the ban in seconds. 0 equals a permanent ban
513         * @param reason
514         *              the reason for the ban, can be null
515         *
516         * @return an array containing the IDs of the first and the second ban entry
517         *
518         * @querycommands 1
519         * @see Client#getId()
520         * @see #addBan(String, String, String, long, String)
521         */
522        public CommandFuture<int[]> banClient(int clientId, long timeInSeconds, String reason) {
523                final CBanClient client = new CBanClient(clientId, timeInSeconds, reason);
524                final CommandFuture<int[]> future = new CommandFuture<>();
525
526                query.doCommandAsync(client, new Callback() {
527                        @Override
528                        public void handle() {
529                                if (hasFailed(client, future)) return;
530
531                                final List<Wrapper> response = client.getResponse();
532                                final int[] banIds = new int[response.size()];
533                                for (int i = 0; i < banIds.length; ++i) {
534                                        banIds[i] = response.get(i).getInt("banid");
535                                }
536                                future.set(banIds);
537                        }
538                });
539                return future;
540        }
541
542        /**
543         * Bans a client with a given client ID permanently for the specified reason.
544         * <p>
545         * Please note that this will create two separate ban rules,
546         * one for the targeted client's IP address and their unique identifier.
547         * </p><p>
548         * <i>Exception:</i> If the banned client connects via a loopback address
549         * (i.e. {@code 127.0.0.1} or {@code localhost}), no IP ban is created
550         * and the returned array will only have 1 entry.
551         * </p>
552         *
553         * @param clientId
554         *              the ID of the client
555         * @param reason
556         *              the reason for the ban, can be null
557         *
558         * @return an array containing the IDs of the first and the second ban entry
559         *
560         * @querycommands 1
561         * @see Client#getId()
562         * @see #addBan(String, String, String, long, String)
563         */
564        public CommandFuture<int[]> banClient(int clientId, String reason) {
565                return banClient(clientId, 0, reason);
566        }
567
568        /**
569         * Sends a text message to all clients on all virtual servers.
570         * These messages will appear to clients in the tab for server messages.
571         *
572         * @param message
573         *              the message to be sent
574         *
575         * @return whether the command succeeded or not
576         *
577         * @querycommands 1
578         */
579        public CommandFuture<Boolean> broadcast(String message) {
580                final CGM broadcast = new CGM(message);
581                return executeAndReturnError(broadcast);
582        }
583
584        /**
585         * Creates a copy of the channel group specified by {@code sourceGroupId},
586         * overwriting any other channel group specified by {@code targetGroupId}.
587         * <p>
588         * The parameter {@code type} can be used to create server query and template groups.
589         * </p>
590         *
591         * @param sourceGroupId
592         *              the ID of the channel group to copy
593         * @param targetGroupId
594         *              the ID of another channel group to overwrite
595         * @param type
596         *              the desired type of channel group
597         *
598         * @return whether the command succeeded or not
599         *
600         * @querycommands 1
601         * @see ChannelGroup#getId()
602         */
603        public CommandFuture<Boolean> copyChannelGroup(int sourceGroupId, int targetGroupId, PermissionGroupDatabaseType type) {
604                if (targetGroupId <= 0) {
605                        throw new IllegalArgumentException("To create a new channel group, use the method with a String argument");
606                }
607
608                final CChannelGroupCopy copy = new CChannelGroupCopy(sourceGroupId, targetGroupId, type);
609                return executeAndReturnError(copy);
610        }
611
612        /**
613         * Creates a copy of the channel group specified by {@code sourceGroupId} with a given name
614         * and returns the ID of the newly created channel group.
615         *
616         * @param sourceGroupId
617         *              the ID of the channel group to copy
618         * @param targetName
619         *              the name for the copy of the channel group
620         * @param type
621         *              the desired type of channel group
622         *
623         * @return the ID of the newly created channel group
624         *
625         * @querycommands 1
626         * @see ChannelGroup#getId()
627         */
628        public CommandFuture<Integer> copyChannelGroup(int sourceGroupId, String targetName, PermissionGroupDatabaseType type) {
629                final CChannelGroupCopy copy = new CChannelGroupCopy(sourceGroupId, targetName, type);
630                return executeAndReturnIntProperty(copy, "cgid");
631        }
632
633        /**
634         * Creates a copy of the server group specified by {@code sourceGroupId},
635         * overwriting another server group specified by {@code targetGroupId}.
636         * <p>
637         * The parameter {@code type} can be used to create server query and template groups.
638         * </p>
639         *
640         * @param sourceGroupId
641         *              the ID of the server group to copy
642         * @param targetGroupId
643         *              the ID of another server group to overwrite
644         * @param type
645         *              the desired type of server group
646         *
647         * @return whether the command succeeded or not
648         *
649         * @querycommands 1
650         * @see ServerGroup#getId()
651         */
652        public CommandFuture<Integer> copyServerGroup(int sourceGroupId, int targetGroupId, PermissionGroupDatabaseType type) {
653                if (targetGroupId <= 0) {
654                        throw new IllegalArgumentException("To create a new server group, use the method with a String argument");
655                }
656
657                final CServerGroupCopy copy = new CServerGroupCopy(sourceGroupId, targetGroupId, type);
658                return executeAndReturnIntProperty(copy, "sgid");
659        }
660
661        /**
662         * Creates a copy of the server group specified by {@code sourceGroupId} with a given name
663         * and returns the ID of the newly created server group.
664         *
665         * @param sourceGroupId
666         *              the ID of the server group to copy
667         * @param targetName
668         *              the name for the copy of the server group
669         * @param type
670         *              the desired type of server group
671         *
672         * @return the ID of the newly created server group
673         *
674         * @querycommands 1
675         * @see ServerGroup#getId()
676         */
677        public CommandFuture<Integer> copyServerGroup(int sourceGroupId, String targetName, PermissionGroupDatabaseType type) {
678                final CServerGroupCopy copy = new CServerGroupCopy(sourceGroupId, targetName, type);
679                return executeAndReturnIntProperty(copy, "sgid");
680        }
681
682        /**
683         * Creates a new channel with a given name using the given properties and returns its ID.
684         *
685         * @param name
686         *              the name for the new channel
687         * @param options
688         *              a map of options that should be set for the channel
689         *
690         * @return the ID of the newly created channel
691         *
692         * @querycommands 1
693         * @see Channel
694         */
695        public CommandFuture<Integer> createChannel(String name, Map<ChannelProperty, String> options) {
696                final CChannelCreate create = new CChannelCreate(name, options);
697                return executeAndReturnIntProperty(create, "cid");
698        }
699
700        /**
701         * Creates a new virtual server with the given name and returns an object containing the ID of the newly
702         * created virtual server, the default server admin token and the virtual server's voice port. Usually,
703         * the virtual server is also automatically started. This can be turned off on the TS3 server, though.
704         * <p>
705         * If {@link VirtualServerProperty#VIRTUALSERVER_PORT} is not specified in the virtual server properties,
706         * the server will test for the first unused UDP port.
707         * </p><p>
708         * Please also note that creating virtual servers usually requires the server query admin account
709         * and that there is a limit to how many virtual servers can be created, which is dependent on your license.
710         * Unlicensed TS3 server instances are limited to 1 virtual server with up to 32 client slots.
711         * </p>
712         *
713         * @param name
714         *              the name for the new virtual server
715         * @param options
716         *              a map of options that should be set for the virtual server
717         *
718         * @return information about the newly created virtual server
719         *
720         * @querycommands 1
721         * @see VirtualServer
722         */
723        public CommandFuture<CreatedVirtualServer> createServer(String name, Map<VirtualServerProperty, String> options) {
724                final CServerCreate create = new CServerCreate(name, options);
725                final CommandFuture<CreatedVirtualServer> future = new CommandFuture<>();
726
727                query.doCommandAsync(create, new Callback() {
728                        @Override
729                        public void handle() {
730                                if (hasFailed(create, future)) return;
731                                future.set(new CreatedVirtualServer(create.getFirstResponse().getMap()));
732                        }
733                });
734                return future;
735        }
736
737        /**
738         * Creates a {@link Snapshot} of the selected virtual server containing all settings,
739         * groups and known client identities. The data from a server snapshot can be
740         * used to restore a virtual servers configuration.
741         *
742         * @return a snapshot of the virtual server
743         *
744         * @querycommands 1
745         * @see #deployServerSnapshot(Snapshot)
746         */
747        public CommandFuture<Snapshot> createServerSnapshot() {
748                final CServerSnapshotCreate create = new CServerSnapshotCreate();
749                final CommandFuture<Snapshot> future = new CommandFuture<>();
750
751                query.doCommandAsync(create, new Callback() {
752                        @Override
753                        public void handle() {
754                                if (hasFailed(create, future)) return;
755                                future.set(new Snapshot(create.getRaw()));
756                        }
757                });
758                return future;
759        }
760
761        /**
762         * Deletes all active ban rules from the server. Use with caution.
763         *
764         * @return whether the command succeeded or not
765         *
766         * @querycommands 1
767         */
768        public CommandFuture<Boolean> deleteAllBans() {
769                final CBanDelAll del = new CBanDelAll();
770                return executeAndReturnError(del);
771        }
772
773        /**
774         * Deletes all complaints about the client with specified database ID from the server.
775         *
776         * @param clientDBId
777         *              the database ID of the client
778         *
779         * @return whether the command succeeded or not
780         *
781         * @querycommands 1
782         * @see Client#getDatabaseId()
783         * @see Complaint
784         */
785        public CommandFuture<Boolean> deleteAllComplaints(int clientDBId) {
786                final CComplainDelAll del = new CComplainDelAll(clientDBId);
787                return executeAndReturnError(del);
788        }
789
790        /**
791         * Deletes the ban rule with the specified ID from the server.
792         *
793         * @param banId
794         *              the ID of the ban to delete
795         *
796         * @return whether the command succeeded or not
797         *
798         * @querycommands 1
799         * @see Ban#getId()
800         */
801        public CommandFuture<Boolean> deleteBan(int banId) {
802                final CBanDel del = new CBanDel(banId);
803                return executeAndReturnError(del);
804        }
805
806        /**
807         * Deletes an existing channel specified by its ID, kicking all clients out of the channel.
808         *
809         * @param channelId
810         *              the ID of the channel to delete
811         *
812         * @return whether the command succeeded or not
813         *
814         * @querycommands 1
815         * @see Channel#getId()
816         * @see #deleteChannel(int, boolean)
817         * @see #kickClientFromChannel(String, int...)
818         */
819        public CommandFuture<Boolean> deleteChannel(int channelId) {
820                return deleteChannel(channelId, true);
821        }
822
823        /**
824         * Deletes an existing channel with a given ID.
825         * If {@code force} is true, the channel will be deleted even if there are clients within,
826         * else the command will fail in this situation.
827         *
828         * @param channelId
829         *              the ID of the channel to delete
830         * @param force
831         *              whether clients should be kicked out of the channel
832         *
833         * @return whether the command succeeded or not
834         *
835         * @querycommands 1
836         * @see Channel#getId()
837         * @see #kickClientFromChannel(String, int...)
838         */
839        public CommandFuture<Boolean> deleteChannel(int channelId, boolean force) {
840                final CChannelDelete del = new CChannelDelete(channelId, force);
841                return executeAndReturnError(del);
842        }
843
844        /**
845         * Removes a specified permission from a client in a specific channel.
846         *
847         * @param channelId
848         *              the ID of the channel wherein the permission should be removed
849         * @param clientDBId
850         *              the database ID of the client
851         * @param permName
852         *              the name of the permission to revoke
853         *
854         * @return whether the command succeeded or not
855         *
856         * @querycommands 1
857         * @see Channel#getId()
858         * @see Client#getDatabaseId()
859         * @see Permission#getName()
860         */
861        public CommandFuture<Boolean> deleteChannelClientPermission(int channelId, int clientDBId, String permName) {
862                final CChannelClientDelPerm del = new CChannelClientDelPerm(channelId, clientDBId, permName);
863                return executeAndReturnError(del);
864        }
865
866        /**
867         * Removes the channel group with the given ID.
868         *
869         * @param groupId
870         *              the ID of the channel group
871         *
872         * @return whether the command succeeded or not
873         *
874         * @querycommands 1
875         * @see ChannelGroup#getId()
876         */
877        public CommandFuture<Boolean> deleteChannelGroup(int groupId) {
878                return deleteChannelGroup(groupId, true);
879        }
880
881        /**
882         * Removes the channel group with the given ID.
883         * If {@code force} is true, the channel group will be deleted even if it still contains clients,
884         * else the command will fail in this situation.
885         *
886         * @param groupId
887         *              the ID of the channel group
888         * @param force
889         *              whether the channel group should be deleted even if it still contains clients
890         *
891         * @return whether the command succeeded or not
892         *
893         * @querycommands 1
894         * @see ChannelGroup#getId()
895         */
896        public CommandFuture<Boolean> deleteChannelGroup(int groupId, boolean force) {
897                final CChannelGroupDel del = new CChannelGroupDel(groupId, force);
898                return executeAndReturnError(del);
899        }
900
901        /**
902         * Removes a permission from the channel group with the given ID.
903         *
904         * @param groupId
905         *              the ID of the channel group
906         * @param permName
907         *              the name of the permission to revoke
908         *
909         * @return whether the command succeeded or not
910         *
911         * @querycommands 1
912         * @see ChannelGroup#getId()
913         * @see Permission#getName()
914         */
915        public CommandFuture<Boolean> deleteChannelGroupPermission(int groupId, String permName) {
916                final CChannelGroupDelPerm del = new CChannelGroupDelPerm(groupId, permName);
917                return executeAndReturnError(del);
918        }
919
920        /**
921         * Removes a permission from the channel with the given ID.
922         *
923         * @param channelId
924         *              the ID of the channel
925         * @param permName
926         *              the name of the permission to revoke
927         *
928         * @return whether the command succeeded or not
929         *
930         * @querycommands 1
931         * @see Channel#getId()
932         * @see Permission#getName()
933         */
934        public CommandFuture<Boolean> deleteChannelPermission(int channelId, String permName) {
935                final CChannelDelPerm del = new CChannelDelPerm(channelId, permName);
936                return executeAndReturnError(del);
937        }
938
939        /**
940         * Removes a permission from a client.
941         *
942         * @param clientDBId
943         *              the database ID of the client
944         * @param permName
945         *              the name of the permission to revoke
946         *
947         * @return whether the command succeeded or not
948         *
949         * @querycommands 1
950         * @see Client#getDatabaseId()
951         * @see Permission#getName()
952         */
953        public CommandFuture<Boolean> deleteClientPermission(int clientDBId, String permName) {
954                final CClientDelPerm del = new CClientDelPerm(clientDBId, permName);
955                return executeAndReturnError(del);
956        }
957
958        /**
959         * Deletes the complaint about the client with database ID {@code targetClientDBId} submitted by
960         * the client with database ID {@code fromClientDBId} from the server.
961         *
962         * @param targetClientDBId
963         *              the database ID of the client the complaint is about
964         * @param fromClientDBId
965         *              the database ID of the client who added the complaint
966         *
967         * @return whether the command succeeded or not
968         *
969         * @querycommands 1
970         * @see Complaint
971         * @see Client#getDatabaseId()
972         */
973        public CommandFuture<Boolean> deleteComplaint(int targetClientDBId, int fromClientDBId) {
974                final CComplainDel del = new CComplainDel(targetClientDBId, fromClientDBId);
975                return executeAndReturnError(del);
976        }
977
978        /**
979         * Removes all stored database information about the specified client.
980         * Please note that this data is also automatically removed after a configured time (usually 90 days).
981         * <p>
982         * See {@link DatabaseClientInfo} for a list of stored information about a client.
983         * </p>
984         *
985         * @param clientDBId
986         *              the database ID of the client
987         *
988         * @return whether the command succeeded or not
989         *
990         * @querycommands 1
991         * @see Client#getDatabaseId()
992         * @see #getDatabaseClientInfo(int)
993         * @see DatabaseClientInfo
994         */
995        public CommandFuture<Boolean> deleteDatabaseClientProperties(int clientDBId) {
996                final CClientDBDelete del = new CClientDBDelete(clientDBId);
997                return executeAndReturnError(del);
998        }
999
1000        /**
1001         * Deletes the offline message with the specified ID.
1002         *
1003         * @param messageId
1004         *              the ID of the offline message to delete
1005         *
1006         * @return whether the command succeeded or not
1007         *
1008         * @querycommands 1
1009         * @see Message#getId()
1010         */
1011        public CommandFuture<Boolean> deleteOfflineMessage(int messageId) {
1012                final CMessageDel del = new CMessageDel(messageId);
1013                return executeAndReturnError(del);
1014        }
1015
1016        /**
1017         * Removes a specified permission from all server groups of the type specified by {@code type} on all virtual servers.
1018         *
1019         * @param type
1020         *              the kind of server group this permission should be removed from
1021         * @param permName
1022         *              the name of the permission to remove
1023         *
1024         * @return whether the command succeeded or not
1025         *
1026         * @querycommands 1
1027         * @see ServerGroupType
1028         * @see Permission#getName()
1029         */
1030        public CommandFuture<Boolean> deletePermissionFromAllServerGroups(ServerGroupType type, String permName) {
1031                final CServerGroupAutoDelPerm del = new CServerGroupAutoDelPerm(type, permName);
1032                return executeAndReturnError(del);
1033        }
1034
1035        /**
1036         * Deletes the privilege key with the given token.
1037         *
1038         * @param token
1039         *              the token of the privilege key
1040         *
1041         * @return whether the command succeeded or not
1042         *
1043         * @querycommands 1
1044         * @see PrivilegeKey
1045         */
1046        public CommandFuture<Boolean> deletePrivilegeKey(String token) {
1047                final CPrivilegeKeyDelete del = new CPrivilegeKeyDelete(token);
1048                return executeAndReturnError(del);
1049        }
1050
1051        /**
1052         * Deletes the virtual server with the specified ID.
1053         * <p>
1054         * Only stopped virtual servers can be deleted.
1055         * </p>
1056         *
1057         * @param serverId
1058         *              the ID of the virtual server
1059         *
1060         * @return whether the command succeeded or not
1061         *
1062         * @querycommands 1
1063         * @see VirtualServer#getId()
1064         * @see #stopServer(int)
1065         */
1066        public CommandFuture<Boolean> deleteServer(int serverId) {
1067                final CServerDelete delete = new CServerDelete(serverId);
1068                return executeAndReturnError(delete);
1069        }
1070
1071        /**
1072         * Deletes the server group with the specified ID, even if the server group still contains clients.
1073         *
1074         * @param groupId
1075         *              the ID of the server group
1076         *
1077         * @return whether the command succeeded or not
1078         *
1079         * @querycommands 1
1080         * @see ServerGroup#getId()
1081         */
1082        public CommandFuture<Boolean> deleteServerGroup(int groupId) {
1083                return deleteServerGroup(groupId, true);
1084        }
1085
1086        /**
1087         * Deletes a server group with the specified ID.
1088         * <p>
1089         * If {@code force} is true, the server group will be deleted even if it contains clients,
1090         * else the command will fail in this situation.
1091         * </p>
1092         *
1093         * @param groupId
1094         *              the ID of the server group
1095         * @param force
1096         *              whether the server group should be deleted if it still contains clients
1097         *
1098         * @return whether the command succeeded or not
1099         *
1100         * @querycommands 1
1101         * @see ServerGroup#getId()
1102         */
1103        public CommandFuture<Boolean> deleteServerGroup(int groupId, boolean force) {
1104                final CServerGroupDel del = new CServerGroupDel(groupId, force);
1105                return executeAndReturnError(del);
1106        }
1107
1108        /**
1109         * Removes a permission from the server group with the given ID.
1110         *
1111         * @param groupId
1112         *              the ID of the server group
1113         * @param permName
1114         *              the name of the permission to revoke
1115         *
1116         * @return whether the command succeeded or not
1117         *
1118         * @querycommands 1
1119         * @see ServerGroup#getId()
1120         * @see Permission#getName()
1121         */
1122        public CommandFuture<Boolean> deleteServerGroupPermission(int groupId, String permName) {
1123                final CServerGroupDelPerm del = new CServerGroupDelPerm(groupId, permName);
1124                return executeAndReturnError(del);
1125        }
1126
1127        /**
1128         * Restores the selected virtual servers configuration using the data from a
1129         * previously created server snapshot.
1130         *
1131         * @param snapshot
1132         *              the snapshot to restore
1133         *
1134         * @return whether the command succeeded or not
1135         *
1136         * @querycommands 1
1137         * @see #createServerSnapshot()
1138         */
1139        public CommandFuture<Boolean> deployServerSnapshot(Snapshot snapshot) {
1140                return deployServerSnapshot(snapshot.get());
1141        }
1142
1143        /**
1144         * Restores the configuration of the selected virtual server using the data from a
1145         * previously created server snapshot.
1146         *
1147         * @param snapshot
1148         *              the snapshot to restore
1149         *
1150         * @return whether the command succeeded or not
1151         *
1152         * @querycommands 1
1153         * @see #createServerSnapshot()
1154         */
1155        public CommandFuture<Boolean> deployServerSnapshot(String snapshot) {
1156                final CServerSnapshotDeploy deploy = new CServerSnapshotDeploy(snapshot);
1157                return executeAndReturnError(deploy);
1158        }
1159
1160        /**
1161         * Changes a channel's configuration using the given properties.
1162         *
1163         * @param channelId
1164         *              the ID of the channel to edit
1165         * @param options
1166         *              the map of properties to modify
1167         *
1168         * @return whether the command succeeded or not
1169         *
1170         * @querycommands 1
1171         * @see Channel#getId()
1172         */
1173        public CommandFuture<Boolean> editChannel(int channelId, Map<ChannelProperty, String> options) {
1174                final CChannelEdit edit = new CChannelEdit(channelId, options);
1175                return executeAndReturnError(edit);
1176        }
1177
1178        /**
1179         * Changes a client's configuration using given properties.
1180         * <p>
1181         * Only {@link ClientProperty#CLIENT_DESCRIPTION} can be changed for other clients.
1182         * To update the current client's properties, use {@link #updateClient(Map)}.
1183         * </p>
1184         *
1185         * @param clientId
1186         *              the ID of the client to edit
1187         * @param options
1188         *              the map of properties to modify
1189         *
1190         * @return whether the command succeeded or not
1191         *
1192         * @querycommands 1
1193         * @see Client#getId()
1194         * @see #updateClient(Map)
1195         */
1196        public CommandFuture<Boolean> editClient(int clientId, Map<ClientProperty, String> options) {
1197                final CClientEdit edit = new CClientEdit(clientId, options);
1198                return executeAndReturnError(edit);
1199        }
1200
1201        /**
1202         * Changes a client's database settings using given properties.
1203         *
1204         * @param clientDBId
1205         *              the database ID of the client to edit
1206         * @param options
1207         *              the map of properties to modify
1208         *
1209         * @return whether the command succeeded or not
1210         *
1211         * @querycommands 1
1212         * @see DatabaseClientInfo
1213         * @see Client#getDatabaseId()
1214         */
1215        public CommandFuture<Boolean> editDatabaseClient(int clientDBId, Map<ClientProperty, String> options) {
1216                final CClientDBEdit edit = new CClientDBEdit(clientDBId, options);
1217                return executeAndReturnError(edit);
1218        }
1219
1220        /**
1221         * Changes the server instance configuration using given properties.
1222         * If the given property is not changeable, {@code IllegalArgumentException} will be thrown.
1223         *
1224         * @param property
1225         *              the property to edit, must be changeable
1226         * @param value
1227         *              the new value for the edit
1228         *
1229         * @return whether the command succeeded or not
1230         *
1231         * @throws IllegalArgumentException
1232         *              if {@code property} is not changeable
1233         * @querycommands 1
1234         * @see ServerInstanceProperty#isChangeable()
1235         */
1236        public CommandFuture<Boolean> editInstance(ServerInstanceProperty property, String value) {
1237                if (!property.isChangeable()) {
1238                        throw new IllegalArgumentException("Property is not changeable");
1239                }
1240
1241                final CInstanceEdit edit = new CInstanceEdit(property, value);
1242                return executeAndReturnError(edit);
1243        }
1244
1245        /**
1246         * Changes the configuration of the selected virtual server using given properties.
1247         *
1248         * @param options
1249         *              the map of properties to edit
1250         *
1251         * @return whether the command succeeded or not
1252         *
1253         * @querycommands 1
1254         * @see VirtualServerProperty
1255         */
1256        public CommandFuture<Boolean> editServer(Map<VirtualServerProperty, String> options) {
1257                final CServerEdit edit = new CServerEdit(options);
1258                return executeAndReturnError(edit);
1259        }
1260
1261        /**
1262         * Gets a list of all bans on the selected virtual server.
1263         *
1264         * @return a list of all bans on the virtual server
1265         *
1266         * @querycommands 1
1267         * @see Ban
1268         */
1269        public CommandFuture<List<Ban>> getBans() {
1270                final CBanList list = new CBanList();
1271                final CommandFuture<List<Ban>> future = new CommandFuture<>();
1272
1273                query.doCommandAsync(list, new Callback() {
1274                        @Override
1275                        public void handle() {
1276                                if (hasFailed(list, future)) return;
1277
1278                                final List<Wrapper> responses = list.getResponse();
1279                                final List<Ban> bans = new ArrayList<>(responses.size());
1280
1281                                for (final Wrapper response : responses) {
1282                                        bans.add(new Ban(response.getMap()));
1283                                }
1284                                future.set(bans);
1285                        }
1286                });
1287                return future;
1288        }
1289
1290        /**
1291         * Gets a list of IP addresses used by the server instance.
1292         *
1293         * @return the list of bound IP addresses
1294         *
1295         * @querycommands 1
1296         * @see Binding
1297         */
1298        public CommandFuture<List<Binding>> getBindings() {
1299                final CBindingList list = new CBindingList();
1300                final CommandFuture<List<Binding>> future = new CommandFuture<>();
1301
1302                query.doCommandAsync(list, new Callback() {
1303                        @Override
1304                        public void handle() {
1305                                if (hasFailed(list, future)) return;
1306
1307                                final List<Wrapper> responses = list.getResponse();
1308                                final List<Binding> bindings = new ArrayList<>(responses.size());
1309
1310                                for (final Wrapper response : responses) {
1311                                        bindings.add(new Binding(response.getMap()));
1312                                }
1313                                future.set(bindings);
1314                        }
1315                });
1316                return future;
1317        }
1318
1319        /**
1320         * Finds and returns the channel matching the given name exactly.
1321         *
1322         * @param name
1323         *              the name of the channel
1324         * @param ignoreCase
1325         *              whether the case of the name should be ignored
1326         *
1327         * @return the found channel or {@code null} if no channel was found
1328         *
1329         * @querycommands 1
1330         * @see Channel
1331         * @see #getChannelsByName(String)
1332         */
1333        public CommandFuture<Channel> getChannelByNameExact(String name, final boolean ignoreCase) {
1334                final CommandFuture<Channel> future = new CommandFuture<>();
1335                final String caseName = ignoreCase ? name.toLowerCase(Locale.ROOT) : name;
1336
1337                getChannels().onSuccess(new CommandFuture.SuccessListener<List<Channel>>() {
1338                        @Override
1339                        public void handleSuccess(final List<Channel> allChannels) {
1340                                for (final Channel c : allChannels) {
1341                                        final String channelName = ignoreCase ? c.getName().toLowerCase(Locale.ROOT) : c.getName();
1342                                        if (caseName.equals(channelName)) {
1343                                                future.set(c);
1344                                                return;
1345                                        }
1346                                }
1347                                future.set(null); // Not found
1348                        }
1349                }).forwardFailure(future);
1350                return future;
1351        }
1352
1353        /**
1354         * Gets a list of channels whose names contain the given search string.
1355         *
1356         * @param name
1357         *              the name to search
1358         *
1359         * @return a list of all channels with names matching the search pattern
1360         *
1361         * @querycommands 2
1362         * @see Channel
1363         * @see #getChannelByNameExact(String, boolean)
1364         */
1365        public CommandFuture<List<Channel>> getChannelsByName(String name) {
1366                final CChannelFind find = new CChannelFind(name);
1367                final CommandFuture<List<Channel>> future = new CommandFuture<>();
1368
1369                getChannels().onSuccess(new CommandFuture.SuccessListener<List<Channel>>() {
1370                        @Override
1371                        public void handleSuccess(final List<Channel> allChannels) {
1372                                query.doCommandAsync(find, new Callback() {
1373                                        @Override
1374                                        public void handle() {
1375                                                if (hasFailed(find, future)) return;
1376
1377                                                final List<Wrapper> responses = find.getResponse();
1378                                                final List<Channel> channels = new ArrayList<>(responses.size());
1379
1380                                                for (final Wrapper response : responses) {
1381                                                        final int channelId = response.getInt("cid");
1382                                                        for (final Channel c : allChannels) {
1383                                                                if (c.getId() == channelId) {
1384                                                                        channels.add(c);
1385                                                                        break;
1386                                                                }
1387                                                        }
1388                                                }
1389                                                future.set(channels);
1390                                        }
1391                                });
1392                        }
1393                }).forwardFailure(future);
1394                return future;
1395        }
1396
1397        /**
1398         * Displays a list of permissions defined for a client in a specific channel.
1399         *
1400         * @param channelId
1401         *              the ID of the channel
1402         * @param clientDBId
1403         *              the database ID of the client
1404         *
1405         * @return a list of permissions for the user in the specified channel
1406         *
1407         * @querycommands 1
1408         * @see Channel#getId()
1409         * @see Client#getDatabaseId()
1410         * @see Permission
1411         */
1412        public CommandFuture<List<Permission>> getChannelClientPermissions(int channelId, int clientDBId) {
1413                final CChannelClientPermList list = new CChannelClientPermList(channelId, clientDBId);
1414                final CommandFuture<List<Permission>> future = new CommandFuture<>();
1415
1416                query.doCommandAsync(list, new Callback() {
1417                        @Override
1418                        public void handle() {
1419                                if (hasFailed(list, future)) return;
1420
1421                                final List<Wrapper> responses = list.getResponse();
1422                                final List<Permission> permissions = new ArrayList<>(responses.size());
1423
1424                                for (final Wrapper response : responses) {
1425                                        permissions.add(new Permission(response.getMap()));
1426                                }
1427                                future.set(permissions);
1428                        }
1429                });
1430                return future;
1431        }
1432
1433        /**
1434         * Gets all client / channel ID combinations currently assigned to channel groups.
1435         * All three parameters are optional and can be turned off by setting it to {@code -1}.
1436         *
1437         * @param channelId
1438         *              restricts the search to the channel with a specified ID. Set to {@code -1} to ignore.
1439         * @param clientDBId
1440         *              restricts the search to the client with a specified database ID. Set to {@code -1} to ignore.
1441         * @param groupId
1442         *              restricts the search to the channel group with the specified ID. Set to {@code -1} to ignore.
1443         *
1444         * @return a list of combinations of channel ID, client database ID and channel group ID
1445         *
1446         * @querycommands 1
1447         * @see Channel#getId()
1448         * @see Client#getDatabaseId()
1449         * @see ChannelGroup#getId()
1450         * @see ChannelGroupClient
1451         */
1452        public CommandFuture<List<ChannelGroupClient>> getChannelGroupClients(int channelId, int clientDBId, int groupId) {
1453                final CChannelGroupClientList list = new CChannelGroupClientList(channelId, clientDBId, groupId);
1454                final CommandFuture<List<ChannelGroupClient>> future = new CommandFuture<>();
1455
1456                query.doCommandAsync(list, new Callback() {
1457                        @Override
1458                        public void handle() {
1459                                if (hasFailed(list, future)) return;
1460
1461                                final List<Wrapper> responses = list.getResponse();
1462                                final List<ChannelGroupClient> clients = new ArrayList<>(responses.size());
1463
1464                                for (final Wrapper response : responses) {
1465                                        clients.add(new ChannelGroupClient(response.getMap()));
1466                                }
1467                                future.set(clients);
1468                        }
1469                });
1470                return future;
1471        }
1472
1473        /**
1474         * Gets all client / channel ID combinations currently assigned to the specified channel group.
1475         *
1476         * @param groupId
1477         *              the ID of the channel group whose client / channel assignments should be returned.
1478         *
1479         * @return a list of combinations of channel ID, client database ID and channel group ID
1480         *
1481         * @querycommands 1
1482         * @see ChannelGroup#getId()
1483         * @see ChannelGroupClient
1484         * @see #getChannelGroupClients(int, int, int)
1485         */
1486        public CommandFuture<List<ChannelGroupClient>> getChannelGroupClientsByChannelGroupId(int groupId) {
1487                return getChannelGroupClients(-1, -1, groupId);
1488        }
1489
1490        /**
1491         * Gets all channel group assignments in the specified channel.
1492         *
1493         * @param channelId
1494         *              the ID of the channel whose channel group assignments should be returned.
1495         *
1496         * @return a list of combinations of channel ID, client database ID and channel group ID
1497         *
1498         * @querycommands 1
1499         * @see Channel#getId()
1500         * @see ChannelGroupClient
1501         * @see #getChannelGroupClients(int, int, int)
1502         */
1503        public CommandFuture<List<ChannelGroupClient>> getChannelGroupClientsByChannelId(int channelId) {
1504                return getChannelGroupClients(channelId, -1, -1);
1505        }
1506
1507        /**
1508         * Gets all channel group assignments for the specified client.
1509         *
1510         * @param clientDBId
1511         *              the database ID of the client whose channel group
1512         *
1513         * @return a list of combinations of channel ID, client database ID and channel group ID
1514         *
1515         * @querycommands 1
1516         * @see Client#getDatabaseId()
1517         * @see ChannelGroupClient
1518         * @see #getChannelGroupClients(int, int, int)
1519         */
1520        public CommandFuture<List<ChannelGroupClient>> getChannelGroupClientsByClientDBId(int clientDBId) {
1521                return getChannelGroupClients(-1, clientDBId, -1);
1522        }
1523
1524        /**
1525         * Gets a list of all permissions assigned to the specified channel group.
1526         *
1527         * @param groupId
1528         *              the ID of the channel group.
1529         *
1530         * @return a list of permissions assigned to the channel group
1531         *
1532         * @querycommands 1
1533         * @see ChannelGroup#getId()
1534         * @see Permission
1535         */
1536        public CommandFuture<List<Permission>> getChannelGroupPermissions(int groupId) {
1537                final CChannelGroupPermList list = new CChannelGroupPermList(groupId);
1538                final CommandFuture<List<Permission>> future = new CommandFuture<>();
1539
1540                query.doCommandAsync(list, new Callback() {
1541                        @Override
1542                        public void handle() {
1543                                if (hasFailed(list, future)) return;
1544
1545                                final List<Wrapper> responses = list.getResponse();
1546                                final List<Permission> permissions = new ArrayList<>(responses.size());
1547
1548                                for (final Wrapper response : responses) {
1549                                        permissions.add(new Permission(response.getMap()));
1550                                }
1551                                future.set(permissions);
1552                        }
1553                });
1554                return future;
1555        }
1556
1557        /**
1558         * Gets a list of all channel groups on the selected virtual server.
1559         *
1560         * @return a list of all channel groups on the virtual server
1561         *
1562         * @querycommands 1
1563         * @see ChannelGroup
1564         */
1565        public CommandFuture<List<ChannelGroup>> getChannelGroups() {
1566                final CChannelGroupList list = new CChannelGroupList();
1567                final CommandFuture<List<ChannelGroup>> future = new CommandFuture<>();
1568
1569                query.doCommandAsync(list, new Callback() {
1570                        @Override
1571                        public void handle() {
1572                                if (hasFailed(list, future)) return;
1573
1574                                final List<Wrapper> responses = list.getResponse();
1575                                final List<ChannelGroup> groups = new ArrayList<>(responses.size());
1576
1577                                for (final Wrapper response : responses) {
1578                                        groups.add(new ChannelGroup(response.getMap()));
1579                                }
1580                                future.set(groups);
1581                        }
1582                });
1583                return future;
1584        }
1585
1586        /**
1587         * Gets detailed configuration information about the channel specified channel.
1588         *
1589         * @param channelId
1590         *              the ID of the channel
1591         *
1592         * @return information about the channel
1593         *
1594         * @querycommands 1
1595         * @see Channel#getId()
1596         * @see ChannelInfo
1597         */
1598        public CommandFuture<ChannelInfo> getChannelInfo(final int channelId) {
1599                final CChannelInfo info = new CChannelInfo(channelId);
1600                final CommandFuture<ChannelInfo> future = new CommandFuture<>();
1601
1602                query.doCommandAsync(info, new Callback() {
1603                        @Override
1604                        public void handle() {
1605                                if (hasFailed(info, future)) return;
1606                                future.set(new ChannelInfo(channelId, info.getFirstResponse().getMap()));
1607                        }
1608                });
1609                return future;
1610        }
1611
1612        /**
1613         * Gets a list of all permissions assigned to the specified channel.
1614         *
1615         * @param channelId
1616         *              the ID of the channel
1617         *
1618         * @return a list of all permissions assigned to the channel
1619         *
1620         * @querycommands 1
1621         * @see Channel#getId()
1622         * @see Permission
1623         */
1624        public CommandFuture<List<Permission>> getChannelPermissions(int channelId) {
1625                final CChannelPermList list = new CChannelPermList(channelId);
1626                final CommandFuture<List<Permission>> future = new CommandFuture<>();
1627
1628                query.doCommandAsync(list, new Callback() {
1629                        @Override
1630                        public void handle() {
1631                                if (hasFailed(list, future)) return;
1632
1633                                final List<Wrapper> responses = list.getResponse();
1634                                final List<Permission> permissions = new ArrayList<>(responses.size());
1635
1636                                for (final Wrapper response : responses) {
1637                                        permissions.add(new Permission(response.getMap()));
1638                                }
1639                                future.set(permissions);
1640                        }
1641                });
1642                return future;
1643        }
1644
1645        /**
1646         * Gets a list of all channels on the selected virtual server.
1647         *
1648         * @return a list of all channels on the virtual server
1649         *
1650         * @querycommands 1
1651         * @see Channel
1652         */
1653        public CommandFuture<List<Channel>> getChannels() {
1654                final CChannelList list = new CChannelList();
1655                final CommandFuture<List<Channel>> future = new CommandFuture<>();
1656
1657                query.doCommandAsync(list, new Callback() {
1658                        @Override
1659                        public void handle() {
1660                                if (hasFailed(list, future)) return;
1661
1662                                final List<Wrapper> responses = list.getResponse();
1663                                final List<Channel> channels = new ArrayList<>(responses.size());
1664
1665                                for (final Wrapper response : responses) {
1666                                        channels.add(new Channel(response.getMap()));
1667                                }
1668                                future.set(channels);
1669                        }
1670                });
1671                return future;
1672        }
1673
1674        /**
1675         * Finds and returns the client whose nickname matches the given name exactly.
1676         *
1677         * @param name
1678         *              the name of the client
1679         * @param ignoreCase
1680         *              whether the case of the name should be ignored
1681         *
1682         * @return the found client or {@code null} if no client was found
1683         *
1684         * @querycommands 1
1685         * @see Client
1686         * @see #getClientsByName(String)
1687         */
1688        public CommandFuture<Client> getClientByNameExact(String name, final boolean ignoreCase) {
1689                final CommandFuture<Client> future = new CommandFuture<>();
1690                final String caseName = ignoreCase ? name.toLowerCase(Locale.ROOT) : name;
1691
1692                getClients().onSuccess(new CommandFuture.SuccessListener<List<Client>>() {
1693                        @Override
1694                        public void handleSuccess(final List<Client> allClients) {
1695                                for (final Client c : allClients) {
1696                                        final String clientName = ignoreCase ? c.getNickname().toLowerCase(Locale.ROOT) : c.getNickname();
1697                                        if (caseName.equals(clientName)) {
1698                                                future.set(c);
1699                                                return;
1700                                        }
1701                                }
1702                                future.set(null); // Not found
1703                        }
1704                }).forwardFailure(future);
1705                return future;
1706        }
1707
1708        /**
1709         * Gets a list of clients whose nicknames contain the given search string.
1710         *
1711         * @param name
1712         *              the name to search
1713         *
1714         * @return a list of all clients with nicknames matching the search pattern
1715         *
1716         * @querycommands 2
1717         * @see Client
1718         * @see #getClientByNameExact(String, boolean)
1719         */
1720        public CommandFuture<List<Client>> getClientsByName(String name) {
1721                final CClientFind find = new CClientFind(name);
1722                final CommandFuture<List<Client>> future = new CommandFuture<>();
1723
1724                getClients().onSuccess(new CommandFuture.SuccessListener<List<Client>>() {
1725                        @Override
1726                        public void handleSuccess(final List<Client> allClients) {
1727                                query.doCommandAsync(find, new Callback() {
1728                                        @Override
1729                                        public void handle() {
1730                                                if (hasFailed(find, future)) return;
1731
1732                                                final List<Wrapper> responses = find.getResponse();
1733                                                final List<Client> clients = new ArrayList<>(responses.size());
1734
1735                                                for (final Wrapper response : responses) {
1736                                                        for (final Client c : allClients) {
1737                                                                if (c.getId() == response.getInt("clid")) {
1738                                                                        clients.add(c);
1739                                                                        break;
1740                                                                }
1741                                                        }
1742                                                }
1743                                                future.set(clients);
1744                                        }
1745                                });
1746                        }
1747                }).forwardFailure(future);
1748                return future;
1749        }
1750
1751        /**
1752         * Gets information about the client with the specified unique identifier.
1753         *
1754         * @param clientUId
1755         *              the unique identifier of the client
1756         *
1757         * @return the client or {@code null} if no client was found
1758         *
1759         * @querycommands 2
1760         * @see Client#getUniqueIdentifier()
1761         * @see ClientInfo
1762         */
1763        public CommandFuture<ClientInfo> getClientByUId(String clientUId) {
1764                final CClientGetIds get = new CClientGetIds(clientUId);
1765                final CommandFuture<ClientInfo> future = new CommandFuture<>();
1766
1767                query.doCommandAsync(get, new Callback() {
1768                        @Override
1769                        public void handle() {
1770                                if (hasFailed(get, future)) return;
1771
1772                                getClientInfo(get.getFirstResponse().getInt("clid")).forwardResult(future);
1773                        }
1774                });
1775                return future;
1776        }
1777
1778        /**
1779         * Gets information about the client with the specified client ID.
1780         *
1781         * @param clientId
1782         *              the client ID of the client
1783         *
1784         * @return the client or {@code null} if no client was found
1785         *
1786         * @querycommands 1
1787         * @see Client#getId()
1788         * @see ClientInfo
1789         */
1790        public CommandFuture<ClientInfo> getClientInfo(final int clientId) {
1791                final CClientInfo info = new CClientInfo(clientId);
1792                final CommandFuture<ClientInfo> future = new CommandFuture<>();
1793
1794                query.doCommandAsync(info, new Callback() {
1795                        @Override
1796                        public void handle() {
1797                                if (hasFailed(info, future)) return;
1798                                future.set(new ClientInfo(clientId, info.getFirstResponse().getMap()));
1799                        }
1800                });
1801                return future;
1802        }
1803
1804        /**
1805         * Gets a list of all permissions assigned to the specified client.
1806         *
1807         * @param clientDBId
1808         *              the database ID of the client
1809         *
1810         * @return a list of all permissions assigned to the client
1811         *
1812         * @querycommands 1
1813         * @see Client#getDatabaseId()
1814         * @see Permission
1815         */
1816        public CommandFuture<List<Permission>> getClientPermissions(int clientDBId) {
1817                final CClientPermList list = new CClientPermList(clientDBId);
1818                final CommandFuture<List<Permission>> future = new CommandFuture<>();
1819
1820                query.doCommandAsync(list, new Callback() {
1821                        @Override
1822                        public void handle() {
1823                                if (hasFailed(list, future)) return;
1824
1825                                final List<Wrapper> responses = list.getResponse();
1826                                final List<Permission> permissions = new ArrayList<>(responses.size());
1827
1828                                for (final Wrapper response : responses) {
1829                                        permissions.add(new Permission(response.getMap()));
1830                                }
1831                                future.set(permissions);
1832                        }
1833                });
1834                return future;
1835        }
1836
1837        /**
1838         * Gets a list of all clients on the selected virtual server.
1839         *
1840         * @return a list of all clients on the virtual server
1841         *
1842         * @querycommands 1
1843         * @see Client
1844         */
1845        public CommandFuture<List<Client>> getClients() {
1846                final CClientList list = new CClientList();
1847                final CommandFuture<List<Client>> future = new CommandFuture<>();
1848
1849                query.doCommandAsync(list, new Callback() {
1850                        @Override
1851                        public void handle() {
1852                                if (hasFailed(list, future)) return;
1853
1854                                final List<Wrapper> responses = list.getResponse();
1855                                final List<Client> clients = new ArrayList<>(responses.size());
1856
1857                                for (final Wrapper response : responses) {
1858                                        clients.add(new Client(response.getMap()));
1859                                }
1860                                future.set(clients);
1861                        }
1862                });
1863                return future;
1864        }
1865
1866        /**
1867         * Gets a list of all complaints on the selected virtual server.
1868         *
1869         * @return a list of all complaints on the virtual server
1870         *
1871         * @querycommands 1
1872         * @see Complaint
1873         * @see #getComplaints(int)
1874         */
1875        public CommandFuture<List<Complaint>> getComplaints() {
1876                return getComplaints(-1);
1877        }
1878
1879        /**
1880         * Gets a list of all complaints about the specified client.
1881         *
1882         * @param clientDBId
1883         *              the database ID of the client
1884         *
1885         * @return a list of all complaints about the specified client
1886         *
1887         * @querycommands 1
1888         * @see Client#getDatabaseId()
1889         * @see Complaint
1890         */
1891        public CommandFuture<List<Complaint>> getComplaints(int clientDBId) {
1892                final CComplainList list = new CComplainList(clientDBId);
1893                final CommandFuture<List<Complaint>> future = new CommandFuture<>();
1894
1895                query.doCommandAsync(list, new Callback() {
1896                        @Override
1897                        public void handle() {
1898                                if (hasFailed(list, future)) return;
1899
1900                                final List<Wrapper> responses = list.getResponse();
1901                                final List<Complaint> complaints = new ArrayList<>(responses.size());
1902
1903                                for (final Wrapper response : responses) {
1904                                        complaints.add(new Complaint(response.getMap()));
1905                                }
1906                                future.set(complaints);
1907                        }
1908                });
1909                return future;
1910        }
1911
1912        /**
1913         * Gets detailed connection information about the selected virtual server.
1914         *
1915         * @return connection information about the selected virtual server
1916         *
1917         * @querycommands 1
1918         * @see ConnectionInfo
1919         * @see #getServerInfo()
1920         */
1921        public CommandFuture<ConnectionInfo> getConnectionInfo() {
1922                final CServerRequestConnectionInfo info = new CServerRequestConnectionInfo();
1923                final CommandFuture<ConnectionInfo> future = new CommandFuture<>();
1924
1925                query.doCommandAsync(info, new Callback() {
1926                        @Override
1927                        public void handle() {
1928                                if (hasFailed(info, future)) return;
1929                                future.set(new ConnectionInfo(info.getFirstResponse().getMap()));
1930                        }
1931                });
1932                return future;
1933        }
1934
1935        /**
1936         * Gets all clients in the database whose last nickname matches the specified name <b>exactly</b>.
1937         *
1938         * @param name
1939         *              the nickname for the clients to match
1940         *
1941         * @return a list of all clients with a matching nickname
1942         *
1943         * @querycommands 1 + n,
1944         * where n is the amount of database clients with a matching nickname
1945         * @see Client#getNickname()
1946         */
1947        public CommandFuture<List<DatabaseClientInfo>> getDatabaseClientsByName(String name) {
1948                final CClientDBFind find = new CClientDBFind(name, false);
1949                final CommandFuture<List<DatabaseClientInfo>> future = new CommandFuture<>();
1950
1951                query.doCommandAsync(find, new Callback() {
1952                        @Override
1953                        public void handle() {
1954                                if (hasFailed(find, future)) return;
1955
1956                                final List<Wrapper> responses = find.getResponse();
1957                                final Collection<CommandFuture<DatabaseClientInfo>> infoFutures = new ArrayList<>(responses.size());
1958                                for (Wrapper response : responses) {
1959                                        final int databaseId = response.getInt("cldbid");
1960                                        infoFutures.add(getDatabaseClientInfo(databaseId));
1961                                }
1962
1963                                CommandFuture.ofAll(infoFutures).forwardResult(future);
1964                        }
1965                });
1966                return future;
1967        }
1968
1969        /**
1970         * Gets information about the client with the specified unique identifier in the server database.
1971         *
1972         * @param clientUId
1973         *              the unique identifier of the client
1974         *
1975         * @return the database client or {@code null} if no client was found
1976         *
1977         * @querycommands 2
1978         * @see Client#getUniqueIdentifier()
1979         * @see DatabaseClientInfo
1980         */
1981        public CommandFuture<DatabaseClientInfo> getDatabaseClientByUId(String clientUId) {
1982                final CClientGetDBIdFromUId get = new CClientGetDBIdFromUId(clientUId);
1983                final CommandFuture<DatabaseClientInfo> future = new CommandFuture<>();
1984
1985                query.doCommandAsync(get, new Callback() {
1986                        @Override
1987                        public void handle() {
1988                                if (hasFailed(get, future)) return;
1989
1990                                getDatabaseClientInfo(get.getFirstResponse().getInt("cldbid")).forwardResult(future);
1991                        }
1992                });
1993                return future;
1994        }
1995
1996        /**
1997         * Gets information about the client with the specified database ID in the server database.
1998         *
1999         * @param clientDBId
2000         *              the database ID of the client
2001         *
2002         * @return the database client or {@code null} if no client was found
2003         *
2004         * @querycommands 1
2005         * @see Client#getDatabaseId()
2006         * @see DatabaseClientInfo
2007         */
2008        public CommandFuture<DatabaseClientInfo> getDatabaseClientInfo(int clientDBId) {
2009                final CClientDBInfo info = new CClientDBInfo(clientDBId);
2010                final CommandFuture<DatabaseClientInfo> future = new CommandFuture<>();
2011
2012                query.doCommandAsync(info, new Callback() {
2013                        @Override
2014                        public void handle() {
2015                                if (hasFailed(info, future)) return;
2016                                future.set(new DatabaseClientInfo(info.getFirstResponse().getMap()));
2017                        }
2018                });
2019                return future;
2020        }
2021
2022        /**
2023         * Gets information about all clients in the server database.
2024         * <p>
2025         * As this method uses internal commands which can only return 200 clients at once,
2026         * this method can take quite some time to execute.
2027         * </p><p>
2028         * Also keep in mind that the client database can easily accumulate several thousand entries.
2029         * </p>
2030         *
2031         * @return a {@link List} of all database clients
2032         *
2033         * @querycommands 1 + n,
2034         * where n = Math.ceil([amount of database clients] / 200)
2035         * @see DatabaseClient
2036         */
2037        public CommandFuture<List<DatabaseClient>> getDatabaseClients() {
2038                final CClientDBList countList = new CClientDBList(0, 1, true);
2039                final CommandFuture<List<DatabaseClient>> future = new CommandFuture<>();
2040
2041                query.doCommandAsync(countList, new Callback() {
2042                        @Override
2043                        public void handle() {
2044                                if (hasFailed(countList, future)) return;
2045
2046                                final int count = countList.getFirstResponse().getInt("count");
2047                                final int futuresCount = ((count - 1) / 200) + 1;
2048                                final Collection<CommandFuture<List<DatabaseClient>>> futures = new ArrayList<>(futuresCount);
2049                                for (int i = 0; i < count; i += 200) {
2050                                        futures.add(getDatabaseClients(i, 200));
2051                                }
2052
2053                                CommandFuture.ofAll(futures).onSuccess(new CommandFuture.SuccessListener<List<List<DatabaseClient>>>() {
2054                                        @Override
2055                                        public void handleSuccess(List<List<DatabaseClient>> result) {
2056                                                int total = 0;
2057                                                for (List<DatabaseClient> list : result) {
2058                                                        total += list.size();
2059                                                }
2060
2061                                                final List<DatabaseClient> combination = new ArrayList<>(total);
2062                                                for (List<DatabaseClient> list : result) {
2063                                                        combination.addAll(list);
2064                                                }
2065                                                future.set(combination);
2066                                        }
2067                                }).forwardFailure(future);
2068                        }
2069                });
2070                return future;
2071        }
2072
2073        /**
2074         * Gets information about a set number of clients in the server database, starting at {@code offset}.
2075         *
2076         * @param offset
2077         *              the index of the first database client to be returned.
2078         *              Note that this is <b>not</b> a database ID, but an arbitrary, 0-based index.
2079         * @param count
2080         *              the number of database clients that should be returned.
2081         *              Any integer greater than 200 might cause problems with the connection
2082         *
2083         * @return a {@link List} of database clients
2084         *
2085         * @querycommands 1
2086         * @see DatabaseClient
2087         */
2088        public CommandFuture<List<DatabaseClient>> getDatabaseClients(final int offset, final int count) {
2089                final CClientDBList list = new CClientDBList(offset, count, false);
2090                final CommandFuture<List<DatabaseClient>> future = new CommandFuture<>();
2091
2092                query.doCommandAsync(list, new Callback() {
2093                        @Override
2094                        public void handle() {
2095                                if (hasFailed(list, future)) return;
2096
2097                                final List<DatabaseClient> clients = new ArrayList<>(count);
2098                                for (final Wrapper response : list.getResponse()) {
2099                                        clients.add(new DatabaseClient(response.getMap()));
2100                                }
2101                                future.set(clients);
2102                        }
2103                });
2104                return future;
2105        }
2106
2107        /**
2108         * Displays detailed configuration information about the server instance including
2109         * uptime, number of virtual servers online, traffic information, etc.
2110         *
2111         * @return information about the host
2112         *
2113         * @querycommands 1
2114         */
2115        public CommandFuture<HostInfo> getHostInfo() {
2116                final CHostInfo info = new CHostInfo();
2117                final CommandFuture<HostInfo> future = new CommandFuture<>();
2118
2119                query.doCommandAsync(info, new Callback() {
2120                        @Override
2121                        public void handle() {
2122                                if (hasFailed(info, future)) return;
2123                                future.set(new HostInfo(info.getFirstResponse().getMap()));
2124                        }
2125                });
2126                return future;
2127        }
2128
2129        /**
2130         * Displays the server instance configuration including database revision number,
2131         * the file transfer port, default group IDs, etc.
2132         *
2133         * @return information about the TeamSpeak server instance.
2134         *
2135         * @querycommands 1
2136         */
2137        public CommandFuture<InstanceInfo> getInstanceInfo() {
2138                final CInstanceInfo info = new CInstanceInfo();
2139                final CommandFuture<InstanceInfo> future = new CommandFuture<>();
2140
2141                query.doCommandAsync(info, new Callback() {
2142                        @Override
2143                        public void handle() {
2144                                if (hasFailed(info, future)) return;
2145                                future.set(new InstanceInfo(info.getFirstResponse().getMap()));
2146                        }
2147                });
2148                return future;
2149        }
2150
2151        /**
2152         * Reads the message body of a message. This will not set the read flag, though.
2153         *
2154         * @param messageId
2155         *              the ID of the message to be read
2156         *
2157         * @return the body of the message with the specified ID or {@code null} if there was no message with that ID
2158         *
2159         * @querycommands 1
2160         * @see Message#getId()
2161         * @see #setMessageRead(int)
2162         */
2163        public CommandFuture<String> getOfflineMessage(int messageId) {
2164                final CMessageGet get = new CMessageGet(messageId);
2165                return executeAndReturnStringProperty(get, "message");
2166        }
2167
2168        /**
2169         * Reads the message body of a message. This will not set the read flag, though.
2170         *
2171         * @param message
2172         *              the message to be read
2173         *
2174         * @return the body of the message with the specified ID or {@code null} if there was no message with that ID
2175         *
2176         * @querycommands 1
2177         * @see Message#getId()
2178         * @see #setMessageRead(Message)
2179         */
2180        public CommandFuture<String> getOfflineMessage(Message message) {
2181                return getOfflineMessage(message.getId());
2182        }
2183
2184        /**
2185         * Gets a list of all offline messages for the server query.
2186         * The returned messages lack their message body, though.
2187         * To read the actual message, use {@link #getOfflineMessage(int)} or {@link #getOfflineMessage(Message)}.
2188         *
2189         * @return a list of all offline messages this server query has received
2190         *
2191         * @querycommands 1
2192         */
2193        public CommandFuture<List<Message>> getOfflineMessages() {
2194                final CMessageList list = new CMessageList();
2195                final CommandFuture<List<Message>> future = new CommandFuture<>();
2196
2197                query.doCommandAsync(list, new Callback() {
2198                        @Override
2199                        public void handle() {
2200                                if (hasFailed(list, future)) return;
2201
2202                                final List<Wrapper> responses = list.getResponse();
2203                                final List<Message> msg = new ArrayList<>(responses.size());
2204
2205                                for (final Wrapper response : responses) {
2206                                        msg.add(new Message(response.getMap()));
2207                                }
2208                                future.set(msg);
2209                        }
2210                });
2211                return future;
2212        }
2213
2214        /**
2215         * Displays detailed information about all assignments of the permission specified
2216         * with {@code permName}. The output includes the type and the ID of the client,
2217         * channel or group associated with the permission.
2218         *
2219         * @param permName
2220         *              the name of the permission
2221         *
2222         * @return a list of permission assignments
2223         *
2224         * @querycommands 1
2225         * @see #getPermissionOverview(int, int)
2226         */
2227        public CommandFuture<List<PermissionAssignment>> getPermissionAssignments(String permName) {
2228                final CPermFind find = new CPermFind(permName);
2229                final CommandFuture<List<PermissionAssignment>> future = new CommandFuture<>();
2230
2231                query.doCommandAsync(find, new Callback() {
2232                        @Override
2233                        public void handle() {
2234                                if (hasFailed(find, future)) return;
2235
2236                                final List<Wrapper> responses = find.getResponse();
2237                                final List<PermissionAssignment> assignments = new ArrayList<>(responses.size());
2238
2239                                for (final Wrapper response : responses) {
2240                                        assignments.add(new PermissionAssignment(response.getMap()));
2241                                }
2242                                future.set(assignments);
2243                        }
2244                });
2245                return future;
2246        }
2247
2248        /**
2249         * Gets the ID of the permission specified by {@code permName}.
2250         * <p>
2251         * Note that the use of numeric permission IDs is deprecated
2252         * and that this API only uses the string variant of the IDs.
2253         * </p>
2254         *
2255         * @param permName
2256         *              the name of the permission
2257         *
2258         * @return the numeric ID of the specified permission
2259         *
2260         * @querycommands 1
2261         */
2262        public CommandFuture<Integer> getPermissionIdByName(String permName) {
2263                final CPermIdGetByName get = new CPermIdGetByName(permName);
2264                return executeAndReturnIntProperty(get, "permid");
2265        }
2266
2267        /**
2268         * Gets a list of all assigned permissions for a client in a specified channel.
2269         * If you do not care about channel permissions, set {@code channelId} to {@code -1}.
2270         *
2271         * @param channelId
2272         *              the ID of the channel
2273         * @param clientDBId
2274         *              the database ID of the client to create the overview for
2275         *
2276         * @return a list of all permission assignments for the client in the specified channel
2277         *
2278         * @querycommands 1
2279         * @see Channel#getId()
2280         * @see Client#getDatabaseId()
2281         */
2282        public CommandFuture<List<PermissionAssignment>> getPermissionOverview(int channelId, int clientDBId) {
2283                final CPermOverview overview = new CPermOverview(channelId, clientDBId);
2284                final CommandFuture<List<PermissionAssignment>> future = new CommandFuture<>();
2285
2286                query.doCommandAsync(overview, new Callback() {
2287                        @Override
2288                        public void handle() {
2289                                if (hasFailed(overview, future)) return;
2290
2291                                final List<Wrapper> responses = overview.getResponse();
2292                                final List<PermissionAssignment> permissions = new ArrayList<>(responses.size());
2293
2294                                for (final Wrapper response : responses) {
2295                                        permissions.add(new PermissionAssignment(response.getMap()));
2296                                }
2297                                future.set(permissions);
2298                        }
2299                });
2300                return future;
2301        }
2302
2303        /**
2304         * Displays a list of all permissions, including ID, name and description.
2305         *
2306         * @return a list of all permissions
2307         *
2308         * @querycommands 1
2309         */
2310        public CommandFuture<List<PermissionInfo>> getPermissions() {
2311                final CPermissionList list = new CPermissionList();
2312                final CommandFuture<List<PermissionInfo>> future = new CommandFuture<>();
2313
2314                query.doCommandAsync(list, new Callback() {
2315                        @Override
2316                        public void handle() {
2317                                if (hasFailed(list, future)) return;
2318
2319                                final List<Wrapper> responses = list.getResponse();
2320                                final List<PermissionInfo> permissions = new ArrayList<>(responses.size());
2321
2322                                for (final Wrapper response : responses) {
2323                                        permissions.add(new PermissionInfo(response.getMap()));
2324                                }
2325                                future.set(permissions);
2326                        }
2327                });
2328                return future;
2329        }
2330
2331        /**
2332         * Displays the current value of the specified permission for this server query instance.
2333         *
2334         * @param permName
2335         *              the name of the permission
2336         *
2337         * @return the permission value, usually ranging from 0 to 100
2338         *
2339         * @querycommands 1
2340         */
2341        public CommandFuture<Integer> getPermissionValue(String permName) {
2342                final CPermGet get = new CPermGet(permName);
2343                return executeAndReturnIntProperty(get, "permvalue");
2344        }
2345
2346        /**
2347         * Gets a list of all available tokens to join channel or server groups,
2348         * including their type and group IDs.
2349         *
2350         * @return a list of all generated, but still unclaimed privilege keys
2351         *
2352         * @querycommands 1
2353         * @see #addPrivilegeKey(PrivilegeKeyType, int, int, String)
2354         * @see #usePrivilegeKey(String)
2355         */
2356        public CommandFuture<List<PrivilegeKey>> getPrivilegeKeys() {
2357                final CPrivilegeKeyList list = new CPrivilegeKeyList();
2358                final CommandFuture<List<PrivilegeKey>> future = new CommandFuture<>();
2359
2360                query.doCommandAsync(list, new Callback() {
2361                        @Override
2362                        public void handle() {
2363                                if (hasFailed(list, future)) return;
2364
2365                                final List<Wrapper> responses = list.getResponse();
2366                                final List<PrivilegeKey> keys = new ArrayList<>(responses.size());
2367
2368                                for (final Wrapper response : responses) {
2369                                        keys.add(new PrivilegeKey(response.getMap()));
2370                                }
2371                                future.set(keys);
2372                        }
2373                });
2374                return future;
2375        }
2376
2377        /**
2378         * Gets a list of all clients in the specified server group.
2379         *
2380         * @param serverGroupId
2381         *              the ID of the server group for which the clients should be looked up
2382         *
2383         * @return a list of all clients in the server group
2384         *
2385         * @querycommands 1
2386         */
2387        public CommandFuture<List<ServerGroupClient>> getServerGroupClients(int serverGroupId) {
2388                final CServerGroupClientList list = new CServerGroupClientList(serverGroupId);
2389                final CommandFuture<List<ServerGroupClient>> future = new CommandFuture<>();
2390
2391                query.doCommandAsync(list, new Callback() {
2392                        @Override
2393                        public void handle() {
2394                                if (hasFailed(list, future)) return;
2395
2396                                final List<Wrapper> responses = list.getResponse();
2397                                final List<ServerGroupClient> clients = new ArrayList<>(responses.size());
2398
2399                                for (final Wrapper response : responses) {
2400                                        clients.add(new ServerGroupClient(response.getMap()));
2401                                }
2402                                future.set(clients);
2403                        }
2404                });
2405                return future;
2406        }
2407
2408        /**
2409         * Gets a list of all clients in the specified server group.
2410         *
2411         * @param serverGroup
2412         *              the server group for which the clients should be looked up
2413         *
2414         * @return a list of all clients in the server group
2415         *
2416         * @querycommands 1
2417         */
2418        public CommandFuture<List<ServerGroupClient>> getServerGroupClients(ServerGroup serverGroup) {
2419                return getServerGroupClients(serverGroup.getId());
2420        }
2421
2422        /**
2423         * Gets a list of all permissions assigned to the specified server group.
2424         *
2425         * @param serverGroupId
2426         *              the ID of the server group for which the permissions should be looked up
2427         *
2428         * @return a list of all permissions assigned to the server group
2429         *
2430         * @querycommands 1
2431         * @see ServerGroup#getId()
2432         * @see #getServerGroupPermissions(ServerGroup)
2433         */
2434        public CommandFuture<List<Permission>> getServerGroupPermissions(int serverGroupId) {
2435                final CServerGroupPermList list = new CServerGroupPermList(serverGroupId);
2436                final CommandFuture<List<Permission>> future = new CommandFuture<>();
2437
2438                query.doCommandAsync(list, new Callback() {
2439                        @Override
2440                        public void handle() {
2441                                if (hasFailed(list, future)) return;
2442
2443                                final List<Wrapper> responses = list.getResponse();
2444                                final List<Permission> permissions = new ArrayList<>(responses.size());
2445
2446                                for (final Wrapper response : responses) {
2447                                        permissions.add(new Permission(response.getMap()));
2448                                }
2449                                future.set(permissions);
2450                        }
2451                });
2452                return future;
2453        }
2454
2455        /**
2456         * Gets a list of all permissions assigned to the specified server group.
2457         *
2458         * @param serverGroup
2459         *              the server group for which the permissions should be looked up
2460         *
2461         * @return a list of all permissions assigned to the server group
2462         *
2463         * @querycommands 1
2464         */
2465        public CommandFuture<List<Permission>> getServerGroupPermissions(ServerGroup serverGroup) {
2466                return getServerGroupPermissions(serverGroup.getId());
2467        }
2468
2469        /**
2470         * Gets a list of all server groups on the virtual server.
2471         * <p>
2472         * Depending on your permissions, the output may also contain
2473         * global server query groups and template groups.
2474         * </p>
2475         *
2476         * @return a list of all server groups
2477         *
2478         * @querycommands 1
2479         */
2480        public CommandFuture<List<ServerGroup>> getServerGroups() {
2481                final CServerGroupList list = new CServerGroupList();
2482                final CommandFuture<List<ServerGroup>> future = new CommandFuture<>();
2483
2484                query.doCommandAsync(list, new Callback() {
2485                        @Override
2486                        public void handle() {
2487                                if (hasFailed(list, future)) return;
2488
2489                                final List<Wrapper> responses = list.getResponse();
2490                                final List<ServerGroup> groups = new ArrayList<>(responses.size());
2491
2492                                for (final Wrapper response : responses) {
2493                                        groups.add(new ServerGroup(response.getMap()));
2494                                }
2495                                future.set(groups);
2496                        }
2497                });
2498                return future;
2499        }
2500
2501        /**
2502         * Gets a list of all server groups set for a client.
2503         *
2504         * @param clientDatabaseId
2505         *              the database ID of the client for which the server groups should be looked up
2506         *
2507         * @return a list of all server groups set for the client
2508         *
2509         * @querycommands 2
2510         * @see Client#getDatabaseId()
2511         * @see #getServerGroupsByClient(Client)
2512         */
2513        public CommandFuture<List<ServerGroup>> getServerGroupsByClientId(int clientDatabaseId) {
2514                final CServerGroupsByClientId client = new CServerGroupsByClientId(clientDatabaseId);
2515                final CommandFuture<List<ServerGroup>> future = new CommandFuture<>();
2516
2517                getServerGroups().onSuccess(new CommandFuture.SuccessListener<List<ServerGroup>>() {
2518                        @Override
2519                        public void handleSuccess(final List<ServerGroup> allServerGroups) {
2520                                query.doCommandAsync(client, new Callback() {
2521                                        @Override
2522                                        public void handle() {
2523                                                if (hasFailed(client, future)) return;
2524
2525                                                final List<Wrapper> responses = client.getResponse();
2526                                                final List<ServerGroup> list = new ArrayList<>(responses.size());
2527
2528                                                for (final Wrapper response : responses) {
2529                                                        for (final ServerGroup s : allServerGroups) {
2530                                                                if (s.getId() == response.getInt("sgid")) {
2531                                                                        list.add(s);
2532                                                                }
2533                                                        }
2534                                                }
2535                                                future.set(list);
2536                                        }
2537                                });
2538                        }
2539                }).forwardFailure(future);
2540                return future;
2541        }
2542
2543        /**
2544         * Gets a list of all server groups set for a client.
2545         *
2546         * @param client
2547         *              the client for which the server groups should be looked up
2548         *
2549         * @return a list of all server group set for the client
2550         *
2551         * @querycommands 2
2552         * @see #getServerGroupsByClientId(int)
2553         */
2554        public CommandFuture<List<ServerGroup>> getServerGroupsByClient(Client client) {
2555                return getServerGroupsByClientId(client.getDatabaseId());
2556        }
2557
2558        /**
2559         * Gets the ID of a virtual server by its port.
2560         *
2561         * @param port
2562         *              the port of a virtual server
2563         *
2564         * @return the ID of the virtual server
2565         *
2566         * @querycommands 1
2567         * @see VirtualServer#getPort()
2568         * @see VirtualServer#getId()
2569         */
2570        public CommandFuture<Integer> getServerIdByPort(int port) {
2571                final CServerIdGetByPort s = new CServerIdGetByPort(port);
2572                return executeAndReturnIntProperty(s, "server_id");
2573        }
2574
2575        /**
2576         * Gets detailed information about the virtual server the server query is currently in.
2577         *
2578         * @return information about the current virtual server
2579         *
2580         * @querycommands 1
2581         */
2582        public CommandFuture<VirtualServerInfo> getServerInfo() {
2583                final CServerInfo info = new CServerInfo();
2584                final CommandFuture<VirtualServerInfo> future = new CommandFuture<>();
2585
2586                query.doCommandAsync(info, new Callback() {
2587                        @Override
2588                        public void handle() {
2589                                if (hasFailed(info, future)) return;
2590                                future.set(new VirtualServerInfo(info.getFirstResponse().getMap()));
2591                        }
2592                });
2593                return future;
2594        }
2595
2596        /**
2597         * Gets the version, build number and platform of the TeamSpeak3 server.
2598         *
2599         * @return the version information of the server
2600         *
2601         * @querycommands 1
2602         */
2603        public CommandFuture<Version> getVersion() {
2604                final CVersion version = new CVersion();
2605                final CommandFuture<Version> future = new CommandFuture<>();
2606
2607                query.doCommandAsync(version, new Callback() {
2608                        @Override
2609                        public void handle() {
2610                                if (hasFailed(version, future)) return;
2611                                future.set(new Version(version.getFirstResponse().getMap()));
2612                        }
2613                });
2614                return future;
2615        }
2616
2617        /**
2618         * Gets a list of all virtual servers including their ID, status, number of clients online, etc.
2619         *
2620         * @return a list of all virtual servers
2621         *
2622         * @querycommands 1
2623         */
2624        public CommandFuture<List<VirtualServer>> getVirtualServers() {
2625                final CServerList serverList = new CServerList();
2626                final CommandFuture<List<VirtualServer>> future = new CommandFuture<>();
2627
2628                query.doCommandAsync(serverList, new Callback() {
2629                        @Override
2630                        public void handle() {
2631                                if (hasFailed(serverList, future)) return;
2632
2633                                final List<Wrapper> responses = serverList.getResponse();
2634                                final List<VirtualServer> servers = new ArrayList<>(responses.size());
2635
2636                                for (final Wrapper response : responses) {
2637                                        servers.add((new VirtualServer(response.getMap())));
2638                                }
2639                                future.set(servers);
2640                        }
2641                });
2642                return future;
2643        }
2644
2645        /**
2646         * Kicks one or more clients from their current channels.
2647         * This will move the kicked clients into the default channel and
2648         * won't do anything if the clients are already in the default channel.
2649         *
2650         * @param clientIds
2651         *              the IDs of the clients to kick
2652         *
2653         * @return whether the command succeeded or not
2654         *
2655         * @querycommands 1
2656         * @see #kickClientFromChannel(Client...)
2657         * @see #kickClientFromChannel(String, int...)
2658         */
2659        public CommandFuture<Boolean> kickClientFromChannel(int... clientIds) {
2660                return kickClients(ReasonIdentifier.REASON_KICK_CHANNEL, null, clientIds);
2661        }
2662
2663        /**
2664         * Kicks one or more clients from their current channels.
2665         * This will move the kicked clients into the default channel and
2666         * won't do anything if the clients are already in the default channel.
2667         *
2668         * @param clients
2669         *              the clients to kick
2670         *
2671         * @return whether the command succeeded or not
2672         *
2673         * @querycommands 1
2674         * @see #kickClientFromChannel(int...)
2675         * @see #kickClientFromChannel(String, Client...)
2676         */
2677        public CommandFuture<Boolean> kickClientFromChannel(Client... clients) {
2678                return kickClients(ReasonIdentifier.REASON_KICK_CHANNEL, null, clients);
2679        }
2680
2681        /**
2682         * Kicks one or more clients from their current channels for the specified reason.
2683         * This will move the kicked clients into the default channel and
2684         * won't do anything if the clients are already in the default channel.
2685         *
2686         * @param message
2687         *              the reason message to display to the clients
2688         * @param clientIds
2689         *              the IDs of the clients to kick
2690         *
2691         * @return whether the command succeeded or not
2692         *
2693         * @querycommands 1
2694         * @see Client#getId()
2695         * @see #kickClientFromChannel(int...)
2696         * @see #kickClientFromChannel(String, Client...)
2697         */
2698        public CommandFuture<Boolean> kickClientFromChannel(String message, int... clientIds) {
2699                return kickClients(ReasonIdentifier.REASON_KICK_CHANNEL, message, clientIds);
2700        }
2701
2702        /**
2703         * Kicks one or more clients from their current channels for the specified reason.
2704         * This will move the kicked clients into the default channel and
2705         * won't do anything if the clients are already in the default channel.
2706         *
2707         * @param message
2708         *              the reason message to display to the clients
2709         * @param clients
2710         *              the clients to kick
2711         *
2712         * @return whether the command succeeded or not
2713         *
2714         * @querycommands 1
2715         * @see #kickClientFromChannel(Client...)
2716         * @see #kickClientFromChannel(String, int...)
2717         */
2718        public CommandFuture<Boolean> kickClientFromChannel(String message, Client... clients) {
2719                return kickClients(ReasonIdentifier.REASON_KICK_CHANNEL, message, clients);
2720        }
2721
2722        /**
2723         * Kicks one or more clients from the server.
2724         *
2725         * @param clientIds
2726         *              the IDs of the clients to kick
2727         *
2728         * @return whether the command succeeded or not
2729         *
2730         * @querycommands 1
2731         * @see Client#getId()
2732         * @see #kickClientFromServer(Client...)
2733         * @see #kickClientFromServer(String, int...)
2734         */
2735        public CommandFuture<Boolean> kickClientFromServer(int... clientIds) {
2736                return kickClients(ReasonIdentifier.REASON_KICK_SERVER, null, clientIds);
2737        }
2738
2739        /**
2740         * Kicks one or more clients from the server.
2741         *
2742         * @param clients
2743         *              the clients to kick
2744         *
2745         * @return whether the command succeeded or not
2746         *
2747         * @querycommands 1
2748         * @see #kickClientFromServer(int...)
2749         * @see #kickClientFromServer(String, Client...)
2750         */
2751        public CommandFuture<Boolean> kickClientFromServer(Client... clients) {
2752                return kickClients(ReasonIdentifier.REASON_KICK_SERVER, null, clients);
2753        }
2754
2755        /**
2756         * Kicks one or more clients from the server for the specified reason.
2757         *
2758         * @param message
2759         *              the reason message to display to the clients
2760         * @param clientIds
2761         *              the IDs of the clients to kick
2762         *
2763         * @return whether the command succeeded or not
2764         *
2765         * @querycommands 1
2766         * @see Client#getId()
2767         * @see #kickClientFromServer(int...)
2768         * @see #kickClientFromServer(String, Client...)
2769         */
2770        public CommandFuture<Boolean> kickClientFromServer(String message, int... clientIds) {
2771                return kickClients(ReasonIdentifier.REASON_KICK_SERVER, message, clientIds);
2772        }
2773
2774        /**
2775         * Kicks one or more clients from the server for the specified reason.
2776         *
2777         * @param message
2778         *              the reason message to display to the clients
2779         * @param clients
2780         *              the clients to kick
2781         *
2782         * @return whether the command succeeded or not
2783         *
2784         * @querycommands 1
2785         * @see #kickClientFromServer(Client...)
2786         * @see #kickClientFromServer(String, int...)
2787         */
2788        public CommandFuture<Boolean> kickClientFromServer(String message, Client... clients) {
2789                return kickClients(ReasonIdentifier.REASON_KICK_SERVER, message, clients);
2790        }
2791
2792        /**
2793         * Kicks a list of clients from either the channel or the server for a given reason.
2794         *
2795         * @param reason
2796         *              where to kick the clients from
2797         * @param message
2798         *              the reason message to display to the clients
2799         * @param clients
2800         *              the clients to kick
2801         *
2802         * @return whether the command succeeded or not
2803         *
2804         * @querycommands 1
2805         */
2806        private CommandFuture<Boolean> kickClients(ReasonIdentifier reason, String message, Client... clients) {
2807                int[] clientIds = new int[clients.length];
2808                for (int i = 0; i < clients.length; ++i) {
2809                        clientIds[i] = clients[i].getId();
2810                }
2811                return kickClients(reason, message, clientIds);
2812        }
2813
2814        /**
2815         * Kicks a list of clients from either the channel or the server for a given reason.
2816         *
2817         * @param reason
2818         *              where to kick the clients from
2819         * @param message
2820         *              the reason message to display to the clients
2821         * @param clientIds
2822         *              the IDs of the clients to kick
2823         *
2824         * @return whether the command succeeded or not
2825         *
2826         * @querycommands 1
2827         * @see Client#getId()
2828         */
2829        private CommandFuture<Boolean> kickClients(ReasonIdentifier reason, String message, int... clientIds) {
2830                final CClientKick kick = new CClientKick(reason, message, clientIds);
2831                return executeAndReturnError(kick);
2832        }
2833
2834        /**
2835         * Logs the server query in using the specified username and password.
2836         * <p>
2837         * Note that you can also set the login in the {@link TS3Config},
2838         * so that you will be logged in right after the connection is established.
2839         * </p>
2840         *
2841         * @param username
2842         *              the username of the server query
2843         * @param password
2844         *              the password to use
2845         *
2846         * @return whether the command succeeded or not
2847         *
2848         * @querycommands 1
2849         * @see #logout()
2850         */
2851        public CommandFuture<Boolean> login(String username, String password) {
2852                final CLogin login = new CLogin(username, password);
2853                return executeAndReturnError(login);
2854        }
2855
2856        /**
2857         * Logs the server query out and deselects the current virtual server.
2858         *
2859         * @return whether the command succeeded or not
2860         *
2861         * @querycommands 1
2862         * @see #login(String, String)
2863         */
2864        public CommandFuture<Boolean> logout() {
2865                final CLogout logout = new CLogout();
2866                return executeAndReturnError(logout);
2867        }
2868
2869        /**
2870         * Moves a channel to a new parent channel specified by its ID.
2871         * To move a channel to root level, set {@code channelTargetId} to {@code 0}.
2872         * <p>
2873         * This will move the channel right below the specified parent channel, above all other child channels.
2874         * This command will fail if the channel already has the specified target channel as the parent channel.
2875         * </p>
2876         *
2877         * @param channelId
2878         *              the channel to move
2879         * @param channelTargetId
2880         *              the new parent channel for the specified channel
2881         *
2882         * @return whether the command succeeded or not
2883         *
2884         * @querycommands 1
2885         * @see Channel#getId()
2886         * @see #moveChannel(int, int, int)
2887         */
2888        public CommandFuture<Boolean> moveChannel(int channelId, int channelTargetId) {
2889                return moveChannel(channelId, channelTargetId, 0);
2890        }
2891
2892        /**
2893         * Moves a channel to a new parent channel specified by its ID.
2894         * To move a channel to root level, set {@code channelTargetId} to {@code 0}.
2895         * <p>
2896         * The channel will be ordered below the channel with the ID specified by {@code order}.
2897         * To move the channel right below the parent channel, set {@code order} to {@code 0}.
2898         * Also note that a channel cannot be re-ordered without also changing its parent channel.
2899         * </p>
2900         *
2901         * @param channelId
2902         *              the channel to move
2903         * @param channelTargetId
2904         *              the new parent channel for the specified channel
2905         * @param order
2906         *              the channel to sort the specified channel below
2907         *
2908         * @return whether the command succeeded or not
2909         *
2910         * @querycommands 1
2911         * @see Channel#getId()
2912         * @see #moveChannel(int, int)
2913         */
2914        public CommandFuture<Boolean> moveChannel(int channelId, int channelTargetId, int order) {
2915                final CChannelMove move = new CChannelMove(channelId, channelTargetId, order);
2916                return executeAndReturnError(move);
2917        }
2918
2919        /**
2920         * Moves the server query into a channel.
2921         *
2922         * @param channelId
2923         *              the ID of the channel to move the server query into
2924         *
2925         * @return whether the command succeeded or not
2926         *
2927         * @querycommands 1
2928         * @see Channel#getId()
2929         */
2930        public CommandFuture<Boolean> moveQuery(int channelId) {
2931                return moveClient(0, channelId, null);
2932        }
2933
2934        /**
2935         * Moves the server query into a channel.
2936         *
2937         * @param channel
2938         *              the channel to move the server query into, cannot be {@code null}
2939         *
2940         * @return whether the command succeeded or not
2941         *
2942         * @throws IllegalArgumentException
2943         *              if {@code channel} is {@code null}
2944         * @querycommands 1
2945         */
2946        public CommandFuture<Boolean> moveQuery(ChannelBase channel) {
2947                if (channel == null) throw new IllegalArgumentException("channel was null");
2948
2949                return moveClient(0, channel.getId(), null);
2950        }
2951
2952        /**
2953         * Moves the server query into a channel using the specified password.
2954         *
2955         * @param channelId
2956         *              the ID of the channel to move the client into
2957         * @param channelPassword
2958         *              the password of the channel, can be {@code null}
2959         *
2960         * @return whether the command succeeded or not
2961         *
2962         * @querycommands 1
2963         * @see Channel#getId()
2964         */
2965        public CommandFuture<Boolean> moveQuery(int channelId, String channelPassword) {
2966                return moveClient(0, channelId, channelPassword);
2967        }
2968
2969        /**
2970         * Moves the server query into a channel using the specified password.
2971         *
2972         * @param channel
2973         *              the channel to move the client into, cannot be {@code null}
2974         * @param channelPassword
2975         *              the password of the channel, can be {@code null}
2976         *
2977         * @return whether the command succeeded or not
2978         *
2979         * @throws IllegalArgumentException
2980         *              if {@code channel} is {@code null}
2981         * @querycommands 1
2982         */
2983        public CommandFuture<Boolean> moveQuery(ChannelBase channel, String channelPassword) {
2984                if (channel == null) throw new IllegalArgumentException("channel was null");
2985
2986                return moveClient(0, channel.getId(), channelPassword);
2987        }
2988
2989        /**
2990         * Moves a client into a channel.
2991         * <p>
2992         * Consider using {@link #moveClients(int[], int)}
2993         * for moving multiple clients.
2994         * </p>
2995         *
2996         * @param clientId
2997         *              the ID of the client to move
2998         * @param channelId
2999         *              the ID of the channel to move the client into
3000         *
3001         * @return whether the command succeeded or not
3002         *
3003         * @querycommands 1
3004         * @see Client#getId()
3005         * @see Channel#getId()
3006         */
3007        public CommandFuture<Boolean> moveClient(int clientId, int channelId) {
3008                return moveClient(clientId, channelId, null);
3009        }
3010
3011        /**
3012         * Moves multiple clients into a channel.
3013         * Immediately returns {@code true} for an empty client ID array.
3014         * <p>
3015         * Use this method instead of {@link #moveClient(int, int)} for moving
3016         * several clients as this will only send 1 command to the server and thus complete faster.
3017         * </p>
3018         *
3019         * @param clientIds
3020         *              the IDs of the clients to move, cannot be {@code null}
3021         * @param channelId
3022         *              the ID of the channel to move the clients into
3023         *
3024         * @return whether the command succeeded or not
3025         *
3026         * @throws IllegalArgumentException
3027         *              if {@code clientIds} is {@code null}
3028         * @querycommands 1
3029         * @see Client#getId()
3030         * @see Channel#getId()
3031         */
3032        public CommandFuture<Boolean> moveClients(int[] clientIds, int channelId) {
3033                return moveClients(clientIds, channelId, null);
3034        }
3035
3036        /**
3037         * Moves a client into a channel.
3038         * <p>
3039         * Consider using {@link #moveClients(Client[], ChannelBase)}
3040         * for moving multiple clients.
3041         * </p>
3042         *
3043         * @param client
3044         *              the client to move, cannot be {@code null}
3045         * @param channel
3046         *              the channel to move the client into, cannot be {@code null}
3047         *
3048         * @return whether the command succeeded or not
3049         *
3050         * @throws IllegalArgumentException
3051         *              if {@code client} or {@code channel} is {@code null}
3052         * @querycommands 1
3053         */
3054        public CommandFuture<Boolean> moveClient(Client client, ChannelBase channel) {
3055                return moveClient(client, channel, null);
3056        }
3057
3058        /**
3059         * Moves multiple clients into a channel.
3060         * Immediately returns {@code true} for an empty client array.
3061         * <p>
3062         * Use this method instead of {@link #moveClient(Client, ChannelBase)} for moving
3063         * several clients as this will only send 1 command to the server and thus complete faster.
3064         * </p>
3065         *
3066         * @param clients
3067         *              the clients to move, cannot be {@code null}
3068         * @param channel
3069         *              the channel to move the clients into, cannot be {@code null}
3070         *
3071         * @return whether the command succeeded or not
3072         *
3073         * @throws IllegalArgumentException
3074         *              if {@code clients} or {@code channel} is {@code null}
3075         * @querycommands 1
3076         */
3077        public CommandFuture<Boolean> moveClients(Client[] clients, ChannelBase channel) {
3078                return moveClients(clients, channel, null);
3079        }
3080
3081        /**
3082         * Moves a client into a channel using the specified password.
3083         *
3084         * @param clientId
3085         *              the ID of the client to move
3086         * @param channelId
3087         *              the ID of the channel to move the client into
3088         * @param channelPassword
3089         *              the password of the channel, can be {@code null}
3090         *
3091         * @return whether the command succeeded or not
3092         *
3093         * @querycommands 1
3094         * @see Client#getId()
3095         * @see Channel#getId()
3096         */
3097        public CommandFuture<Boolean> moveClient(int clientId, int channelId, String channelPassword) {
3098                final CClientMove move = new CClientMove(clientId, channelId, channelPassword);
3099                return executeAndReturnError(move);
3100        }
3101
3102        /**
3103         * Moves multiple clients into a channel using the specified password.
3104         * Immediately returns {@code true} for an empty client ID array.
3105         * <p>
3106         * Use this method instead of {@link #moveClient(int, int, String)} for moving
3107         * several clients as this will only send 1 command to the server and thus complete faster.
3108         * </p>
3109         *
3110         * @param clientIds
3111         *              the IDs of the clients to move, cannot be {@code null}
3112         * @param channelId
3113         *              the ID of the channel to move the clients into
3114         * @param channelPassword
3115         *              the password of the channel, can be {@code null}
3116         *
3117         * @return whether the command succeeded or not
3118         *
3119         * @throws IllegalArgumentException
3120         *              if {@code clientIds} is {@code null}
3121         * @querycommands 1
3122         * @see Client#getId()
3123         * @see Channel#getId()
3124         */
3125        public CommandFuture<Boolean> moveClients(int[] clientIds, int channelId, String channelPassword) {
3126                if (clientIds == null) throw new IllegalArgumentException("clientIds was null");
3127                if (clientIds.length == 0) return CommandFuture.immediate(true);
3128
3129                final CClientMove move = new CClientMove(clientIds, channelId, channelPassword);
3130                return executeAndReturnError(move);
3131        }
3132
3133        /**
3134         * Moves a client into a channel using the specified password.
3135         * <p>
3136         * Consider using {@link #moveClients(Client[], ChannelBase, String)}
3137         * for moving multiple clients.
3138         * </p>
3139         *
3140         * @param client
3141         *              the client to move, cannot be {@code null}
3142         * @param channel
3143         *              the channel to move the client into, cannot be {@code null}
3144         * @param channelPassword
3145         *              the password of the channel, can be {@code null}
3146         *
3147         * @return whether the command succeeded or not
3148         *
3149         * @throws IllegalArgumentException
3150         *              if {@code client} or {@code channel} is {@code null}
3151         * @querycommands 1
3152         */
3153        public CommandFuture<Boolean> moveClient(Client client, ChannelBase channel, String channelPassword) {
3154                if (client == null) throw new IllegalArgumentException("client was null");
3155                if (channel == null) throw new IllegalArgumentException("channel was null");
3156
3157                return moveClient(client.getId(), channel.getId(), channelPassword);
3158        }
3159
3160        /**
3161         * Moves multiple clients into a channel using the specified password.
3162         * Immediately returns {@code true} for an empty client array.
3163         * <p>
3164         * Use this method instead of {@link #moveClient(Client, ChannelBase, String)} for moving
3165         * several clients as this will only send 1 command to the server and thus complete faster.
3166         * </p>
3167         *
3168         * @param clients
3169         *              the clients to move, cannot be {@code null}
3170         * @param channel
3171         *              the channel to move the clients into, cannot be {@code null}
3172         * @param channelPassword
3173         *              the password of the channel, can be {@code null}
3174         *
3175         * @return whether the command succeeded or not
3176         *
3177         * @throws IllegalArgumentException
3178         *              if {@code clients} or {@code channel} is {@code null}
3179         * @querycommands 1
3180         */
3181        public CommandFuture<Boolean> moveClients(Client[] clients, ChannelBase channel, String channelPassword) {
3182                if (clients == null) throw new IllegalArgumentException("clients was null");
3183                if (channel == null) throw new IllegalArgumentException("channel was null");
3184
3185                int[] clientIds = new int[clients.length];
3186                for (int i = 0; i < clients.length; i++) {
3187                        Client client = clients[i];
3188                        clientIds[i] = client.getId();
3189                }
3190                return moveClients(clientIds, channel.getId(), channelPassword);
3191        }
3192
3193        /**
3194         * Pokes the client with the specified client ID.
3195         * This opens up a small popup window for the client containing your message and plays a sound.
3196         * The displayed message will be formatted like this: <br>
3197         * {@code hh:mm:ss - "Your Nickname" poked you: <your message in green color>}
3198         * <p>
3199         * The displayed message length is limited to 100 UTF-8 bytes.
3200         * If a client has already received a poke message, all subsequent pokes will simply add a line
3201         * to the already opened popup window and will still play a sound.
3202         * </p>
3203         *
3204         * @param clientId
3205         *              the ID of the client to poke
3206         * @param message
3207         *              the message to send, may contain BB codes
3208         *
3209         * @return whether the command succeeded or not
3210         *
3211         * @querycommands 1
3212         * @see Client#getId()
3213         */
3214        public CommandFuture<Boolean> pokeClient(int clientId, String message) {
3215                final CClientPoke poke = new CClientPoke(clientId, message);
3216                return executeAndReturnError(poke);
3217        }
3218
3219        /**
3220         * Terminates the connection with the TeamSpeak3 server.
3221         * This command should never be executed manually.
3222         *
3223         * @return whether the command succeeded or not
3224         *
3225         * @querycommands 1
3226         * @deprecated This command leaves the query in an undefined state,
3227         * where the connection is closed without the socket being closed and no more command can be executed.
3228         * To terminate a connection, use {@link TS3Query#exit()}.
3229         */
3230        @Deprecated
3231        public CommandFuture<Boolean> quit() {
3232                final CQuit quit = new CQuit();
3233                return executeAndReturnError(quit);
3234        }
3235
3236        /**
3237         * Registers the server query to receive notifications about all server events.
3238         * <p>
3239         * This means that the following actions will trigger event notifications:
3240         * </p>
3241         * <ul>
3242         * <li>A client joins the server or disconnects from it</li>
3243         * <li>A client switches channels</li>
3244         * <li>A client sends a server message</li>
3245         * <li>A client sends a channel message <b>in the channel the query is in</b></li>
3246         * <li>A client sends <b>the server query</b> a private message or a response to a private message</li>
3247         * </ul>
3248         * <p>
3249         * The limitations to when the query receives notifications about chat events cannot be circumvented.
3250         * </p>
3251         * To be able to process these events in your application, register an event listener.
3252         *
3253         * @return whether all commands succeeded or not
3254         *
3255         * @querycommands 6
3256         * @see #addTS3Listeners(TS3Listener...)
3257         */
3258        public CommandFuture<Boolean> registerAllEvents() {
3259                final CommandFuture<Boolean> future = new CommandFuture<>();
3260                final Collection<CommandFuture<Boolean>> eventFutures = new ArrayList<>(5);
3261
3262                eventFutures.add(registerEvent(TS3EventType.SERVER));
3263                eventFutures.add(registerEvent(TS3EventType.TEXT_SERVER));
3264                eventFutures.add(registerEvent(TS3EventType.CHANNEL, 0));
3265                eventFutures.add(registerEvent(TS3EventType.TEXT_CHANNEL, 0));
3266                eventFutures.add(registerEvent(TS3EventType.TEXT_PRIVATE));
3267                eventFutures.add(registerEvent(TS3EventType.PRIVILEGE_KEY_USED));
3268
3269                CommandFuture.ofAll(eventFutures).onSuccess(new CommandFuture.SuccessListener<List<Boolean>>() {
3270                        @Override
3271                        public void handleSuccess(List<Boolean> result) {
3272                                future.set(true);
3273                        }
3274                }).forwardFailure(future);
3275                return future;
3276        }
3277
3278        /**
3279         * Registers the server query to receive notifications about a given event type.
3280         * <p>
3281         * If used with {@link TS3EventType#TEXT_CHANNEL}, this will listen to chat events in the current channel.
3282         * If used with {@link TS3EventType#CHANNEL}, this will listen to <b>all</b> channel events.
3283         * To specify a different channel for channel events, use {@link #registerEvent(TS3EventType, int)}.
3284         * </p>
3285         *
3286         * @param eventType
3287         *              the event type to be notified about
3288         *
3289         * @return whether the command succeeded or not
3290         *
3291         * @querycommands 1
3292         * @see #addTS3Listeners(TS3Listener...)
3293         * @see #registerEvent(TS3EventType, int)
3294         * @see #registerAllEvents()
3295         */
3296        public CommandFuture<Boolean> registerEvent(TS3EventType eventType) {
3297                if (eventType == TS3EventType.CHANNEL || eventType == TS3EventType.TEXT_CHANNEL) {
3298                        return registerEvent(eventType, 0);
3299                }
3300                return registerEvent(eventType, -1);
3301        }
3302
3303        /**
3304         * Registers the server query to receive notifications about a given event type.
3305         *
3306         * @param eventType
3307         *              the event type to be notified about
3308         * @param channelId
3309         *              the ID of the channel to listen to, will be ignored if set to {@code -1}.
3310         *              Can be set to {@code 0} for {@link TS3EventType#CHANNEL} to receive notifications about all channel switches.
3311         *
3312         * @return whether the command succeeded or not
3313         *
3314         * @querycommands 1
3315         * @see Channel#getId()
3316         * @see #addTS3Listeners(TS3Listener...)
3317         * @see #registerAllEvents()
3318         */
3319        public CommandFuture<Boolean> registerEvent(TS3EventType eventType, int channelId) {
3320                final CServerNotifyRegister register = new CServerNotifyRegister(eventType, channelId);
3321                return executeAndReturnError(register);
3322        }
3323
3324        /**
3325         * Registers the server query to receive notifications about multiple given event types.
3326         * <p>
3327         * If used with {@link TS3EventType#TEXT_CHANNEL}, this will listen to chat events in the current channel.
3328         * If used with {@link TS3EventType#CHANNEL}, this will listen to <b>all</b> channel events.
3329         * To specify a different channel for channel events, use {@link #registerEvent(TS3EventType, int)}.
3330         * </p>
3331         *
3332         * @param eventTypes
3333         *              the event types to be notified about
3334         *
3335         * @return whether the command succeeded or not
3336         *
3337         * @querycommands n, one command per TS3EventType
3338         * @see #addTS3Listeners(TS3Listener...)
3339         * @see #registerEvent(TS3EventType, int)
3340         * @see #registerAllEvents()
3341         */
3342        public CommandFuture<Boolean> registerEvents(TS3EventType... eventTypes) {
3343                if (eventTypes.length == 0) return CommandFuture.immediate(true);
3344
3345                final Collection<CommandFuture<Boolean>> registerFutures = new ArrayList<>(eventTypes.length);
3346                for (final TS3EventType type : eventTypes) {
3347                        registerFutures.add(registerEvent(type));
3348                }
3349
3350                final CommandFuture<Boolean> future = new CommandFuture<>();
3351                CommandFuture.ofAll(registerFutures).onSuccess(new CommandFuture.SuccessListener<List<Boolean>>() {
3352                        @Override
3353                        public void handleSuccess(List<Boolean> result) {
3354                                future.set(true);
3355                        }
3356                }).forwardFailure(future);
3357                return future;
3358        }
3359
3360        /**
3361         * Removes the client specified by its database ID from the specified server group.
3362         *
3363         * @param serverGroupId
3364         *              the ID of the server group
3365         * @param clientDatabaseId
3366         *              the database ID of the client
3367         *
3368         * @return whether the command succeeded or not
3369         *
3370         * @querycommands 1
3371         * @see ServerGroup#getId()
3372         * @see Client#getDatabaseId()
3373         * @see #removeClientFromServerGroup(ServerGroup, Client)
3374         */
3375        public CommandFuture<Boolean> removeClientFromServerGroup(int serverGroupId, int clientDatabaseId) {
3376                final CServerGroupDelClient del = new CServerGroupDelClient(serverGroupId, clientDatabaseId);
3377                return executeAndReturnError(del);
3378        }
3379
3380        /**
3381         * Removes the specified client from the specified server group.
3382         *
3383         * @param serverGroup
3384         *              the server group to remove the client from
3385         * @param client
3386         *              the client to remove from the server group
3387         *
3388         * @return whether the command succeeded or not
3389         *
3390         * @querycommands 1
3391         * @see #removeClientFromServerGroup(int, int)
3392         */
3393        public CommandFuture<Boolean> removeClientFromServerGroup(ServerGroup serverGroup, Client client) {
3394                return removeClientFromServerGroup(serverGroup.getId(), client.getDatabaseId());
3395        }
3396
3397        /**
3398         * Removes one or more {@link TS3Listener}s to the event manager of the query.
3399         * <p>
3400         * If a listener was not actually registered, it will be ignored and no exception will be thrown.
3401         * </p>
3402         *
3403         * @param listeners
3404         *              one or more listeners to remove
3405         *
3406         * @see #addTS3Listeners(TS3Listener...)
3407         * @see TS3Listener
3408         * @see TS3EventType
3409         */
3410        public void removeTS3Listeners(TS3Listener... listeners) {
3411                query.getEventManager().removeListeners(listeners);
3412        }
3413
3414        /**
3415         * Renames the channel group with the specified ID.
3416         *
3417         * @param channelGroupId
3418         *              the ID of the channel group to rename
3419         * @param name
3420         *              the new name for the channel group
3421         *
3422         * @return whether the command succeeded or not
3423         *
3424         * @querycommands 1
3425         * @see ChannelGroup#getId()
3426         * @see #renameChannelGroup(ChannelGroup, String)
3427         */
3428        public CommandFuture<Boolean> renameChannelGroup(int channelGroupId, String name) {
3429                final CChannelGroupRename rename = new CChannelGroupRename(channelGroupId, name);
3430                return executeAndReturnError(rename);
3431        }
3432
3433        /**
3434         * Renames the specified channel group.
3435         *
3436         * @param channelGroup
3437         *              the channel group to rename
3438         * @param name
3439         *              the new name for the channel group
3440         *
3441         * @return whether the command succeeded or not
3442         *
3443         * @querycommands 1
3444         * @see #renameChannelGroup(int, String)
3445         */
3446        public CommandFuture<Boolean> renameChannelGroup(ChannelGroup channelGroup, String name) {
3447                return renameChannelGroup(channelGroup.getId(), name);
3448        }
3449
3450        /**
3451         * Renames the server group with the specified ID.
3452         *
3453         * @param serverGroupId
3454         *              the ID of the server group to rename
3455         * @param name
3456         *              the new name for the server group
3457         *
3458         * @return whether the command succeeded or not
3459         *
3460         * @querycommands 1
3461         * @see ServerGroup#getId()
3462         * @see #renameServerGroup(ServerGroup, String)
3463         */
3464        public CommandFuture<Boolean> renameServerGroup(int serverGroupId, String name) {
3465                final CServerGroupRename rename = new CServerGroupRename(serverGroupId, name);
3466                return executeAndReturnError(rename);
3467        }
3468
3469        /**
3470         * Renames the specified server group.
3471         *
3472         * @param serverGroup
3473         *              the server group to rename
3474         * @param name
3475         *              the new name for the server group
3476         *
3477         * @return whether the command succeeded or not
3478         *
3479         * @querycommands 1
3480         * @see #renameServerGroup(int, String)
3481         */
3482        public CommandFuture<Boolean> renameServerGroup(ServerGroup serverGroup, String name) {
3483                return renameChannelGroup(serverGroup.getId(), name);
3484        }
3485
3486        /**
3487         * Resets all permissions and deletes all server / channel groups. Use carefully.
3488         *
3489         * @return a token for a new administrator account
3490         *
3491         * @querycommands 1
3492         */
3493        public CommandFuture<String> resetPermissions() {
3494                final CPermReset reset = new CPermReset();
3495                return executeAndReturnStringProperty(reset, "token");
3496        }
3497
3498        /**
3499         * Moves the server query into the virtual server with the specified ID.
3500         *
3501         * @param id
3502         *              the ID of the virtual server
3503         *
3504         * @return whether the command succeeded or not
3505         *
3506         * @querycommands 1
3507         * @see VirtualServer#getId()
3508         * @see #selectVirtualServerByPort(int)
3509         * @see #selectVirtualServer(VirtualServer)
3510         */
3511        public CommandFuture<Boolean> selectVirtualServerById(int id) {
3512                final CUse use = new CUse(id, -1);
3513                return executeAndReturnError(use);
3514        }
3515
3516        /**
3517         * Moves the server query into the virtual server with the specified voice port.
3518         *
3519         * @param port
3520         *              the voice port of the virtual server
3521         *
3522         * @return whether the command succeeded or not
3523         *
3524         * @querycommands 1
3525         * @see VirtualServer#getPort()
3526         * @see #selectVirtualServerById(int)
3527         * @see #selectVirtualServer(VirtualServer)
3528         */
3529        public CommandFuture<Boolean> selectVirtualServerByPort(int port) {
3530                final CUse use = new CUse(-1, port);
3531                return executeAndReturnError(use);
3532        }
3533
3534        /**
3535         * Moves the server query into the specified virtual server.
3536         *
3537         * @param server
3538         *              the virtual server to move into
3539         *
3540         * @return whether the command succeeded or not
3541         *
3542         * @querycommands 1
3543         * @see #selectVirtualServerById(int)
3544         * @see #selectVirtualServerByPort(int)
3545         */
3546        public CommandFuture<Boolean> selectVirtualServer(VirtualServer server) {
3547                return selectVirtualServerById(server.getId());
3548        }
3549
3550        /**
3551         * Sends an offline message to the client with the given unique identifier.
3552         * <p>
3553         * The message subject's length is limited to 200 UTF-8 bytes and BB codes in it will be ignored.
3554         * The message body's length is limited to 4096 UTF-8 bytes and accepts BB codes
3555         * </p>
3556         *
3557         * @param clientUId
3558         *              the unique identifier of the client to send the message to
3559         * @param subject
3560         *              the subject for the message, may not contain BB codes
3561         * @param message
3562         *              the actual message body, may contain BB codes
3563         *
3564         * @return whether the command succeeded or not
3565         *
3566         * @querycommands 1
3567         * @see Client#getUniqueIdentifier()
3568         * @see Message
3569         */
3570        public CommandFuture<Boolean> sendOfflineMessage(String clientUId, String subject, String message) {
3571                final CMessageAdd add = new CMessageAdd(clientUId, subject, message);
3572                return executeAndReturnError(add);
3573        }
3574
3575        /**
3576         * Sends a text message either to the whole virtual server, a channel or specific client.
3577         * Your message may contain BB codes, but its length is limited to 1024 UTF-8 bytes.
3578         * <p>
3579         * To send a message to all virtual servers, use {@link #broadcast(String)}.
3580         * To send an offline message, use {@link #sendOfflineMessage(String, String, String)}.
3581         * </p>
3582         *
3583         * @param targetMode
3584         *              where the message should be sent to
3585         * @param targetId
3586         *              the client ID of the recipient of this message. This value is ignored unless {@code targetMode} is {@code CLIENT}
3587         * @param message
3588         *              the text message to send
3589         *
3590         * @return whether the command succeeded or not
3591         *
3592         * @querycommands 1
3593         * @see Client#getId()
3594         */
3595        public CommandFuture<Boolean> sendTextMessage(TextMessageTargetMode targetMode, int targetId, String message) {
3596                final CSendTextMessage msg = new CSendTextMessage(targetMode.getIndex(), targetId, message);
3597                return executeAndReturnError(msg);
3598        }
3599
3600        /**
3601         * Sends a text message to the channel with the specified ID.
3602         * Your message may contain BB codes, but its length is limited to 1024 UTF-8 bytes.
3603         * <p>
3604         * This will move the client into the channel with the specified channel ID,
3605         * <b>but will not move it back to the original channel!</b>
3606         * </p>
3607         *
3608         * @param channelId
3609         *              the ID of the channel to which the message should be sent to
3610         * @param message
3611         *              the text message to send
3612         *
3613         * @return whether the command succeeded or not
3614         *
3615         * @querycommands 1
3616         * @see #sendChannelMessage(String)
3617         * @see Channel#getId()
3618         */
3619        public CommandFuture<Boolean> sendChannelMessage(int channelId, final String message) {
3620                final CommandFuture<Boolean> future = new CommandFuture<>();
3621
3622                moveQuery(channelId).onSuccess(new CommandFuture.SuccessListener<Boolean>() {
3623                        @Override
3624                        public void handleSuccess(Boolean result) {
3625                                sendTextMessage(TextMessageTargetMode.CHANNEL, 0, message).forwardResult(future);
3626                        }
3627                }).forwardFailure(future);
3628
3629                return future;
3630        }
3631
3632        /**
3633         * Sends a text message to the channel the server query is currently in.
3634         * Your message may contain BB codes, but its length is limited to 1024 UTF-8 bytes.
3635         *
3636         * @param message
3637         *              the text message to send
3638         *
3639         * @return whether the command succeeded or not
3640         *
3641         * @querycommands 1
3642         */
3643        public CommandFuture<Boolean> sendChannelMessage(String message) {
3644                return sendTextMessage(TextMessageTargetMode.CHANNEL, 0, message);
3645        }
3646
3647        /**
3648         * Sends a text message to the virtual server with the specified ID.
3649         * Your message may contain BB codes, but its length is limited to 1024 UTF-8 bytes.
3650         * <p>
3651         * This will move the client to the virtual server with the specified server ID,
3652         * <b>but will not move it back to the original virtual server!</b>
3653         * </p>
3654         *
3655         * @param serverId
3656         *              the ID of the virtual server to which the message should be sent to
3657         * @param message
3658         *              the text message to send
3659         *
3660         * @return whether the command succeeded or not
3661         *
3662         * @querycommands 1
3663         * @see #sendServerMessage(String)
3664         * @see VirtualServer#getId()
3665         */
3666        public CommandFuture<Boolean> sendServerMessage(int serverId, final String message) {
3667                final CommandFuture<Boolean> future = new CommandFuture<>();
3668
3669                selectVirtualServerById(serverId).onSuccess(new CommandFuture.SuccessListener<Boolean>() {
3670                        @Override
3671                        public void handleSuccess(Boolean result) {
3672                                sendTextMessage(TextMessageTargetMode.SERVER, 0, message).forwardResult(future);
3673                        }
3674                }).forwardFailure(future);
3675
3676                return future;
3677        }
3678
3679        /**
3680         * Sends a text message to the virtual server the server query is currently in.
3681         * Your message may contain BB codes, but its length is limited to 1024 UTF-8 bytes.
3682         *
3683         * @param message
3684         *              the text message to send
3685         *
3686         * @return whether the command succeeded or not
3687         *
3688         * @querycommands 1
3689         */
3690        public CommandFuture<Boolean> sendServerMessage(String message) {
3691                return sendTextMessage(TextMessageTargetMode.SERVER, 0, message);
3692        }
3693
3694        /**
3695         * Sends a private message to the client with the specified client ID.
3696         * Your message may contain BB codes, but its length is limited to 1024 UTF-8 bytes.
3697         *
3698         * @param clientId
3699         *              the ID of the client to send the message to
3700         * @param message
3701         *              the text message to send
3702         *
3703         * @return whether the command succeeded or not
3704         *
3705         * @querycommands 1
3706         * @see Client#getId()
3707         */
3708        public CommandFuture<Boolean> sendPrivateMessage(int clientId, String message) {
3709                return sendTextMessage(TextMessageTargetMode.CLIENT, clientId, message);
3710        }
3711
3712        /**
3713         * Sets a channel group for a client in a specific channel.
3714         *
3715         * @param groupId
3716         *              the ID of the group the client should join
3717         * @param channelId
3718         *              the ID of the channel where the channel group should be assigned
3719         * @param clientDBId
3720         *              the database ID of the client for which the channel group should be set
3721         *
3722         * @return whether the command succeeded or not
3723         *
3724         * @querycommands 1
3725         * @see ChannelGroup#getId()
3726         * @see Channel#getId()
3727         * @see Client#getDatabaseId()
3728         */
3729        public CommandFuture<Boolean> setClientChannelGroup(int groupId, int channelId, int clientDBId) {
3730                final CSetClientChannelGroup group = new CSetClientChannelGroup(groupId, channelId, clientDBId);
3731                return executeAndReturnError(group);
3732        }
3733
3734        /**
3735         * Sets the read flag to true for a given message. This will not delete the message.
3736         *
3737         * @param messageId
3738         *              the ID of the message for which the read flag should be set
3739         *
3740         * @return whether the command succeeded or not
3741         *
3742         * @querycommands 1
3743         * @see #setMessageReadFlag(int, boolean)
3744         */
3745        public CommandFuture<Boolean> setMessageRead(int messageId) {
3746                return setMessageReadFlag(messageId, true);
3747        }
3748
3749        /**
3750         * Sets the read flag to true for a given message. This will not delete the message.
3751         *
3752         * @param message
3753         *              the message for which the read flag should be set
3754         *
3755         * @return whether the command succeeded or not
3756         *
3757         * @querycommands 1
3758         * @see #setMessageRead(int)
3759         * @see #setMessageReadFlag(Message, boolean)
3760         * @see #deleteOfflineMessage(int)
3761         */
3762        public CommandFuture<Boolean> setMessageRead(Message message) {
3763                return setMessageReadFlag(message.getId(), true);
3764        }
3765
3766        /**
3767         * Sets the read flag for a given message. This will not delete the message.
3768         *
3769         * @param messageId
3770         *              the ID of the message for which the read flag should be set
3771         * @param read
3772         *              the boolean value to which the read flag should be set
3773         *
3774         * @return whether the command succeeded or not
3775         *
3776         * @querycommands 1
3777         * @see #setMessageRead(int)
3778         * @see #setMessageReadFlag(Message, boolean)
3779         * @see #deleteOfflineMessage(int)
3780         */
3781        public CommandFuture<Boolean> setMessageReadFlag(int messageId, boolean read) {
3782                final CMessageUpdateFlag flag = new CMessageUpdateFlag(messageId, read);
3783                return executeAndReturnError(flag);
3784        }
3785
3786        /**
3787         * Sets the read flag for a given message. This will not delete the message.
3788         *
3789         * @param message
3790         *              the message for which the read flag should be set
3791         * @param read
3792         *              the boolean value to which the read flag should be set
3793         *
3794         * @return whether the command succeeded or not
3795         *
3796         * @querycommands 1
3797         * @see #setMessageRead(Message)
3798         * @see #setMessageReadFlag(int, boolean)
3799         * @see #deleteOfflineMessage(int)
3800         */
3801        public CommandFuture<Boolean> setMessageReadFlag(Message message, boolean read) {
3802                return setMessageReadFlag(message.getId(), read);
3803        }
3804
3805        /**
3806         * Sets the nickname of the server query client.
3807         * The nickname must be between 3 and 30 UTF-8 bytes long and BB codes will be ignored.
3808         *
3809         * @param nickname
3810         *              the new nickname, may not contain any BB codes and may not be {@code null}
3811         *
3812         * @return whether the command succeeded or not
3813         *
3814         * @querycommands 1
3815         * @see #updateClient(Map)
3816         */
3817        public CommandFuture<Boolean> setNickname(String nickname) {
3818                final Map<ClientProperty, String> options = Collections.singletonMap(ClientProperty.CLIENT_NICKNAME, nickname);
3819                return updateClient(options);
3820        }
3821
3822        /**
3823         * Starts the virtual server with the specified ID.
3824         *
3825         * @param serverId
3826         *              the ID of the virtual server
3827         *
3828         * @return whether the command succeeded or not
3829         *
3830         * @querycommands 1
3831         */
3832        public CommandFuture<Boolean> startServer(int serverId) {
3833                final CServerStart start = new CServerStart(serverId);
3834                return executeAndReturnError(start);
3835        }
3836
3837        /**
3838         * Starts the specified virtual server.
3839         *
3840         * @param virtualServer
3841         *              the virtual server to start
3842         *
3843         * @return whether the command succeeded or not
3844         *
3845         * @querycommands 1
3846         */
3847        public CommandFuture<Boolean> startServer(VirtualServer virtualServer) {
3848                return startServer(virtualServer.getId());
3849        }
3850
3851        /**
3852         * Stops the virtual server with the specified ID.
3853         *
3854         * @param serverId
3855         *              the ID of the virtual server
3856         *
3857         * @return whether the command succeeded or not
3858         *
3859         * @querycommands 1
3860         */
3861        public CommandFuture<Boolean> stopServer(int serverId) {
3862                final CServerStop stop = new CServerStop(serverId);
3863                return executeAndReturnError(stop);
3864        }
3865
3866        /**
3867         * Stops the specified virtual server.
3868         *
3869         * @param virtualServer
3870         *              the virtual server to stop
3871         *
3872         * @return whether the command succeeded or not
3873         *
3874         * @querycommands 1
3875         */
3876        public CommandFuture<Boolean> stopServer(VirtualServer virtualServer) {
3877                return stopServer(virtualServer.getId());
3878        }
3879
3880        /**
3881         * Stops the entire TeamSpeak 3 Server instance by shutting down the process.
3882         * <p>
3883         * To have permission to use this command, you need to use the server query admin login.
3884         * </p>
3885         *
3886         * @return whether the command succeeded or not
3887         *
3888         * @querycommands 1
3889         */
3890        public CommandFuture<Boolean> stopServerProcess() {
3891                final CServerProcessStop stop = new CServerProcessStop();
3892                return executeAndReturnError(stop);
3893        }
3894
3895        /**
3896         * Unregisters the server query from receiving any event notifications.
3897         *
3898         * @return whether the command succeeded or not
3899         *
3900         * @querycommands 1
3901         */
3902        public CommandFuture<Boolean> unregisterAllEvents() {
3903                final CServerNotifyUnregister unr = new CServerNotifyUnregister();
3904                return executeAndReturnError(unr);
3905        }
3906
3907        /**
3908         * Updates several client properties for this server query instance.
3909         *
3910         * @param options
3911         *              the map of properties to update
3912         *
3913         * @return whether the command succeeded or not
3914         *
3915         * @querycommands 1
3916         * @see #editClient(int, Map)
3917         */
3918        public CommandFuture<Boolean> updateClient(Map<ClientProperty, String> options) {
3919                final CClientUpdate update = new CClientUpdate(options);
3920                return executeAndReturnError(update);
3921        }
3922
3923        /**
3924         * Generates new login credentials for the currently connected server query instance, using the given name.
3925         * <p>
3926         * <b>This will remove the current login credentials!</b> You won't be logged out, but after disconnecting,
3927         * the old credentials will no longer work. Make sure to not lock yourselves out!
3928         * </p>
3929         *
3930         * @param loginName
3931         *              the name for the server query login
3932         *
3933         * @return the generated password for the server query login
3934         *
3935         * @querycommands 1
3936         */
3937        public CommandFuture<String> updateServerQueryLogin(String loginName) {
3938                final CClientSetServerQueryLogin login = new CClientSetServerQueryLogin(loginName);
3939                return executeAndReturnStringProperty(login, "client_login_password");
3940        }
3941
3942        /**
3943         * Uses an existing privilege key to join a server or channel group.
3944         *
3945         * @param token
3946         *              the privilege key to use
3947         *
3948         * @return whether the command succeeded or not
3949         *
3950         * @querycommands 1
3951         * @see PrivilegeKey
3952         * @see #addPrivilegeKey(PrivilegeKeyType, int, int, String)
3953         * @see #usePrivilegeKey(PrivilegeKey)
3954         */
3955        public CommandFuture<Boolean> usePrivilegeKey(String token) {
3956                final CPrivilegeKeyUse use = new CPrivilegeKeyUse(token);
3957                return executeAndReturnError(use);
3958        }
3959
3960        /**
3961         * Uses an existing privilege key to join a server or channel group.
3962         *
3963         * @param privilegeKey
3964         *              the privilege key to use
3965         *
3966         * @return whether the command succeeded or not
3967         *
3968         * @querycommands 1
3969         * @see PrivilegeKey
3970         * @see #addPrivilegeKey(PrivilegeKeyType, int, int, String)
3971         * @see #usePrivilegeKey(String)
3972         */
3973        public CommandFuture<Boolean> usePrivilegeKey(PrivilegeKey privilegeKey) {
3974                return usePrivilegeKey(privilegeKey.getToken());
3975        }
3976
3977        /**
3978         * Gets information about the current server query instance.
3979         *
3980         * @return information about the server query instance
3981         *
3982         * @querycommands 1
3983         * @see #getClientInfo(int)
3984         */
3985        public CommandFuture<ServerQueryInfo> whoAmI() {
3986                final CWhoAmI whoAmI = new CWhoAmI();
3987                final CommandFuture<ServerQueryInfo> future = new CommandFuture<>();
3988
3989                query.doCommandAsync(whoAmI, new Callback() {
3990                        @Override
3991                        public void handle() {
3992                                if (hasFailed(whoAmI, future)) return;
3993                                future.set(new ServerQueryInfo(whoAmI.getFirstResponse().getMap()));
3994                        }
3995                });
3996                return future;
3997        }
3998
3999        /**
4000         * Executes a command, checking for failure and returning true if the command succeeded.
4001         *
4002         * @param command
4003         *              the command to execute
4004         *
4005         * @return whether the command succeeded or not
4006         */
4007        private CommandFuture<Boolean> executeAndReturnError(final Command command) {
4008                final CommandFuture<Boolean> future = new CommandFuture<>();
4009
4010                query.doCommandAsync(command, new Callback() {
4011                        @Override
4012                        public void handle() {
4013                                if (hasFailed(command, future)) return;
4014                                future.set(true);
4015                        }
4016                });
4017                return future;
4018        }
4019
4020        /**
4021         * Executes a command, checking for failure and returning a single
4022         * {@code String} property from the first response map.
4023         *
4024         * @param command
4025         *              the command to execute
4026         * @param property
4027         *              the name of the property to return
4028         *
4029         * @return the value of the specified {@code String} property
4030         */
4031        private CommandFuture<String> executeAndReturnStringProperty(final Command command, final String property) {
4032                final CommandFuture<String> future = new CommandFuture<>();
4033
4034                query.doCommandAsync(command, new Callback() {
4035                        @Override
4036                        public void handle() {
4037                                if (hasFailed(command, future)) return;
4038                                future.set(command.getFirstResponse().get(property));
4039                        }
4040                });
4041                return future;
4042        }
4043
4044        /**
4045         * Executes a command, checking for failure and returning a single
4046         * {@code Integer} property from the first response map.
4047         *
4048         * @param command
4049         *              the command to execute
4050         * @param property
4051         *              the name of the property to return
4052         *
4053         * @return the value of the specified {@code Integer} property
4054         */
4055        private CommandFuture<Integer> executeAndReturnIntProperty(final Command command, final String property) {
4056                final CommandFuture<Integer> future = new CommandFuture<>();
4057
4058                query.doCommandAsync(command, new Callback() {
4059                        @Override
4060                        public void handle() {
4061                                if (hasFailed(command, future)) return;
4062                                future.set(command.getFirstResponse().getInt(property));
4063                        }
4064                });
4065                return future;
4066        }
4067
4068        /**
4069         * If a command has failed (i.e. the error ID is not 0),
4070         * the future will be marked as failed and true will be returned.
4071         *
4072         * @param command
4073         *              the command to be checked for failure
4074         * @param future
4075         *              the future to be notified in case of a failure
4076         *
4077         * @return true if the command has failed
4078         *
4079         * @see Command
4080         */
4081        private boolean hasFailed(Command command, CommandFuture<?> future) {
4082                final QueryError error = command.getError();
4083                if (error.isSuccessful()) return false;
4084
4085                future.fail(error);
4086                return true;
4087        }
4088}