me.ferrybig.javacoding.teamspeakconnector.TeamspeakConnection.java Source code

Java tutorial

Introduction

Here is the source code for me.ferrybig.javacoding.teamspeakconnector.TeamspeakConnection.java

Source

/*
 * The MIT License
 *
 * Copyright 2017 Fernando van Loenhout.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package me.ferrybig.javacoding.teamspeakconnector;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.util.concurrent.Future;
import java.io.Closeable;
import static java.lang.Integer.parseInt;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.logging.Level;
import java.util.logging.Logger;
import me.ferrybig.javacoding.teamspeakconnector.event.ChannelMessageEvent;
import me.ferrybig.javacoding.teamspeakconnector.event.ChannelMessageListener;
import me.ferrybig.javacoding.teamspeakconnector.event.ChangeReason;
import me.ferrybig.javacoding.teamspeakconnector.event.ClientEnterViewEvent;
import me.ferrybig.javacoding.teamspeakconnector.event.Handler;
import me.ferrybig.javacoding.teamspeakconnector.event.PrivateMessageEvent;
import me.ferrybig.javacoding.teamspeakconnector.event.PrivateMessageListener;
import me.ferrybig.javacoding.teamspeakconnector.event.ServerEditEvent;
import me.ferrybig.javacoding.teamspeakconnector.event.ServerListener;
import me.ferrybig.javacoding.teamspeakconnector.event.ServerMessageEvent;
import me.ferrybig.javacoding.teamspeakconnector.event.ServerMessageListener;
import me.ferrybig.javacoding.teamspeakconnector.event.TokenListener;
import me.ferrybig.javacoding.teamspeakconnector.event.TokenUsedEvent;
import me.ferrybig.javacoding.teamspeakconnector.internal.SendBehaviour;
import me.ferrybig.javacoding.teamspeakconnector.internal.SubscriptionHandler;
import me.ferrybig.javacoding.teamspeakconnector.internal.TeamspeakIO;
import me.ferrybig.javacoding.teamspeakconnector.internal.packets.ComplexRequestBuilder;
import me.ferrybig.javacoding.teamspeakconnector.internal.packets.Response;

public class TeamspeakConnection implements Closeable {

    private static final Logger LOG = Logger.getLogger(TeamspeakConnection.class.getName());
    private final TeamspeakIO io;
    private final SubscriptionHandler<ServerMessageListener> serverMessageHandler = new SubscriptionHandler<>(this,
            new ComplexRequestBuilder("servernotifyregister").addData("event", "textserver").build(),
            new ComplexRequestBuilder("servernotifyunregister").addData("event", "textserver").build());
    private final SubscriptionHandler<PrivateMessageListener> privateMessageHandler = new SubscriptionHandler<>(
            this, new ComplexRequestBuilder("servernotifyregister").addData("event", "textprivate").build(),
            new ComplexRequestBuilder("servernotifyunregister").addData("event", "textprivate").build());
    private final SubscriptionHandler<ChannelMessageListener> channelMessageHandler = new SubscriptionHandler<>(
            this, new ComplexRequestBuilder("servernotifyregister").addData("event", "textchannel").build(),
            new ComplexRequestBuilder("servernotifyunregister").addData("event", "textchannel").build());
    private final SubscriptionHandler<ServerListener> serverHandler = new SubscriptionHandler<>(this,
            new ComplexRequestBuilder("servernotifyregister").addData("event", "server").build(),
            new ComplexRequestBuilder("servernotifyunregister").addData("event", "server").build());
    private final SubscriptionHandler<TokenListener> tokenUsedHandler = new SubscriptionHandler<>(this,
            new ComplexRequestBuilder("servernotifyregister").addData("event", "tokenused").build(),
            new ComplexRequestBuilder("servernotifyunregister").addData("event", "tokenused").build());

    public TeamspeakConnection(TeamspeakIO channel) {
        this.io = channel;
    }

    public final TeamspeakIO io() {
        return io;
    }

    public void start() {
        this.io.registerConnection(this);
        this.io.start();
        this.io.getChannel().pipeline().addLast(new SimpleChannelInboundHandler<Response>() {

            private Response lastPacket = null;
            //notifyclientleftview cfid=1 ctid=0 reasonid=8 reasonmsg=leaving clid=1
            //notifycliententerview cfid=0 ctid=1 reasonid=0 clid=4 client_unique_identifier=P+m\/uXn4o2nLwN5gOimuQcfQZIQ= client_nickname=Ferrybig client_input_muted=0 client_output_muted=0 client_outputonly_muted=0 client_input_hardware=0 client_output_hardware=1 client_meta_data client_is_recording=0 client_database_id=2 client_channel_group_id=5 client_servergroups=6,10 client_away=0 client_away_message client_type=0 client_flag_avatar=12f359409d033f5eebcc821a5dbcecf5 client_talk_power=75 client_talk_request=0 client_talk_request_msg client_description client_is_talker=0 client_is_priority_speaker=0 client_unread_messages=0 client_nickname_phonetic=Ferrybig client_needed_serverquery_view_power=75 client_icon_id=0 client_is_channel_commander=0 client_country client_channel_group_inherited_channel_id=1 client_badges=Overwolf=0
            //notifyclientleftview cfid=72 ctid=0 reasonid=8 reasonmsg=leaving clid=4
            //notifycliententerview cfid=0 ctid=1 reasonid=0 clid=5 client_unique_identifier=P+m\/uXn4o2nLwN5gOimuQcfQZIQ= client_nickname=Ferrybig client_input_muted=0 client_output_muted=0 client_outputonly_muted=0 client_input_hardware=0 client_output_hardware=1 client_meta_data client_is_recording=0 client_database_id=2 client_channel_group_id=5 client_servergroups=6,10 client_away=0 client_away_message client_type=0 client_flag_avatar=12f359409d033f5eebcc821a5dbcecf5 client_talk_power=75 client_talk_request=0 client_talk_request_msg client_description client_is_talker=0 client_is_priority_speaker=0 client_unread_messages=0 client_nickname_phonetic=Ferrybig client_needed_serverquery_view_power=75 client_icon_id=0 client_is_channel_commander=0 client_country client_channel_group_inherited_channel_id=1 client_badges=Overwolf=0

            @Override
            protected void messageReceived(ChannelHandlerContext ctx, Response msg) throws Exception {
                final Map<String, String> options = msg.getOptions();
                LOG.log(Level.FINE, "Handling packet: {0}", msg);
                switch (msg.getCmd()) {
                case "notifytextmessage": {
                    Future<User> whoami = io.whoAmI();
                    if (whoami.isSuccess()) {
                        handleMessage(msg, whoami.get());
                    } else {
                        whoami.addListener(f -> {
                            assert f == whoami;
                            LOG.fine("Handling delayed message delivery because whoami is not known");
                            handleMessage(msg, whoami.get());
                        });
                    }
                }
                    break;
                case "notifycliententerview": {
                    if (msg.equals(lastPacket)) {
                        LOG.log(Level.FINE,
                                "Dropping packet {0} because teamspeak usually sends dublicate packets when both channel and server listener is active",
                                msg);
                        lastPacket = null;
                        return;
                    }
                    lastPacket = msg;
                    options.put("cid", options.get("ctid"));
                    ShallowUser client = io.mapShallowUser(options);
                    UnresolvedChannel from = "0".equals(options.get("cfid")) ? null
                            : getUnresolvedChannelById(parseInt(options.get("cfid")));
                    ChangeReason reason = ChangeReason.getById(parseInt(options.get("reasonid")));
                    ClientEnterViewEvent event;
                    if (true && from == null) {
                        event = new ClientEnterViewEvent(client, client.getChannel(), reason, null);
                        serverHandler.callAll(ServerListener::onClientEnterView, event);
                    } else {
                        // TODO: channel change event
                    }

                }
                    break;
                case "notifyclientleftview": {
                    // TODO: leave notification
                }
                    break;
                case "notifyserveredited": {
                    final int invokerId = Integer.parseInt(options.get("invokerid"));
                    final String invokerName = options.get("invokername");
                    final String invokeruid = options.get("invokeruid");
                    serverHandler.callAll(ServerListener::onEditServer,
                            new ServerEditEvent(ChangeReason.getById(parseInt(options.get("reasonid"))),
                                    getUnresolvedNamedUser(invokerId, invokerName, invokeruid)));
                }
                    break;
                case "notifytokenused": {
                    // clid=5 cldbid=4 cluid=zhPQ0oNLH8boM42jlbgTWC6G\\/64= token=4oquHhp03YKofI4dYVBLWZ9Ik+Mf0M6ogomh5RsU tokencustomset token1=7 token2=0
                    final UnresolvedUser client = getUnresolvedUserById(parseInt(options.get("clid")));
                    final int databaseId = parseInt(options.get("cldbid"));
                    final String uniqueId = options.get("cluid");
                    final String token = options.get("token");
                    final String tokencustomset = options.get("tokencustomset");
                    final String token1 = options.get("token1");
                    final String token2 = options.get("token2");
                    tokenUsedHandler.callAll(TokenListener::onTokenUsed, new TokenUsedEvent(client, databaseId,
                            uniqueId, token, tokencustomset, token1, token2));
                }
                    break;
                default: {
                    LOG.log(Level.WARNING, "Unhandled packet: {0}", msg);
                }
                }
            }

        });
    }

    protected void handleMessage(Response msg, User whoAmI) {
        LOG.log(Level.FINEST, "Who I am: {0}", whoAmI);
        final Map<String, String> options = msg.getOptions();
        final int invokerId = Integer.parseInt(options.get("invokerid"));
        if (invokerId == whoAmI.getId()) {
            LOG.finer("Dropped packet coming from our user");
            return;
        }

        final String message = options.get("msg");
        final String invokerName = options.get("invokername");
        final String invokeruid = options.get("invokeruid");
        final NamedUser invoker = invokerId == 0 ? null
                : getUnresolvedNamedUser(invokerId, invokerName, invokeruid);
        switch (parseInt(options.get("targetmode"))) {
        case 1: {
            privateMessageHandler.callAll(PrivateMessageListener::onPrivateMessage, new PrivateMessageEvent(
                    getUnresolvedUserById(parseInt(options.get("target"))), message, invoker));
        }
            break;
        case 2: {
            channelMessageHandler.callAll(ChannelMessageListener::onChannelMessage,
                    new ChannelMessageEvent(message, invoker));
        }
            break;
        case 3: {
            serverMessageHandler.callAll(ServerMessageListener::onServerMessage,
                    new ServerMessageEvent(message, invoker));
        }
            break;
        default: {
            assert false : "Target mode " + options.get("targetmode") + " invalid";
        }
        }

    }

    public Handler<ServerMessageListener> getServerMessageHandler() {
        return serverMessageHandler;
    }

    public Handler<PrivateMessageListener> getPrivateMessageHandler() {
        return privateMessageHandler;
    }

    public Handler<ChannelMessageListener> getChannelMessageHandler() {
        return channelMessageHandler;
    }

    public Handler<ServerListener> getServerHandler() {
        return serverHandler;
    }

    public UnresolvedChannel getUnresolvedChannelById(int id) {
        return new UnresolvedChannel(this, id);
    }

    public UnresolvedGroup getUnresolvedGroupById(int id) {
        return null; // TODO
    }

    public UnresolvedChannelGroup getUnresolvedChannelGroupById(int id) {
        return null; // TODO
    }

    public Future<UnresolvedFile> getUnresolvedFileByChannelAndName(UnresolvedChannel channel, String name) {
        throw new UnsupportedOperationException(); // TODO;
    }

    public Future<File> getFileByChannelAndName(UnresolvedChannel channel, String name) {
        throw new UnsupportedOperationException(); // TODO;
    }

    public UnresolvedServer getUnresolvedServerById(int id) {
        return new UnresolvedServer(this, id);
    }

    public Future<User> getUserById(int id) {
        return io.mapComplexReponse(
                io.sendPacket(new ComplexRequestBuilder("clientinfo").addData("clid", String.valueOf(id)).build()),
                io::mapUser);
    }

    public UnresolvedUser getUnresolvedUserById(int id) {
        return new UnresolvedUser(this, id);
    }

    public NamedUser getUnresolvedNamedUser(int id, String nickname, String uniqueId) {
        return new NamedUser(this, id, nickname, uniqueId);
    }

    public Future<Channel> getChannelById(int id) {
        return io.mapComplexReponse(
                io.sendPacket(new ComplexRequestBuilder("channelinfo").addData("cid", String.valueOf(id)).build()),
                m -> {
                    m.put("cid", String.valueOf(id)); // This is needed because teamspeak doesn't repeat our send channel id
                    return io.mapChannel(m);
                });
    }

    public Future<Server> getServer() {
        return io.mapComplexReponse(io.sendPacket(new ComplexRequestBuilder("serverinfo").build()), io::mapServer);
    }

    public Future<TeamspeakConnection> login(String username, String password) {
        return io.chainFuture(io.sendPacket(new ComplexRequestBuilder("login")
                .addData("client_login_name", username).addData("client_login_password", password).build()),
                packet -> this);
    }

    public Future<?> shutdownServer() {
        return io.sendPacket(new ComplexRequestBuilder("serverprocessstop").build(),
                SendBehaviour.CLOSE_CONNECTION);
    }

    public Future<TeamspeakConnection> logout() {
        return io.chainFuture(io.sendPacket(new ComplexRequestBuilder("quit").build()), packet -> this);
    }

    public Future<List<Server>> getServerList() {
        return io.mapComplexReponseList(
                io.sendPacket(new ComplexRequestBuilder("serverlist").addOption("virtual").build()), io::mapServer);
    }

    public Future<List<Channel>> getChannelList() {
        return io.mapComplexReponseList(io.sendPacket(new ComplexRequestBuilder("channellist").addOption("topic")
                .addOption("flags").addOption("voice").addOption("limits").addOption("icon").build()),
                io::mapChannel);
    }

    public Future<List<User>> getUsersList() {
        return io.mapComplexReponseList(io.sendPacket(new ComplexRequestBuilder("clientlist").addOption("uid")
                .addOption("away").addOption("voice").addOption("groups").addOption("times").addOption("info")
                .addOption("icon").addOption("country").addOption("ip").build()), io::mapUser);
    }

    @Override
    public void close() throws TeamspeakException {
        try {
            io.sendPacket(new ComplexRequestBuilder("quit").build(), SendBehaviour.FORCE_CLOSE_CONNECTION).get();
        } catch (InterruptedException | ExecutionException ex) {
            if (ex instanceof InterruptedException) {
                Thread.currentThread().interrupt();
            }
            throw new TeamspeakException(ex);
        }
    }

    public Future<?> quit() {
        return io.sendPacket(new ComplexRequestBuilder("quit").build(), SendBehaviour.FORCE_CLOSE_CONNECTION);
    }

    public Future<?> setOwnName(String name) {
        return io.sendPacket(new ComplexRequestBuilder("clientupdate").addData("client_nickname", name).build());
    }

}