Lors de la création d'un robot slack, je pense que j'utiliserai Python ou Node.js dans la plupart des cas, mais cette fois j'oserai aller avec Java. Je n'ai pas trouvé d'article sur la création d'un robot slack sans serveur via AWS avec Java en japonais ou en anglais, je vais donc écrire la procédure de mise en œuvre ici. Je suis un amateur de Java, il vaut donc mieux faire ça! Je vous serais reconnaissant de bien vouloir souligner certains points. Le code entier est sur github https://github.com/kamata1729/sampleSlackBotWithJava
Tout d'abord, créons un Slack Bot! https://api.slack.com/apps À partir de là, appuyez sur Créer une nouvelle application pour créer une nouvelle application.
Et ajoutez un utilisateur bot à l'application créée
À partir de là, nous allons créer un point de terminaison pour recevoir les événements de Slack sur AWS. Plus précisément, API Gateway reçoit l'événement et le transmet à la fonction lambda.
Ouvrez IAM à partir de la console AWS et créez un nouveau rôle.
Puisqu'il sera utilisé dans lambda, sélectionnez "lambda" et procédez d'abord.
Cette fois, je veux pouvoir sortir le journal dans CloudWatch Log, donc sélectionnez ʻAWS LambdaBasicExecutionRoleavec l'autorisation d'écriture
Je ne vais pas l'utiliser cette fois pour créer la prochaine balise, vous pouvez donc l'ignorer. Sur l'écran de confirmation suivant, donnez un nom de rôle et vous avez terminé! Cette fois, je l'ai nommé «sampleBotRole».
Accédez maintenant à la console lambda et choisissez de créer une fonction.
Sélectionnez «Créer à partir de zéro», cette fois sélectionnez «sampleBot» pour le nom de la fonction et «java 8» pour le runtime.
De plus, pour le rôle, j'ai sélectionné le sampleBotRole
créé plus tôt.
Tout d'abord, sélectionnez «Créer une API» sur la page API Gateway de la console aws et créez-la avec le nom de l'API («sampleBotAPI» cette fois).
Depuis l'écran créé, sélectionnez ʻAction-> Create Method pour ajouter la méthode
POST. ![image.png](https://qiita-image-store.s3.amazonaws.com/0/262908/12a7ec34-251a-41eb-e776-0d000634d735.png) Appuyez sur la coche pour créer une méthode POST, définissez-la pour qu'elle corresponde à la fonction lambda créée précédemment sur l'écran de configuration et enregistrez-la. ![image.png](https://qiita-image-store.s3.amazonaws.com/0/262908/3f5da402-3d95-0c7a-7461-91b6cc428eb4.png) Sur l'écran suivant, sélectionnez ʻAction-> Deploy API
et entrez le nom de l'étape à déployer.
À ce stade, l'URL pour appeler le point de terminaison sera affichée en haut de l'écran, alors notez-la.
Ceci termine la création du point final!
Avant d'implémenter la fonction lambda, parlons de l'authentification de l'API d'événement slack. Avec l'API d'événement slack, vous devez d'abord renvoyer une chaîne spécifique pour voir si le programme est destiné à l'API d'événement.
Pour l'authentification, le json suivant est d'abord envoyé.
{
"token": "Jhj5dZrVaK7ZwHHjRyZWjbDl",
"challenge": "3eZbrw1aBm2rZgRNFdxV2595E9CY3gmdALWMmHkvFXO7tYXAYM8P",
"type": "url_verification"
}
En revanche, si le contenu de «challenge» peut être renvoyé dans les 3 secondes, l'authentification est terminée. Il existe trois formats de renvoi. Pour plus de détails, veuillez consulter 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"}
Lors de la définition des E / S AWS lambda en Java, vous devez implémenter une implémentation de gestionnaire, ce qui peut être effectué de trois manières. Je vais l'écrire brièvement ici, mais voir ci-dessous pour plus de détails 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
Il utilise les types implémentés par défaut sans définir de types d'entrée / sortie spéciaux. Mettre en œuvre comme suit
outputType handler-name(inputType input, Context context) {
...
}
Il semble que ʻinputType, ʻoutputType
de ceci prennent en charge le type de chaîne, le type entier, le type booléen, le type de carte et le type de liste par défaut.
(Voir ci-dessous pour plus de détails https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/java-programming-model-req-resp.html)
Par exemple, l'implémentation suivante est possible
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);
}
}
Si vous souhaitez spécifier votre propre type pour ʻinputType et ʻoutputType
, vous pouvez également définir et utiliser le type qui correspond au format d'entrée / sortie.
Cela peut être mis en œuvre comme suit:
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);
}
}
Il existe également un moyen d'activer n'importe quelle entrée et sortie en utilisant ʻInputStream et ʻOutputStream
.
Vous pouvez répondre en écrivant une chaîne d'octets dans ʻOutputStream`.
Un exemple est un programme qui met en majuscule une chaîne donnée et la renvoie.
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));
}
}
}
Cette fois, il est difficile de recevoir des jsons imbriqués de type POJO, je vais donc utiliser RequestStreamHandler.
Sur la base de ce qui précède, nous coderons la fonction lambda, mais contrairement à python, etc., dans le cas de Java, le code ne peut pas être édité en ligne. Par conséquent, cette fois, je vais créer un projet java avec maven local et le télécharger dans un zip en utilisant gradle.
Commencez par créer un projet avec maven. (Maven est utilisé uniquement pour créer un projet, donc peu importe si vous le créez d'une autre manière.)
$ mvn archetype:generate
On vous demandera peut-être beaucoup de choses en chemin, mais à l'exception de groupId
et ʻartifactId, vous pouvez entrer. Cette fois,
groupId est
jp.com.hoge, et ʻartifactId
est SampleBot
.
Une fois exécuté, un dossier avec le nom du projet sera créé et src / main / java / jp / com / hoge / App.java
sera généré.
Modifiez App.java comme suit.
Même si vous ne parvenez pas à obtenir le contenu de challenge
, si vous ne renvoyez pas de chaîne de caractères, cela sera considéré comme un timeout et l'événement sera renvoyé après 1 minute et 5 minutes, donc pour le moment" OK Je répondrai par ".
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 {
//Réponse par défaut Si vous ne répondez rien, le même événement sera envoyé plusieurs fois
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.La sortie de out est écrite dans CloudWatch Logs
JSONObject json = new JSONObject(jsonText);
if (json.has("type")) {
String eventType = json.get("type").toString();
if (eventType.equals("url_verification")) {
//Définir le contenu du défi à la réponse
response = "HTTP 200 OK\nContent-type: text/plain\n" + json.get("challenge").toString();
}
}
} catch(IOException e) {
e.printStackTrace();
} finally {
//Ecrire le contenu de la réponse dans 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();
}
}
De plus, placez build.gradle
directement sous le dossier du projet.
Si vous avez d'autres bibliothèques que vous souhaitez utiliser, vous pouvez les écrire dans les dépendances
ou créer un dossier lib et y placer le fichier .jar.
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
À ce stade, exécutez ce qui suit directement sous le dossier du projet à générer.
$ gradle build
Si la construction réussit, build / distribution / SampleBot.zip
doit être généré.
Ouvrez la console AWS lambda et téléchargez le SampleBot.zip
que vous venez de créer.
Entrez jp.com.hoge.App
dans le gestionnaire et n'oubliez pas de l'enregistrer.
Testons d'abord la fonction lambda. Sélectionnez "Créer un événement de test" à partir de la flèche vers le bas dans la zone qui dit "Sélectionner un événement de test ..." en haut à droite de l'écran, et créez-le comme indiqué dans la figure ci-dessous.
Cliquez sur le bouton "Test" en haut à droite de l'écran pour exécuter le test. En cas de succès, le résultat de l'exécution sera affiché et si vous appuyez sur «Détails», il apparaîtra comme indiqué ci-dessous. Vous pouvez voir que la sortie est faite correctement
En regardant CloudWatch, le contenu de la sortie inputStream sous forme de journal est affiché.
À ce stade, vous pouvez enfin vous abonner à l'API Slack Event! Sélectionnez "Abonnements aux événements" sur le côté gauche de la page lorsque vous avez créé le bot Activez l'option Activer les événements et entrez l'URL que vous avez notée lors du déploiement de l'API dans le champ URL de la demande. Une fois la réponse confirmée, elle ressemblera à celle ci-dessous et vous pourrez vous abonner aux événements!
Cette fois, recevons un événement appelé ʻapp_mention`. Il s'agit d'un événement qui réagit lorsqu'un bot est mentionné avec un @. Appuyez sur "Oublier les modifications enregistrées" une fois ajoutées
Ensuite, installez le bot dans l'espace de travail Sélectionnez "OAuth & Permissions" et faites défiler vers le bas jusqu'au menu appelé Scopes. Sélectionnez "Send messages as SampleBot" et "Save Changes". Vous pouvez maintenant envoyer un message sous forme de SampleBot.
Ensuite, installez avec "Install App to Workspace".
ʻOAuth Access Tokenet
Bot User OAuth Access Token` sont affichés, alors notez-les également.
À partir de là, je vais créer un exemple de Bot qui répond à une déclaration qui m'est adressée en renvoyant un perroquet. Le code source complet est publié sur github https://github.com/kamata1729/sampleSlackBotWithJava
ʻApp_mention` L'événement est envoyé comme suit.
{
"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"
]
}
D'autre part, sur le même canal, créez un Bot qui publie `` Quelle est l'heure de la perle, <@ U061F7AUR>? '' `Avec la mention changée en nom d'utilisateur de l'autre utilisateur.
Nous devons remplacer l'ID utilisateur dans le texte, donc obtenez l'ID utilisateur du bot. Tout d'abord, obtenez le jeton d'espace waork parmi les éléments suivants. https://api.slack.com/custom-integrations/legacy-tokens Utilisez ce jeton pour accéder à l'url suivante, obtenir l'ID du samplebot et en prendre note. Une chaîne commençant par un U supérieur. https://slack.com/api/users.list?token=取得したtoken
Je l'ai noté en tant que variable d'environnement sur la page AWS lambda,
SLACK_BOT_USER_ACCESS_TOKEN
: jeton commençant par xoxp-SLACK_APP_AUTH_TOKEN
: jeton commençant par xoxb-Enregistrez les trois. Vous pouvez maintenant le récupérer à partir de la méthode System.getenv
.
Vous pouvez publier un message en lançant JSON dans l'API chat.postMessage. Le json à ce moment-là est envoyé de cette manière.
{
"token": SLACK_APP_AUTH_TOKEN,
"channel": channel,
"text": message,
"username": "sampleBot"
}
À ce moment-là, en tant que propriété de demande
" Content-Type ":" application / json; charset = UTF-8 "
,
"Authorization": "Bearer " + SLACK_BOT_USER_ACCESS_TOKEN
Doit être réglé.
Sur la base de ce qui précède, j'ai modifié App.java comme suit.
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 {
//Lecture des variables d'environnement
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");
//Réponse par défaut Si vous ne répondez rien, le même événement sera envoyé plusieurs fois
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.La sortie de out est écrite dans CloudWatch Logs
JSONObject json = new JSONObject(jsonText);
//Lors du test de l'API d'événement
if (json.has("type")) {
String eventType = json.get("type").toString();
if (eventType.equals("url_verification")) {
//Définir le contenu du défi à la réponse
response = "HTTP 200 OK\nContent-type: text/plain\n" + json.get("challenge").toString();
}
}
// app_Au moment de l'événement de mention
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; } //Ignorer si l'instruction est l'utilisateur du bot lui-même
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 {
//Ecrire le contenu de la réponse dans 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;
}
}
Téléchargez-le simplement avec gradle build
comme avant et vous avez terminé!
Jusqu'à présent, nous avons quelque chose qui fonctionne réellement. Mais il y a encore des écueils. En effet, le délai d'expiration du délai n'est que de 3 secondes, donc si vous incluez le temps de démarrage de la fonction lambda, il expirera rapidement de manière inattendue!
Cela fonctionne comme prévu sans délai.
C'est le résultat de son déplacement après un certain temps. Le délai d'expiration est écoulé, probablement parce que la fonction lambda doit être redémarrée. Puisque ** slack l'a jugé comme une erreur et a renvoyé l'événement plusieurs fois **, la fonction lambda a été appelée plusieurs fois et publiée plusieurs fois.
Cela fait longtemps jusqu'à présent, je vais donc vous expliquer comment résoudre ce problème dans le prochain article. S'il vous plaît voir cela aussi [AWS lambda] Empêcher Slack Bot de répondre plusieurs fois avec des délais d'expiration
Recommended Posts