From 2574aa61f5b12a489b822496f96fb6bfc3b826c5 Mon Sep 17 00:00:00 2001 From: Martin Battaglino Date: Fri, 12 Mar 2021 16:48:15 -0300 Subject: [PATCH] [Samples] Add 25.message-reaction sample (#1055) * Add pom for sample * Add deploymentTemplates * Add webapp * Add teamsAppManifest folder * Add application.properties file * Add README * Migrate main code of message reaction sample * Add empty test * Remove double braces for createMessageActivity method due to problem of mapping * Populate empty test --- .../com/microsoft/bot/schema/Activity.java | 10 +- samples/25.message-reaction/README.md | 77 +++++ .../template-with-new-rg.json | 291 +++++++++++++++++ .../template-with-preexisting-rg.json | 259 ++++++++++++++++ samples/25.message-reaction/pom.xml | 238 ++++++++++++++ .../sample/messagereaction/ActivityLog.java | 64 ++++ .../sample/messagereaction/Application.java | 72 +++++ .../messagereaction/MessageReactionBot.java | 110 +++++++ .../sample/messagereaction/package-info.java | 8 + .../src/main/resources/application.properties | 3 + .../src/main/webapp/META-INF/MANIFEST.MF | 3 + .../src/main/webapp/WEB-INF/web.xml | 12 + .../src/main/webapp/index.html | 293 ++++++++++++++++++ .../messagereaction/ApplicationTest.java | 20 ++ .../teamsAppManifest/icon-color.png | Bin 0 -> 1229 bytes .../teamsAppManifest/icon-outline.png | Bin 0 -> 383 bytes .../teamsAppManifest/manifest.json | 43 +++ 17 files changed, 1497 insertions(+), 6 deletions(-) create mode 100644 samples/25.message-reaction/README.md create mode 100644 samples/25.message-reaction/deploymentTemplates/template-with-new-rg.json create mode 100644 samples/25.message-reaction/deploymentTemplates/template-with-preexisting-rg.json create mode 100644 samples/25.message-reaction/pom.xml create mode 100644 samples/25.message-reaction/src/main/java/com/microsoft/bot/sample/messagereaction/ActivityLog.java create mode 100644 samples/25.message-reaction/src/main/java/com/microsoft/bot/sample/messagereaction/Application.java create mode 100644 samples/25.message-reaction/src/main/java/com/microsoft/bot/sample/messagereaction/MessageReactionBot.java create mode 100644 samples/25.message-reaction/src/main/java/com/microsoft/bot/sample/messagereaction/package-info.java create mode 100644 samples/25.message-reaction/src/main/resources/application.properties create mode 100644 samples/25.message-reaction/src/main/webapp/META-INF/MANIFEST.MF create mode 100644 samples/25.message-reaction/src/main/webapp/WEB-INF/web.xml create mode 100644 samples/25.message-reaction/src/main/webapp/index.html create mode 100644 samples/25.message-reaction/src/test/java/com/microsoft/bot/sample/messagereaction/ApplicationTest.java create mode 100644 samples/25.message-reaction/teamsAppManifest/icon-color.png create mode 100644 samples/25.message-reaction/teamsAppManifest/icon-outline.png create mode 100644 samples/25.message-reaction/teamsAppManifest/manifest.json diff --git a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/Activity.java b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/Activity.java index 560c41d9..6acaa8ff 100644 --- a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/Activity.java +++ b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/Activity.java @@ -273,12 +273,10 @@ public class Activity { * @return A message Activity type. */ public static Activity createMessageActivity() { - return new Activity(ActivityTypes.MESSAGE) { - { - setAttachments(new ArrayList<>()); - setEntities(new ArrayList<>()); - } - }; + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setAttachments(new ArrayList<>()); + activity.setEntities(new ArrayList<>()); + return activity; } /** diff --git a/samples/25.message-reaction/README.md b/samples/25.message-reaction/README.md new file mode 100644 index 00000000..b08ad6fa --- /dev/null +++ b/samples/25.message-reaction/README.md @@ -0,0 +1,77 @@ +## Message Reactions Bot + +Bot Framework [message reactions](https://docs.microsoft.com/en-us/microsoftteams/platform/bots/how-to/conversations/subscribe-to-conversation-events?tabs=dotnet#message-reaction-events) bot sample. + +This bot has been created using [Bot Framework](https://dev.botframework.com), it shows how to create a simple bot that responds to Message Reactions. + +## Prerequisites + +This samples **requires** prerequisites in order to run. + +### Overview + +- Java 1.8+ +- Install [Maven](https://maven.apache.org/) +- An account on [Azure](https://azure.microsoft.com) if you want to deploy to Azure. +- Microsoft Teams is installed and you have an account +- [ngrok](https://ngrok.com/) or equivalent tunnelling solution + +## To try this sample + +> Note these instructions are for running the sample on your local machine, the tunnelling solution is required because +the Teams service needs to call into the bot. + +1) Clone the repository + + ```bash + git clone https://github.com/Microsoft/botbuilder-java.git + ``` + +1) Run ngrok - point to port 3978 + + ```bash + ngrok http -host-header=rewrite 3978 + ``` + +1) Create [Bot Framework registration resource](https://docs.microsoft.com/en-us/azure/bot-service/bot-service-quickstart-registration) in Azure + - Use the current `https` URL you were given by running ngrok. Append with the path `/api/messages` used by this sample + - Ensure that you've [enabled the Teams Channel](https://docs.microsoft.com/en-us/azure/bot-service/channel-connect-teams?view=azure-bot-service-4.0) + - __*If you don't have an Azure account*__ you can use this [Bot Framework registration](https://docs.microsoft.com/en-us/microsoftteams/platform/bots/how-to/create-a-bot-for-teams#register-your-web-service-with-the-bot-framework) + +1) Update the `resources/application.properties` configuration for the bot to use the Microsoft App Id and App Password from the Bot Framework registration. (Note the App Password is referred to as the "client secret" in the azure portal and you can always create a new client secret anytime.) + +1) Update `CustomForm.html` to replace your Microsoft App Id *everywhere* you see the place holder string `<>` + +1) __*This step is specific to Teams.*__ + - **Edit** the `manifest.json` contained in the `teamsAppManifest` folder to replace your Microsoft App Id (that was created when you registered your bot earlier) *everywhere* you see the place holder string `<>` (depending on the scenario the Microsoft App Id may occur multiple times in the `manifest.json`). **Note:** the Task Modules containing pages will require the deployed bot's domain in validDomains of the manifest. + - **Zip** up the contents of the `teamsAppManifest` folder to create a `manifest.zip` + - **Upload** the `manifest.zip` to Teams (in the Apps view click "Upload a custom app") + +1) From the root of this project folder: + - Build the sample using `mvn package` + - Unless done previously, install the packages in the local cache by using `mvn install` + - Run it by using `java -jar .\target\bot-messagereaction-sample.jar` + +## Testing the bot using Bot Framework Emulator + +[Bot Framework Emulator](https://github.com/microsoft/botframework-emulator) is a desktop application that allows bot developers to test and debug their bots on localhost or running remotely through a tunnel. + +- Install the latest Bot Framework Emulator from [here](https://github.com/Microsoft/BotFramework-Emulator/releases) + +### Connect to the bot using Bot Framework Emulator + +- Launch Bot Framework Emulator +- File -> Open Bot +- Enter a Bot URL of `http://localhost:3978/api/messages` + +## Interacting with the bot in Teams + +Message the bot and it will respond with an 'Echo: [your message]'. Add a message reaction to the bots response, and the bot will reply accordingly. + +## Deploy the bot to Azure + +To learn more about deploying a bot to Azure, see [Deploy your bot to Azure](https://aka.ms/azuredeployment) for a complete list of deployment instructions. + +## Further reading + +- [Teams Message Reaction Events](https://docs.microsoft.com/en-us/microsoftteams/platform/bots/how-to/conversations/subscribe-to-conversation-events?tabs=dotnet#message-reaction-events) diff --git a/samples/25.message-reaction/deploymentTemplates/template-with-new-rg.json b/samples/25.message-reaction/deploymentTemplates/template-with-new-rg.json new file mode 100644 index 00000000..ec2460d3 --- /dev/null +++ b/samples/25.message-reaction/deploymentTemplates/template-with-new-rg.json @@ -0,0 +1,291 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "groupLocation": { + "defaultValue": "", + "type": "string", + "metadata": { + "description": "Specifies the location of the Resource Group." + } + }, + "groupName": { + "type": "string", + "metadata": { + "description": "Specifies the name of the Resource Group." + } + }, + "appId": { + "type": "string", + "metadata": { + "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." + } + }, + "appSecret": { + "type": "string", + "metadata": { + "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings." + } + }, + "botId": { + "type": "string", + "metadata": { + "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." + } + }, + "botSku": { + "defaultValue": "S1", + "type": "string", + "metadata": { + "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." + } + }, + "newAppServicePlanName": { + "defaultValue": "", + "type": "string", + "metadata": { + "description": "The name of the App Service Plan." + } + }, + "newAppServicePlanSku": { + "type": "object", + "defaultValue": { + "name": "P1v2", + "tier": "PremiumV2", + "size": "P1v2", + "family": "Pv2", + "capacity": 1 + }, + "metadata": { + "description": "The SKU of the App Service Plan. Defaults to Standard values." + } + }, + "newAppServicePlanLocation": { + "defaultValue": "", + "type": "string", + "metadata": { + "description": "The location of the App Service Plan. Defaults to \"westus\"." + } + }, + "newWebAppName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." + } + } + }, + "variables": { + "appServicePlanName": "[parameters('newAppServicePlanName')]", + "resourcesLocation": "[parameters('newAppServicePlanLocation')]", + "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", + "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", + "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]", + "publishingUsername": "[concat('$', parameters('newWebAppName'))]", + "resourceGroupId": "[concat(subscription().id, '/resourceGroups/', parameters('groupName'))]" + }, + "resources": [ + { + "name": "[parameters('groupName')]", + "type": "Microsoft.Resources/resourceGroups", + "apiVersion": "2018-05-01", + "location": "[parameters('groupLocation')]", + "properties": {} + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2018-05-01", + "name": "storageDeployment", + "resourceGroup": "[parameters('groupName')]", + "dependsOn": [ + "[resourceId('Microsoft.Resources/resourceGroups/', parameters('groupName'))]" + ], + "properties": { + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": {}, + "variables": {}, + "resources": [ + { + "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", + "type": "Microsoft.Web/serverfarms", + "name": "[variables('appServicePlanName')]", + "apiVersion": "2018-02-01", + "location": "[variables('resourcesLocation')]", + "sku": "[parameters('newAppServicePlanSku')]", + "kind": "linux", + "properties": { + "perSiteScaling": false, + "maximumElasticWorkerCount": 1, + "isSpot": false, + "reserved": true, + "isXenon": false, + "hyperV": false, + "targetWorkerCount": 0, + "targetWorkerSizeId": 0 + } + }, + { + "comments": "Create a Web App using a Linux App Service Plan", + "type": "Microsoft.Web/sites", + "apiVersion": "2018-11-01", + "location": "[variables('resourcesLocation')]", + "kind": "app,linux", + "dependsOn": [ + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" + ], + "name": "[variables('webAppName')]", + "properties": { + "name": "[variables('webAppName')]", + "hostNameSslStates": [ + { + "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Standard" + }, + { + "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Repository" + } + ], + "serverFarmId": "[variables('appServicePlanName')]", + "reserved": true, + "isXenon": false, + "hyperV": false, + "scmSiteAlsoStopped": false, + "clientAffinityEnabled": true, + "clientCertEnabled": false, + "hostNamesDisabled": false, + "containerSize": 0, + "dailyMemoryTimeQuota": 0, + "httpsOnly": false, + "redundancyMode": "None", + "siteConfig": { + "appSettings": [ + { + "name": "JAVA_OPTS", + "value": "-Dserver.port=80" + }, + { + "name": "MicrosoftAppId", + "value": "[parameters('appId')]" + }, + { + "name": "MicrosoftAppPassword", + "value": "[parameters('appSecret')]" + } + ], + "cors": { + "allowedOrigins": [ + "https://botservice.hosting.portal.azure.net", + "https://hosting.onecloud.azure-test.net/" + ] + } + } + } + }, + { + "type": "Microsoft.Web/sites/config", + "apiVersion": "2018-11-01", + "name": "[concat(variables('webAppName'), '/web')]", + "location": "[variables('resourcesLocation')]", + "dependsOn": [ + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" + ], + "properties": { + "numberOfWorkers": 1, + "defaultDocuments": [ + "Default.htm", + "Default.html", + "Default.asp", + "index.htm", + "index.html", + "iisstart.htm", + "default.aspx", + "index.php", + "hostingstart.html" + ], + "netFrameworkVersion": "v4.0", + "linuxFxVersion": "JAVA|8-jre8", + "requestTracingEnabled": false, + "remoteDebuggingEnabled": false, + "httpLoggingEnabled": false, + "logsDirectorySizeLimit": 35, + "detailedErrorLoggingEnabled": false, + "publishingUsername": "[variables('publishingUsername')]", + "scmType": "None", + "use32BitWorkerProcess": true, + "webSocketsEnabled": false, + "alwaysOn": true, + "managedPipelineMode": "Integrated", + "virtualApplications": [ + { + "virtualPath": "/", + "physicalPath": "site\\wwwroot", + "preloadEnabled": true + } + ], + "loadBalancing": "LeastRequests", + "experiments": { + "rampUpRules": [] + }, + "autoHealEnabled": false, + "localMySqlEnabled": false, + "ipSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictionsUseMain": false, + "http20Enabled": false, + "minTlsVersion": "1.2", + "ftpsState": "AllAllowed", + "reservedInstanceCount": 0 + } + }, + { + "apiVersion": "2017-12-01", + "type": "Microsoft.BotService/botServices", + "name": "[parameters('botId')]", + "location": "global", + "kind": "bot", + "sku": { + "name": "[parameters('botSku')]" + }, + "properties": { + "name": "[parameters('botId')]", + "displayName": "[parameters('botId')]", + "endpoint": "[variables('botEndpoint')]", + "msaAppId": "[parameters('appId')]", + "developerAppInsightsApplicationId": null, + "developerAppInsightKey": null, + "publishingCredentials": null, + "storageResourceId": null + }, + "dependsOn": [ + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" + ] + } + ], + "outputs": {} + } + } + } + ] +} diff --git a/samples/25.message-reaction/deploymentTemplates/template-with-preexisting-rg.json b/samples/25.message-reaction/deploymentTemplates/template-with-preexisting-rg.json new file mode 100644 index 00000000..024dcf08 --- /dev/null +++ b/samples/25.message-reaction/deploymentTemplates/template-with-preexisting-rg.json @@ -0,0 +1,259 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "appId": { + "type": "string", + "metadata": { + "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." + } + }, + "appSecret": { + "type": "string", + "metadata": { + "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings. Defaults to \"\"." + } + }, + "botId": { + "type": "string", + "metadata": { + "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." + } + }, + "botSku": { + "defaultValue": "S1", + "type": "string", + "metadata": { + "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." + } + }, + "newAppServicePlanName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The name of the new App Service Plan." + } + }, + "newAppServicePlanSku": { + "type": "object", + "defaultValue": { + "name": "P1v2", + "tier": "PremiumV2", + "size": "P1v2", + "family": "Pv2", + "capacity": 1 + }, + "metadata": { + "description": "The SKU of the App Service Plan. Defaults to Standard values." + } + }, + "appServicePlanLocation": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The location of the App Service Plan." + } + }, + "existingAppServicePlan": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Name of the existing App Service Plan used to create the Web App for the bot." + } + }, + "newWebAppName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." + } + } + }, + "variables": { + "defaultAppServicePlanName": "[if(empty(parameters('existingAppServicePlan')), 'createNewAppServicePlan', parameters('existingAppServicePlan'))]", + "useExistingAppServicePlan": "[not(equals(variables('defaultAppServicePlanName'), 'createNewAppServicePlan'))]", + "servicePlanName": "[if(variables('useExistingAppServicePlan'), parameters('existingAppServicePlan'), parameters('newAppServicePlanName'))]", + "publishingUsername": "[concat('$', parameters('newWebAppName'))]", + "resourcesLocation": "[parameters('appServicePlanLocation')]", + "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", + "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", + "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]" + }, + "resources": [ + { + "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", + "type": "Microsoft.Web/serverfarms", + "condition": "[not(variables('useExistingAppServicePlan'))]", + "name": "[variables('servicePlanName')]", + "apiVersion": "2018-02-01", + "location": "[variables('resourcesLocation')]", + "sku": "[parameters('newAppServicePlanSku')]", + "kind": "linux", + "properties": { + "perSiteScaling": false, + "maximumElasticWorkerCount": 1, + "isSpot": false, + "reserved": true, + "isXenon": false, + "hyperV": false, + "targetWorkerCount": 0, + "targetWorkerSizeId": 0 + } + }, + { + "comments": "Create a Web App using a Linux App Service Plan", + "type": "Microsoft.Web/sites", + "apiVersion": "2018-11-01", + "location": "[variables('resourcesLocation')]", + "kind": "app,linux", + "dependsOn": [ + "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]" + ], + "name": "[variables('webAppName')]", + "properties": { + "name": "[variables('webAppName')]", + "hostNameSslStates": [ + { + "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Standard" + }, + { + "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Repository" + } + ], + "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]", + "reserved": true, + "isXenon": false, + "hyperV": false, + "scmSiteAlsoStopped": false, + "clientAffinityEnabled": true, + "clientCertEnabled": false, + "hostNamesDisabled": false, + "containerSize": 0, + "dailyMemoryTimeQuota": 0, + "httpsOnly": false, + "redundancyMode": "None", + "siteConfig": { + "appSettings": [ + { + "name": "JAVA_OPTS", + "value": "-Dserver.port=80" + }, + { + "name": "MicrosoftAppId", + "value": "[parameters('appId')]" + }, + { + "name": "MicrosoftAppPassword", + "value": "[parameters('appSecret')]" + } + ], + "cors": { + "allowedOrigins": [ + "https://botservice.hosting.portal.azure.net", + "https://hosting.onecloud.azure-test.net/" + ] + } + } + } + }, + { + "type": "Microsoft.Web/sites/config", + "apiVersion": "2018-11-01", + "name": "[concat(variables('webAppName'), '/web')]", + "location": "[variables('resourcesLocation')]", + "dependsOn": [ + "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" + ], + "properties": { + "numberOfWorkers": 1, + "defaultDocuments": [ + "Default.htm", + "Default.html", + "Default.asp", + "index.htm", + "index.html", + "iisstart.htm", + "default.aspx", + "index.php", + "hostingstart.html" + ], + "netFrameworkVersion": "v4.0", + "linuxFxVersion": "JAVA|8-jre8", + "requestTracingEnabled": false, + "remoteDebuggingEnabled": false, + "httpLoggingEnabled": false, + "logsDirectorySizeLimit": 35, + "detailedErrorLoggingEnabled": false, + "publishingUsername": "[variables('publishingUsername')]", + "scmType": "None", + "use32BitWorkerProcess": true, + "webSocketsEnabled": false, + "alwaysOn": true, + "managedPipelineMode": "Integrated", + "virtualApplications": [ + { + "virtualPath": "/", + "physicalPath": "site\\wwwroot", + "preloadEnabled": true + } + ], + "loadBalancing": "LeastRequests", + "experiments": { + "rampUpRules": [] + }, + "autoHealEnabled": false, + "localMySqlEnabled": false, + "ipSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictionsUseMain": false, + "http20Enabled": false, + "minTlsVersion": "1.2", + "ftpsState": "AllAllowed", + "reservedInstanceCount": 0 + } + }, + { + "apiVersion": "2017-12-01", + "type": "Microsoft.BotService/botServices", + "name": "[parameters('botId')]", + "location": "global", + "kind": "bot", + "sku": { + "name": "[parameters('botSku')]" + }, + "properties": { + "name": "[parameters('botId')]", + "displayName": "[parameters('botId')]", + "endpoint": "[variables('botEndpoint')]", + "msaAppId": "[parameters('appId')]", + "developerAppInsightsApplicationId": null, + "developerAppInsightKey": null, + "publishingCredentials": null, + "storageResourceId": null + }, + "dependsOn": [ + "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" + ] + } + ] +} diff --git a/samples/25.message-reaction/pom.xml b/samples/25.message-reaction/pom.xml new file mode 100644 index 00000000..e8ac589e --- /dev/null +++ b/samples/25.message-reaction/pom.xml @@ -0,0 +1,238 @@ + + + + 4.0.0 + + com.microsoft.bot.sample + bot-messagereaction + sample + jar + + ${project.groupId}:${project.artifactId} + This package contains a Java Message-reaction Bot sample using Spring Boot. + http://maven.apache.org + + + org.springframework.boot + spring-boot-starter-parent + 2.4.0 + + + + + + MIT License + http://www.opensource.org/licenses/mit-license.php + + + + + + Bot Framework Development + + Microsoft + https://dev.botframework.com/ + + + + + 1.8 + 1.8 + 1.8 + com.microsoft.bot.sample.messagereaction.Application + + + + + org.springframework.boot + spring-boot-starter-test + 2.4.0 + test + + + junit + junit + 4.13.1 + test + + + org.junit.vintage + junit-vintage-engine + test + + + + org.slf4j + slf4j-api + + + org.apache.logging.log4j + log4j-api + 2.11.0 + + + org.apache.logging.log4j + log4j-core + 2.13.2 + + + + com.microsoft.bot + bot-integration-spring + 4.6.0-preview9 + compile + + + + + + build + + true + + + + + src/main/resources + false + + + + + maven-compiler-plugin + 3.8.1 + + + maven-war-plugin + 3.2.3 + + src/main/webapp + + + + org.springframework.boot + spring-boot-maven-plugin + + + + repackage + + + com.microsoft.bot.sample.messagereaction.Application + + + + + + com.microsoft.azure + azure-webapp-maven-plugin + 1.12.0 + + V2 + ${groupname} + ${botname} + + + JAVA_OPTS + -Dserver.port=80 + + + + linux + Java 8 + Java SE + + + + + ${project.basedir}/target + + *.jar + + + + + + + + + + + + publish + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + maven-war-plugin + 3.2.3 + + src/main/webapp + + + + + org.sonatype.plugins + nexus-staging-maven-plugin + 1.6.8 + true + + true + ossrh + https://oss.sonatype.org/ + true + + + + + org.apache.maven.plugins + maven-gpg-plugin + + + sign-artifacts + verify + + sign + + + + + + org.apache.maven.plugins + maven-source-plugin + + + attach-sources + + jar + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + + 8 + false + + + + attach-javadocs + + jar + + + + + + + + + diff --git a/samples/25.message-reaction/src/main/java/com/microsoft/bot/sample/messagereaction/ActivityLog.java b/samples/25.message-reaction/src/main/java/com/microsoft/bot/sample/messagereaction/ActivityLog.java new file mode 100644 index 00000000..f1d1ca5c --- /dev/null +++ b/samples/25.message-reaction/src/main/java/com/microsoft/bot/sample/messagereaction/ActivityLog.java @@ -0,0 +1,64 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.messagereaction; + +import com.microsoft.bot.builder.Storage; +import com.microsoft.bot.schema.Activity; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +/** + * `Activity`'s class with a Storage provider. + */ +public class ActivityLog { + + private Storage storage; + + /** + * Initializes a new instance of the [ActivityLog](xref:reactions-bot.ActivityLog) class. + * + * @param withStorage A storage provider that stores and retrieves plain old JSON objects. + */ + public ActivityLog(Storage withStorage) { + storage = withStorage; + } + + /** + * Saves an {@link Activity} with its associated id into the storage. + * + * @param activityId {@link Activity}'s Id. + * @param activity The {@link Activity} object. + * @return A CompletableFuture + */ + public CompletableFuture append(String activityId, Activity activity) { + if (activityId == null) { + throw new IllegalArgumentException("activityId"); + } + + if (activity == null) { + throw new IllegalArgumentException("activity"); + } + + Map dictionary = new HashMap(); + dictionary.put(activityId, activity); + return storage.write((Map) dictionary); + } + + /** + * Retrieves an {@link Activity} from the storage by a given Id. + * + * @param activityId {@link Activity}'s Id. + * @return The {@link Activity}'s object retrieved from storage. + */ + public CompletableFuture find(String activityId) { + if (activityId == null) { + throw new IllegalArgumentException("activityId"); + } + + return storage.read(new String[]{activityId}) + .thenApply(activitiesResult -> + activitiesResult.size() >= 1 ? ((Activity) activitiesResult.get(activityId)) : null); + } +} diff --git a/samples/25.message-reaction/src/main/java/com/microsoft/bot/sample/messagereaction/Application.java b/samples/25.message-reaction/src/main/java/com/microsoft/bot/sample/messagereaction/Application.java new file mode 100644 index 00000000..878deca5 --- /dev/null +++ b/samples/25.message-reaction/src/main/java/com/microsoft/bot/sample/messagereaction/Application.java @@ -0,0 +1,72 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.messagereaction; + +import com.microsoft.bot.builder.Bot; +import com.microsoft.bot.builder.Storage; +import com.microsoft.bot.integration.AdapterWithErrorHandler; +import com.microsoft.bot.integration.BotFrameworkHttpAdapter; +import com.microsoft.bot.integration.Configuration; +import com.microsoft.bot.integration.spring.BotController; +import com.microsoft.bot.integration.spring.BotDependencyConfiguration; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Import; + +/** + * This is the starting point of the Sprint Boot Bot application. + */ +@SpringBootApplication + +// Use the default BotController to receive incoming Channel messages. A custom +// controller could be used by eliminating this import and creating a new +// org.springframework.web.bind.annotation.RestController. +// The default controller is created by the Spring Boot container using +// dependency injection. The default route is /api/messages. +@Import({BotController.class}) + +/** + * This class extends the BotDependencyConfiguration which provides the default + * implementations for a Bot application. The Application class should + * override methods in order to provide custom implementations. + */ +public class Application extends BotDependencyConfiguration { + + /** + * The start method. + * + * @param args The args. + */ + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } + + /** + * Returns the Bot for this application. + * + *

+ * The @Component annotation could be used on the Bot class instead of this method with the + * @Bean annotation. + *

+ * + * @return The Bot implementation for this application. + */ + @Bean + public Bot getBot(Storage storage) { + ActivityLog log = new ActivityLog(storage); + return new MessageReactionBot(log); + } + + /** + * Returns a custom Adapter that provides error handling. + * + * @param configuration The Configuration object to use. + * @return An error handling BotFrameworkHttpAdapter. + */ + @Override + public BotFrameworkHttpAdapter getBotFrameworkHttpAdaptor(Configuration configuration) { + return new AdapterWithErrorHandler(configuration); + } +} diff --git a/samples/25.message-reaction/src/main/java/com/microsoft/bot/sample/messagereaction/MessageReactionBot.java b/samples/25.message-reaction/src/main/java/com/microsoft/bot/sample/messagereaction/MessageReactionBot.java new file mode 100644 index 00000000..9794d4ab --- /dev/null +++ b/samples/25.message-reaction/src/main/java/com/microsoft/bot/sample/messagereaction/MessageReactionBot.java @@ -0,0 +1,110 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.messagereaction; + +import com.microsoft.bot.builder.ActivityHandler; +import com.microsoft.bot.builder.MessageFactory; +import com.microsoft.bot.builder.TurnContext; +import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.MessageReaction; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +/** + * From the UI you need to @mention the bot, then add a message reaction to the message the bot sent. + */ +public class MessageReactionBot extends ActivityHandler { + + private final ActivityLog log; + + /** + * Initializes a new instance of the {@link MessageReactionBot} class. + * + * @param withLog The {@link ActivityLog}. + */ + public MessageReactionBot(ActivityLog withLog) { + log = withLog; + } + + /** + * {@inheritDoc} + */ + @Override + protected CompletableFuture onMessageActivity(TurnContext turnContext) { + return sendMessageAndLogActivityId(turnContext, String.format("echo: %s", turnContext.getActivity().getText())); + } + + /** + * {@inheritDoc} + */ + @Override + protected CompletableFuture onReactionsAdded( + List messageReactions, + TurnContext turnContext + ) { + for (MessageReaction reaction : messageReactions) { + // The ReplyToId property of the inbound MessageReaction Activity will correspond to a Message Activity + // which had previously been sent from this bot. + log.find(turnContext.getActivity().getReplyToId()).thenCompose(resultActivity -> { + if (resultActivity == null) { + // If we had sent the message from the error handler + // we wouldn't have recorded the Activity Id and so we shouldn't expect to see it in the log. + return sendMessageAndLogActivityId( + turnContext, + String.format("Activity %s not found in the log.", turnContext.getActivity().getReplyToId())); + } + + return sendMessageAndLogActivityId( + turnContext, + String.format("You added '%s' regarding '%s'", reaction.getType(), resultActivity.getText())); + }); + } + + return CompletableFuture.completedFuture(null); + } + + /** + * {@inheritDoc} + */ + @Override + protected CompletableFuture onReactionsRemoved( + List messageReactions, + TurnContext turnContext + ) { + for (MessageReaction reaction : messageReactions) { + // The ReplyToId property of the inbound MessageReaction Activity will correspond to a Message Activity + // which was previously sent from this bot. + log.find(turnContext.getActivity().getReplyToId()).thenCompose(resultActivity -> { + if (resultActivity == null) { + // If we had sent the message from the error handler + // we wouldn't have recorded the Activity Id and so we shouldn't expect to see it in the log. + return sendMessageAndLogActivityId( + turnContext, + String.format("Activity %s not found in the log.", turnContext.getActivity().getReplyToId())); + } + + return sendMessageAndLogActivityId( + turnContext, + String.format("You removed '%s' regarding '%s'", reaction.getType(), resultActivity.getText())); + }); + } + + return CompletableFuture.completedFuture(null); + } + + /** + * Sends an activity reply. + * + * @param context The {@link TurnContext}. + * @param text The {@link Activity}'s text. + */ + private CompletableFuture sendMessageAndLogActivityId(TurnContext turnContext, String text) { + // We need to record the Activity Id from the Activity just sent + // in order to understand what the reaction is a reaction too. + Activity replyActivity = MessageFactory.text(text); + return turnContext.sendActivity(replyActivity) + .thenCompose(resourceResponse -> log.append(resourceResponse.getId(), replyActivity) + .thenApply(result -> null)); + } +} diff --git a/samples/25.message-reaction/src/main/java/com/microsoft/bot/sample/messagereaction/package-info.java b/samples/25.message-reaction/src/main/java/com/microsoft/bot/sample/messagereaction/package-info.java new file mode 100644 index 00000000..9fb1b900 --- /dev/null +++ b/samples/25.message-reaction/src/main/java/com/microsoft/bot/sample/messagereaction/package-info.java @@ -0,0 +1,8 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for +// license information. + +/** + * This package contains the classes for the message reaction sample. + */ +package com.microsoft.bot.sample.messagereaction; diff --git a/samples/25.message-reaction/src/main/resources/application.properties b/samples/25.message-reaction/src/main/resources/application.properties new file mode 100644 index 00000000..d7d0ee86 --- /dev/null +++ b/samples/25.message-reaction/src/main/resources/application.properties @@ -0,0 +1,3 @@ +MicrosoftAppId= +MicrosoftAppPassword= +server.port=3978 diff --git a/samples/25.message-reaction/src/main/webapp/META-INF/MANIFEST.MF b/samples/25.message-reaction/src/main/webapp/META-INF/MANIFEST.MF new file mode 100644 index 00000000..254272e1 --- /dev/null +++ b/samples/25.message-reaction/src/main/webapp/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Class-Path: + diff --git a/samples/25.message-reaction/src/main/webapp/WEB-INF/web.xml b/samples/25.message-reaction/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 00000000..383c1900 --- /dev/null +++ b/samples/25.message-reaction/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,12 @@ + + + dispatcher + + org.springframework.web.servlet.DispatcherServlet + + + contextConfigLocation + /WEB-INF/spring/dispatcher-config.xml + + 1 + \ No newline at end of file diff --git a/samples/25.message-reaction/src/main/webapp/index.html b/samples/25.message-reaction/src/main/webapp/index.html new file mode 100644 index 00000000..fe10f902 --- /dev/null +++ b/samples/25.message-reaction/src/main/webapp/index.html @@ -0,0 +1,293 @@ + + + + + + + Teams Message Reaction Bot + + + + + +
+
+
+
Teams Message Reaction Bot
+
+
+
+
+
Your bot is ready!
+
+ You can now test your bot in Teams.
+
+ Visit + Azure + Bot Service + to register your bot and add it to the
+ Teams channel. The bot's endpoint URL typically looks + like this: +
+
https://your_bots_hostname/api/messages
+
+
+
+ +
+
+ + + diff --git a/samples/25.message-reaction/src/test/java/com/microsoft/bot/sample/messagereaction/ApplicationTest.java b/samples/25.message-reaction/src/test/java/com/microsoft/bot/sample/messagereaction/ApplicationTest.java new file mode 100644 index 00000000..d2e86a45 --- /dev/null +++ b/samples/25.message-reaction/src/test/java/com/microsoft/bot/sample/messagereaction/ApplicationTest.java @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.messagereaction; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class ApplicationTest { + + @Test + public void contextLoads() { + } + +} + diff --git a/samples/25.message-reaction/teamsAppManifest/icon-color.png b/samples/25.message-reaction/teamsAppManifest/icon-color.png new file mode 100644 index 0000000000000000000000000000000000000000..48a2de13303e1e8a25f76391f4a34c7c4700fd3d GIT binary patch literal 1229 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD1|%QND7OGojKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WCe1|JzX3_D&pSWuFnWfl{x;g|9jrEYf8Vqrkk2Ba|%ol3OT){=#|7ID~|e{ zODQ{kU&ME#@`*-tm%Tukt_gFr+`F?$dx9wg-jad`^gsMn2_%Kh%WH91&SjKq5 zgkdI|!exdOVgw@>>=!Tjnk6q)zV*T8$FdgRFYC{kQ7``NOcl@R(_%_8e5e0E;>v0G zEM9kb)2itgOTSfH7M=b3-S61B?PiazMdwXZwrS)^5UUS#HQjaoua5h_{Gx*_Zz|XK z$tf0mZ&=tpf2!!Q)!A_l&o_$g*|JM$VZa~F^0{x1T{=QFu*x$`=V%~jUW=G`iqqp=lquB-`P{Qjw`=zEu3cMc_x7m2f#9m}uoFBMMQ^+%cOL)F_)N@JZ}Axoxi1y= zeebq`y==e!nl+?cK-PhOec!3%|IupShHrcjW8sSt)F1>NW*{ zW%ljk2)nk%-}+F&?gi=7^$L#VeX3@kp%f{n}fR z`}uZPx$IY~r8R5%gMlrc`jP!L3IloKFoq~sFFH5|cdklX=R08T)}71BhaN8$`AsNf0_ zq>WNhAtCd|-nBlTU=y5zl_vXlXZ~bkuaYENMp>3QSQ_#zuYZ+eQh*OIHRxP~s(}ic zN2J4$u=AQcPt)|>F3zZLsjtP;Tajkugx;NcYED2~JVBlVO>{`uAY?Q4O|AA z=16}CJieK^5P_TKnou!zGR`$!PUC)DqtkO;?!`p!+9v3lP_mu=%Vt3BkoWsq%;FN1sp58w*zfr-z^7tIb*q>!yncCjrzLuOk3N+d&~^Cxd| z>", + "packageName": "com.teams.sample.messageReaction", + "developer": { + "name": "MessageReactionBot", + "websiteUrl": "https://www.microsoft.com", + "privacyUrl": "https://www.teams.com/privacy", + "termsOfUseUrl": "https://www.teams.com/termsofuser" + }, + "icons": { + "color": "icon-color.png", + "outline": "icon-outline.png" + }, + "name": { + "short": "MessageReactionBot", + "full": "MessageReactionBot" + }, + "description": { + "short": "MessageReactionBot", + "full": "MessageReactionBot" + }, + "accentColor": "#FFFFFF", + "bots": [ + { + "botId": "<>", + "scopes": [ + "groupchat", + "team", + "personal" + ], + "supportsFiles": false, + "isNotificationOnly": false + } + ], + "permissions": [ + "identity", + "messageTeamMembers" + ], + "validDomains": [] +}