diff --git a/README.md b/README.md index 9528d6ab..9845efd5 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,47 @@ -# slack-client +# slack-client -An asychronous HTTP client wrapping Slack's [RPC-style web api](https://api.slack.com/web). The API here is simple: you just need a `SlackClient` and you're good to go. +![license](https://img.shields.io/github/license/HubSpot/slack-client.svg?style=social) +![GitHub last commit](https://img.shields.io/github/last-commit/HubSpot/slack-client.svg?style=social) + [![Build Status](https://travis-ci.org/HubSpot/slack-client.svg?branch=master)](https://travis-ci.org/HubSpot/slack-client) ![GitHub release](https://img.shields.io/github/release/HubSpot/slack-client.svg) ![Maven metadata URI](https://img.shields.io/maven-metadata/v/http/central.maven.org/maven2/com/hubspot/slack/slack-client/maven-metadata.xml.svg) + +* [Overview](overview) +* [Usage](usage) +* [Setting up Guice](setting-up-guice) +* [Using the Client](using-the-client) + * [Find a user by email, then send a message](find-a-user-by-email-then-send-a-message) + * [Filtering messages in a QA environment to specific channels](filtering-messages-in-a-qa-environment-to-specific-channels) + * [Printing requests for specific methods](printing-requests-for-specific-methods) + + +## Overview + +An asychronous HTTP client wrapping Slack's [RPC-style web api](https://api.slack.com/web). Provides an extensible API with builder-style parameters and responses, allowing you to focus on your interactions with users, rather than your interactions with Slack. Notably, we: + +* Implement most (if not all) of Slack's [web API](https://api.slack.com/web), and actively maintain this project +* Provide per-method in-memory rate limiting so you don't have to worry about overwhelming slack from a single process +* Expose highly configurable hooks to allow filtering and debugging in an extensible way ## Usage -Include the base and client modules in your POM: +To use with Maven-based projects, add the following dependencies:: ```xml com.hubspot.slack slack-base - 1.0 + {latest version} com.hubspot.slack slack-java-client - 1.0 + {latest version} ``` +Latest version can be seen [here, on Maven central](https://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.hubspot.slack%22). + +### Seting up Guice + Install the `SlackClientModule` in the Guice module you want to use to talk to slack. ```java @@ -52,11 +75,142 @@ public class MySlacker { ) { this.slackClient = clientFactory.build( SlackClientRuntimeConfig.builder() + .setTokenSupplier(() -> "your token here") + // ... all your configuration here + .build() + ); + } + + // then just use the client! +} +``` + +### Using the Client + +If you're not familiar with Java 8's `CompletableFuture` API, know that you can transform this client from an asynchronous one to a synchronous one by calling `join` on any of the futures returned. To simplify getting started, here are some examples of using the client for common tasks. + +#### Find a user by email, then send a message + +```java +void sendMeAMessage() { + Optional meMaybe = findMe(); + if (!meMaybe.isPresent()) { + LOG.error("Couldn't find me in slack!"); + } + + slackMe(meMaybe.get()); +} + +Optional findMe() { + /** + * Since we may have to enumerate a large set, we chunk it up into pages, and handle each page separately. + */ + Iterable, SlackError>>> userPageFutures = slackClient.listUsers(); + + /** + * We'll enumerate the pages so we can short-circuit and break out early if we can + */ + for (CompletableFuture, SlackError>> userPageFuture : userPageFutures) { + Result, SlackError> pageResult = userPageFuture.join(); + + // If there was a problem fetching the page, it'll percolate here as a RTE + List slackUsers = pageResult.unwrapOrElseThrow(); + Optional matchingUser = slackUsers.stream() + .filter(user -> + user.getProfile() + .flatMap(UserProfile::getEmail) // flatMap just says the profile could be absent, or the email could be absent + .filter("eszabowexler@hubspot.com"::equalsIgnoreCase) // keep it only if it's the email we want... + .isPresent()) // and we'll filter the original SlackUser list to find the one with the profile that's me! + .findFirst(); + + if (matchingUser.isPresent()) { + return matchingUser; + } + } + + return Optional.empty(); +} + +ChatPostMessageResponse slackMe(SlackUser me) { + Result postResult = slackClient.postMessage( + ChatPostMessageParams.builder() + .setText("Hello me! Here's a slack message!") + .setChannelId("a fancy channel ID here") + .build() + ).join(); + + return postResult.unwrapOrElseThrow();// again, release failure here as a RTE +} +``` + +#### Filtering messages in a QA environment to specific channels + +```java +public class MySlacker { + private final SlackClient slackClient; + + public MySlacker( + SlackWebClient.Factory clientFactory + ) { + this.slackClient = clientFactory.build( + SlackClientRuntimeConfig.builder() + .setTokenSupplier(() -> "your token here") + .setMethodFilter( + new SlackMethodAcceptor() { + @Override + public String getFailureExplanation(SlackMethod method, Object params) { + return "Only allow WRITE methods to our special channel in QA!"; + } + + @Override + public boolean test(SlackMethod slackMethod, Object o) { + if (isQa() && slackMethod.getWriteMode() == MethodWriteMode.WRITE) { + if (o instanceof HasChannel && ((HasChannel) o).getChannelId().equals("snazzy id")) { + return true; + } + return false; + } + + return true; + } + }) + // ... all your configuration here + .build() + ); + } + + // then just use the client! +} +``` + +#### Printing requests for specific methods + +```java +public class MySlacker { + private final SlackClient slackClient; + + public MySlacker( + SlackWebClient.Factory clientFactory + ) { + this.slackClient = clientFactory.build( + SlackClientRuntimeConfig.builder() + .setTokenSupplier(() -> "your token here") + .setRequestDebugger( + new RequestDebugger() { + @Override + public void debug(long requestId, SlackMethod method, HttpRequest request) { + if (method == SlackMethods.chat_postEphemeral) { + LOG.info("Posting ephemeral message {}", format(request)); + } + } + } + ) // ... all your configuration here - .builder() + .build() ); } // then just use the client! } ``` +```