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

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

The 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, String rawMessage) {
      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 + " raw message:" + rawMessage);
      }
    }

    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();
    String extra = "&batch_presence_aware=true&presence_sub=true";
    HttpGet request = new HttpGet(SLACK_HTTPS_AUTH_URL + authToken + extra);
    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, null);
      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, null);
        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 sendPresenceSubMessageOverWebSocket(String[] ids) {
    SlackMessageHandleImpl handle = new SlackMessageHandleImpl<>(getNextMessageId());
    try {

      JsonArray userIdArray = new JsonArray();
      for (String userId : ids) {
        userIdArray.add(userId);
      }
      JsonObject messageJSON = new JsonObject();
      // messageJSON.addProperty("id", handle.getMessageId());
      messageJSON.addProperty("type", "presence_sub");
      messageJSON.add("ids", userIdArray);

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

  @Override
  public SlackMessageHandle sendRawMessageOverWebSocket(JsonObject messageJSON) {
    SlackMessageHandleImpl handle = new SlackMessageHandleImpl<>(getNextMessageId());
    try {
      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 if ("hello".equals(object.get("type").getAsString())) {
      
      LOGGER.debug("hello message received.");
      
      Collection slackUsers = getUsers();
      List userIdList=new ArrayList();
      for(SlackUser u:slackUsers) {
        userIdList.add(u.getId());
      }
      sendPresenceSubMessageOverWebSocket(userIdList.toArray(new String[] {}));
      
    } 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, message);
    }
  }

  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