Brought in my code and set up some unit tests to start out
This commit is contained in:
@@ -0,0 +1,300 @@
|
||||
package com.github.manevolent.ts3j;
|
||||
|
||||
import com.github.manevolent.ts3j.command.Command;
|
||||
import com.github.manevolent.ts3j.command.part.CommandSingleParameter;
|
||||
import com.github.manevolent.ts3j.command.response.CommandResponse;
|
||||
import com.github.manevolent.ts3j.enums.ConnectionState;
|
||||
import com.github.manevolent.ts3j.enums.PacketFlags;
|
||||
import com.github.manevolent.ts3j.enums.PacketType;
|
||||
import com.github.manevolent.ts3j.handler.TeamspeakClientHandler;
|
||||
import com.github.manevolent.ts3j.util.Ts3Logging;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.DatagramPacket;
|
||||
import java.net.DatagramSocket;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketException;
|
||||
import java.util.Date;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
public class TeamspeakClient {
|
||||
private final PacketReceiver packetReceiver = new PacketReceiver();
|
||||
|
||||
private final DatagramSocket datagramSocket;
|
||||
private final DatagramPacket datagramPacket = new DatagramPacket(new byte[1500], 1500);
|
||||
|
||||
private final Object connectionStateLock = new Object();
|
||||
|
||||
private TeamspeakClientHandler handler;
|
||||
|
||||
private InetSocketAddress remote = null;
|
||||
private ConnectionState connectionState = null;
|
||||
private int returnCodeIndex = 0;
|
||||
|
||||
public TeamspeakClient(DatagramSocket datagramSocket) {
|
||||
this.datagramSocket = datagramSocket;
|
||||
}
|
||||
|
||||
public TeamspeakClient() throws SocketException {
|
||||
this(new DatagramSocket());
|
||||
|
||||
setConnectionState(ConnectionState.DISCONNECTED);
|
||||
}
|
||||
|
||||
protected void setConnectionState(ConnectionState state) {
|
||||
synchronized (connectionStateLock) {
|
||||
if (state != this.connectionState) {
|
||||
boolean wasDisconnected = this.connectionState == ConnectionState.DISCONNECTED;
|
||||
|
||||
this.connectionState = state;
|
||||
|
||||
if (wasDisconnected) {
|
||||
Ts3Logging.debug("Starting network receiver thread...");
|
||||
|
||||
Thread receiverThread = new Thread(new PacketReceiver());
|
||||
receiverThread.setDaemon(true);
|
||||
receiverThread.start();
|
||||
}
|
||||
|
||||
if (handler != null)
|
||||
handler.handleConnectionStateChanging(state);
|
||||
|
||||
if (handler == null || handler.getClass() != state.getHandlerClass()) {
|
||||
Ts3Logging.debug("Assigning " + state.getHandlerClass().getName() + " handler...");
|
||||
|
||||
handler = state.createHandler(this);
|
||||
|
||||
try {
|
||||
handler.onAssigned();
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
connectionStateLock.notifyAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isConnected() {
|
||||
return connectionState == ConnectionState.CONNECTED;
|
||||
}
|
||||
|
||||
public ConnectionState getConnectionState() {
|
||||
return connectionState;
|
||||
}
|
||||
|
||||
private void joinConnectionState(ConnectionState state, long wait)
|
||||
throws InterruptedException, TimeoutException {
|
||||
synchronized (connectionStateLock) {
|
||||
if (connectionState != state) {
|
||||
connectionStateLock.wait(wait);
|
||||
|
||||
if (connectionState != state)
|
||||
throw new TimeoutException("timeout waiting for " + state.name() + " state");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public CommandResponse sendCommand(Command command) throws IOException {
|
||||
if (command.willExpectResponse() && !isConnected())
|
||||
throw new IOException("not connected");
|
||||
|
||||
CommandResponse response;
|
||||
|
||||
if (command.willExpectResponse()) {
|
||||
int returnCodeIndex = ++ this.returnCodeIndex;
|
||||
command.appendParameter(new CommandSingleParameter("return_code", returnCodeIndex));
|
||||
response = new CommandResponse(command, returnCodeIndex);
|
||||
} else {
|
||||
response = new CommandResponse(command, -1);
|
||||
}
|
||||
|
||||
String commandString = command.toString();
|
||||
|
||||
response.setDispatchedTime(System.currentTimeMillis());
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
public void connect(InetSocketAddress remote, String password, long timeout) throws IOException {
|
||||
try {
|
||||
if (connectionState != ConnectionState.DISCONNECTED)
|
||||
throw new IllegalStateException(connectionState.name());
|
||||
|
||||
datagramSocket.connect(remote);
|
||||
|
||||
setConnectionState(ConnectionState.CONNECTING);
|
||||
joinConnectionState(ConnectionState.CONNECTED, timeout);
|
||||
} catch (Throwable e) {
|
||||
setConnectionState(ConnectionState.DISCONNECTED);
|
||||
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void send(PacketType type, byte[] data) throws IOException {
|
||||
C2SPacket packet = createOutgoingPacket(type, data);
|
||||
}
|
||||
|
||||
private C2SPacket createOutgoingPacket(PacketType type, byte[] data) throws IOException {
|
||||
C2SPacket packet = new C2SPacket();
|
||||
|
||||
//TODO
|
||||
packet.setPacketId(0);
|
||||
|
||||
//TODO
|
||||
packet.setGenerationId(0);
|
||||
|
||||
//TODO
|
||||
packet.setClientId(0);
|
||||
|
||||
//TODO
|
||||
packet.packetFlags |= 0;
|
||||
|
||||
switch (type) {
|
||||
case VOICE:
|
||||
case VOICE_WHISPER:
|
||||
|
||||
break;
|
||||
case COMMAND:
|
||||
case COMMAND_LOW:
|
||||
|
||||
break;
|
||||
case PING:
|
||||
|
||||
break;
|
||||
case PONG:
|
||||
|
||||
break;
|
||||
case ACK:
|
||||
case ACK_LOW:
|
||||
|
||||
break;
|
||||
case INIT:
|
||||
packet.setPacketFlags(packet.getPacketFlags() | PacketFlags.UNENCRYPTED.getIndex());
|
||||
break;
|
||||
}
|
||||
|
||||
return packet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wire packet write
|
||||
* @param packet Packet to write
|
||||
* @throws IOException
|
||||
*/
|
||||
private void sendNetworkPacket(DatagramPacket packet) throws IOException {
|
||||
Ts3Logging.debug(
|
||||
"[NETWORK] WRITE Len=" + packet.getLength() + " to " + datagramSocket.getRemoteSocketAddress() +
|
||||
"\n" + Ts3Logging.getHex(packet.getData())
|
||||
);
|
||||
|
||||
packet.setSocketAddress(datagramSocket.getRemoteSocketAddress());
|
||||
|
||||
datagramSocket.send(packet);
|
||||
}
|
||||
|
||||
private DatagramPacket receiveNetworkPacket() throws IOException {
|
||||
datagramSocket.receive(datagramPacket);
|
||||
return datagramPacket;
|
||||
}
|
||||
|
||||
private void handleNetworkPacket(DatagramPacket packet) {
|
||||
Ts3Logging.debug(
|
||||
"[NETWORK] READ Len=" + packet.getLength() + " from " + packet.getSocketAddress() +
|
||||
"\n" + Ts3Logging.getHex(packet.getData())
|
||||
);
|
||||
|
||||
if (handler != null) handler.handleNetworkPacket(packet);
|
||||
}
|
||||
|
||||
public InetSocketAddress getRemote() {
|
||||
return remote;
|
||||
}
|
||||
|
||||
private class PacketReceiver implements Runnable {
|
||||
@Override
|
||||
public void run() {
|
||||
while (connectionState != ConnectionState.DISCONNECTED) {
|
||||
try {
|
||||
handleNetworkPacket(receiveNetworkPacket());
|
||||
} catch (IOException e) {
|
||||
Ts3Logging.info("Problem handling packet: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
Ts3Logging.debug("Leaving packet receiver");
|
||||
}
|
||||
}
|
||||
|
||||
private class C2SPacket {
|
||||
private int packetId;
|
||||
private int generationId;
|
||||
private int clientId;
|
||||
private int packetFlags;
|
||||
|
||||
private PacketType type;
|
||||
private byte[] data;
|
||||
private Date lastSendTime;
|
||||
|
||||
public PacketType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(PacketType type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public byte[] getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public void setData(byte[] data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public Date getLastSendTime() {
|
||||
return lastSendTime;
|
||||
}
|
||||
|
||||
public int getPacketId() {
|
||||
return packetId;
|
||||
}
|
||||
|
||||
public void setPacketId(int packetId) {
|
||||
this.packetId = packetId;
|
||||
}
|
||||
|
||||
public int getGenerationId() {
|
||||
return generationId;
|
||||
}
|
||||
|
||||
public void setGenerationId(int generationId) {
|
||||
this.generationId = generationId;
|
||||
}
|
||||
|
||||
public int getClientId() {
|
||||
return clientId;
|
||||
}
|
||||
|
||||
public void setClientId(int clientId) {
|
||||
this.clientId = clientId;
|
||||
}
|
||||
|
||||
public int getPacketFlags() {
|
||||
return packetFlags;
|
||||
}
|
||||
|
||||
public void setPacketFlags(int packetFlags) {
|
||||
this.packetFlags = packetFlags;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends, or re-sends, a packet
|
||||
*/
|
||||
public void send() {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.github.manevolent.ts3j.command;
|
||||
|
||||
import com.github.manevolent.ts3j.command.part.CommandParameter;
|
||||
|
||||
public interface Command {
|
||||
boolean willExpectResponse();
|
||||
|
||||
Iterable<CommandParameter> getParameters();
|
||||
|
||||
void appendParameter(CommandParameter parameter);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.github.manevolent.ts3j.command.part;
|
||||
|
||||
import com.github.manevolent.ts3j.enums.CommandPartType;
|
||||
|
||||
public class CommandOption implements CommandParameter {
|
||||
private final String value;
|
||||
|
||||
public CommandOption(String value) {
|
||||
this.value = " -" + value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommandPartType getType() {
|
||||
return CommandPartType.OPTION;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.github.manevolent.ts3j.command.part;
|
||||
|
||||
import com.github.manevolent.ts3j.enums.CommandPartType;
|
||||
|
||||
public interface CommandParameter {
|
||||
|
||||
CommandPartType getType();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package com.github.manevolent.ts3j.command.part;
|
||||
|
||||
import com.github.manevolent.ts3j.enums.CommandPartType;
|
||||
|
||||
public class CommandSingleParameter implements CommandParameter {
|
||||
private final String key, value;
|
||||
|
||||
public CommandSingleParameter(String key, String value) {
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public CommandSingleParameter(String key, boolean value) {
|
||||
this.key = key;
|
||||
this.value = serialize(value);
|
||||
}
|
||||
|
||||
public CommandSingleParameter(String key, byte value) {
|
||||
this.key = key;
|
||||
this.value = serialize(value);
|
||||
}
|
||||
|
||||
public CommandSingleParameter(String key, short value) {
|
||||
this.key = key;
|
||||
this.value = serialize(value);
|
||||
}
|
||||
|
||||
public CommandSingleParameter(String key, int value) {
|
||||
this.key = key;
|
||||
this.value = serialize(value);
|
||||
}
|
||||
|
||||
public CommandSingleParameter(String key, long value) {
|
||||
this.key = key;
|
||||
this.value = serialize(value);
|
||||
}
|
||||
|
||||
public CommandSingleParameter(String key, float value) {
|
||||
this.key = key;
|
||||
this.value = serialize(value);
|
||||
}
|
||||
|
||||
public CommandSingleParameter(String key, double value) {
|
||||
this.key = key;
|
||||
this.value = serialize(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommandPartType getType() {
|
||||
return CommandPartType.SINGLE_PARAMETER;
|
||||
}
|
||||
|
||||
public static String serialize(boolean value) {
|
||||
return value ? "1": "0";
|
||||
}
|
||||
|
||||
public static String serialize(byte value) {
|
||||
return Byte.toString(value);
|
||||
}
|
||||
|
||||
public static String serialize(short value) {
|
||||
return Short.toString(value);
|
||||
}
|
||||
|
||||
public static String serialize(int value) {
|
||||
return Integer.toString(value);
|
||||
}
|
||||
|
||||
public static String serialize(long value) {
|
||||
return Long.toString(value);
|
||||
}
|
||||
|
||||
public static String serialize(float value) {
|
||||
return Float.toString(value);
|
||||
}
|
||||
|
||||
public static String serialize(double value) {
|
||||
return Double.toString(value);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.github.manevolent.ts3j.command.response;
|
||||
|
||||
import com.github.manevolent.ts3j.command.Command;
|
||||
|
||||
public class CommandResponse {
|
||||
private final Object returnLock = new Object();
|
||||
private final Command command;
|
||||
private final int returnCode;
|
||||
private long dispatchedTime;
|
||||
|
||||
public CommandResponse(Command command, int returnCode) {
|
||||
this.command = command;
|
||||
this.returnCode = returnCode;
|
||||
}
|
||||
|
||||
public Command getCommand() {
|
||||
return command;
|
||||
}
|
||||
|
||||
public int getReturnCode() {
|
||||
return returnCode;
|
||||
}
|
||||
|
||||
public CommandResponse join() throws InterruptedException {
|
||||
synchronized (returnLock) {
|
||||
returnLock.wait();
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public CommandResponse join(int millis) throws InterruptedException {
|
||||
synchronized (returnLock) {
|
||||
returnLock.wait(millis);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public long getDispatchedTime() {
|
||||
return dispatchedTime;
|
||||
}
|
||||
|
||||
public void setDispatchedTime(long dispatchedTime) {
|
||||
this.dispatchedTime = dispatchedTime;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.github.manevolent.ts3j.enums;
|
||||
|
||||
public enum ClientListOptions {
|
||||
UID(1 << 0),
|
||||
AWAY(1 << 1),
|
||||
VOICE(1 << 2),
|
||||
TIMES(1 << 3),
|
||||
GROUPS(1 << 4),
|
||||
INFO(1 << 5),
|
||||
ICON(1 << 6),
|
||||
COUNTRY(1 << 7);
|
||||
|
||||
private final int index;
|
||||
|
||||
ClientListOptions(int index) {
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
public int getIndex() {
|
||||
return index;
|
||||
}
|
||||
|
||||
public static ClientListOptions fromId(int index) {
|
||||
for (ClientListOptions value : values())
|
||||
if (value.getIndex() == index) return value;
|
||||
|
||||
throw new IllegalArgumentException("invalid index: " + index);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.github.manevolent.ts3j.enums;
|
||||
|
||||
public enum ClientType {
|
||||
FULL(0),
|
||||
QUERY(1);
|
||||
|
||||
private final int index;
|
||||
|
||||
ClientType(int index) {
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
public int getIndex() {
|
||||
return index;
|
||||
}
|
||||
|
||||
public static ClientType fromId(int index) {
|
||||
for (ClientType value : values())
|
||||
if (value.getIndex() == index) return value;
|
||||
|
||||
throw new IllegalArgumentException("invalid index: " + index);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.github.manevolent.ts3j.enums;
|
||||
|
||||
public enum CodecType {
|
||||
SPEEX_NARROWBAND(0),
|
||||
SPEEX_WIDEBAND(1),
|
||||
SPEEX_ULTRA_WIDEBAND(2),
|
||||
CELT_MONO(3),
|
||||
OPUS_VOICE(4),
|
||||
OPUS_MUSIC(5),
|
||||
|
||||
RAW(127)
|
||||
|
||||
;
|
||||
|
||||
private final int index;
|
||||
|
||||
CodecType(int index) {
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
public int getIndex() {
|
||||
return index;
|
||||
}
|
||||
|
||||
public static CodecType fromId(int index) {
|
||||
for (CodecType value : values())
|
||||
if (value.getIndex() == index) return value;
|
||||
|
||||
throw new IllegalArgumentException("invalid index: " + index);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.github.manevolent.ts3j.enums;
|
||||
|
||||
public enum CommandPartType {
|
||||
SINGLE_PARAMETER,
|
||||
MULTI_PAREMTER,
|
||||
OPTION;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
|
||||
package com.github.manevolent.ts3j.enums;
|
||||
|
||||
import com.github.manevolent.ts3j.TeamspeakClient;
|
||||
import com.github.manevolent.ts3j.handler.*;
|
||||
|
||||
public enum ConnectionState {
|
||||
CONNECTING(TeamspeakClientHandlerConnecting.class),
|
||||
NEGOTIATING(TeamspeakClientHandlerNegotiating.class),
|
||||
CONNECTED(TeamspeakClientHandlerConnected.class),
|
||||
DISCONNECTED(TeamspeakClientHandlerDisconnected.class);
|
||||
|
||||
private final Class<? extends TeamspeakClientHandler> clazz;
|
||||
|
||||
ConnectionState(Class<? extends TeamspeakClientHandler> clazz) {
|
||||
this.clazz = clazz;
|
||||
}
|
||||
|
||||
public Class<? extends TeamspeakClientHandler> getHandlerClass() {
|
||||
return clazz;
|
||||
}
|
||||
|
||||
public TeamspeakClientHandler createHandler(TeamspeakClient client) {
|
||||
try {
|
||||
return clazz.getConstructor(TeamspeakClient.class).newInstance(client);
|
||||
} catch (ReflectiveOperationException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.github.manevolent.ts3j.enums;
|
||||
|
||||
public enum DisconnectReason {
|
||||
USER_ACTION(0),
|
||||
USER_OR_CHANNEL_MOVED(1),
|
||||
SUBSCRIPTION_CHANGED(2),
|
||||
TIMEOUT(3),
|
||||
KICKED_FROM_CHANNEL(4),
|
||||
KICKED_FROM_SERVER(5),
|
||||
BANNED(6),
|
||||
SERVER_STOPPED(7),
|
||||
LEFT_SERVER(8),
|
||||
CHANNEL_UPDATED(9),
|
||||
SERVER_OR_CHANNEL_EDITED(10),
|
||||
SERVER_SHUTDOWN(11)
|
||||
|
||||
;
|
||||
|
||||
private final int index;
|
||||
|
||||
DisconnectReason(int index) {
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
public int getIndex() {
|
||||
return index;
|
||||
}
|
||||
|
||||
public static DisconnectReason fromId(int index) {
|
||||
for (DisconnectReason value : values())
|
||||
if (value.getIndex() == index) return value;
|
||||
|
||||
throw new IllegalArgumentException("invalid index: " + index);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.github.manevolent.ts3j.enums;
|
||||
|
||||
public enum GroupNamingMode {
|
||||
UID(1 << 0),
|
||||
AWAY(1 << 1),
|
||||
VOICE(1 << 2),
|
||||
TIMES(1 << 3),
|
||||
GROUPS(1 << 4),
|
||||
INFO(1 << 5),
|
||||
ICON(1 << 6),
|
||||
COUNTRY(1 << 7);
|
||||
|
||||
private final int index;
|
||||
|
||||
GroupNamingMode(int index) {
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
public int getIndex() {
|
||||
return index;
|
||||
}
|
||||
|
||||
public static GroupNamingMode fromId(int index) {
|
||||
for (GroupNamingMode value : values())
|
||||
if (value.getIndex() == index) return value;
|
||||
|
||||
throw new IllegalArgumentException("invalid index: " + index);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.github.manevolent.ts3j.enums;
|
||||
|
||||
public enum GroupType {
|
||||
TEMPLATE(0),
|
||||
REGULAR(1),
|
||||
QUERY(2)
|
||||
|
||||
;
|
||||
|
||||
private final int index;
|
||||
|
||||
GroupType(int index) {
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
public int getIndex() {
|
||||
return index;
|
||||
}
|
||||
|
||||
public static GroupType fromId(int index) {
|
||||
for (GroupType value : values())
|
||||
if (value.getIndex() == index) return value;
|
||||
|
||||
throw new IllegalArgumentException("invalid index: " + index);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.github.manevolent.ts3j.enums;
|
||||
|
||||
public enum GroupWhisperType {
|
||||
ALL_CHANNELS(0),
|
||||
CURRENT_CHANNEL(1),
|
||||
ALL_PARENT_CHANNEL(2),
|
||||
CHANNEL_FAMILY(3),
|
||||
COMPLETE_CHANNEL_FAMILY(4),
|
||||
SUBCHANNELS(5)
|
||||
|
||||
;
|
||||
|
||||
private final int index;
|
||||
|
||||
GroupWhisperType(int index) {
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
public int getIndex() {
|
||||
return index;
|
||||
}
|
||||
|
||||
public static GroupWhisperType fromId(int index) {
|
||||
for (GroupWhisperType value : values())
|
||||
if (value.getIndex() == index) return value;
|
||||
|
||||
throw new IllegalArgumentException("invalid index: " + index);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.github.manevolent.ts3j.enums;
|
||||
|
||||
public enum HostBannerMode {
|
||||
NO_ADJUST(0),
|
||||
IGNORE_ASPECT_RATIO(1),
|
||||
KEEP_ASPECT(2)
|
||||
|
||||
;
|
||||
|
||||
private final int index;
|
||||
|
||||
HostBannerMode(int index) {
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
public int getIndex() {
|
||||
return index;
|
||||
}
|
||||
|
||||
public static HostBannerMode fromId(int index) {
|
||||
for (HostBannerMode value : values())
|
||||
if (value.getIndex() == index) return value;
|
||||
|
||||
throw new IllegalArgumentException("invalid index: " + index);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.github.manevolent.ts3j.enums;
|
||||
|
||||
public enum HostMessageMode {
|
||||
NONE(0),
|
||||
LOG(1),
|
||||
MODAL(2),
|
||||
MODAL_QUIT(3)
|
||||
|
||||
;
|
||||
|
||||
private final int index;
|
||||
|
||||
HostMessageMode(int index) {
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
public int getIndex() {
|
||||
return index;
|
||||
}
|
||||
|
||||
public static HostMessageMode fromId(int index) {
|
||||
for (HostMessageMode value : values())
|
||||
if (value.getIndex() == index) return value;
|
||||
|
||||
throw new IllegalArgumentException("invalid index: " + index);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.github.manevolent.ts3j.enums;
|
||||
|
||||
public enum KickIdentifier {
|
||||
CHANNEL(4),
|
||||
SERVER(5)
|
||||
|
||||
;
|
||||
|
||||
private final int index;
|
||||
|
||||
KickIdentifier(int index) {
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
public int getIndex() {
|
||||
return index;
|
||||
}
|
||||
|
||||
public static KickIdentifier fromId(int index) {
|
||||
for (KickIdentifier value : values())
|
||||
if (value.getIndex() == index) return value;
|
||||
|
||||
throw new IllegalArgumentException("invalid index: " + index);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.github.manevolent.ts3j.enums;
|
||||
|
||||
public enum LicenseType {
|
||||
NO_LICENSE(0),
|
||||
ATHP(1),
|
||||
LAN(2),
|
||||
NPL(3),
|
||||
UNKNOWN(4)
|
||||
|
||||
;
|
||||
|
||||
private final int index;
|
||||
|
||||
LicenseType(int index) {
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
public int getIndex() {
|
||||
return index;
|
||||
}
|
||||
|
||||
public static LicenseType fromId(int index) {
|
||||
for (LicenseType value : values())
|
||||
if (value.getIndex() == index) return value;
|
||||
|
||||
throw new IllegalArgumentException("invalid index: " + index);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.github.manevolent.ts3j.enums;
|
||||
|
||||
public enum LogLevel {
|
||||
ERROR(1),
|
||||
WARNING(2),
|
||||
DEBUG(3),
|
||||
INFO(4)
|
||||
|
||||
;
|
||||
|
||||
private final int index;
|
||||
|
||||
LogLevel(int index) {
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
public int getIndex() {
|
||||
return index;
|
||||
}
|
||||
|
||||
public static LogLevel fromId(int index) {
|
||||
for (LogLevel value : values())
|
||||
if (value.getIndex() == index) return value;
|
||||
|
||||
throw new IllegalArgumentException("invalid index: " + index);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.github.manevolent.ts3j.enums;
|
||||
|
||||
public enum PacketFlags {
|
||||
NONE(0x0),
|
||||
FRAGMENTED(0X10),
|
||||
NEW_PROTOCOL(0x20),
|
||||
COMPRESSED(0x40),
|
||||
UNENCRYPTED(0x80);
|
||||
|
||||
private final int index;
|
||||
|
||||
PacketFlags(int index) {
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
public int getIndex() {
|
||||
return index;
|
||||
}
|
||||
|
||||
public static PacketFlags fromId(int index) {
|
||||
for (PacketFlags value : values())
|
||||
if (value.getIndex() == index) return value;
|
||||
|
||||
throw new IllegalArgumentException("invalid index: " + index);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.github.manevolent.ts3j.enums;
|
||||
|
||||
public enum PacketType {
|
||||
VOICE(0x0),
|
||||
VOICE_WHISPER(0x1),
|
||||
COMMAND(0x2),
|
||||
COMMAND_LOW(0x3),
|
||||
PING(0x4),
|
||||
PONG(0x5),
|
||||
ACK(0x6),
|
||||
ACK_LOW(0x7),
|
||||
INIT(0x8)
|
||||
;
|
||||
|
||||
private final int index;
|
||||
|
||||
PacketType(int index) {
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
public int getIndex() {
|
||||
return index;
|
||||
}
|
||||
|
||||
public static PacketType fromId(int index) {
|
||||
for (PacketType value : values())
|
||||
if (value.getIndex() == index) return value;
|
||||
|
||||
throw new IllegalArgumentException("invalid index: " + index);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.github.manevolent.ts3j.enums;
|
||||
|
||||
public enum PermissionGroupType {
|
||||
SERVER_GROUP(0),
|
||||
GLOBAL_CLIENT(1),
|
||||
CHANNEL(2),
|
||||
CHANNEL_GROUP(3),
|
||||
CHANNEL_CLIENT(4)
|
||||
|
||||
;
|
||||
|
||||
private final int index;
|
||||
|
||||
PermissionGroupType(int index) {
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
public int getIndex() {
|
||||
return index;
|
||||
}
|
||||
|
||||
public static PermissionGroupType fromId(int index) {
|
||||
for (PermissionGroupType value : values())
|
||||
if (value.getIndex() == index) return value;
|
||||
|
||||
throw new IllegalArgumentException("invalid index: " + index);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.github.manevolent.ts3j.enums;
|
||||
|
||||
public enum PluginTargetMode {
|
||||
CURRENT_CHANNEL(0),
|
||||
SERVER(1),
|
||||
CLIENT(2),
|
||||
CURRENT_CHANNEL_SUBSCRIBED_CLIENTS(3)
|
||||
|
||||
;
|
||||
|
||||
private final int index;
|
||||
|
||||
PluginTargetMode(int index) {
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
public int getIndex() {
|
||||
return index;
|
||||
}
|
||||
|
||||
public static PluginTargetMode fromId(int index) {
|
||||
for (PluginTargetMode value : values())
|
||||
if (value.getIndex() == index) return value;
|
||||
|
||||
throw new IllegalArgumentException("invalid index: " + index);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.github.manevolent.ts3j.enums;
|
||||
|
||||
public enum PriviledgeTokenType {
|
||||
SERVER_GROUP(0),
|
||||
CHANNEL_GROUP(1)
|
||||
|
||||
;
|
||||
|
||||
private final int index;
|
||||
|
||||
PriviledgeTokenType(int index) {
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
public int getIndex() {
|
||||
return index;
|
||||
}
|
||||
|
||||
public static PriviledgeTokenType fromId(int index) {
|
||||
for (PriviledgeTokenType value : values())
|
||||
if (value.getIndex() == index) return value;
|
||||
|
||||
throw new IllegalArgumentException("invalid index: " + index);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.github.manevolent.ts3j.enums;
|
||||
|
||||
public enum TextMessageTargetMode {
|
||||
PRIVATE(1),
|
||||
CHANNEL(2),
|
||||
SERVER(3)
|
||||
|
||||
;
|
||||
|
||||
private final int index;
|
||||
|
||||
TextMessageTargetMode(int index) {
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
public int getIndex() {
|
||||
return index;
|
||||
}
|
||||
|
||||
public static TextMessageTargetMode fromId(int index) {
|
||||
for (TextMessageTargetMode value : values())
|
||||
if (value.getIndex() == index) return value;
|
||||
|
||||
throw new IllegalArgumentException("invalid index: " + index);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.github.manevolent.ts3j.enums;
|
||||
|
||||
public enum VoiceEncryptionMode {
|
||||
INDIVIDUAL(0),
|
||||
DISABLED(1),
|
||||
ENABLED(2)
|
||||
|
||||
;
|
||||
|
||||
private final int index;
|
||||
|
||||
VoiceEncryptionMode(int index) {
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
public int getIndex() {
|
||||
return index;
|
||||
}
|
||||
|
||||
public static VoiceEncryptionMode fromId(int index) {
|
||||
for (VoiceEncryptionMode value : values())
|
||||
if (value.getIndex() == index) return value;
|
||||
|
||||
throw new IllegalArgumentException("invalid index: " + index);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.github.manevolent.ts3j.handler;
|
||||
|
||||
import com.github.manevolent.ts3j.TeamspeakClient;
|
||||
import com.github.manevolent.ts3j.enums.ConnectionState;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.DatagramPacket;
|
||||
|
||||
public abstract class TeamspeakClientHandler {
|
||||
private final TeamspeakClient client;
|
||||
|
||||
protected TeamspeakClientHandler(TeamspeakClient client) {
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
public void handleConnectionStateChanging(ConnectionState connectionState) {
|
||||
|
||||
}
|
||||
|
||||
public void handleNetworkPacket(DatagramPacket packet) {
|
||||
|
||||
}
|
||||
|
||||
public void onAssigned() throws IOException {
|
||||
|
||||
}
|
||||
|
||||
protected TeamspeakClient getClient() {
|
||||
return client;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.github.manevolent.ts3j.handler;
|
||||
|
||||
import com.github.manevolent.ts3j.TeamspeakClient;
|
||||
|
||||
public class TeamspeakClientHandlerConnected extends TeamspeakClientHandler {
|
||||
public TeamspeakClientHandlerConnected(TeamspeakClient client) {
|
||||
super(client);
|
||||
}
|
||||
}
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
package com.github.manevolent.ts3j.handler;
|
||||
|
||||
import com.github.manevolent.ts3j.TeamspeakClient;
|
||||
import com.github.manevolent.ts3j.enums.PacketType;
|
||||
import com.github.manevolent.ts3j.util.Ts3Logging;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.Random;
|
||||
|
||||
public class TeamspeakClientHandlerConnecting extends TeamspeakClientHandler {
|
||||
|
||||
|
||||
public TeamspeakClientHandlerConnecting(TeamspeakClient client) {
|
||||
super(client);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAssigned() throws IOException {
|
||||
Ts3Logging.debug("Connecting: sending Init1...");
|
||||
|
||||
processInit1();
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the first client dispatch of the Init1 packet (C2S initial handshake packet)
|
||||
*/
|
||||
public void processInit1() throws IOException {
|
||||
processInit1(null);
|
||||
}
|
||||
|
||||
public void processInit1(byte[] data) throws IOException {
|
||||
int versionLen = 4;
|
||||
int initTypeLen = 1;
|
||||
Integer type = null;
|
||||
|
||||
if (data != null) {
|
||||
type = (int) data[0];
|
||||
|
||||
if (data.length < initTypeLen)
|
||||
throw new IllegalArgumentException("invalid Init1 packet (too short)");
|
||||
}
|
||||
|
||||
ByteBuffer buffer = null;
|
||||
|
||||
if (type == null) {
|
||||
buffer = ByteBuffer.allocate(versionLen + initTypeLen + 4 + 4 + 8);
|
||||
|
||||
buffer.put(new byte[] { 0x09, (byte)0x83, (byte)0x8C, (byte)0xCF });
|
||||
buffer.put((byte) 0x00); // initType
|
||||
buffer.order(ByteOrder.BIG_ENDIAN);
|
||||
buffer.putInt((int) (System.currentTimeMillis() / 1000L));
|
||||
|
||||
Random random = new Random();
|
||||
for (int i = 0; i < 4; i++)
|
||||
buffer.put((byte) random.nextInt(256)); // 4byte random
|
||||
} else {
|
||||
switch (type) {
|
||||
case 1:
|
||||
|
||||
break;
|
||||
case 3:
|
||||
|
||||
break;
|
||||
case 0x7F:
|
||||
// 0x7F: Some strange servers do this
|
||||
// the normal client responds by starting again
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("invalid Init1 packet id: " + type);
|
||||
}
|
||||
}
|
||||
|
||||
if (buffer == null || buffer.position() <= 0)
|
||||
throw new IllegalStateException("invalid send buffer");
|
||||
|
||||
getClient().send(PacketType.INIT, buffer.array());
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
package com.github.manevolent.ts3j.handler;
|
||||
|
||||
import com.github.manevolent.ts3j.TeamspeakClient;
|
||||
|
||||
public class TeamspeakClientHandlerDisconnected extends TeamspeakClientHandler {
|
||||
public TeamspeakClientHandlerDisconnected(TeamspeakClient client) {
|
||||
super(client);
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
package com.github.manevolent.ts3j.handler;
|
||||
|
||||
import com.github.manevolent.ts3j.TeamspeakClient;
|
||||
|
||||
public class TeamspeakClientHandlerNegotiating extends TeamspeakClientHandler {
|
||||
public TeamspeakClientHandlerNegotiating(TeamspeakClient client) {
|
||||
super(client);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.github.manevolent.ts3j.util;
|
||||
|
||||
public final class Ts3Const {
|
||||
// Common Definitions
|
||||
|
||||
//limited length, measured in characters
|
||||
public static int MaxSizeChannelName = 40;
|
||||
public static int MaxSizeVirtualserverName = 64;
|
||||
public static int MaxSizeClientNicknameSdk = 64;
|
||||
public static int MinSizeClientNicknameSdk = 3;
|
||||
public static int MaxSizeReasonMessage = 80;
|
||||
|
||||
//limited length, measured in bytes (utf8 encoded)
|
||||
public static int MaxSizeTextMessage = 1024;
|
||||
public static int MaxSizeChannelTopic = 255;
|
||||
public static int MaxSizeChannelDescription = 8192;
|
||||
public static int MaxSizeVirtualserverWelcomeMessage = 1024;
|
||||
|
||||
// Rare Definitions
|
||||
|
||||
//limited length, measured in characters
|
||||
public static int MaxSizeClientNickname = 30;
|
||||
public static int MinSizeClientNickname = 3;
|
||||
public static int MaxSizeAwayMessage = 80;
|
||||
public static int MaxSizeGroupName = 30;
|
||||
public static int MaxSizeTalkRequestMessage = 50;
|
||||
public static int MaxSizeComplainMessage = 200;
|
||||
public static int MaxSizeClientDescription = 200;
|
||||
public static int MaxSizeHostMessage = 200;
|
||||
public static int MaxSizeHostbuttonTooltip = 50;
|
||||
public static int MaxSizePokeMessage = 100;
|
||||
public static int MaxSizeOfflineMessage = 4096;
|
||||
public static int MaxSizeOfflineMessageSubject = 200;
|
||||
|
||||
//limited length, measured in bytes (utf8 encoded)
|
||||
public static int MaxSizePluginCommand = 1024 * 8;
|
||||
public static int MaxSizeVirtualserverHostbannerGfxUrl = 2000;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.github.manevolent.ts3j.util;
|
||||
|
||||
public final class Ts3Logging {
|
||||
private static final String HEXES = "0123456789ABCDEF";
|
||||
public static String getHex(byte[] raw) {
|
||||
final StringBuilder hex = new StringBuilder(2 * raw.length);
|
||||
for (final byte b : raw) {
|
||||
hex.append(HEXES.charAt((b & 0xF0) >> 4)).append(HEXES.charAt((b & 0x0F)));
|
||||
}
|
||||
return hex.toString();
|
||||
}
|
||||
|
||||
public static void info(String message) {
|
||||
System.err.println("[INFO] " + message);
|
||||
}
|
||||
|
||||
public static void debug(String message) {
|
||||
System.err.println("[DEBUG] " + message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package com.github.manevolent.ts3j.util;
|
||||
|
||||
public final class Ts3String {
|
||||
public static String escape(String string) {
|
||||
StringBuilder sb = new StringBuilder(string.length());
|
||||
|
||||
for (int i = 0; i < string.length(); i ++) {
|
||||
switch (string.charAt(i)) {
|
||||
case '\\': sb.append("\\\\"); break;
|
||||
case '/': sb.append("\\/"); break;
|
||||
case ' ': sb.append("\\s"); break;
|
||||
case '|': sb.append("\\p"); break;
|
||||
case '\f': sb.append("\\f"); break;
|
||||
case '\n': sb.append("\\n"); break;
|
||||
case '\r': sb.append("\\r"); break;
|
||||
case '\t': sb.append("\\t"); break;
|
||||
//case '\v': sb.append("\\v"); break;
|
||||
default:
|
||||
sb.append(string.charAt(i));
|
||||
}
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static String unescape(String string) {
|
||||
StringBuilder sb = new StringBuilder(string.length());
|
||||
|
||||
for (int i = 0; i < string.length(); i ++) {
|
||||
char c = string.charAt(i);
|
||||
if (c == '\\') {
|
||||
if (i++ >= string.length()) throw new IllegalArgumentException("illegal escape at end of string");
|
||||
switch (string.charAt(i)) {
|
||||
//case 'v': sb.append('\v'); break;
|
||||
case 't': sb.append('\t'); break;
|
||||
case 'r': sb.append('\r'); break;
|
||||
case 'n': sb.append('\n'); break;
|
||||
case 'f': sb.append('\f'); break;
|
||||
case 'p': sb.append('|'); break;
|
||||
case 's': sb.append(' '); break;
|
||||
case '/': sb.append('/'); break;
|
||||
case '\\': sb.append('\\'); break;
|
||||
default:
|
||||
throw new IllegalArgumentException("invalid escape code: " + c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static int getTokenLength(String s) {
|
||||
return (int) s.length() + (int) s.chars().filter(x -> isDoubleChar((char)x)).count();
|
||||
}
|
||||
|
||||
public static boolean isDoubleChar(char c) {
|
||||
return c == '\\' ||
|
||||
c == '/' ||
|
||||
c == ' ' ||
|
||||
c == '|' ||
|
||||
c == '\f' ||
|
||||
c == '\n' ||
|
||||
c == '\r' ||
|
||||
c == '\t';// ||
|
||||
//c == '\v';
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user