package com.github.masahitojp.botan.adapter; import com.github.masahitojp.botan.Botan; import com.github.masahitojp.botan.exception.BotanException; import com.github.masahitojp.botan.message.BotanMessage; import com.github.masahitojp.botan.message.BotanMessageSimple; import com.github.masahitojp.botan.utils.BotanUtils; import com.ullink.slack.simpleslackapi.SlackChannel; import com.ullink.slack.simpleslackapi.SlackSession; import com.ullink.slack.simpleslackapi.SlackUser; import com.ullink.slack.simpleslackapi.impl.SlackSessionFactory; import lombok.extern.slf4j.Slf4j; import java.io.IOException; import java.util.Optional; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Adapter for Slack Real Time API */ @SuppressWarnings("unused") @Slf4j public final class SlackRTMAdapter implements BotanAdapter { private final String apiToken; private final AtomicBoolean flag = new AtomicBoolean(true); private Botan botan; private SlackSession session; private Thread thread; private final AtomicReference<Pattern> patternRef = new AtomicReference<>(); public SlackRTMAdapter() { this(BotanUtils.envToOpt("SLACK_API_TOKEN").orElse("")); } public SlackRTMAdapter(final String apiToken) { if(apiToken == null || apiToken.equals("")) { throw new NullPointerException("SLACK_API_TOKEN is null"); } this.apiToken = apiToken; } private Pattern getPattern() { Pattern pattern = patternRef.get(); if (pattern == null) { synchronized (this.patternRef) { final String convertSlackReferenceToBotName = "<@([^>]+)>[:,]?(\\s)+?(?<body>.*)"; pattern = Pattern.compile(convertSlackReferenceToBotName, Pattern.DOTALL); this.patternRef.set(pattern); } } return pattern; } // Slackで"@botan body" と入力した際に、 <@DDDD> という内部表現になるので、差し替える private String convertSlackMessageContentForBotan(final String messageContent) { final Matcher matcher = getPattern().matcher(messageContent); if (matcher.find()) { return String.format("%s %s", session.sessionPersona().getUserName(), matcher.group("body")); } else { return messageContent; } } @Override public void run() throws BotanException { // bind event session.addMessagePostedListener((e, s) -> { final String body = convertSlackMessageContentForBotan(e.getMessageContent()); botan.receive(new BotanMessageSimple( body, e.getSender().getUserName(), e.getSender().getUserName(), e.getChannel().getName(), e.getEventType().ordinal() )); } ); thread = new Thread(() -> { while (flag.get()) { try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e1) { e1.printStackTrace(); } } }); thread.start(); } @Override public void say(final BotanMessage message) { SlackChannel slackSession = session.findChannelByName(message.getTo()); if (slackSession == null) { slackSession = session.findChannelById(message.getTo()); } if (slackSession == null) { final SlackUser user = session.findUserByUserName(message.getTo()); if(user != null)slackSession = session.findChannelById(user.getId()); } if (slackSession != null) { session.sendMessageOverWebSocket(slackSession, message.getBody()); } else { log.warn("reply failure {}", message.getTo()); } } @Override public void initialize(final Botan botan) { session = SlackSessionFactory.createWebSocketSlackSession(this.apiToken); try { session.connect(); } catch (final IOException e1) { log.warn("{}", e1); } this.botan = botan; } @Override public void beforeShutdown() { flag.compareAndSet(true, false); try { session.disconnect(); thread.join(); } catch (final IOException | InterruptedException e1) { log.warn("{}", e1); } } public Optional<String> getFromAdapterName() { if (session != null) { return Optional.ofNullable(session.sessionPersona().getUserName()); } else { return Optional.empty(); } } }