Wenn ich einen Slack-Bot mache, denke ich, dass ich in den meisten Fällen Python oder Node.js verwenden werde, aber dieses Mal werde ich es wagen, mit Java zu arbeiten. Ich konnte keinen Artikel über das Erstellen eines serverlosen Slack-Bots über AWS mit Java in Japanisch oder Englisch finden. Daher werde ich das Implementierungsverfahren hier schreiben. Ich bin ein Amateur, wenn es um Java geht, also ist es besser, dies zu tun! Ich würde mich freuen, wenn Sie auf Punkte hinweisen könnten. Der ganze Code ist auf Github https://github.com/kamata1729/sampleSlackBotWithJava
Lassen Sie uns zuerst einen Slack Bot machen! https://api.slack.com/apps Klicken Sie hier auf Neue App erstellen, um eine neue App zu erstellen.
Und füge Bot User zur erstellten App hinzu
Von hier aus erstellen wir einen Endpunkt, um Ereignisse von Slack on AWS zu empfangen. Insbesondere empfängt API Gateway das Ereignis und übergibt es an die Lambda-Funktion.
Öffnen Sie IAM über die AWS-Konsole und erstellen Sie eine neue Rolle.
Da es in Lambda verwendet wird, wählen Sie "Lambda" und fahren Sie zuerst fort.
Dieses Mal möchte ich das Protokoll in das CloudWatch-Protokoll ausgeben können. Wählen Sie daher "AWSLambdaBasicExecutionRole" mit Schreibberechtigung aus `
Ich werde es dieses Mal nicht zum Erstellen des nächsten Tags verwenden, sodass Sie es ignorieren können. Geben Sie im nächsten Bestätigungsbildschirm einen Rollennamen ein und Sie sind fertig! Dieses Mal habe ich es "sampleBotRole" genannt.
Gehen Sie nun zur Lambda-Konsole und erstellen Sie eine Funktion. Wählen Sie "Von Grund auf neu erstellen", diesmal "sampleBot" als Funktionsnamen und "Java 8" als Laufzeit. Außerdem habe ich für die Rolle die zuvor erstellte "sampleBotRole" ausgewählt.
Wählen Sie zunächst "API erstellen" auf der Seite "API-Gateway" der aws-Konsole aus und erstellen Sie sie mit dem API-Namen (diesmal "sampleBotAPI"). Wählen Sie im erstellten Bildschirm "Aktion-> Methode erstellen", um die "POST" -Methode hinzuzufügen. Drücken Sie das Häkchen, um eine POST-Methode zu erstellen, stellen Sie sie so ein, dass sie mit der zuvor auf dem Setup-Bildschirm erstellten Lambda-Funktion übereinstimmt, und speichern Sie sie. Wählen Sie im folgenden Bildschirm "Aktion-> API bereitstellen" und geben Sie den zu bereitstellenden Stufennamen ein. Zu diesem Zeitpunkt wird die URL zum Aufrufen des Endpunkts oben auf dem Bildschirm angezeigt. Notieren Sie sich diese. Damit ist die Endpunkterstellung abgeschlossen!
Lassen Sie uns vor der Implementierung der Lambda-Funktion über die Authentifizierung der Slack-Event-API sprechen. Bei der Slack-Ereignis-API müssen Sie zuerst eine bestimmte Zeichenfolge zurücksenden, um festzustellen, ob das Programm für die Ereignis-API vorgesehen ist.
Zur Authentifizierung wird zuerst der folgende JSON gesendet.
{
"token": "Jhj5dZrVaK7ZwHHjRyZWjbDl",
"challenge": "3eZbrw1aBm2rZgRNFdxV2595E9CY3gmdALWMmHkvFXO7tYXAYM8P",
"type": "url_verification"
}
Wenn andererseits der Inhalt von "Herausforderung" innerhalb von 3 Sekunden zurückgesendet werden kann, ist die Authentifizierung abgeschlossen. Es gibt drei Formate zum Zurücksenden. Weitere Informationen finden Sie unter https://api.slack.com/events/url_verification.
HTTP 200 OK
Content-type: text/plain
3eZbrw1aBm2rZgRNFdxV2595E9CY3gmdALWMmHkvFXO7tYXAYM8P
HTTP 200 OK
Content-type: application/x-www-form-urlencoded
challenge=3eZbrw1aBm2rZgRNFdxV2595E9CY3gmdALWMmHkvFXO7tYXAYM8P
HTTP 200 OK
Content-type: application/json
{"challenge":"3eZbrw1aBm2rZgRNFdxV2595E9CY3gmdALWMmHkvFXO7tYXAYM8P"}
Wenn Sie AWS Lambda I / O in Java definieren, müssen Sie eine Handler-Implementierung implementieren, die auf drei Arten erfolgen kann. Ich werde es hier kurz schreiben, aber siehe unten für Details https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/java-programming-model-handler-types.html https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/java-handler-using-predefined-interfaces.html
Es werden die standardmäßig implementierten Typen verwendet, ohne dass spezielle Eingabe- / Ausgabetypen definiert werden. Implementieren Sie wie folgt
outputType handler-name(inputType input, Context context) {
...
}
Es scheint, dass der "inputType", "outputType" standardmäßig String-Typ, Integer-Typ, Boolescher Typ, Map-Typ und Listentyp unterstützt. (Einzelheiten siehe unten https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/java-programming-model-req-resp.html)
Beispielsweise ist die folgende Implementierung möglich
package example;
import com.amazonaws.services.lambda.runtime.Context;
public class Hello {
public String myHandler(String name, Context context) {
return String.format("Hello %s.", name);
}
}
Wenn Sie Ihren eigenen Typ für "inputType" und "outputType" angeben möchten, können Sie auch den Typ definieren und verwenden, der dem Eingabe- / Ausgabeformat entspricht.
Dies kann wie folgt implementiert werden:
package example;
import com.amazonaws.services.lambda.runtime.Context;
public class HelloPojo {
// Define two classes/POJOs for use with Lambda function.
public static class RequestClass {
...
}
public static class ResponseClass {
...
}
public static ResponseClass myHandler(RequestClass request, Context context) {
String greetingString = String.format("Hello %s, %s.", request.getFirstName(), request.getLastName());
return new ResponseClass(greetingString);
}
}
Es gibt auch eine Möglichkeit, Ein- und Ausgänge mit "InputStream" und "OutputStream" zu aktivieren. Sie können antworten, indem Sie eine Byte-Zeichenfolge in "OutputStream" schreiben. Ein Beispiel ist ein Programm, das eine bestimmte Zeichenfolge großschreibt und zurückgibt.
package example;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import com.amazonaws.services.lambda.runtime.RequestStreamHandler;
import com.amazonaws.services.lambda.runtime.Context;
public class Hello implements RequestStreamHandler {
// if input is "test", then return "TEST"
public void handleRequest(InputStream inputStream, OutputStream outputStream, Context context)
throws IOException {
int letter;
while((letter = inputStream.read()) != -1)
{
outputStream.write(Character.toUpperCase(letter));
}
}
}
Dieses Mal ist es schwierig, verschachtelte Jsons im POJO-Typ zu empfangen, daher verwende ich RequestStreamHandler.
Basierend auf dem oben Gesagten werden wir die Lambda-Funktion codieren, aber im Gegensatz zu Python usw. kann der Code im Fall von Java nicht inline bearbeitet werden. Daher werde ich dieses Mal ein Java-Projekt mit lokalem Maven erstellen und es mit gradle in eine Zip-Datei hochladen.
Erstellen Sie zunächst ein Projekt mit maven. (Maven wird nur zum Erstellen eines Projekts verwendet. Es spielt also keine Rolle, ob Sie es auf andere Weise erstellen.)
$ mvn archetype:generate
Unterwegs werden Sie häufig gefragt, aber mit Ausnahme von "groupId" und "artefaktId" können Sie eingeben. Diesmal ist "groupId" "jp.com.hoge" und "artefaktId" ist "SampleBot". Bei der Ausführung wird ein Ordner mit dem Projektnamen erstellt und "src / main / java / jp / com / hoge / App.java" generiert.
Bearbeiten Sie App.java wie folgt. Selbst wenn Sie den Inhalt von "Herausforderung" nicht erhalten und eine Zeichenfolge nicht zurücksenden, wird dies als Zeitüberschreitung betrachtet und das Ereignis wird nach 1 Minute und 5 Minuten erneut gesendet, daher vorerst "OK" Ich werde mit "antworten.
src/main/java/jp/com/hoge/App.java
package jp.com.hoge;
import java.io.*;
import java.net.URLEncoder;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.Charset;
import java.lang.StringBuilder;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONArray;
import com.amazonaws.services.lambda.runtime.*;
public class App implements RequestStreamHandler
{
@Override
public void handleRequest(InputStream inputStream, OutputStream outputStream, Context context) throws IOException {
//Standardantwort Wenn Sie nichts antworten, wird dasselbe Ereignis mehrmals gesendet
String response = "HTTP 200 OK\nContent-type: text/plain\nOK";
try{
BufferedReader rd = new BufferedReader(new InputStreamReader(inputStream, Charset.forName("UTF-8")));
String jsonText = readAll(rd);
System.out.println(jsonText); //System.Die Ausgabe von out wird in CloudWatch-Protokolle geschrieben
JSONObject json = new JSONObject(jsonText);
if (json.has("type")) {
String eventType = json.get("type").toString();
if (eventType.equals("url_verification")) {
//Stellen Sie den Inhalt der Herausforderung auf Antwort ein
response = "HTTP 200 OK\nContent-type: text/plain\n" + json.get("challenge").toString();
}
}
} catch(IOException e) {
e.printStackTrace();
} finally {
//Schreiben Sie den Inhalt der Antwort auf outputStream
outputStream.write(response.getBytes());
outputStream.flush();
outputStream.close();
}
return;
}
/* get String from BufferedReader */
private static String readAll(Reader rd) throws IOException {
StringBuilder sb = new StringBuilder();
int cp;
while ((cp = rd.read()) != -1) {
sb.append((char) cp);
}
return sb.toString();
}
}
Platzieren Sie außerdem "build.gradle" direkt unter dem Projektordner. Wenn Sie andere Bibliotheken haben, die Sie verwenden möchten, können Sie diese in "Abhängigkeiten" schreiben oder einen lib-Ordner erstellen und die JAR-Datei darin ablegen.
build.gradle
apply plugin: 'java'
repositories {
mavenCentral()
}
dependencies {
compile (
'com.amazonaws:aws-lambda-java-core:1.1.0',
'com.amazonaws:aws-lambda-java-events:1.1.0',
'org.json:json:20180813'
)
testCompile 'junit:junit:4.+'
}
task buildZip(type: Zip) {
from compileJava
from processResources
into('lib') {
from configurations.compileClasspath
}
}
build.dependsOn buildZip
Führen Sie zu diesem Zeitpunkt Folgendes direkt unter dem zu erstellenden Projektordner aus.
$ gradle build
Wenn der Build erfolgreich ist, sollte "build / Distribution / SampleBot.zip" generiert werden.
Öffnen Sie die AWS Lambda-Konsole und laden Sie die soeben erstellte SampleBot.zip
hoch.
Geben Sie "jp.com.hoge.App" in den Handler ein und vergessen Sie nicht, ihn zu speichern.
Lassen Sie uns zuerst die Lambda-Funktion testen. Wählen Sie "Testereignis erstellen" aus dem Abwärtspfeil im Feld "Testereignis auswählen ..." oben rechts auf dem Bildschirm und erstellen Sie es wie in der folgenden Abbildung gezeigt.
Klicken Sie oben rechts auf dem Bildschirm auf die Schaltfläche "Test", um den Test auszuführen. Bei Erfolg wird das Ausführungsergebnis angezeigt. Wenn Sie auf "Details" klicken, wird es wie unten gezeigt angezeigt. Sie können sehen, dass die Ausgabe ordnungsgemäß erfolgt
In CloudWatch wird der Inhalt der inputStream-Ausgabe als Protokoll angezeigt.
An diesem Punkt können Sie endlich die Slack Event API abonnieren! Wählen Sie "Ereignisabonnements" auf der linken Seite, wenn Sie den Bot erstellt haben Aktivieren Sie Ereignisse aktivieren und geben Sie die URL, die Sie beim Bereitstellen der API notiert haben, in das Feld URL anfordern ein. Sobald die Antwort bestätigt wurde, sieht sie wie folgt aus und Sie können Ereignisse abonnieren!
Dieses Mal erhalten wir ein Ereignis namens "app_mention". Dies ist ein Ereignis, das reagiert, wenn ein Bot mit einem @ erwähnt wird. Drücken Sie nach dem Hinzufügen auf "Änderungen vergessen"
Installieren Sie dann den Bot im Arbeitsbereich Wählen Sie "OAuth & Permissions" und scrollen Sie nach unten zum Menü "Scopes". Wählen Sie "Nachrichten als SampleBot senden" und "Änderungen speichern". Sie können jetzt eine Nachricht als SampleBot senden.
Dann installieren Sie mit "App in Workspace installieren". Das OAuth-Zugriffstoken und das OAuth-Zugriffstoken für Bot-Benutzer werden angezeigt. Notieren Sie sich diese ebenfalls.
Von hier aus werde ich ein Beispiel für einen Bot erstellen, der auf eine an mich gerichtete Aussage reagiert, indem er einen Papagei zurückgibt. Der gesamte Quellcode wird auf github veröffentlicht https://github.com/kamata1729/sampleSlackBotWithJava
Das Ereignis "app_mention" wird wie folgt gesendet.
{
"token": "ZZZZZZWSxiZZZ2yIvs3peJ",
"team_id": "T061EG9R6",
"api_app_id": "A0MDYCDME",
"event": {
"type": "app_mention",
"user": "U061F7AUR",
"text": "What is the hour of the pearl, <@U0LAN0Z89>?",
"ts": "1515449522.000016",
"channel": "C0LAN2Q65",
"event_ts": "1515449522000016"
},
"type": "event_callback",
"event_id": "Ev0LAN670R",
"event_time": 1515449522000016,
"authed_users": [
"U0LAN0Z89"
]
}
Erstellen Sie andererseits auf demselben Kanal einen Bot, der "Was ist die Stunde der Perle, <@ U061F7AUR>?" Veröffentlicht, wobei die Erwähnung in den Benutzernamen des anderen Benutzers geändert wird.
Wir müssen die Benutzer-ID im Text ersetzen, also die Benutzer-ID des Bots abrufen. Holen Sie sich zunächst das Token für den Arbeitsbereich aus dem Folgenden. https://api.slack.com/custom-integrations/legacy-tokens Verwenden Sie dieses Token, um auf die folgende URL zuzugreifen, die ID des Samplebots abzurufen und diese zu notieren. Eine Zeichenfolge, die mit einem oberen U beginnt. https://slack.com/api/users.list?token=取得したtoken
Ich habe es als Umgebungsvariable auf der AWS-Lambda-Seite niedergeschrieben.
SLACK_BOT_USER_ACCESS_TOKEN
: Token beginnend mit xoxp-SLACK_APP_AUTH_TOKEN
: Token beginnend mit xoxb-USER_ID
: Benutzer-ID des SamplebotsRegistrieren Sie die drei. Sie können es jetzt von der System.getenv-Methode abrufen.
Sie können eine Nachricht veröffentlichen, indem Sie JSON in die API chat.postMessage werfen. Der json zu diesem Zeitpunkt wird auf diese Weise gesendet.
{
"token": SLACK_APP_AUTH_TOKEN,
"channel": channel,
"text": message,
"username": "sampleBot"
}
Zu diesem Zeitpunkt als Request-Eigenschaft,
"Content-Type": "application / json; charset = UTF-8",
"Authorization": "Bearer " + SLACK_BOT_USER_ACCESS_TOKEN
Muss eingestellt werden.
Basierend auf dem oben Gesagten habe ich App.java wie folgt bearbeitet.
App.java
package jp.com.hoge;
import java.io.*;
import java.net.URLEncoder;
import java.net.HttpURLConnection;
import java.net.ProtocolException;
import java.net.URL;
import java.nio.charset.Charset;
import java.lang.StringBuilder;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONArray;
import com.amazonaws.services.lambda.runtime.*;
public class App implements RequestStreamHandler
{
public static String SLACK_BOT_USER_ACCESS_TOKEN = "";
public static String SLACK_APP_AUTH_TOKEN = "";
public static String USER_ID = "";
@Override
public void handleRequest(InputStream inputStream, OutputStream outputStream, Context context) throws IOException {
//Umgebungsvariablen lesen
App.SLACK_BOT_USER_ACCESS_TOKEN = System.getenv("SLACK_BOT_USER_ACCESS_TOKEN");
App.SLACK_APP_AUTH_TOKEN = System.getenv("SLACK_APP_AUTH_TOKEN");
App.USER_ID = System.getenv("USER_ID");
//Standardantwort Wenn Sie nichts antworten, wird dasselbe Ereignis mehrmals gesendet
String response = "HTTP 200 OK\nContent-type: text/plain\nOK";
try{
BufferedReader rd = new BufferedReader(new InputStreamReader(inputStream, Charset.forName("UTF-8")));
String jsonText = readAll(rd);
System.out.println(jsonText); //System.Die Ausgabe von out wird in CloudWatch-Protokolle geschrieben
JSONObject json = new JSONObject(jsonText);
//Beim Testen der Ereignis-API
if (json.has("type")) {
String eventType = json.get("type").toString();
if (eventType.equals("url_verification")) {
//Stellen Sie den Inhalt der Herausforderung auf Antwort ein
response = "HTTP 200 OK\nContent-type: text/plain\n" + json.get("challenge").toString();
}
}
// app_Zum Zeitpunkt der Erwähnung Veranstaltung
if (json.has("event")) {
JSONObject eventObject = json.getJSONObject("event");
if(eventObject.has("type")) {
String eventType = eventObject.get("type").toString();
if (eventType.equals("app_mention")){
String user = eventObject.get("user").toString();
if (user.equals(App.USER_ID)) { return; } //Ignorieren Sie, ob die Anweisung der Bot-Benutzer selbst ist
String channel = eventObject.get("channel").toString();
String text = eventObject.get("text").toString();
String responseText = text.replace(App.USER_ID, user);
System.out.println(responseText);
System.out.println(postMessage(responseText, channel));
}
}
}
} catch(IOException e) {
e.printStackTrace();
} finally {
//Schreiben Sie den Inhalt der Antwort auf outputStream
outputStream.write(response.getBytes());
outputStream.flush();
outputStream.close();
}
return;
}
/* get String from BufferedReader */
private static String readAll(Reader rd) throws IOException {
StringBuilder sb = new StringBuilder();
int cp;
while ((cp = rd.read()) != -1) {
sb.append((char) cp);
}
return sb.toString();
}
/* post message to selected channel */
public static String postMessage(String message, String channel) {
String strUrl = "https://slack.com/api/chat.postMessage";
String ret = "";
URL url;
HttpURLConnection urlConnection = null;
try {
url = new URL(strUrl);
urlConnection = (HttpURLConnection) url.openConnection();
} catch(IOException e) {
e.printStackTrace();
return "IOException";
}
urlConnection.setDoOutput(true);
urlConnection.setConnectTimeout(100000);
urlConnection.setReadTimeout(100000);
urlConnection.setRequestProperty("Content-Type", "application/json; charset=UTF-8");
String auth = "Bearer " + App.SLACK_BOT_USER_ACCESS_TOKEN;
urlConnection.setRequestProperty("Authorization", auth);
try {
urlConnection.setRequestMethod("POST");
} catch(ProtocolException e) {
e.printStackTrace();
return "ProtocolException";
}
try {
urlConnection.connect();
} catch(IOException e) {
e.printStackTrace();
return "IOException";
}
HashMap<String, Object> jsonMap = new HashMap<>();
jsonMap.put("token", App.SLACK_APP_AUTH_TOKEN);
jsonMap.put("channel", channel);
jsonMap.put("text", message);
jsonMap.put("username", " sampleBot");
OutputStream outputStream = null;
try {
outputStream = urlConnection.getOutputStream();
} catch(IOException e) {
e.printStackTrace();
return "IOException";
}
if (jsonMap.size() > 0) {
JSONObject responseJsonObject = new JSONObject(jsonMap);
String jsonText = responseJsonObject.toString();
PrintStream ps = new PrintStream(outputStream);
ps.print(jsonText);
ps.close();
}
try {
if (outputStream != null) {
outputStream.close();
}
int responseCode = urlConnection.getResponseCode();
BufferedReader rd = new BufferedReader(new InputStreamReader(urlConnection.getInputStream(), "UTF-8"));
ret = readAll(rd);
} catch(IOException e) {
e.printStackTrace();
return "IOException";
} finally {
if (urlConnection != null) {
urlConnection.disconnect();
}
}
return ret;
}
}
Laden Sie es einfach wie bisher mit "gradle build" hoch und fertig!
Bisher haben wir etwas, das tatsächlich funktioniert. Aber es gibt immer noch Fallstricke. Dies liegt daran, dass die Zeitüberschreitung nur 3 Sekunden beträgt. Wenn Sie also die Zeit zum Starten der Lambda-Funktion angeben, tritt eine unerwartet schnelle Zeitüberschreitung auf!
Es funktioniert wie erwartet ohne Zeitüberschreitung.
Dies ist das Ergebnis einer Verschiebung nach einer Weile. Die Timeout-Zeit ist abgelaufen, wahrscheinlich weil die Lambda-Funktion erneut gestartet werden muss. Da ** slack es als Fehler beurteilte und das Ereignis mehrmals erneut sendete **, wurde die Lambda-Funktion mehrmals aufgerufen und mehrmals veröffentlicht.
Bisher ist es lange her, daher werde ich im nächsten Artikel erklären, wie dies gelöst werden kann. Bitte sehen Sie das auch [AWS Lambda] Verhindert, dass Slack Bot wiederholt mit Zeitüberschreitungen reagiert
Recommended Posts