Merge remote-tracking branch 'origin/master'

This commit is contained in:
Manevolent
2018-08-31 21:54:19 -04:00
10 changed files with 214 additions and 178 deletions
+3 -3
View File
@@ -2,8 +2,6 @@
Discord: https://discord.gg/4q9jpCa
Teamspeak: teamlixo.net (that's an SRV record; ts.teamlixo.net is the A record)
TS3J is an open-source implementation of the reverse-engineered Teamspeak3 full server/client protocol, as an adaptation of Splamy's C# TS3Client source code. You can find that here: https://github.com/Splamy/TS3AudioBot/.
# Maven
@@ -112,4 +110,6 @@ public byte[] provide() {
```
# Receiving audio
This is definitely possible, but it's a work in progress for now. Once I have a need for this in my own bot (I can forsee there being an opportunity), I will be adding full support to TS3J.
Refer to the `setVoiceHandler` and `setWhisperHandler` methods to supply a Consumer object to receive Voice and Whisper packets. You will need to decode the packets yourself, and insert packet-loss-correction as needed.
Note that the first 5 packets starting a voice session are marked with the COMPRESSED flag. The final voice packet, intended to singal to close your decoder and flush samples, is always empty (0-length byte array).
+6
View File
@@ -45,5 +45,11 @@
<artifactId>commons-lang</artifactId>
<version>2.6</version>
</dependency>
<dependency>
<groupId>dnsjava</groupId>
<artifactId>dnsjava</artifactId>
<version>2.1.8</version>
</dependency>
</dependencies>
</project>
@@ -0,0 +1,43 @@
package com.github.manevolent.ts3j.protocol;
import org.xbill.DNS.*;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.UnknownHostException;
import java.util.*;
import java.util.stream.Collectors;
public class TS3DNS {
private static final int TS3_VOICE_DEFAULT_PORT = 9987;
private static final int TS3_QUERY_DEFAULT_PORT = 10011;
private static final int TS3_FILETRANSFER_DEFAULT_PORT = 30033;
private static final int TSDNS_DEFAULT_PORT = 41144;
private static final String DNS_PREFIX_TCP = "_tsdns._tcp.";
private static final String DNS_PREFIX_UDP = "_ts3._udp.";
private static List<InetSocketAddress> lookupSRV(String domain) throws IOException {
org.xbill.DNS.Record[] records = new Lookup(domain, Type.SRV).run();
if (records == null || records.length <= 0) throw new UnknownHostException("Host not found");
return Arrays.stream(records)
.filter(Objects::nonNull)
.map(x -> (SRVRecord) x)
.filter(x -> x.getTarget() != null)
.sorted((a, b) -> {
// Loose sorting
int prioComp = Integer.compare(a.getPriority(), b.getPriority());
if (prioComp != 0) return prioComp;
else return Integer.compare(a.getWeight(), b.getWeight());
})
.map(x -> new InetSocketAddress(x.getTarget().toString(), x.getPort()))
.collect(Collectors.toList());
}
public static List<InetSocketAddress> lookup(String domain) throws IOException {
return lookupSRV(DNS_PREFIX_UDP + domain);
}
}
@@ -40,7 +40,7 @@ public class PacketReassembly {
temporaryCounter.setPacketId(currentPacketId.getKey());
temporaryCounter.setGeneration(currentPacketId.getValue());
while (true) {
while (queue.size() > 0) {
Pair<Integer, Integer> thisPacketId = temporaryCounter.current();
Packet packet = queue.get(thisPacketId.getKey());
@@ -1,9 +1,11 @@
package com.github.manevolent.ts3j.protocol.packet.handler.client;
import com.github.manevolent.ts3j.command.Command;
import com.github.manevolent.ts3j.command.SingleCommand;
import com.github.manevolent.ts3j.command.parameter.CommandSingleParameter;
import com.github.manevolent.ts3j.protocol.Packet;
import com.github.manevolent.ts3j.protocol.ProtocolRole;
import com.github.manevolent.ts3j.protocol.TS3DNS;
import com.github.manevolent.ts3j.protocol.client.ClientConnectionState;
import com.github.manevolent.ts3j.protocol.packet.PacketBody2Command;
import com.github.manevolent.ts3j.protocol.packet.PacketBody8Init1;
@@ -188,49 +190,48 @@ public class LocalClientHandlerConnecting extends LocalClientHandler {
alphaBytes = null;
getClient().writePacket(new PacketBody2Command(
Command clientinit = new SingleCommand(
"clientinit",
ProtocolRole.CLIENT,
new SingleCommand(
"clientinit",
ProtocolRole.CLIENT,
new CommandSingleParameter("client_nickname", getClient().getNickname()),
new CommandSingleParameter("client_version", "3.?.? [Build: 5680278000]"),
new CommandSingleParameter("client_platform", "Windows"),
new CommandSingleParameter(
"client_version_sign",
"DX5NIYLvfJEUjuIbCidnoeozxIDRRkpq3I9vVMBmE9L2qnekOoBzSenkzsg2lC9CMv8K5hkEzhr2TYUYSwUXCg=="
),
new CommandSingleParameter("client_input_hardware", "1"),
new CommandSingleParameter("client_output_hardware", "1"),
new CommandSingleParameter("client_default_channel",
getClient().getOption("client.default_channel", String.class)
),
new CommandSingleParameter("client_default_channel_password",
getClient().getOption("client.default_channel_password", String.class)
),
new CommandSingleParameter(
"client_server_password",
getClient().getOption("client.server_password", String.class)
),
new CommandSingleParameter("client_nickname_phonetic",
getClient().getOption("client.nickname_phonetic", String.class)
),
new CommandSingleParameter("client_meta_data", ""),
new CommandSingleParameter("client_default_token",
getClient().getOption("client.default_token", String.class)
),
new CommandSingleParameter(
"client_key_offset",
Long.toString(getClient().getIdentity().getKeyOffset())
),
new CommandSingleParameter(
"hwid",
getClient().getOption("client.hwid", String.class) != null ?
getClient().getOption("client.hwid", String.class) :
"+LyYqbDqOvEEpN5pdAbF8/v5kZ0="
)
new CommandSingleParameter("client_nickname", getClient().getNickname()),
new CommandSingleParameter("client_version", "3.?.? [Build: 5680278000]"),
new CommandSingleParameter("client_platform", "Windows"),
new CommandSingleParameter(
"client_version_sign",
"DX5NIYLvfJEUjuIbCidnoeozxIDRRkpq3I9vVMBmE9L2qnekOoBzSenkzsg2lC9CMv8K5hkEzhr2TYUYSwUXCg=="
),
new CommandSingleParameter("client_input_hardware", "1"),
new CommandSingleParameter("client_output_hardware", "1"),
new CommandSingleParameter("client_default_channel",
getClient().getOption("client.default_channel", String.class)
),
new CommandSingleParameter("client_default_channel_password",
getClient().getOption("client.default_channel_password", String.class)
),
new CommandSingleParameter("client_server_password",
getClient().getOption("client.server_password", String.class)
),
new CommandSingleParameter("client_nickname_phonetic",
getClient().getOption("client.nickname_phonetic", String.class)
),
new CommandSingleParameter("client_meta_data", ""),
new CommandSingleParameter("client_default_token",
getClient().getOption("client.default_token", String.class)
),
new CommandSingleParameter("client_key_offset",
Long.toString(getClient().getIdentity().getKeyOffset())
),
new CommandSingleParameter(
"hwid",
getClient().getOption("client.hwid", String.class) != null ?
getClient().getOption("client.hwid", String.class) :
"+LyYqbDqOvEEpN5pdAbF8/v5kZ0="
)
));
);
Ts3Debugging.debug(clientinit.build());
getClient().writePacket(new PacketBody2Command(ProtocolRole.CLIENT, clientinit));
getClient().setCommandProcessor(getClient());
getClient().setState(ClientConnectionState.RETRIEVING_DATA);
@@ -1,9 +1,31 @@
package com.github.manevolent.ts3j.protocol.packet.handler.client;
import com.github.manevolent.ts3j.command.CommandProcessException;
import com.github.manevolent.ts3j.command.CommandProcessor;
import com.github.manevolent.ts3j.command.MultiCommand;
import com.github.manevolent.ts3j.protocol.client.ClientConnectionState;
import com.github.manevolent.ts3j.protocol.socket.client.LocalTeamspeakClientSocket;
public class LocalClientHandlerRetrievingData extends LocalClientHandlerFull {
public LocalClientHandlerRetrievingData(LocalTeamspeakClientSocket client) {
super(client);
}
@Override
protected void handleCommand(MultiCommand command) throws CommandProcessException {
if (command.getName().equalsIgnoreCase("error")) {
try {
getClient().setState(ClientConnectionState.DISCONNECTED);
} catch (Exception e) {
getClient().getExceptionHandler().accept(e);
}
throw new CommandProcessException(command.simplifyOne().get("msg").getValue());
}
CommandProcessor processor = getClient().getCommandProcessor();
if (processor != null) processor.process(getClient(), command);
}
}
@@ -107,8 +107,8 @@ public abstract class AbstractTeamspeakClientSocket
private boolean reading = false;
protected AbstractTeamspeakClientSocket() {
super(SocketRole.CLIENT);
protected AbstractTeamspeakClientSocket(SocketRole role) {
super(role);
// Initialize reassembly queue
for (PacketBodyType bodyType : PacketBodyType.values())
@@ -205,8 +205,6 @@ public abstract class AbstractTeamspeakClientSocket
}
protected void onDisconnect() {
clientOptions.clear();
clientId = -1;
readQueue.clear();
@@ -12,9 +12,7 @@ import com.github.manevolent.ts3j.command.response.CommandResponse;
import com.github.manevolent.ts3j.event.*;
import com.github.manevolent.ts3j.identity.LocalIdentity;
import com.github.manevolent.ts3j.identity.Uid;
import com.github.manevolent.ts3j.protocol.NetworkPacket;
import com.github.manevolent.ts3j.protocol.PacketKind;
import com.github.manevolent.ts3j.protocol.ProtocolRole;
import com.github.manevolent.ts3j.protocol.*;
import com.github.manevolent.ts3j.protocol.client.ClientConnectionState;
import com.github.manevolent.ts3j.protocol.header.PacketHeader;
import com.github.manevolent.ts3j.protocol.packet.PacketBody0Voice;
@@ -60,7 +58,7 @@ public class LocalTeamspeakClientSocket
private Map<Integer, ClientCommandResponse> awaitingCommands = new ConcurrentHashMap<>();
public LocalTeamspeakClientSocket() {
super();
super(SocketRole.CLIENT);
namedProcessors.put("initserver", new InitServerHandler());
@@ -254,12 +252,34 @@ public class LocalTeamspeakClientSocket
remote = null;
}
/**
* Initiates a connection to a server
* @param hostname Hostname to contact
* @param password server password (may be null)
* @param timeout timeout, in milliseconds, to complete a connection.
*/
public void connect(String hostname, String password, long timeout) throws
IOException,
TimeoutException {
InetSocketAddress address;
try {
// Attempt TS3DNS
address = TS3DNS.lookup(hostname).stream().findFirst().orElseThrow(() -> new UnknownHostException(hostname));
} catch (UnknownHostException e) {
address = new InetSocketAddress(hostname, 9987);
}
connect(address, password, timeout);
}
/**
* Initiates a connection to a server
* @param remote remote sever to contact
* @param password server password
* @param password server password (may be null)
* @param timeout timeout, in milliseconds, to complete a connection.
* @throws IOException
*/
public void connect(InetSocketAddress remote, String password, long timeout)
throws IOException, TimeoutException {
@@ -286,7 +306,7 @@ public class LocalTeamspeakClientSocket
setOption("client.hostname", remote.getHostString());
if (password != null) setOption("client.password", password);
if (password != null && password.length() > 0) setOption("client.password", password);
setState(ClientConnectionState.CONNECTING);
@@ -297,6 +317,7 @@ public class LocalTeamspeakClientSocket
0.01F,
new MicrophoneTask()
);
microphoneThread.setDaemon(true);
microphoneThread.start();
} catch (TimeoutException e) {
@@ -629,6 +650,14 @@ public class LocalTeamspeakClientSocket
super.setNickname(nickname);
}
public void setHWID(String hwid) {
setOption("client.hwid", hwid);
}
public String getHWID() {
return getOption("client.hwid", String.class);
}
/**
* Commands
*/
@@ -878,6 +907,26 @@ public class LocalTeamspeakClientSocket
return executeCommand(command, x -> new Client(x.toMap())).get();
}
public void serverGroupAddClient(int groupId, int clientDatabaseId)
throws IOException, TimeoutException, ExecutionException, InterruptedException {
Command command = new SingleCommand("servergroupaddclient", ProtocolRole.CLIENT);
command.add(new CommandSingleParameter("sgid", Integer.toString(groupId)));
command.add(new CommandSingleParameter("cldbid", Integer.toString(clientDatabaseId)));
executeCommand(command).complete();
}
public void serverGroupRemoveClient(int groupId, int clientDatabaseId)
throws IOException, TimeoutException, ExecutionException, InterruptedException {
Command command = new SingleCommand("servergroupdelclient", ProtocolRole.CLIENT);
command.add(new CommandSingleParameter("sgid", Integer.toString(groupId)));
command.add(new CommandSingleParameter("cldbid", Integer.toString(clientDatabaseId)));
executeCommand(command).complete();
}
/**
* Moves one or more clients specified with clid to the channel with ID cid. If
the target channel has a password, it needs to be specified with cpw. If the
@@ -31,135 +31,25 @@ public class ServerConnectionTest {
identity.improveSecurity(10);
client.setIdentity(identity);
client.setNickname("Hello from Java");
client.setHWID("JAVAJAVAJAVA");
client.setOption("client.hwid", "JAVAJAVAJAVA");
client.connect(
new InetSocketAddress(
"ts.teamlixo.net",
9987
),
null,
10000L
);
while (true) {
try {
client.addListener(new TS3Listener() {
@Override
public void onTextMessage(TextMessageEvent e) {
client.connect(
"teamlixo.net",
null,
10000L
);
assertEquals(client.getState(), ClientConnectionState.CONNECTED);
} catch (Exception ex) {
ex.printStackTrace();
}
@Override
public void onClientJoin(ClientJoinEvent e) {
System.err.println(e.get("cid"));
}
@Override
public void onClientLeave(ClientLeaveEvent e) {
}
@Override
public void onServerEdit(ServerEditedEvent e) {
}
@Override
public void onChannelEdit(ChannelEditedEvent e) {
}
@Override
public void onChannelDescriptionChanged(ChannelDescriptionEditedEvent e) {
}
@Override
public void onClientMoved(ClientMovedEvent e) {
}
@Override
public void onChannelCreate(ChannelCreateEvent e) {
}
@Override
public void onChannelDeleted(ChannelDeletedEvent e) {
}
@Override
public void onChannelMoved(ChannelMovedEvent e) {
}
@Override
public void onChannelPasswordChanged(ChannelPasswordChangedEvent e) {
}
@Override
public void onChannelList(ChannelListEvent e) {
}
@Override
public void onPrivilegeKeyUsed(PrivilegeKeyUsedEvent e) {
}
@Override
public void onChannelGroupList(ChannelGroupListEvent e) {
}
@Override
public void onServerGroupList(ServerGroupListEvent e) {
}
@Override
public void onClientNeededPermissions(ClientNeededPermissionsEvent e) {
}
@Override
public void onClientChannelGroupChanged(ClientChannelGroupChangedEvent e) {
}
@Override
public void onClientChanged(ClientUpdatedEvent e) {
}
@Override
public void onDisconnected(DisconnectedEvent e) {
}
@Override
public void onChannelSubscribed(ChannelSubscribedEvent e) {
}
});
assertEquals(client.getState(), ClientConnectionState.CONNECTED);
client.subscribeAll();
try {
client.setVoiceHandler(packetBody0Voice -> {
System.err.println("clientId=" + packetBody0Voice.getClientId());
System.err.println("packetId=" + packetBody0Voice.getPacketId());
System.err.println("type=" + packetBody0Voice.getCodecType());
System.err.println("len=" + packetBody0Voice.getCodecData().length);
});
} catch (Exception ex) {
ex.printStackTrace();
client.disconnect();
}
}
}
@@ -0,0 +1,27 @@
package com.github.manevolent.ts3j;
import com.github.manevolent.ts3j.protocol.TS3DNS;
import com.github.manevolent.ts3j.util.Ts3Debugging;
import junit.framework.TestCase;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.List;
public class TS3DNSTest extends TestCase {
public static void main(String[] args) throws Exception {
new TS3DNSTest().testParser();
}
public void testParser() throws Exception {
Ts3Debugging.setEnabled(true);
List<InetSocketAddress> soaddr = TS3DNS.lookup("voice.teamspeak.com");
assertTrue(soaddr.size() > 0);
for (SocketAddress socketAddress : soaddr)
Ts3Debugging.debug(socketAddress);
}
}