All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.riversun.xternal.simpleslackapi.impl.SlackWebSocketSessionImpl Maven / Gradle / Ivy

There is a newer version: 1.0.4
Show newest version
package org.riversun.xternal.simpleslackapi.impl;

import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;

import org.apache.http.HttpHost;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.DefaultProxyRoutePlanner;
import org.apache.http.message.BasicNameValuePair;
import org.riversun.xternal.log.Logger;
import org.riversun.xternal.log.LoggerFactory;
import org.riversun.xternal.simpleslackapi.*;
import org.riversun.xternal.simpleslackapi.SlackChatConfiguration.Avatar;
import org.riversun.xternal.simpleslackapi.events.*;
import org.riversun.xternal.simpleslackapi.listeners.PresenceChangeListener;
import org.riversun.xternal.simpleslackapi.listeners.SlackChannelArchivedListener;
import org.riversun.xternal.simpleslackapi.listeners.SlackChannelCreatedListener;
import org.riversun.xternal.simpleslackapi.listeners.SlackChannelDeletedListener;
import org.riversun.xternal.simpleslackapi.listeners.SlackChannelRenamedListener;
import org.riversun.xternal.simpleslackapi.listeners.SlackChannelUnarchivedListener;
import org.riversun.xternal.simpleslackapi.listeners.SlackEventListener;
import org.riversun.xternal.simpleslackapi.listeners.SlackTeamJoinListener;
import org.riversun.xternal.simpleslackapi.listeners.SlackUserChangeListener;
import org.riversun.xternal.simpleslackapi.replies.*;
import org.riversun.xternal.simpleslackapi.utils.ReaderUtils;

import javax.websocket.*;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.ConnectException;
import java.net.Proxy;
import java.net.URI;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;

class SlackWebSocketSessionImpl extends AbstractSlackSessionImpl implements SlackSession, MessageHandler.Whole {
    private static final String SLACK_API_SCHEME = "https";

    private static final String SLACK_API_HOST = "slack.com";

    private static final String SLACK_API_PATH = "/api";

    private static final String SLACK_API_HTTPS_ROOT      = SLACK_API_SCHEME + "://" + SLACK_API_HOST + SLACK_API_PATH + "/";

    private static final String DIRECT_MESSAGE_OPEN_CHANNEL_COMMAND = "im.open";

    private static final String MULTIPARTY_DIRECT_MESSAGE_OPEN_CHANNEL_COMMAND = "mpim.open";

    private static final String CHANNELS_LEAVE_COMMAND    = "channels.leave";

    private static final String CHANNELS_JOIN_COMMAND     = "channels.join";

    private static final String CHANNELS_SET_TOPIC_COMMAND     = "channels.setTopic";
    
    private static final String CHANNELS_INVITE_COMMAND     = "channels.invite";
    
    private static final String CHANNELS_ARCHIVE_COMMAND     = "channels.archive";

    private static final String CHANNELS_UNARCHIVE_COMMAND     = "channels.unarchive";

    private static final String CHAT_POST_MESSAGE_COMMAND = "chat.postMessage";

    private static final String FILE_UPLOAD_COMMAND       = "files.upload";

    private static final String CHAT_DELETE_COMMAND       = "chat.delete";

    private static final String CHAT_UPDATE_COMMAND       = "chat.update";

    private static final String REACTIONS_ADD_COMMAND     = "reactions.add";

    private static final String REACTIONS_REMOVE_COMMAND     = "reactions.remove";

    private static final String INVITE_USER_COMMAND     = "users.admin.invite";

    private static final String SET_PERSONA_ACTIVE = "users.setPresence";

    private static final String LIST_EMOJI_COMMAND = "emoji.list";

    private static final String LIST_USERS = "users.list";


    private static final Logger               LOGGER                     = LoggerFactory.getLogger(SlackWebSocketSessionImpl.class);

    private static final String               SLACK_HTTPS_AUTH_URL       = "https://slack.com/api/rtm.start?token=";

    private  static final int                 DEFAULT_HEARTBEAT_IN_MILLIS = 30000;

    private volatile Session                  websocketSession;
    private String                            authToken;
    private String                            proxyAddress;
    private int                               proxyPort                  = -1;
    HttpHost                                  proxyHost;
    private volatile long                     lastPingSent;
    private volatile long                     lastPingAck;

    private AtomicLong                        messageId = new AtomicLong();

    private final boolean                     reconnectOnDisconnection;
    private volatile boolean                  wantDisconnect;

    private Thread                            connectionMonitoringThread;
    private EventDispatcher                   dispatcher                 = new EventDispatcher();
    private final long                        heartbeat;
    private WebSocketContainerProvider        webSocketContainerProvider;
    private volatile String                   webSocketConnectionURL;

    @Override
    public SlackMessageHandle sendMessageToUser(SlackUser user, SlackPreparedMessage message) {
        SlackChannel iMChannel = getIMChannelForUser(user);
        return sendMessage(iMChannel, message);
    }

    @Override
    public SlackMessageHandle sendMessageToUser(SlackUser user, String message, SlackAttachment attachment) {
        SlackChannel iMChannel = getIMChannelForUser(user);
        return sendMessage(iMChannel, message, attachment, DEFAULT_CONFIGURATION);
    }

    @Override
    public SlackMessageHandle sendMessageToUser(String userName, String message, SlackAttachment attachment) {
        return sendMessageToUser(findUserByUserName(userName), message, attachment);
    }

    private List getAllIMChannels() {
        Collection allChannels = getChannels();
        List iMChannels = new ArrayList<>();
        for (SlackChannel channel : allChannels) {
            if (channel.isDirect()) {
                iMChannels.add(channel);
            }
        }
        return iMChannels;
    }

    private SlackChannel getIMChannelForUser(SlackUser user) {
        List imcs = getAllIMChannels();
        for (SlackChannel channel : imcs) {
            if (channel.getMembers().contains(user)) {
                return channel;
            }
        }
        SlackMessageHandle reply = openDirectMessageChannel(user);
        return reply.getReply().getSlackChannel();
    }

    public class EventDispatcher {

        void dispatch(SlackEvent event) {
            switch (event.getEventType()) {
                case SLACK_CHANNEL_ARCHIVED:
                    dispatchImpl((SlackChannelArchived) event, channelArchiveListener);
                    break;
                case SLACK_CHANNEL_CREATED:
                    dispatchImpl((SlackChannelCreated) event, channelCreateListener);
                    break;
                case SLACK_CHANNEL_DELETED:
                    dispatchImpl((SlackChannelDeleted) event, channelDeleteListener);
                    break;
                case SLACK_CHANNEL_RENAMED:
                    dispatchImpl((SlackChannelRenamed) event, channelRenamedListener);
                    break;
                case SLACK_CHANNEL_UNARCHIVED:
                    dispatchImpl((SlackChannelUnarchived) event, channelUnarchiveListener);
                    break;
                case SLACK_CHANNEL_JOINED:
                    dispatchImpl((SlackChannelJoined) event, channelJoinedListener);
                    break;
                case SLACK_CHANNEL_LEFT:
                    dispatchImpl((SlackChannelLeft) event, channelLeftListener);
                    break;
                case SLACK_GROUP_JOINED:
                    dispatchImpl((SlackGroupJoined) event, groupJoinedListener);
                    break;
                case SLACK_MESSAGE_DELETED:
                    dispatchImpl((SlackMessageDeleted) event, messageDeletedListener);
                    break;
                case SLACK_MESSAGE_POSTED:
                    dispatchImpl((SlackMessagePosted) event, messagePostedListener);
                    break;
                case SLACK_MESSAGE_UPDATED:
                    dispatchImpl((SlackMessageUpdated) event, messageUpdatedListener);
                    break;
                case SLACK_CONNECTED:
                    dispatchImpl((SlackConnected) event, slackConnectedListener);
                    break;
                case REACTION_ADDED:
                    dispatchImpl((ReactionAdded) event, reactionAddedListener);
                    break;
                case REACTION_REMOVED:
                    dispatchImpl((ReactionRemoved) event, reactionRemovedListener);
                    break;
                case SLACK_USER_CHANGE:
                    dispatchImpl((SlackUserChange) event, slackUserChangeListener);
                    break;
                case SLACK_TEAM_JOIN:
                    dispatchImpl((SlackTeamJoin) event, slackTeamJoinListener);
                    break;
                case PIN_ADDED:
                    dispatchImpl((PinAdded) event, pinAddedListener);
                    break;
                case PIN_REMOVED:
                    dispatchImpl((PinRemoved) event, pinRemovedListener);
                    break;
                case PRESENCE_CHANGE:
                    dispatchImpl((PresenceChange) event, presenceChangeListener);
                    break;
                case SLACK_DISCONNECTED:
                    dispatchImpl((SlackDisconnected) event, slackDisconnectedListener);
                    break;
                case USER_TYPING:
                    dispatchImpl((UserTyping) event, userTypingListener);
                    break;
                case UNKNOWN:
                    LOGGER.warn("event of type " + event.getEventType() + " not handled: " + event);
            }
        }

        private > void dispatchImpl(E event, List listeners) {
            for (L listener : listeners) {
                try {
                    listener.onEvent(event, SlackWebSocketSessionImpl.this);
                } catch (Throwable thr) {
                    LOGGER.error("caught exception in dispatchImpl", thr);
                }
            }
        }
    }

    SlackWebSocketSessionImpl(WebSocketContainerProvider webSocketContainerProvider, String authToken, boolean reconnectOnDisconnection, long heartbeat, TimeUnit unit) {
        this.authToken = authToken;
        this.reconnectOnDisconnection = reconnectOnDisconnection;
        this.heartbeat = heartbeat != 0 ? unit.toMillis(heartbeat) : 30000;
        this.webSocketContainerProvider = webSocketContainerProvider != null ? webSocketContainerProvider : new DefaultWebSocketContainerProvider(null,0);
        addInternalListeners();
    }

    SlackWebSocketSessionImpl(WebSocketContainerProvider webSocketContainerProvider, String authToken, Proxy.Type proxyType, String proxyAddress, int proxyPort, boolean reconnectOnDisconnection, long heartbeat, TimeUnit unit) {
        this.authToken = authToken;
        this.proxyAddress = proxyAddress;
        this.proxyPort = proxyPort;
        this.proxyHost = new HttpHost(proxyAddress, proxyPort);
        this.reconnectOnDisconnection = reconnectOnDisconnection;
        this.heartbeat = heartbeat != 0 ? unit.toMillis(heartbeat) : DEFAULT_HEARTBEAT_IN_MILLIS;
        this.webSocketContainerProvider = webSocketContainerProvider != null ? webSocketContainerProvider : new DefaultWebSocketContainerProvider(proxyAddress,proxyPort);
        addInternalListeners();
    }

    private void addInternalListeners()
    {
        addPresenceChangeListener(INTERNAL_PRESENCE_CHANGE_LISTENER);
        addChannelArchivedListener(INTERNAL_CHANNEL_ARCHIVE_LISTENER);
        addChannelCreatedListener(INTERNAL_CHANNEL_CREATED_LISTENER);
        addChannelDeletedListener(INTERNAL_CHANNEL_DELETED_LISTENER);
        addChannelRenamedListener(INTERNAL_CHANNEL_RENAMED_LISTENER);
        addChannelUnarchivedListener(INTERNAL_CHANNEL_UNARCHIVED_LISTENER);
        addSlackTeamJoinListener(INTERNAL_TEAM_JOIN_LISTENER);
        addSlackUserChangeListener(INTERNAL_USER_CHANGE_LISTENER);
    }

    @Override
    public void connect() throws IOException {
        wantDisconnect = false;
        connectImpl();
        LOGGER.debug("starting actions monitoring");
        startConnectionMonitoring();
    }

    @Override
    public void disconnect() {
        wantDisconnect = true;
        LOGGER.debug("Disconnecting from the Slack server");
        disconnectImpl();
        stopConnectionMonitoring();
    }
    @Override
    public boolean isConnected()
    {
        return websocketSession != null && websocketSession.isOpen();
    }

    private void connectImpl() throws IOException
    {
        LOGGER.info("connecting to slack");
        HttpClient httpClient = getHttpClient();
        HttpGet request = new HttpGet(SLACK_HTTPS_AUTH_URL + authToken);
        HttpResponse response;
        response = httpClient.execute(request);
        LOGGER.debug(response.getStatusLine().toString());
        String jsonResponse = consumeToString(response.getEntity().getContent());
        SlackJSONSessionStatusParser sessionParser = new SlackJSONSessionStatusParser(jsonResponse);
        sessionParser.parse();
        if (sessionParser.getError() != null)
        {
            LOGGER.error("Error during authentication : " + sessionParser.getError());
            throw new ConnectException(sessionParser.getError());
        }

        users = sessionParser.getUsers();
        integrations = sessionParser.getIntegrations();
        channels = sessionParser.getChannels();
        sessionPersona = sessionParser.getSessionPersona();
        team = sessionParser.getTeam();
        LOGGER.info("Team " + team.getId() + " : " + team.getName());
        LOGGER.info("Self " + sessionPersona.getId() + " : " + sessionPersona.getUserName());
        LOGGER.info(users.size() + " users found on this session");
        LOGGER.info(channels.size() + " channels found on this session");
        webSocketConnectionURL = sessionParser.getWebSocketURL();
        LOGGER.debug("retrieved websocket URL : " + webSocketConnectionURL);
        establishWebsocketConnection();
    }

    private void establishWebsocketConnection() throws IOException
    {
        lastPingSent = 0;
        lastPingAck = 0;
        WebSocketContainer client = webSocketContainerProvider.getWebSocketContainer();
        final MessageHandler handler = this;
        LOGGER.debug("initiating actions to websocket");

        try {
            websocketSession = client.connectToServer(new Endpoint()
            {
                @Override
                public void onOpen(Session session, EndpointConfig config)
                {
                    session.addMessageHandler(handler);
                }

                @Override
                public void onError(Session session, Throwable thr) {
                    LOGGER.error("Endpoint#onError called", thr);
                    websocketSession = null;
                }

            }, URI.create(webSocketConnectionURL));
        }
        catch (DeploymentException e) {
            LOGGER.error(e.toString());
            throw new IOException(e);
        }
        if (websocketSession != null) {
            SlackConnectedImpl slackConnectedImpl = new SlackConnectedImpl(sessionPersona);
            dispatcher.dispatch(slackConnectedImpl);
            LOGGER.debug("websocket actions established");
            LOGGER.info("slack session ready");
        } else {
            throw new IOException("Unable to establish a connection to this websocket URL " + webSocketConnectionURL);
        }
    }

    private String consumeToString(InputStream content) throws IOException
    {
        Reader reader = new InputStreamReader(content, "UTF-8");
        StringBuffer buf = new StringBuffer();
        char data[] = new char[16384];
        int numread;
        while (0 <= (numread = reader.read(data)))
            buf.append(data, 0, numread);
        return buf.toString();
    }


    private void disconnectImpl()
    {
        if (websocketSession != null)
        {
            try
            {
                websocketSession.close();
            }
            catch (IOException ex) {
                // ignored.
            }
            finally {
                SlackDisconnectedImpl slackDisconnected = new SlackDisconnectedImpl(sessionPersona);
                dispatcher.dispatch(slackDisconnected);
                websocketSession = null;
            }
        }
    }

    private void startConnectionMonitoring() {
        connectionMonitoringThread = new Thread() {
            @Override
            public void run() {
                LOGGER.debug("monitoring thread started");
                while (true) {
                    try {
                        Thread.sleep(heartbeat);

                        // disconnect() was called.
                        if (wantDisconnect) {
                            this.interrupt();
                        }

                        if (lastPingSent != lastPingAck || websocketSession == null) {
                            // disconnection happened
                            LOGGER.warn("Connection lost...");
                            try {
                                if (websocketSession != null)
                                {
                                    websocketSession.close();
                                }
                            }
                            catch (IOException e) {
                                LOGGER.error("exception while trying to close the websocket ", e);
                            }
                            websocketSession = null;
                            if (reconnectOnDisconnection) {
                                establishWebsocketConnection();
                            }
                            else {
                                this.interrupt();
                            }
                        }
                        else {
                            lastPingSent = getNextMessageId();
                            LOGGER.debug("sending ping " + lastPingSent);
                            try {
                                if (websocketSession.isOpen()) {
                                    websocketSession.getBasicRemote().sendText("{\"type\":\"ping\",\"id\":" + lastPingSent + "}");
                                }
                                else if (reconnectOnDisconnection) {
                                    establishWebsocketConnection();
                                }
                            }
                            catch (IllegalStateException e) {
                                LOGGER.warn("exception caught while using websocket ", e);
                                // websocketSession might be closed in this case
                                if (reconnectOnDisconnection) {
                                    establishWebsocketConnection();
                                }
                            }
                        }
                    }
                    catch (InterruptedException e) {
                        LOGGER.info("monitoring thread interrupted");
                        break;
                    }
                    catch (IOException e) {
                        LOGGER.error("unexpected exception on monitoring thread ", e);
                    }
                }
                LOGGER.debug("monitoring thread stopped");
            }
        };

        if (!wantDisconnect) {
            connectionMonitoringThread.start();
        }
    }

    private void stopConnectionMonitoring() {
        if (connectionMonitoringThread != null) {
            while (true) {
                try {
                    connectionMonitoringThread.interrupt();
                    connectionMonitoringThread.join();
                    break;
                }
                catch (InterruptedException ex) {
                    // ouch - let's try again!
                }
            }
        }
    }

    @Override
    public SlackMessageHandle sendMessage(SlackChannel channel, SlackPreparedMessage preparedMessage, SlackChatConfiguration chatConfiguration) {
        SlackMessageHandleImpl handle = new SlackMessageHandleImpl<>(getNextMessageId());
        Map arguments = new HashMap<>();
        arguments.put("token", authToken);
        arguments.put("channel", channel.getId());
        arguments.put("text", preparedMessage.getMessage());
        if (chatConfiguration.isAsUser())
        {
            arguments.put("as_user", "true");
        }
        if (chatConfiguration.getAvatar() == Avatar.ICON_URL)
        {
            arguments.put("icon_url", chatConfiguration.getAvatarDescription());
        }
        if (chatConfiguration.getAvatar() == Avatar.EMOJI)
        {
            arguments.put("icon_emoji", chatConfiguration.getAvatarDescription());
        }
        if (chatConfiguration.getUserName() != null)
        {
            arguments.put("username", chatConfiguration.getUserName());
        }
        if (preparedMessage.getAttachments() != null && preparedMessage.getAttachments().length > 0)
        {
            arguments.put("attachments", SlackJSONAttachmentFormatter
                    .encodeAttachments(preparedMessage.getAttachments()).toString());
        }
        if (!preparedMessage.isUnfurl())
        {
            arguments.put("unfurl_links", "false");
            arguments.put("unfurl_media", "false");
        }
        if (preparedMessage.isLinkNames())
        {
            arguments.put("link_names", "1");
        }

        postSlackCommand(arguments, CHAT_POST_MESSAGE_COMMAND, handle);
        return handle;
    }

    @Override
    public SlackMessageHandle sendFileToUser(String userName, byte[] data, String fileName) {
        return sendFileToUser(findUserByUserName(userName), data, fileName);
    }

    @Override
    public SlackMessageHandle sendFileToUser(SlackUser user, byte[] data, String fileName) {
        SlackChannel iMChannel = getIMChannelForUser(user);
        return sendFile(iMChannel, data, fileName);
    }

    @Override
    public SlackMessageHandle sendFile(SlackChannel channel, byte[] data, String fileName) {
        SlackMessageHandleImpl handle = new SlackMessageHandleImpl<>(getNextMessageId());
        Map arguments = new HashMap<>();
        arguments.put("token", authToken);
        arguments.put("channels", channel.getId());
        arguments.put("filename", fileName);
        postSlackCommandWithFile(arguments, data, fileName,FILE_UPLOAD_COMMAND, handle);
        return handle;
    }

    @Override
    public SlackMessageHandle deleteMessage(String timeStamp, SlackChannel channel) {
        SlackMessageHandleImpl handle = new SlackMessageHandleImpl<>(getNextMessageId());
        Map arguments = new HashMap<>();
        arguments.put("token", authToken);
        arguments.put("channel", channel.getId());
        arguments.put("ts", timeStamp);
        postSlackCommand(arguments, CHAT_DELETE_COMMAND, handle);
        return handle;
    }

    @Override
    public SlackMessageHandle updateMessage(String timeStamp, SlackChannel channel, String message) {
        SlackMessageHandleImpl handle = new SlackMessageHandleImpl<>(getNextMessageId());
        Map arguments = new HashMap<>();
        arguments.put("token", authToken);
        arguments.put("ts", timeStamp);
        arguments.put("channel", channel.getId());
        arguments.put("text", message);
        postSlackCommand(arguments, CHAT_UPDATE_COMMAND, handle);
        return handle;
    }

    @Override
    public SlackMessageHandle addReactionToMessage(SlackChannel channel, String messageTimeStamp, String emojiCode) {
        SlackMessageHandleImpl handle = new SlackMessageHandleImpl<>(getNextMessageId());
        Map arguments = new HashMap<>();
        arguments.put("token", authToken);
        arguments.put("channel", channel.getId());
        arguments.put("timestamp", messageTimeStamp);
        arguments.put("name", emojiCode);
        postSlackCommand(arguments, REACTIONS_ADD_COMMAND, handle);
        return handle;
    }

    @Override
    public SlackMessageHandle removeReactionFromMessage(SlackChannel channel, String messageTimeStamp, String emojiCode) {
        SlackMessageHandleImpl handle = new SlackMessageHandleImpl<>(getNextMessageId());
        Map arguments = new HashMap<>();
        arguments.put("token", authToken);
        arguments.put("channel", channel.getId());
        arguments.put("timestamp", messageTimeStamp);
        arguments.put("name", emojiCode);
        postSlackCommand(arguments, REACTIONS_REMOVE_COMMAND, handle);
        return handle;
    }

    @Override
    public SlackMessageHandle joinChannel(String channelName) {
        SlackMessageHandleImpl handle = new SlackMessageHandleImpl<>(getNextMessageId());
        Map arguments = new HashMap<>();
        arguments.put("token", authToken);
        arguments.put("name", channelName);
        postSlackCommand(arguments, CHANNELS_JOIN_COMMAND, handle);
        return handle;
    }

    @Override
    public SlackMessageHandle setChannelTopic(SlackChannel channel, String topic) {
        SlackMessageHandleImpl handle = new SlackMessageHandleImpl<>(getNextMessageId());
        Map arguments = new HashMap<>();
        arguments.put("token", authToken);
        arguments.put("channel", channel.getId());
        arguments.put("topic", topic);
        postSlackCommand(arguments, CHANNELS_SET_TOPIC_COMMAND, handle);
        return handle;
    }

    @Override
    public SlackMessageHandle leaveChannel(SlackChannel channel) {
        SlackMessageHandleImpl handle = new SlackMessageHandleImpl<>(getNextMessageId());
        Map arguments = new HashMap<>();
        arguments.put("token", authToken);
        arguments.put("channel", channel.getId());
        postSlackCommand(arguments, CHANNELS_LEAVE_COMMAND, handle);
        return handle;
    }

    @Override
    public SlackMessageHandle inviteToChannel(SlackChannel channel, SlackUser user) {
      SlackMessageHandleImpl handle = new SlackMessageHandleImpl<>(getNextMessageId());
      Map arguments = new HashMap<>();
      arguments.put("token", authToken);
      arguments.put("channel", channel.getId());
      arguments.put("user", user.getId());
      postSlackCommand(arguments, CHANNELS_INVITE_COMMAND, handle);
      return handle;
    }
    
    @Override
    public SlackMessageHandle archiveChannel(SlackChannel channel) {
      SlackMessageHandleImpl handle = new SlackMessageHandleImpl<>(getNextMessageId());
      Map arguments = new HashMap<>();
      arguments.put("token", authToken);
      arguments.put("channel", channel.getId());
      postSlackCommand(arguments, CHANNELS_ARCHIVE_COMMAND, handle);
      return handle;
    }

    @Override public SlackMessageHandle unarchiveChannel(SlackChannel channel)
    {
        SlackMessageHandleImpl handle = new SlackMessageHandleImpl<>(getNextMessageId());
        Map arguments = new HashMap<>();
        arguments.put("token", authToken);
        arguments.put("channel", channel.getId());
        postSlackCommand(arguments, CHANNELS_UNARCHIVE_COMMAND, handle);
        return handle;
    }

    @Override
    public SlackMessageHandle openDirectMessageChannel(SlackUser user) {
        SlackMessageHandleImpl handle = new SlackMessageHandleImpl<>(getNextMessageId());
        Map arguments = new HashMap<>();
        arguments.put("token", authToken);
        arguments.put("user", user.getId());
        postSlackCommand(arguments, DIRECT_MESSAGE_OPEN_CHANNEL_COMMAND, handle);
        return handle;
    }

    @Override
    public SlackMessageHandle openMultipartyDirectMessageChannel(SlackUser... users) {
        SlackMessageHandleImpl handle = new SlackMessageHandleImpl<>(getNextMessageId());
        Map arguments = new HashMap<>();
        arguments.put("token", authToken);
        StringBuilder strBuilder = new StringBuilder();
        for (int i = 0 ; i < users.length ; i++) {
            if (i != 0) {
                strBuilder.append(',');
            }
            strBuilder.append(users[i].getId());
        }
        arguments.put("users", strBuilder.toString());
        postSlackCommand(arguments, MULTIPARTY_DIRECT_MESSAGE_OPEN_CHANNEL_COMMAND, handle);
        if (!handle.getReply().isOk()) {
            LOGGER.debug("Error occurred while performing command: '" + handle.getReply().getErrorMessage() + "'");
            return null;
        }
        return handle;
    }

    public SlackMessageHandle listEmoji() {
        SlackMessageHandleImpl handle = new SlackMessageHandleImpl<>(getNextMessageId());
        Map arguments = new HashMap<>();
        arguments.put("token", authToken);
        postSlackCommand(arguments, LIST_EMOJI_COMMAND, handle);
        return handle;
    }

    @Override
    public void refetchUsers() {
        Map params = new HashMap<>();
        params.put("presence", "1");
        SlackMessageHandle handle = postGenericSlackCommand(params, LIST_USERS);
        GenericSlackReply replyEv = handle.getReply();
        String answer = replyEv.getPlainAnswer();
        JsonParser parser = new JsonParser();
        JsonObject answerJson = parser.parse(answer).getAsJsonObject();
        JsonArray membersjson = answerJson.get("members").getAsJsonArray();
        Map members = new HashMap<>();
        if (membersjson != null) {
            for (JsonElement member : membersjson) {
                SlackUser user = SlackJSONParsingUtils.buildSlackUser(member.getAsJsonObject());
                members.put(user.getId(), user);
            }
        }

        //blindly replace cache
        users = members;
    }

    private void postSlackCommand(Map params, String command, SlackMessageHandleImpl handle) {
        HttpClient client = getHttpClient();
        HttpPost request = new HttpPost(SLACK_API_HTTPS_ROOT + command);
        List nameValuePairList = new ArrayList<>();
        for (Map.Entry arg : params.entrySet())
        {
            nameValuePairList.add(new BasicNameValuePair(arg.getKey(), arg.getValue()));
        }
        try
        {
            request.setEntity(new UrlEncodedFormEntity(nameValuePairList, "UTF-8"));
            HttpResponse response = client.execute(request);
            String jsonResponse = consumeToString(response.getEntity().getContent());
            LOGGER.debug("PostMessage return: " + jsonResponse);
            ParsedSlackReply reply = SlackJSONReplyParser.decode(parseObject(jsonResponse),this);
            handle.setReply(reply);
        }
        catch (Exception e)
        {
            // TODO : improve exception handling
            e.printStackTrace();
        }
    }

    private void postSlackCommandWithFile(Map params, byte [] fileContent, String fileName, String command, SlackMessageHandleImpl handle) {
        URIBuilder uriBuilder = new URIBuilder();
        uriBuilder.setScheme(SLACK_API_SCHEME).setHost(SLACK_API_HOST).setPath(SLACK_API_PATH+"/"+command);
        for (Map.Entry arg : params.entrySet())
        {
            uriBuilder.setParameter(arg.getKey(),arg.getValue());
        }
        HttpPost request = new HttpPost(uriBuilder.toString());
        HttpClient client = getHttpClient();
        MultipartEntityBuilder builder = MultipartEntityBuilder.create();
        try
        {
            builder.addBinaryBody("file",fileContent, ContentType.DEFAULT_BINARY,fileName);
            request.setEntity(builder.build());
            HttpResponse response = client.execute(request);
            String jsonResponse = ReaderUtils.readAll(new InputStreamReader(response.getEntity().getContent()));
            LOGGER.debug("PostMessage return: " + jsonResponse);
            ParsedSlackReply reply = SlackJSONReplyParser.decode(parseObject(jsonResponse),this);
            handle.setReply(reply);
        }
        catch (Exception e)
        {
            // TODO : improve exception handling
            e.printStackTrace();
        }
    }

    @Override
    public SlackMessageHandle postGenericSlackCommand(Map params, String command) {
        HttpClient client = getHttpClient();
        HttpPost request = new HttpPost(SLACK_API_HTTPS_ROOT + command);
        List nameValuePairList = new ArrayList<>();
        for (Map.Entry arg : params.entrySet())
        {
            if (!"token".equals(arg.getKey())) {
                nameValuePairList.add(new BasicNameValuePair(arg.getKey(), arg.getValue()));
            }
        }
        nameValuePairList.add(new BasicNameValuePair("token", authToken));
        try
        {
            SlackMessageHandleImpl handle = new SlackMessageHandleImpl<>(getNextMessageId());
            request.setEntity(new UrlEncodedFormEntity(nameValuePairList, "UTF-8"));
            HttpResponse response = client.execute(request);
            String jsonResponse = consumeToString(response.getEntity().getContent());
            LOGGER.debug("PostMessage return: " + jsonResponse);
            GenericSlackReplyImpl reply = new GenericSlackReplyImpl(jsonResponse);
            handle.setReply(reply);
            return handle;
        }
        catch (Exception e)
        {
            // TODO : improve exception handling
            e.printStackTrace();
        }
        return null;
    }

    private HttpClient getHttpClient() {
        HttpClient client;
        if (proxyHost != null)
        {
            client = HttpClientBuilder.create().setRoutePlanner(new DefaultProxyRoutePlanner(proxyHost)).build();
        }
        else
        {
            client = HttpClientBuilder.create().build();
        }
        return client;
    }

    @Override
    public SlackMessageHandle sendMessageOverWebSocket(SlackChannel channel, String message) {
        SlackMessageHandleImpl handle = new SlackMessageHandleImpl<>(getNextMessageId());
        try
        {
            JsonObject messageJSON = new JsonObject();
            messageJSON.addProperty("id", handle.getMessageId());
            messageJSON.addProperty("type", "message");
            messageJSON.addProperty("channel", channel.getId());
            messageJSON.addProperty("text", message);

            websocketSession.getBasicRemote().sendText(messageJSON.toString());
        }
        catch (Exception e)
        {
            // TODO : improve exception handling
            e.printStackTrace();
        }
        return handle;
    }

    @Override
    public SlackMessageHandle sendTyping(SlackChannel channel) {
        SlackMessageHandleImpl handle = new SlackMessageHandleImpl<>(getNextMessageId());
        try
        {
            JsonObject messageJSON = new JsonObject();
            messageJSON.addProperty("id", handle.getMessageId());
            messageJSON.addProperty("type", "typing");
            messageJSON.addProperty("channel", channel.getId());
            websocketSession.getBasicRemote().sendText(messageJSON.toString());
        }
        catch (Exception e)
        {
            // TODO : improve exception handling
            e.printStackTrace();
        }
        return handle;
    }

    @Override
    public SlackPersona.SlackPresence getPresence(SlackPersona persona) {
        HttpClient client = getHttpClient();
        HttpPost request = new HttpPost("https://slack.com/api/users.getPresence");
        List nameValuePairList = new ArrayList<>();
        nameValuePairList.add(new BasicNameValuePair("token", authToken));
        nameValuePairList.add(new BasicNameValuePair("user", persona.getId()));
        try
        {
            request.setEntity(new UrlEncodedFormEntity(nameValuePairList, "UTF-8"));
            HttpResponse response = client.execute(request);
            String jsonResponse = consumeToString(response.getEntity().getContent());
            LOGGER.debug("PostMessage return: " + jsonResponse);
            JsonObject resultObject = parseObject(jsonResponse);
            //quite hacky need to refactor this
            SlackUserPresenceReply reply = (SlackUserPresenceReply)SlackJSONReplyParser.decode(resultObject,this);
            if (!reply.isOk())
            {
                return SlackPersona.SlackPresence.UNKNOWN;
            }
            String presence = resultObject.get("presence") != null ? resultObject.get("presence").getAsString() : null;

            if ("active".equals(presence))
            {
                return SlackPersona.SlackPresence.ACTIVE;
            }
            if ("away".equals(presence))
            {
                return SlackPersona.SlackPresence.AWAY;
            }
        }
        catch (Exception e)
        {
            // TODO : improve exception handling
            e.printStackTrace();
        }
        return SlackPersona.SlackPresence.UNKNOWN;
    }

    public void setPresence(SlackPersona.SlackPresence presence) {
        if(presence == SlackPersona.SlackPresence.UNKNOWN || presence == SlackPersona.SlackPresence.ACTIVE) {
            throw new IllegalArgumentException("Presence must be either AWAY or AUTO");
        }
        HttpClient client = getHttpClient();
        HttpPost request = new HttpPost(SLACK_API_HTTPS_ROOT + SET_PERSONA_ACTIVE);
        List nameValuePairList = new ArrayList<>();
        nameValuePairList.add(new BasicNameValuePair("token", authToken));
        nameValuePairList.add(new BasicNameValuePair("presence", presence.toString().toLowerCase()));
        try {
            request.setEntity(new UrlEncodedFormEntity(nameValuePairList, "UTF-8"));
            HttpResponse response = client.execute(request);
            String JSONResponse = consumeToString(response.getEntity().getContent());
            LOGGER.debug("JSON Response=" + JSONResponse);
        }catch(IOException e) {
            e.printStackTrace();
        }

    }

    private long getNextMessageId() {
        return messageId.getAndIncrement();
    }

    @Override
    public void onMessage(String message) {
        final JsonObject object = parseObject(message);

        LOGGER.debug("receiving from websocket " + message);
        if ("pong".equals(object.get("type").getAsString())) {
            lastPingAck = object.get("reply_to").getAsInt();
            LOGGER.debug("pong received " + lastPingAck);
        }
        else if ("reconnect_url".equals(object.get("type").getAsString())) {
            webSocketConnectionURL = object.get("url").getAsString();
            LOGGER.debug("new websocket connection received " + webSocketConnectionURL);
        }
        else
        {
            SlackEvent slackEvent = SlackJSONMessageParser.decode(this, object);
            if (slackEvent instanceof SlackChannelCreated)
            {
                SlackChannelCreated slackChannelCreated = (SlackChannelCreated) slackEvent;
                channels.put(slackChannelCreated.getSlackChannel().getId(), slackChannelCreated.getSlackChannel());
            }
            if (slackEvent instanceof SlackGroupJoined)
            {
                SlackGroupJoined slackGroupJoined = (SlackGroupJoined) slackEvent;
                channels.put(slackGroupJoined.getSlackChannel().getId(), slackGroupJoined.getSlackChannel());
            }
            if (slackEvent instanceof SlackUserChangeEvent)
            {
                SlackUserChangeEvent slackUserChangeEvent = (SlackUserChangeEvent) slackEvent;
                users.put(slackUserChangeEvent.getUser().getId(), slackUserChangeEvent.getUser());
            }
            dispatcher.dispatch(slackEvent);
        }
    }

    private JsonObject parseObject(String json)
    {
        JsonParser parser = new JsonParser();
        return parser.parse(json).getAsJsonObject();
    }

    @Override
    public SlackMessageHandle inviteUser(String email, String firstName, boolean setActive) {

        SlackMessageHandleImpl handle = new SlackMessageHandleImpl<>(getNextMessageId());
        Map arguments = new HashMap<>();
        arguments.put("token", authToken);
        arguments.put("email", email);
        arguments.put("first_name", firstName);
        arguments.put("set_active", ""+setActive);
        postSlackCommand(arguments, INVITE_USER_COMMAND, handle);
        return handle;
    }

    public long getHeartbeat() {
        return TimeUnit.MILLISECONDS.toSeconds(heartbeat);
    }

    private final PresenceChangeListener INTERNAL_PRESENCE_CHANGE_LISTENER = new PresenceChangeListener()
    {
        @Override public void onEvent(PresenceChange event, SlackSession session)
        {
            SlackUser user = users.get(event.getUserId());
            SlackUserImpl newUser = new SlackUserImpl(user.getId(), user.getUserName(), user.getRealName(), user.getUserMail(), user.getUserSkype(), user.getUserTitle(), user.getUserPhone(),
                user.isDeleted(), user.isAdmin(), user.isOwner(), user.isPrimaryOwner(), user.isRestricted(),
                user.isUltraRestricted(), user.isBot(), user.getTimeZone(), user.getTimeZoneLabel(), user.getTimeZoneOffset(),
                event.getPresence());
            users.put(event.getUserId(), newUser);
        }
    };

    private final SlackChannelArchivedListener INTERNAL_CHANNEL_ARCHIVE_LISTENER = new SlackChannelArchivedListener()
    {
        @Override public void onEvent(SlackChannelArchived event, SlackSession session)
        {
            SlackChannel channel = channels.get(event.getSlackChannel().getId());
            SlackChannelImpl newChannel = new SlackChannelImpl(channel.getId(), channel.getName(), channel.getTopic(), channel.getPurpose(), channel.isDirect(), channel.isMember(), true);
            channels.put(newChannel.getId(), newChannel);
        }
    };

    private final SlackChannelCreatedListener INTERNAL_CHANNEL_CREATED_LISTENER = new SlackChannelCreatedListener()
    {
        @Override public void onEvent(SlackChannelCreated event, SlackSession session)
        {
            channels.put(event.getSlackChannel().getId(), event.getSlackChannel());
        }
    };

    private final SlackChannelDeletedListener INTERNAL_CHANNEL_DELETED_LISTENER = new SlackChannelDeletedListener()
    {
        @Override public void onEvent(SlackChannelDeleted event, SlackSession session)
        {
            channels.remove(event.getSlackChannel().getId());
        }
    };

    private final SlackChannelRenamedListener INTERNAL_CHANNEL_RENAMED_LISTENER = new SlackChannelRenamedListener()
    {
        @Override public void onEvent(SlackChannelRenamed event, SlackSession session)
        {
            SlackChannel channel = channels.get(event.getSlackChannel().getId());
            SlackChannelImpl newChannel = new SlackChannelImpl(channel.getId(), event.getNewName(), channel.getTopic(), channel.getPurpose(), channel.isDirect(), channel.isMember(), channel.isArchived());
            channels.put(newChannel.getId(), newChannel);
        }
    };

    private final SlackChannelUnarchivedListener INTERNAL_CHANNEL_UNARCHIVED_LISTENER = new SlackChannelUnarchivedListener()
    {
        @Override public void onEvent(SlackChannelUnarchived event, SlackSession session)
        {
            SlackChannel channel = channels.get(event.getSlackChannel().getId());
            SlackChannelImpl newChannel = new SlackChannelImpl(channel.getId(), channel.getName(), channel.getTopic(), channel.getPurpose(), channel.isDirect(), channel.isMember(), false);
            channels.put(newChannel.getId(), newChannel);
        }
    };

    private final SlackTeamJoinListener INTERNAL_TEAM_JOIN_LISTENER = new SlackTeamJoinListener()
    {
        @Override public void onEvent(SlackTeamJoin event, SlackSession session)
        {
            users.put(event.getUser().getId(), event.getUser());
        }
    };

    private final SlackUserChangeListener INTERNAL_USER_CHANGE_LISTENER = new SlackUserChangeListener()
    {
        @Override public void onEvent(SlackUserChange event, SlackSession session)
        {
            users.put(event.getUser().getId(), event.getUser());
        }
    };

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy