├── deploymentScripts ├── linux │ ├── .deployment │ └── deploy.sh └── windows │ ├── .deployment │ └── deploy.cmd ├── appPackage ├── color.png ├── outline.png ├── README.md └── manifest.json ├── media ├── demo-video.gif ├── learn-TV.png ├── stream-play.png └── youtube-play.png ├── .env ├── .eslintrc.js ├── CODE_OF_CONDUCT.md ├── package.json ├── LICENSE ├── SUPPORT.md ├── deploymentTemplates ├── preexisting-rg-parameters.json ├── new-rg-parameters.json ├── template-with-preexisting-rg.json ├── linux │ └── template.json └── template-with-new-rg.json ├── .gitignore ├── SECURITY.md ├── aad_auth.js ├── index.js ├── bot.js ├── README.md └── adaptiveCards └── cards.js /deploymentScripts/linux/.deployment: -------------------------------------------------------------------------------- 1 | [config] 2 | command = ./deploy.sh -------------------------------------------------------------------------------- /deploymentScripts/windows/.deployment: -------------------------------------------------------------------------------- 1 | [config] 2 | command = deploy.cmd -------------------------------------------------------------------------------- /appPackage/color.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OfficeDev/Teams-Video-Player-Bot/HEAD/appPackage/color.png -------------------------------------------------------------------------------- /media/demo-video.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OfficeDev/Teams-Video-Player-Bot/HEAD/media/demo-video.gif -------------------------------------------------------------------------------- /media/learn-TV.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OfficeDev/Teams-Video-Player-Bot/HEAD/media/learn-TV.png -------------------------------------------------------------------------------- /appPackage/outline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OfficeDev/Teams-Video-Player-Bot/HEAD/appPackage/outline.png -------------------------------------------------------------------------------- /media/stream-play.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OfficeDev/Teams-Video-Player-Bot/HEAD/media/stream-play.png -------------------------------------------------------------------------------- /media/youtube-play.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OfficeDev/Teams-Video-Player-Bot/HEAD/media/youtube-play.png -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | MicrosoftAppId = '' 2 | MicrosoftAppPassword = '' 3 | MicrosoftAppType = '' 4 | MicrosoftAppTenantId = '' 5 | 6 | # id of the app set in the app manifest 7 | TeamsExternalAppID = '4e500d53-05f0-49e5-ae37-f3ef50747f90' 8 | 9 | # If running locally, set baseURL to your ngrok URL in https:// format 10 | baseURL = 'https://[REPLACE_HERE].ngrok.io' 11 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | module.exports = { 3 | "extends": "standard", 4 | "rules": { 5 | "semi": [2, "always"], 6 | "indent": [2, 4], 7 | "no-return-await": 0, 8 | "space-before-function-paren": [2, { 9 | "named": "never", 10 | "anonymous": "never", 11 | "asyncArrow": "always" 12 | }], 13 | "template-curly-spacing": [2, "always"] 14 | } 15 | }; -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Microsoft Open Source Code of Conduct 2 | 3 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 4 | 5 | Resources: 6 | 7 | - [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/) 8 | - [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) 9 | - Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "videoplayerbot", 3 | "version": "1.0.0", 4 | "description": "Bot Builder v4 echo bot sample", 5 | "author": "Microsoft", 6 | "license": "MIT", 7 | "main": "index.js", 8 | "scripts": { 9 | "start": "node ./index.js", 10 | "watch": "nodemon ./index.js", 11 | "lint": "eslint .", 12 | "test": "echo \"Error: no test specified\" && exit 1" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "https://github.com" 17 | }, 18 | "dependencies": { 19 | "@azure/msal-node": "^1.5.0", 20 | "@microsoft/microsoft-graph-client": "^3.0.1", 21 | "botbuilder": "~4.15.0", 22 | "dotenv": "^8.2.0", 23 | "isomorphic-fetch": "^3.0.0", 24 | "restify": "~8.6.0", 25 | "url": "^0.11.0" 26 | }, 27 | "devDependencies": { 28 | "eslint": "^7.0.0", 29 | "eslint-config-standard": "^14.1.1", 30 | "eslint-plugin-import": "^2.20.2", 31 | "eslint-plugin-node": "^11.1.0", 32 | "eslint-plugin-promise": "^4.2.1", 33 | "eslint-plugin-standard": "^4.0.1", 34 | "nodemon": "~2.0.4" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /appPackage/README.md: -------------------------------------------------------------------------------- 1 | # App Package 2 | 3 | Your Teams "app package" is used to publish your app configuration to Teams by manually using the "Upload a custom app" from the app gallery in Teams. 4 | 5 | This package can also be used to manually publish your app to the Teams store or to your organization's store. 6 | 7 | You need to provide and remplace the following information in the manifest.json file: 8 | - **`<>`** - Is the Application ID (aka client ID) from Azure AD 9 | - **`<>`** - Is the domain name of where your videos are hosted - You can remove this property if you use SharePoint only 10 | - **`<>`** - Is the domain name of your SharePoint Online site (if videos are hosted on SharePoint) 11 | 12 | To create the ZIP package, selection the files 'outline.png', 'color.png' and 'manifest.json' to create a ZIP file. Please make sure the 'manifest.json' is updated first based on the above instrusctions and that you select the 3 files direclty to create the ZIP file (don't ZIP the directory that contains the files) 13 | 14 | **NOTE: Values in the manifest.json file will need to be updated before creating the zip package to use for publishing.** -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Microsoft Corporation. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE 22 | -------------------------------------------------------------------------------- /SUPPORT.md: -------------------------------------------------------------------------------- 1 | # TODO: The maintainer of this repo has not yet edited this file 2 | 3 | **REPO OWNER**: Do you want Customer Service & Support (CSS) support for this product/project? 4 | 5 | - **No CSS support:** Fill out this template with information about how to file issues and get help. 6 | - **Yes CSS support:** Fill out an intake form at [aka.ms/spot](https://aka.ms/spot). CSS will work with/help you to determine next steps. More details also available at [aka.ms/onboardsupport](https://aka.ms/onboardsupport). 7 | - **Not sure?** Fill out a SPOT intake as though the answer were "Yes". CSS will help you decide. 8 | 9 | *Then remove this first heading from this SUPPORT.MD file before publishing your repo.* 10 | 11 | # Support 12 | 13 | ## How to file issues and get help 14 | 15 | This project uses GitHub Issues to track bugs and feature requests. Please search the existing 16 | issues before filing new issues to avoid duplicates. For new issues, file your bug or 17 | feature request as a new Issue. 18 | 19 | For help and questions about using this project, please **REPO MAINTAINER: INSERT INSTRUCTIONS HERE 20 | FOR HOW TO ENGAGE REPO OWNERS OR COMMUNITY FOR HELP. COULD BE A STACK OVERFLOW TAG OR OTHER 21 | CHANNEL. WHERE WILL YOU HELP PEOPLE?**. 22 | 23 | ## Microsoft Support Policy 24 | 25 | Support for this **PROJECT or PRODUCT** is limited to the resources listed above. 26 | -------------------------------------------------------------------------------- /deploymentTemplates/preexisting-rg-parameters.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "appId": { 6 | "value": "" 7 | }, 8 | "appSecret": { 9 | "value": "" 10 | }, 11 | "appType": { 12 | "value": "MultiTenant" 13 | }, 14 | "botId": { 15 | "value": "" 16 | }, 17 | "botSku": { 18 | "value": "" 19 | }, 20 | "newAppServicePlanName": { 21 | "value": "" 22 | }, 23 | "newAppServicePlanSku": { 24 | "value": { 25 | "name": "S1", 26 | "tier": "Standard", 27 | "size": "S1", 28 | "family": "S", 29 | "capacity": 1 30 | } 31 | }, 32 | "appServicePlanLocation": { 33 | "value": "" 34 | }, 35 | "existingAppServicePlan": { 36 | "value": "" 37 | }, 38 | "newWebAppName": { 39 | "value": "" 40 | }, 41 | "tenantId": { 42 | "value": "" 43 | }, 44 | "existingUserAssignedMSIName": { 45 | "value": "" 46 | }, 47 | "existingUserAssignedMSIResourceGroupName": { 48 | "value": "" 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /deploymentTemplates/new-rg-parameters.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "groupLocation": { 6 | "value": "" 7 | }, 8 | "groupName": { 9 | "value": "" 10 | }, 11 | "appId": { 12 | "value": "" 13 | }, 14 | "appSecret": { 15 | "value": "" 16 | }, 17 | "appType": { 18 | "value": "MultiTenant" 19 | }, 20 | "botId": { 21 | "value": "" 22 | }, 23 | "botSku": { 24 | "value": "" 25 | }, 26 | "newAppServicePlanName": { 27 | "value": "" 28 | }, 29 | "newAppServicePlanSku": { 30 | "value": { 31 | "name": "S1", 32 | "tier": "Standard", 33 | "size": "S1", 34 | "family": "S", 35 | "capacity": 1 36 | } 37 | }, 38 | "newAppServicePlanLocation": { 39 | "value": "" 40 | }, 41 | "newWebAppName": { 42 | "value": "" 43 | }, 44 | "tenantId": { 45 | "value": "" 46 | }, 47 | "existingUserAssignedMSIName": { 48 | "value": "" 49 | }, 50 | "existingUserAssignedMSIResourceGroupName": { 51 | "value": "" 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | manifest.json.bkp 75 | cardbkp.json 76 | 77 | # parcel-bundler cache (https://parceljs.org/) 78 | .cache 79 | 80 | # Next.js build output 81 | .next 82 | 83 | # Nuxt.js build / generate output 84 | .nuxt 85 | dist 86 | 87 | # Gatsby files 88 | .cache/ 89 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 90 | # https://nextjs.org/blog/next-9-1#public-directory-support 91 | # public 92 | 93 | # vuepress build output 94 | .vuepress/dist 95 | 96 | # Serverless directories 97 | .serverless/ 98 | 99 | # FuseBox cache 100 | .fusebox/ 101 | 102 | # DynamoDB Local files 103 | .dynamodb/ 104 | 105 | # TernJS port file 106 | .tern-port 107 | 108 | # Video media files 109 | *.mp4 110 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Security 4 | 5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). 6 | 7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://docs.microsoft.com/en-us/previous-versions/tn-archive/cc751383(v=technet.10)), please report it to us as described below. 8 | 9 | ## Reporting Security Issues 10 | 11 | **Please do not report security vulnerabilities through public GitHub issues.** 12 | 13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://msrc.microsoft.com/create-report). 14 | 15 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://www.microsoft.com/en-us/msrc/pgp-key-msrc). 16 | 17 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc). 18 | 19 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: 20 | 21 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) 22 | * Full paths of source file(s) related to the manifestation of the issue 23 | * The location of the affected source code (tag/branch/commit or direct URL) 24 | * Any special configuration required to reproduce the issue 25 | * Step-by-step instructions to reproduce the issue 26 | * Proof-of-concept or exploit code (if possible) 27 | * Impact of the issue, including how an attacker might exploit the issue 28 | 29 | This information will help us triage your report more quickly. 30 | 31 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://microsoft.com/msrc/bounty) page for more details about our active programs. 32 | 33 | ## Preferred Languages 34 | 35 | We prefer all communications to be in English. 36 | 37 | ## Policy 38 | 39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://www.microsoft.com/en-us/msrc/cvd). 40 | 41 | -------------------------------------------------------------------------------- /appPackage/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.11/MicrosoftTeams.schema.json", 3 | "manifestVersion": "1.11", 4 | "version": "1.0.0", 5 | "id": "4e500d53-05f0-49e5-ae37-f3ef50747f90", 6 | "packageName": "com.microsoft.teams.videoplayerbot", 7 | "developer": { 8 | "name": "Teams App, Inc.", 9 | "websiteUrl": "https://localhost:3000", 10 | "privacyUrl": "https://localhost:3000/privacy", 11 | "termsOfUseUrl": "https://localhost:3000/termsofuse" 12 | }, 13 | "icons": { 14 | "color": "color.png", 15 | "outline": "outline.png" 16 | }, 17 | "name": { 18 | "short": "Video Player Bot", 19 | "full": "Video Player Bot" 20 | }, 21 | "description": { 22 | "short": "This bot sends and Adaptive Cards that plays a video in a Stage View.", 23 | "full": "This bot sends and Adaptive Cards that plays a video in a Stage View." 24 | }, 25 | "accentColor": "#FFFFFF", 26 | "staticTabs": [ 27 | { 28 | "contentUrl": "https://<>.sharepoint.com/_layouts/15/TeamsLogon.aspx?spfx=true&dest=/", 29 | "entityId": "sharePoint-49e5-f3ef50747f90", 30 | "name": "SharePoint", 31 | "scopes": [ 32 | "personal" 33 | ], 34 | "websiteUrl": "https:/<>.sharepoint.com/" 35 | } 36 | ], 37 | "bots": [ 38 | { 39 | "botId": "<>", 40 | "scopes": [ 41 | "personal" 42 | ], 43 | "commandLists": [ 44 | { 45 | "scopes": [ 46 | "personal" 47 | ], 48 | "commands": [ 49 | { 50 | "title": "Hello", 51 | "description": "Say hello to select an option" 52 | }, 53 | { 54 | "title": "AppID", 55 | "description": "Get Teams app ID from MS Graph" 56 | } 57 | ] 58 | } 59 | ], 60 | "supportsFiles": false, 61 | "isNotificationOnly": false 62 | } 63 | ], 64 | "permissions": [ 65 | "identity" 66 | ], 67 | "validDomains": [ 68 | "<>", 69 | "*.youtube.com", 70 | "*.sharepoint.com", 71 | "*.sharepoint-df.com", 72 | "*.login.microsoftonline.com", 73 | "spoppe-a.akamaihd.net", 74 | "spoprod-a.akamaihd.net", 75 | "msft.spoppe.com" 76 | ], 77 | "webApplicationInfo": { 78 | "id": "00000003-0000-0ff1-ce00-000000000000", 79 | "resource": "https://<>.sharepoint.com" 80 | } 81 | } -------------------------------------------------------------------------------- /aad_auth.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. 4 | */ 5 | 6 | var msal = require('@azure/msal-node'); 7 | var graph = require('@microsoft/microsoft-graph-client'); 8 | require('isomorphic-fetch'); 9 | 10 | // Build MSAL ClientApplication Configuration object 11 | const clientConfig = { 12 | auth: { 13 | "clientId": process.env.MicrosoftAppId, 14 | "authority": `https://login.microsoftonline.com/${process.env.MicrosoftAppTenantId}`, 15 | "clientSecret": process.env.MicrosoftAppPassword 16 | } 17 | }; 18 | 19 | function getClientCredentialsToken(cca) { 20 | // With client credentials flows permissions need to be granted in the portal by a tenant administrator. 21 | // The scope is always in the format "/.default" 22 | const clientCredentialRequest = { 23 | scopes: ["https://graph.microsoft.com/.default"], 24 | skipCache: true // (optional) this skips the cache and forces MSAL to get a new token from Azure AD 25 | }; 26 | 27 | return cca 28 | .acquireTokenByClientCredential(clientCredentialRequest) 29 | .then((response) => { 30 | // Uncomment to see the successful response logged 31 | //console.log("Response: ", response); 32 | return response.accessToken; 33 | }).catch((error) => { 34 | // Uncomment to see the errors logges 35 | console.log(JSON.stringify(error)); 36 | throw error; 37 | }); 38 | } 39 | 40 | // Execute sample application with the configured MSAL PublicClientApplication 41 | const getAccessToken = async () => { 42 | const confidentialClientApplication = new msal.ConfidentialClientApplication(clientConfig); 43 | return await getClientCredentialsToken(confidentialClientApplication) 44 | .then((token) => { 45 | return token; 46 | }); 47 | } 48 | 49 | // Create a MS Graph client using MSAL access token 50 | function getAuthenticatedClient(accessToken) { 51 | // Initialize Graph client 52 | const client = graph.Client.init({ 53 | // Use the provided access token to authenticate requests 54 | authProvider: (done) => { 55 | done(null, accessToken); 56 | } 57 | }); 58 | return client; 59 | } 60 | 61 | const getTeamsAppID = async (accessToken, AADUserID, externalAppID) => { 62 | const client = getAuthenticatedClient(accessToken); 63 | const response = await client 64 | .api(`/users/${AADUserID}/teamwork/installedApps`) 65 | .expand(`teamsApp`) 66 | .filter(`teamsApp/externalId eq '${externalAppID}'`) 67 | .get(); 68 | if (response.value.length != 0) { 69 | return response.value[0].teamsApp; 70 | } 71 | else return []; 72 | } 73 | 74 | module.exports = { getAccessToken, getTeamsAppID }; -------------------------------------------------------------------------------- /deploymentScripts/linux/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # ---------------------- 4 | # KUDU Deployment Script 5 | # Version: 1.0.17 6 | # ---------------------- 7 | 8 | # Helpers 9 | # ------- 10 | 11 | exitWithMessageOnError () { 12 | if [ ! $? -eq 0 ]; then 13 | echo "An error has occurred during web site deployment." 14 | echo $1 15 | exit 1 16 | fi 17 | } 18 | 19 | # Prerequisites 20 | # ------------- 21 | 22 | # Verify node.js installed 23 | hash node 2>/dev/null 24 | exitWithMessageOnError "Missing node.js executable, please install node.js, if already installed make sure it can be reached from current environment." 25 | 26 | # Setup 27 | # ----- 28 | 29 | SCRIPT_DIR="${BASH_SOURCE[0]%\\*}" 30 | SCRIPT_DIR="${SCRIPT_DIR%/*}" 31 | ARTIFACTS=$SCRIPT_DIR/../artifacts 32 | KUDU_SYNC_CMD=${KUDU_SYNC_CMD//\"} 33 | 34 | if [[ ! -n "$DEPLOYMENT_SOURCE" ]]; then 35 | DEPLOYMENT_SOURCE=$SCRIPT_DIR 36 | fi 37 | 38 | if [[ ! -n "$NEXT_MANIFEST_PATH" ]]; then 39 | NEXT_MANIFEST_PATH=$ARTIFACTS/manifest 40 | 41 | if [[ ! -n "$PREVIOUS_MANIFEST_PATH" ]]; then 42 | PREVIOUS_MANIFEST_PATH=$NEXT_MANIFEST_PATH 43 | fi 44 | fi 45 | 46 | if [[ ! -n "$DEPLOYMENT_TARGET" ]]; then 47 | DEPLOYMENT_TARGET=$ARTIFACTS/wwwroot 48 | else 49 | KUDU_SERVICE=true 50 | fi 51 | 52 | if [[ ! -n "$KUDU_SYNC_CMD" ]]; then 53 | # Install kudu sync 54 | echo Installing Kudu Sync 55 | npm install kudusync -g --silent 56 | exitWithMessageOnError "npm failed" 57 | 58 | if [[ ! -n "$KUDU_SERVICE" ]]; then 59 | # In case we are running locally this is the correct location of kuduSync 60 | KUDU_SYNC_CMD=kuduSync 61 | else 62 | # In case we are running on kudu service this is the correct location of kuduSync 63 | KUDU_SYNC_CMD=$APPDATA/npm/node_modules/kuduSync/bin/kuduSync 64 | fi 65 | fi 66 | 67 | # Node Helpers 68 | # ------------ 69 | 70 | selectNodeVersion () { 71 | NPM_CMD=npm 72 | NODE_EXE=node 73 | } 74 | 75 | ################################################################################################################################## 76 | # Deployment 77 | # ---------- 78 | 79 | echo Handling node.js deployment. 80 | 81 | # 1. KuduSync 82 | if [[ "$IN_PLACE_DEPLOYMENT" -ne "1" ]]; then 83 | "$KUDU_SYNC_CMD" -v 50 -f "$DEPLOYMENT_SOURCE" -t "$DEPLOYMENT_TARGET" -n "$NEXT_MANIFEST_PATH" -p "$PREVIOUS_MANIFEST_PATH" -i ".git;.hg;.deployment;deploy.sh" 84 | exitWithMessageOnError "Kudu Sync failed" 85 | fi 86 | 87 | # 2. Select node version 88 | selectNodeVersion 89 | 90 | # 3. Install npm packages 91 | if [ -e "$DEPLOYMENT_TARGET/package.json" ]; then 92 | cd "$DEPLOYMENT_TARGET" 93 | echo "Running $NPM_CMD install --production" 94 | eval $NPM_CMD install --production 95 | exitWithMessageOnError "npm failed" 96 | cd - > /dev/null 97 | fi 98 | 99 | ################################################################################################################################## 100 | echo "Finished successfully." -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | const path = require('path'); 5 | 6 | const dotenv = require('dotenv'); 7 | // Import required bot configuration. 8 | const ENV_FILE = path.join(__dirname, '.env'); 9 | dotenv.config({ path: ENV_FILE }); 10 | 11 | const restify = require('restify'); 12 | 13 | // Import required bot services. 14 | // See https://aka.ms/bot-services to learn more about the different parts of a bot. 15 | const { 16 | CloudAdapter, 17 | ConfigurationServiceClientCredentialFactory, 18 | createBotFrameworkAuthenticationFromConfiguration 19 | } = require('botbuilder'); 20 | 21 | // This bot's main dialog. 22 | const { VideoPlayerBot } = require('./bot'); 23 | 24 | // Create HTTP server 25 | const server = restify.createServer(); 26 | server.use(restify.plugins.bodyParser()); 27 | server.get('/media/*', restify.plugins.serveStatic({ 28 | directory: __dirname, 29 | })) 30 | 31 | server.listen(process.env.port || process.env.PORT || 3978, () => { 32 | console.log(`\n${ server.name } listening to ${ server.url }`); 33 | console.log('\nGet Bot Framework Emulator: https://aka.ms/botframework-emulator'); 34 | console.log('\nTo talk to your bot, open the emulator select "Open Bot"'); 35 | }); 36 | 37 | const credentialsFactory = new ConfigurationServiceClientCredentialFactory({ 38 | MicrosoftAppId: process.env.MicrosoftAppId, 39 | MicrosoftAppPassword: process.env.MicrosoftAppPassword, 40 | MicrosoftAppType: process.env.MicrosoftAppType, 41 | MicrosoftAppTenantId: process.env.MicrosoftAppTenantId 42 | }); 43 | 44 | const botFrameworkAuthentication = createBotFrameworkAuthenticationFromConfiguration(null, credentialsFactory); 45 | 46 | // Create adapter. 47 | // See https://aka.ms/about-bot-adapter to learn more about how bots work. 48 | const adapter = new CloudAdapter(botFrameworkAuthentication); 49 | 50 | // Catch-all for errors. 51 | const onTurnErrorHandler = async (context, error) => { 52 | // This check writes out errors to console log .vs. app insights. 53 | // NOTE: In production environment, you should consider logging this to Azure 54 | // application insights. See https://aka.ms/bottelemetry for telemetry 55 | // configuration instructions. 56 | console.error(`\n [onTurnError] unhandled error: ${ error }`); 57 | 58 | // Send a trace activity, which will be displayed in Bot Framework Emulator 59 | await context.sendTraceActivity( 60 | 'OnTurnError Trace', 61 | `${ error }`, 62 | 'https://www.botframework.com/schemas/error', 63 | 'TurnError' 64 | ); 65 | 66 | // Send a message to the user 67 | await context.sendActivity('The bot encountered an error or bug.'); 68 | await context.sendActivity('To continue to run this bot, please fix the bot source code.'); 69 | }; 70 | 71 | // Set the onTurnError for the singleton CloudAdapter. 72 | adapter.onTurnError = onTurnErrorHandler; 73 | 74 | // Create the main dialog. 75 | const myBot = new VideoPlayerBot(); 76 | 77 | // Listen for incoming requests. 78 | server.post('/api/messages', async (req, res) => { 79 | // Route received a request to adapter for processing 80 | await adapter.process(req, res, (context) => myBot.run(context)); 81 | }); 82 | 83 | // Listen for Upgrade requests for Streaming. 84 | server.on('upgrade', async (req, socket, head) => { 85 | // Create an adapter scoped to this WebSocket connection to allow storing session data. 86 | const streamingAdapter = new CloudAdapter(botFrameworkAuthentication); 87 | // Set onTurnError for the CloudAdapter created for each connection. 88 | streamingAdapter.onTurnError = onTurnErrorHandler; 89 | 90 | await streamingAdapter.process(req, socket, head, (context) => myBot.run(context)); 91 | }); -------------------------------------------------------------------------------- /deploymentScripts/windows/deploy.cmd: -------------------------------------------------------------------------------- 1 | @if "%SCM_TRACE_LEVEL%" NEQ "4" @echo off 2 | 3 | :: ---------------------- 4 | :: KUDU Deployment Script 5 | :: Version: 1.0.17 6 | :: ---------------------- 7 | 8 | :: Prerequisites 9 | :: ------------- 10 | 11 | :: Verify node.js installed 12 | where node 2>nul >nul 13 | IF %ERRORLEVEL% NEQ 0 ( 14 | echo Missing node.js executable, please install node.js, if already installed make sure it can be reached from current environment. 15 | goto error 16 | ) 17 | 18 | :: Setup 19 | :: ----- 20 | 21 | setlocal enabledelayedexpansion 22 | 23 | SET ARTIFACTS=%~dp0%..\artifacts 24 | 25 | IF NOT DEFINED DEPLOYMENT_SOURCE ( 26 | SET DEPLOYMENT_SOURCE=%~dp0%. 27 | ) 28 | 29 | IF NOT DEFINED DEPLOYMENT_TARGET ( 30 | SET DEPLOYMENT_TARGET=%ARTIFACTS%\wwwroot 31 | ) 32 | 33 | IF NOT DEFINED NEXT_MANIFEST_PATH ( 34 | SET NEXT_MANIFEST_PATH=%ARTIFACTS%\manifest 35 | 36 | IF NOT DEFINED PREVIOUS_MANIFEST_PATH ( 37 | SET PREVIOUS_MANIFEST_PATH=%ARTIFACTS%\manifest 38 | ) 39 | ) 40 | 41 | IF NOT DEFINED KUDU_SYNC_CMD ( 42 | :: Install kudu sync 43 | echo Installing Kudu Sync 44 | call npm install kudusync -g --silent 45 | IF !ERRORLEVEL! NEQ 0 goto error 46 | 47 | :: Locally just running "kuduSync" would also work 48 | SET KUDU_SYNC_CMD=%appdata%\npm\kuduSync.cmd 49 | ) 50 | goto Deployment 51 | 52 | :: Utility Functions 53 | :: ----------------- 54 | 55 | :SelectNodeVersion 56 | 57 | IF DEFINED KUDU_SELECT_NODE_VERSION_CMD ( 58 | :: The following are done only on Windows Azure Websites environment 59 | call %KUDU_SELECT_NODE_VERSION_CMD% "%DEPLOYMENT_SOURCE%" "%DEPLOYMENT_TARGET%" "%DEPLOYMENT_TEMP%" 60 | IF !ERRORLEVEL! NEQ 0 goto error 61 | 62 | IF EXIST "%DEPLOYMENT_TEMP%\__nodeVersion.tmp" ( 63 | SET /p NODE_EXE=<"%DEPLOYMENT_TEMP%\__nodeVersion.tmp" 64 | IF !ERRORLEVEL! NEQ 0 goto error 65 | ) 66 | 67 | IF EXIST "%DEPLOYMENT_TEMP%\__npmVersion.tmp" ( 68 | SET /p NPM_JS_PATH=<"%DEPLOYMENT_TEMP%\__npmVersion.tmp" 69 | IF !ERRORLEVEL! NEQ 0 goto error 70 | ) 71 | 72 | IF NOT DEFINED NODE_EXE ( 73 | SET NODE_EXE=node 74 | ) 75 | 76 | SET NPM_CMD="!NODE_EXE!" "!NPM_JS_PATH!" 77 | ) ELSE ( 78 | SET NPM_CMD=npm 79 | SET NODE_EXE=node 80 | ) 81 | 82 | goto :EOF 83 | 84 | :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: 85 | :: Deployment 86 | :: ---------- 87 | 88 | :Deployment 89 | echo Handling node.js deployment. 90 | 91 | :: 1. KuduSync 92 | IF /I "%IN_PLACE_DEPLOYMENT%" NEQ "1" ( 93 | call :ExecuteCmd "%KUDU_SYNC_CMD%" -v 50 -f "%DEPLOYMENT_SOURCE%" -t "%DEPLOYMENT_TARGET%" -n "%NEXT_MANIFEST_PATH%" -p "%PREVIOUS_MANIFEST_PATH%" -i ".git;.hg;.deployment;deploy.cmd" 94 | IF !ERRORLEVEL! NEQ 0 goto error 95 | ) 96 | 97 | :: 2. Select node version 98 | call :SelectNodeVersion 99 | 100 | :: 3. Install npm packages 101 | IF EXIST "%DEPLOYMENT_TARGET%\package.json" ( 102 | pushd "%DEPLOYMENT_TARGET%" 103 | call :ExecuteCmd !NPM_CMD! install --production 104 | IF !ERRORLEVEL! NEQ 0 goto error 105 | popd 106 | ) 107 | 108 | :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: 109 | goto end 110 | 111 | :: Execute command routine that will echo out when error 112 | :ExecuteCmd 113 | setlocal 114 | set _CMD_=%* 115 | call %_CMD_% 116 | if "%ERRORLEVEL%" NEQ "0" echo Failed exitCode=%ERRORLEVEL%, command=%_CMD_% 117 | exit /b %ERRORLEVEL% 118 | 119 | :error 120 | endlocal 121 | echo An error has occurred during web site deployment. 122 | call :exitSetErrorLevel 123 | call :exitFromFunction 2>nul 124 | 125 | :exitSetErrorLevel 126 | exit /b 1 127 | 128 | :exitFromFunction 129 | () 130 | 131 | :end 132 | endlocal 133 | echo Finished successfully. 134 | -------------------------------------------------------------------------------- /bot.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | const { TeamsInfo , TeamsActivityHandler, MessageFactory, CardFactory } = require('botbuilder'); 5 | const cards = require('./adaptiveCards/cards') 6 | const aad = require('./aad_auth'); 7 | 8 | class VideoPlayerBot extends TeamsActivityHandler { 9 | constructor(){ 10 | super(); 11 | 12 | this.onEvent(async (context, next) => { 13 | console.log(context); 14 | // By calling next() you ensure that the next BotHandler is run. 15 | await next(); 16 | }) 17 | 18 | this.onTurn(async (context, next) => { 19 | console.log("Activity received of type: ", context.activity.type); 20 | // By calling next() you ensure that the next BotHandler is run. 21 | await next(); 22 | }) 23 | 24 | // See https://aka.ms/about-bot-activity-message to learn more about the message and other activity types. 25 | this.onMessage(async (context, next) => { 26 | let msg = ''; 27 | if ( 'text' in context.activity) { msg = context.activity.text.trim().toLowerCase() } 28 | else if ( 'value' in context.activity) { msg = context.activity.value.type } 29 | 30 | const aadObjectId = context.activity.from.aadObjectId; 31 | const TeamsExternalAppID = process.env.TeamsExternalAppID; 32 | const accessToken = await aad.getAccessToken(); 33 | const appInfo = await aad.getTeamsAppID(accessToken, aadObjectId, TeamsExternalAppID); 34 | if (appInfo.length ===0) { msg = 'error-appid' }; 35 | 36 | switch (msg) { 37 | case 'hello': 38 | // Send choice card 39 | const choiceCard = cards.getStaticCard('choiceCard', appInfo.id); 40 | const choiceCardAC = CardFactory.adaptiveCard(choiceCard); 41 | await context.sendActivity(MessageFactory.attachment(choiceCardAC)); 42 | break; 43 | 44 | case 'appid': 45 | // Send application access token 46 | const appInfoAC = CardFactory.adaptiveCard({ 47 | "type": "AdaptiveCard", 48 | "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", 49 | "version": "1.0", 50 | "body": [ 51 | { 52 | "type": "FactSet", 53 | "facts": [ 54 | { "title": "TeamsAppID", "value": appInfo.id }, 55 | { "title": "externalId", "value": appInfo.externalId }, 56 | { "title": "displayName", "value": appInfo.displayName }, 57 | { "title": "distributionMethod","value": appInfo.distributionMethod } 58 | ] 59 | } 60 | ] 61 | }); 62 | await context.sendActivity(MessageFactory.attachment(appInfoAC)); 63 | break; 64 | 65 | case 'demoCard': 66 | // Send demo card 67 | const demoCard = cards.getStaticCard('demoCard', appInfo.id); 68 | const demoCardAC = CardFactory.adaptiveCard(demoCard); 69 | await context.sendActivity(MessageFactory.attachment(demoCardAC)); 70 | break; 71 | 72 | case 'inputCard': 73 | // Send custom card to get video inputs 74 | const inputCard = cards.getStaticCard('inputCard', appInfo.id); 75 | const inputCardAC = CardFactory.adaptiveCard(inputCard); 76 | await context.sendActivity(MessageFactory.attachment(inputCardAC)); 77 | break; 78 | 79 | case 'videoInputs': 80 | // Send the custom card to play the video 81 | const videoCard = cards.generateVideoCard(context.activity.value.videoName, context.activity.value.videoURL, context.activity.value.websiteURL, appInfo.id); 82 | const videoCardAC = CardFactory.adaptiveCard(videoCard); 83 | await context.sendActivity(MessageFactory.attachment(videoCardAC)); 84 | break; 85 | 86 | case 'error-appid': 87 | // Send error message - "Could not get internal app ID form MS Graph API" 88 | const errorAC = CardFactory.adaptiveCard({ 89 | "type": "AdaptiveCard", 90 | "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", 91 | "version": "1.0", 92 | "body": [ 93 | { 94 | "type": "TextBlock", 95 | "text": "Error - Could not get internal app ID form MS Graph API", 96 | "wrap": true, 97 | "id": "ErrMsgTitle", 98 | "color": "Attention", 99 | "weight": "Bolder" 100 | }, 101 | { 102 | "type": "TextBlock", 103 | "text": "- Check that the **app permissions** set to **TeamsAppInstallation.ReadForUser.All** in Azure AD \n- Check that **TeamsExternalAppID** is correctly set in **.env file**", 104 | "wrap": true, 105 | "id": "ErrMsgText" 106 | } 107 | ] 108 | }); 109 | await context.sendActivity(MessageFactory.attachment(errorAC)); 110 | break; 111 | 112 | default : // Echo 113 | const replyText = `Echo: ${ msg }`; 114 | await context.sendActivity(MessageFactory.text(replyText, replyText)); 115 | } 116 | // By calling next() you ensure that the next BotHandler is run. 117 | await next(); 118 | }); 119 | 120 | this.onMembersAdded(async (context, next) => { 121 | const membersAdded = context.activity.membersAdded; 122 | const welcomeText = 'Hello and welcome!'; 123 | const instructionText = 'Please choose the type of card you want to test'; 124 | const choiceCard = cards.getStaticCard('choiceCard'); 125 | const choiceCardAC = CardFactory.adaptiveCard(choiceCard); 126 | 127 | for (let cnt = 0; cnt < membersAdded.length; ++cnt) { 128 | if (membersAdded[cnt].id !== context.activity.recipient.id) { 129 | await context.sendActivity(MessageFactory.text(welcomeText, welcomeText)); 130 | await context.sendActivity(MessageFactory.text(instructionText, instructionText)); 131 | await context.sendActivity(MessageFactory.attachment(choiceCardAC)); 132 | } 133 | } 134 | // By calling next() you ensure that the next BotHandler is run. 135 | await next(); 136 | }); 137 | 138 | 139 | } 140 | 141 | } 142 | 143 | module.exports.VideoPlayerBot = VideoPlayerBot; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Teams Video Player Bot 3 | 4 | Bot Framework v4 Conversation Bot sample for Teams. 5 | 6 | This bot has been created using [Bot Framework](https://dev.botframework.com) and uses the source code provided on GitHub [microsoft/BotBuilder-Samples](https://github.com/microsoft/BotBuilder-Samples/tree/main/samples/javascript_nodejs/02.echo-bot) has a baseline. 7 | 8 | This sample shows how to play a video within Microsoft Teams from an Adaptive Card. This demonstrator supports public hosted videos (like YouTube) as well as content hosted on Microsoft SharePoint online. For SharePoint online, the solution implements SSO: the current user watching the video in Teams is transparently authenticated on SharePoint Online and can read the video based on the permissions set on the SharePoint site (user needs to be authorized) 9 | 10 |

11 | Teams Video Player in action 12 |

13 | 14 | ## Prerequisites 15 | 16 | - Microsoft Teams is installed and you have an account 17 | - [NodeJS](https://nodejs.org/en/) to run the code 18 | - [ngrok](https://ngrok.com/) or equivalent tunnelling solution 19 | - A SharePoint site to host the video content (or a public site to host the videos) 20 | - An Azure subscription to register the Azure Bot (and optionally deploy the solution) 21 | 22 | ## To try this sample 23 | 24 | > Note: these instructions are for running the sample on your local machine, the tunnelling solution (like ngrok) is required because the Teams service needs to call into the bot. 25 | 26 | 1) Clone the repository 27 | 28 | ```bash 29 | git clone https://github.com/OfficeDev/Teams-Video-Player-Bot.git 30 | ``` 31 | 32 | 2) In a terminal, navigate to `Teams-Video-Player-Bot` 33 | 34 | ```bash 35 | cd ./Teams-Video-Player-Bot 36 | ``` 37 | 38 | 3) Install modules 39 | 40 | ```bash 41 | npm install 42 | ``` 43 | 44 | 4) Run ngrok - point to port 3978 45 | 46 | ```bash 47 | ngrok http -host-header=rewrite 3978 48 | ``` 49 | 50 | 5) Create [Bot Framework registration resource](https://docs.microsoft.com/en-us/azure/bot-service/bot-service-quickstart-registration) in Azure 51 | 52 | **Note for the deployment**: don't select "UserAssignedMSI" if you run your bot locally - This option only works when running in Azure. Please select **"Single-Tenant"** or **"Multi-Tenant"** instead. 53 | 54 | Once the Azure Bot is deployed, go to your Azure Bot under "Configuration": 55 | 56 | - For the "Messaging endpoint" URL, use the current https URL you were given by running ngrok and append it with the path /api/messages. It should look like **https://{subdomain}.ngrok.io/api/messages** to work. 57 | - Select "Manage" close to "Microsoft App ID" to open the Azure AD app registration - Go to "API permissions" > "Add permission" > "Microsoft Graph" > "Application permissions" > "**TeamsAppInstallation.ReadForUser.All**" > "Add permissions" - Then "Grant admin consent" for your tenant - [Learn more about permissions and consent](https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-permissions-and-consent?WT.mc_id=Portal-Microsoft_AAD_RegisteredApps) 58 | - 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) 59 | 60 | **Note:** the permission TeamsAppInstallation.ReadForUser.All is required to automatically get the internal Teams appID from the external appID set in the manifest.json file. This internal appID is generated dynamically when the app is uploaded into Teams (sideloaded or via the store) 61 | 62 | 63 | 6) Create the [Teams application package](https://docs.microsoft.com/en-us/microsoftteams/platform/concepts/build-and-test/apps-package) 64 | - **Edit** the `manifest.json` contained in the `appPackage` folder to replace: 65 | - your **Microsoft App Id** (that was created when you registered your bot earlier) in the place holder string `<>` 66 | - your **SharePoint domain** (the SharePoint site where your videos are located) in the place holder string `<>` - Note: if you **don't intend** to play videos hosted on SharePoint, you can remove the sections named "staticTabs" and "webApplicationInfo" from the app manifest. 67 | - your **Video site domain** (the public location where your videos are located) in the place holder string `<>` 68 | - **Zip** up the contents of the `appPackage` folder to create a `manifest.zip` - Note: please make sure that you zip the files directly and not the folder that contains the files. 69 | - **Upload** the `appPackage.zip` to Teams (in the Apps view click "Upload a custom app") - More info on this [page](https://docs.microsoft.com/en-us/microsoftteams/platform/concepts/deploy-and-publish/apps-upload) 70 | 71 | 7) Update the `.env` configuration for the Azure Bot settings and provided the parameters value: 72 | - **Microsoft App Id** (aka client ID) 73 | - **Microsoft App Password** (aka client secret) 74 | - **MicrosoftAppType** (aka Bot Type) 75 | - **MicrosoftAppTenantId** 76 | 77 | [More info here](https://docs.microsoft.com/en-us/azure/bot-service/bot-service-quickstart-registration?view=azure-bot-service-4.0&tabs=userassigned#bot-identity-information) on the how to set these values. 78 | 79 | Last, update the .env file with the baseURL if you run your app locally - This value will be the URL generated by ngrok. 80 | 81 | 8) Run your bot at the command line: 82 | 83 | ```bash 84 | npm start 85 | ``` 86 | 87 | ## Interacting with the bot 88 | 89 | You can interact with this bot by sending it a message, or selecting a command from the command list. To start the conversation, just send **Hello** in the chat with the bot - For simplicity, the bot only support personal chats. 90 | 91 | When you say "Hello", the bot sends a card with option to use the demo card (running a YouTube video) or create a custom one. 92 | - If you select "demo card", the bot sends an adaptive card with the same format as the [Company Communicator](https://github.com/OfficeDev/microsoft-teams-apps-company-communicator) application. 93 | - If you select "custom card", the bot send an adaptive card to request information about your video - You need to provide the following info: 94 | - **Video name** : The name of your video 95 | - **Video URL** : The location of your video - URL has to be in **https://** format 96 | - **Website URL**: The URL where the video is located (can be the same as the video URL itself) - URL has to be in **https://** format 97 | 98 | The bot will then answer with an Adaptive Card that contains an image - Click on the image to play the video in a Stage View. 99 | 100 | **Note for SharePoint videos:** 101 | - to get the URL of the video, go to SharePoint online, slect the video and click on "Details" - Then copy the "Path" value (e.g. https://microsoft.sharepoint.com/teams/mysite/Shared%20Documents/myvideosfolder/myvideo.mp4) 102 | - make sure that users that receive the videos also have access to its SharePoint location. 103 | 104 | ## Deploy the bot to Azure 105 | 106 | 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. 107 | 108 | ## Further reading 109 | 110 | - [How Microsoft Teams bots work](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-basics-teams?view=azure-bot-service-4.0&tabs=javascript) 111 | - [Tabs link unfurling and Stage View](https://docs.microsoft.com/en-us/microsoftteams/platform/tabs/tabs-link-unfurling) 112 | 113 | ## Contributing 114 | 115 | This project welcomes contributions and suggestions. Most contributions require you to agree to a 116 | Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us 117 | the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com. 118 | 119 | When you submit a pull request, a CLA bot will automatically determine whether you need to provide 120 | a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions 121 | provided by the bot. You will only need to do this once across all repos using our CLA. 122 | 123 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 124 | For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or 125 | contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. 126 | 127 | ## Trademarks 128 | 129 | This project may contain trademarks or logos for projects, products, or services. Authorized use of Microsoft 130 | trademarks or logos is subject to and must follow 131 | [Microsoft's Trademark & Brand Guidelines](https://www.microsoft.com/en-us/legal/intellectualproperty/trademarks/usage/general). 132 | Use of Microsoft trademarks or logos in modified versions of this project must not cause confusion or imply Microsoft sponsorship. 133 | Any use of third-party trademarks or logos are subject to those third-party's policies. 134 | -------------------------------------------------------------------------------- /deploymentTemplates/template-with-preexisting-rg.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "appId": { 6 | "type": "string", 7 | "metadata": { 8 | "description": "Active Directory App ID or User-Assigned Managed Identity Client ID, set as MicrosoftAppId in the Web App's Application Settings." 9 | } 10 | }, 11 | "appSecret": { 12 | "type": "string", 13 | "defaultValue": "", 14 | "metadata": { 15 | "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings. Required for MultiTenant and SingleTenant app types. Defaults to \"\"." 16 | } 17 | }, 18 | "appType": { 19 | "type": "string", 20 | "defaultValue": "MultiTenant", 21 | "allowedValues": [ 22 | "MultiTenant", 23 | "SingleTenant", 24 | "UserAssignedMSI" 25 | ], 26 | "metadata": { 27 | "description": "Type of Bot Authentication. set as MicrosoftAppType in the Web App's Application Settings. Allowed values are: MultiTenant, SingleTenant, UserAssignedMSI. Defaults to \"MultiTenant\"." 28 | } 29 | }, 30 | "botId": { 31 | "type": "string", 32 | "metadata": { 33 | "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." 34 | } 35 | }, 36 | "botSku": { 37 | "defaultValue": "F0", 38 | "type": "string", 39 | "metadata": { 40 | "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." 41 | } 42 | }, 43 | "newAppServicePlanName": { 44 | "type": "string", 45 | "defaultValue": "", 46 | "metadata": { 47 | "description": "The name of the new App Service Plan." 48 | } 49 | }, 50 | "newAppServicePlanSku": { 51 | "type": "object", 52 | "defaultValue": { 53 | "name": "S1", 54 | "tier": "Standard", 55 | "size": "S1", 56 | "family": "S", 57 | "capacity": 1 58 | }, 59 | "metadata": { 60 | "description": "The SKU of the App Service Plan. Defaults to Standard values." 61 | } 62 | }, 63 | "appServicePlanLocation": { 64 | "type": "string", 65 | "metadata": { 66 | "description": "The location of the App Service Plan." 67 | } 68 | }, 69 | "existingAppServicePlan": { 70 | "type": "string", 71 | "defaultValue": "", 72 | "metadata": { 73 | "description": "Name of the existing App Service Plan used to create the Web App for the bot." 74 | } 75 | }, 76 | "newWebAppName": { 77 | "type": "string", 78 | "defaultValue": "", 79 | "metadata": { 80 | "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." 81 | } 82 | }, 83 | "tenantId": { 84 | "type": "string", 85 | "defaultValue": "[subscription().tenantId]", 86 | "metadata": { 87 | "description": "The Azure AD Tenant ID to use as part of the Bot's Authentication. Only used for SingleTenant and UserAssignedMSI app types. Defaults to \"Subscription Tenant ID\"." 88 | } 89 | }, 90 | "existingUserAssignedMSIName": { 91 | "type": "string", 92 | "defaultValue": "", 93 | "metadata": { 94 | "description": "The User-Assigned Managed Identity Resource used for the Bot's Authentication. Defaults to \"\"." 95 | } 96 | }, 97 | "existingUserAssignedMSIResourceGroupName": { 98 | "type": "string", 99 | "defaultValue": "", 100 | "metadata": { 101 | "description": "The User-Assigned Managed Identity Resource Group used for the Bot's Authentication. Defaults to \"\"." 102 | } 103 | } 104 | }, 105 | "variables": { 106 | "defaultAppServicePlanName": "[if(empty(parameters('existingAppServicePlan')), 'createNewAppServicePlan', parameters('existingAppServicePlan'))]", 107 | "useExistingAppServicePlan": "[not(equals(variables('defaultAppServicePlanName'), 'createNewAppServicePlan'))]", 108 | "servicePlanName": "[if(variables('useExistingAppServicePlan'), parameters('existingAppServicePlan'), parameters('newAppServicePlanName'))]", 109 | "resourcesLocation": "[parameters('appServicePlanLocation')]", 110 | "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", 111 | "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", 112 | "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]", 113 | "msiResourceId": "[concat(subscription().id, '/resourceGroups/', parameters('existingUserAssignedMSIResourceGroupName'), '/providers/', 'Microsoft.ManagedIdentity/userAssignedIdentities/', parameters('existingUserAssignedMSIName'))]", 114 | "appTypeDef": { 115 | "MultiTenant": { 116 | "tenantId": "", 117 | "msiResourceId": "", 118 | "identity": { "type": "None" } 119 | }, 120 | "SingleTenant": { 121 | "tenantId": "[parameters('tenantId')]", 122 | "msiResourceId": "", 123 | "identity": { "type": "None" } 124 | }, 125 | "UserAssignedMSI": { 126 | "tenantId": "[parameters('tenantId')]", 127 | "msiResourceId": "[variables('msiResourceId')]", 128 | "identity": { 129 | "type": "UserAssigned", 130 | "userAssignedIdentities": { 131 | "[variables('msiResourceId')]": {} 132 | } 133 | } 134 | } 135 | }, 136 | "appType": { 137 | "tenantId": "[variables('appTypeDef')[parameters('appType')].tenantId]", 138 | "msiResourceId": "[variables('appTypeDef')[parameters('appType')].msiResourceId]", 139 | "identity": "[variables('appTypeDef')[parameters('appType')].identity]" 140 | } 141 | }, 142 | "resources": [ 143 | { 144 | "comments": "Create a new App Service Plan if no existing App Service Plan name was passed in.", 145 | "type": "Microsoft.Web/serverfarms", 146 | "condition": "[not(variables('useExistingAppServicePlan'))]", 147 | "name": "[variables('servicePlanName')]", 148 | "apiVersion": "2018-02-01", 149 | "location": "[variables('resourcesLocation')]", 150 | "sku": "[parameters('newAppServicePlanSku')]", 151 | "properties": { 152 | "name": "[variables('servicePlanName')]" 153 | } 154 | }, 155 | { 156 | "comments": "Create a Web App using an App Service Plan", 157 | "type": "Microsoft.Web/sites", 158 | "apiVersion": "2015-08-01", 159 | "location": "[variables('resourcesLocation')]", 160 | "kind": "app", 161 | "dependsOn": [ 162 | "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]" 163 | ], 164 | "name": "[variables('webAppName')]", 165 | "identity": "[variables('appType').identity]", 166 | "properties": { 167 | "name": "[variables('webAppName')]", 168 | "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]", 169 | "siteConfig": { 170 | "appSettings": [ 171 | { 172 | "name": "WEBSITE_NODE_DEFAULT_VERSION", 173 | "value": "10.14.1" 174 | }, 175 | { 176 | "name": "MicrosoftAppType", 177 | "value": "[parameters('appType')]" 178 | }, 179 | { 180 | "name": "MicrosoftAppId", 181 | "value": "[parameters('appId')]" 182 | }, 183 | { 184 | "name": "MicrosoftAppPassword", 185 | "value": "[parameters('appSecret')]" 186 | }, 187 | { 188 | "name": "MicrosoftAppTenantId", 189 | "value": "[variables('appType').tenantId]" 190 | } 191 | ], 192 | "cors": { 193 | "allowedOrigins": [ 194 | "https://botservice.hosting.portal.azure.net", 195 | "https://hosting.onecloud.azure-test.net/" 196 | ] 197 | } 198 | } 199 | } 200 | }, 201 | { 202 | "apiVersion": "2021-03-01", 203 | "type": "Microsoft.BotService/botServices", 204 | "name": "[parameters('botId')]", 205 | "location": "global", 206 | "kind": "azurebot", 207 | "sku": { 208 | "name": "[parameters('botSku')]" 209 | }, 210 | "properties": { 211 | "name": "[parameters('botId')]", 212 | "displayName": "[parameters('botId')]", 213 | "iconUrl": "https://docs.botframework.com/static/devportal/client/images/bot-framework-default.png", 214 | "endpoint": "[variables('botEndpoint')]", 215 | "msaAppId": "[parameters('appId')]", 216 | "msaAppTenantId": "[variables('appType').tenantId]", 217 | "msaAppMSIResourceId": "[variables('appType').msiResourceId]", 218 | "msaAppType": "[parameters('appType')]", 219 | "luisAppIds": [], 220 | "schemaTransformationVersion": "1.3", 221 | "isCmekEnabled": false, 222 | "isIsolated": false 223 | }, 224 | "dependsOn": [ 225 | "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" 226 | ] 227 | } 228 | ] 229 | } 230 | -------------------------------------------------------------------------------- /adaptiveCards/cards.js: -------------------------------------------------------------------------------- 1 | var urlParser = require('url'); 2 | 3 | const getStaticCard = (cardName, TeamsInternalAppID) => { 4 | // console.log(TeamsInternalAppID) 5 | switch(cardName) { 6 | case 'choiceCard': 7 | return { 8 | "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", 9 | "version": "1.4", 10 | "type": "AdaptiveCard", 11 | "body": [ 12 | { 13 | "type": "TextBlock", 14 | "size": "Medium", 15 | "weight": "Bolder", 16 | "text": "Which type of video card do you want to try?" 17 | }, 18 | { 19 | "type": "Image", 20 | "url": `${process.env.baseURL}/media/demo-video.gif`, 21 | "horizontalAlignment": "Center", 22 | } 23 | ], 24 | "actions": [ 25 | { 26 | "type": "Action.Submit", 27 | "title": "Demo Card", 28 | "data": { 29 | "type": "demoCard" 30 | } 31 | }, 32 | { 33 | "type": "Action.Submit", 34 | "title": "Custom Card", 35 | "data": { 36 | "type": "inputCard" 37 | } 38 | } 39 | ] 40 | } 41 | case 'demoCard': 42 | return { 43 | "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", 44 | "version": "1.3", 45 | "type": "AdaptiveCard", 46 | "body": [ 47 | { 48 | "size": "ExtraLarge", 49 | "text": "Learn Together", 50 | "weight": "Bolder", 51 | "wrap": true, 52 | "type": "TextBlock" 53 | }, 54 | { 55 | "type": "TextBlock", 56 | "text": "Developing apps for Microsoft Teams", 57 | "wrap": true, 58 | "spacing": "None" 59 | }, 60 | { 61 | "text": "Click on the image to watch the video in Teams", 62 | "weight": "Bolder", 63 | "type": "TextBlock", 64 | "isSubtle": true 65 | }, 66 | { 67 | "type": "Image", 68 | "url": `${process.env.baseURL}/media/learn-TV.png`, 69 | "horizontalAlignment": "Center", 70 | "selectAction": { 71 | "type": "Action.OpenUrl", 72 | "url": `https://teams.microsoft.com/l/stage/${TeamsInternalAppID}/0?context=${urlEncoder('Learn Together - Developing apps for Microsoft Teams', 'https://www.youtube.com/embed/xxkCJKpU3vA', 'https://www.youtube.com/watch?v=xxkCJKpU3vA')}` } 73 | }, 74 | { 75 | "text": "Check out the stream on learn TV to ask your questions live: [https://aka.ms/learntv](https://aka.ms/learntv)\n\nCurrently, there are 115+ million Teams daily active users. That is millions of new users that developers can reach when they develop apps for Teams.\n\nJoin us for a free two-hour livestream event for developers by developers. Let's talk app dev for Microsoft Teams.\n\nWhat you will learn:\n\nMillions of new Microsoft Teams users are looking to you, the developers, to create engaging and unique application experiences on Teams. In this two-hour livestream on Learn TV, you’ll learn:\n\n- Why you should consider building apps for Teams\n- How to get started building apps for Teams in VS Code\n- Where you can integrate your apps in the Teams user experience\n\nQuickly get started learning how to build these apps and stick around for some fun trivia and prizes.", 76 | "wrap": true, 77 | "type": "TextBlock" 78 | }, 79 | { 80 | "size": "Small", 81 | "text": "Microsoft Developer", 82 | "weight": "Lighter", 83 | "wrap": true, 84 | "type": "TextBlock" 85 | } 86 | ], 87 | "actions": [ 88 | { 89 | "url": "https://www.youtube.com/watch?v=xxkCJKpU3vA", 90 | "title": "Watch the video on YouTube", 91 | "type": "Action.OpenUrl" 92 | } 93 | ] 94 | } 95 | case 'demoCardtest': 96 | return { 97 | "type": "AdaptiveCard", 98 | "body": [ 99 | { 100 | "type": "ActionSet", 101 | "actions": [ 102 | { 103 | "type": "Action.Submit", 104 | "title": "View", 105 | "data": { 106 | "msteams": { 107 | "type": "invoke", 108 | "value": { 109 | "type": "tab/tabInfoAction", 110 | "tabInfo": { 111 | "websiteURL": "https://www.youtube.com/embed/f71Fv6t4fl4", 112 | "websiteUrl": "https://www.youtube.com/watch?v=f71Fv6t4fl4", 113 | "name": "Test", 114 | "entityId": "stageView" 115 | } 116 | } 117 | } 118 | } 119 | } 120 | ] 121 | } 122 | ], 123 | "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", 124 | "version": "1.3" 125 | } 126 | case 'inputCard': 127 | return { 128 | "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", 129 | "version": "1.4", 130 | "type": "AdaptiveCard", 131 | "body": [ 132 | { 133 | "type": "TextBlock", 134 | "size": "Medium", 135 | "weight": "Bolder", 136 | "text": "Please provide content information" 137 | }, 138 | { 139 | "type": "Input.Text", 140 | "id": "videoName", 141 | "isRequired": true, 142 | "label": "Video Name", 143 | "placeholder": "The name of your video", 144 | "errorMessage": "Please make sure you provided a name for your video" 145 | }, 146 | { 147 | "type": "Input.Text", 148 | "placeholder": "The location of your video in https:// format", 149 | "id": "videoURL", 150 | "isRequired": true, 151 | "label": "Video URL", 152 | "errorMessage": "Please make sure you provided a URL for your video" 153 | }, 154 | { 155 | "type": "Input.Text", 156 | "placeholder": "The URL where the video is located in https:// format", 157 | "id": "websiteURL", 158 | "label": "Website URL", 159 | "isRequired": true, 160 | } 161 | ], 162 | "actions": [ 163 | { 164 | "type": "Action.Submit", 165 | "title": "Validate", 166 | "data": { 167 | "type": "videoInputs" 168 | } 169 | } 170 | ] 171 | } 172 | default : 173 | return {}; 174 | } 175 | } 176 | 177 | const urlEncoder = (videoName, videoURL, websiteURL) => { 178 | const urlObject = urlParser.parse(videoURL, false); 179 | const isSPO = urlObject.host.toString().includes('.sharepoint.com') ? true : false; 180 | if (isSPO) { 181 | const TeamsLogon = '/_layouts/15/teamslogon.aspx?spfx=true&dest='; 182 | const videoURLSPO = `https://${urlObject.hostname}${TeamsLogon}${urlObject.path}`; 183 | return encodeURIComponent(`{"contentUrl":"${videoURLSPO}","websiteUrl":"${websiteURL}","name":"${videoName}"}`); 184 | } 185 | else { 186 | return encodeURIComponent(`{"contentUrl":"${videoURL}","websiteUrl":"${websiteURL}","name":"${videoName}"}`); 187 | } 188 | } 189 | 190 | const generateVideoCard = (videoName, videoURL, websiteURL, TeamsInternalAppID) => ({ 191 | "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", 192 | "version": "1.4", 193 | "type": "AdaptiveCard", 194 | "body": [ 195 | { 196 | "size": "ExtraLarge", 197 | "text": `${videoName}`, 198 | "weight": "Bolder", 199 | "wrap": true, 200 | "type": "TextBlock" 201 | }, 202 | { 203 | "text": `Click on -Play- button to start the video`, 204 | "weight": "Bolder", 205 | "type": "TextBlock", 206 | "isSubtle": true 207 | }, 208 | { 209 | "type": "Container", 210 | "items": [ 211 | { 212 | "type": "Image", 213 | "url": `${process.env.baseURL}/media/stream-play.png`, 214 | "horizontalAlignment": "Center", 215 | "selectAction": { 216 | "type": "Action.OpenUrl", 217 | "url": `https://teams.microsoft.com/l/stage/${TeamsInternalAppID}/0?context=${urlEncoder(videoName, videoURL, websiteURL)}` 218 | }, 219 | "height": "80px" 220 | } 221 | ], 222 | "minHeight": "120px", 223 | "verticalContentAlignment": "Center" 224 | } 225 | ], 226 | "actions": [ 227 | { 228 | "url": `${websiteURL}`, 229 | "title": "Click here to access to the video content", 230 | "type": "Action.OpenUrl" 231 | } 232 | ] 233 | }) 234 | 235 | module.exports = { getStaticCard, generateVideoCard } -------------------------------------------------------------------------------- /deploymentTemplates/linux/template.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "botName": { 6 | "type": "string", 7 | "minLength": 2 8 | }, 9 | "sku": { 10 | "defaultValue": { 11 | "name": "S1", 12 | "tier": "Standard", 13 | "size": "S1", 14 | "family": "S", 15 | "capacity": 1 16 | }, 17 | "type": "object" 18 | }, 19 | "linuxFxVersion": { 20 | "type": "string", 21 | "defaultValue": "NODE|10.14" 22 | }, 23 | "location": { 24 | "type": "string", 25 | "defaultValue": "West US", 26 | "metadata": { 27 | "description": "Location for all resources." 28 | } 29 | }, 30 | "appId": { 31 | "defaultValue": "1234", 32 | "type": "string" 33 | }, 34 | "appSecret": { 35 | "defaultValue": "", 36 | "type": "string" 37 | }, 38 | "appType": { 39 | "defaultValue": "MultiTenant", 40 | "type": "string", 41 | "allowedValues": [ 42 | "MultiTenant", 43 | "SingleTenant", 44 | "UserAssignedMSI" 45 | ] 46 | }, 47 | "tenantId": { 48 | "type": "string", 49 | "defaultValue": "[subscription().tenantId]" 50 | }, 51 | "existingUserAssignedMSIName": { 52 | "type": "string", 53 | "defaultValue": "" 54 | }, 55 | "existingUserAssignedMSIResourceGroupName": { 56 | "type": "string", 57 | "defaultValue": "" 58 | } 59 | }, 60 | "variables": { 61 | "siteHost": "[concat(parameters('botName'), '.azurewebsites.net')]", 62 | "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]", 63 | "msiResourceId": "[concat(subscription().id, '/resourceGroups/', parameters('existingUserAssignedMSIResourceGroupName'), '/providers/', 'Microsoft.ManagedIdentity/userAssignedIdentities/', parameters('existingUserAssignedMSIName'))]", 64 | "appTypeDef": { 65 | "MultiTenant": { 66 | "tenantId": "", 67 | "msiResourceId": "", 68 | "identity": { "type": "None" } 69 | }, 70 | "SingleTenant": { 71 | "tenantId": "[parameters('tenantId')]", 72 | "msiResourceId": "", 73 | "identity": { "type": "None" } 74 | }, 75 | "UserAssignedMSI": { 76 | "tenantId": "[parameters('tenantId')]", 77 | "msiResourceId": "[variables('msiResourceId')]", 78 | "identity": { 79 | "type": "UserAssigned", 80 | "userAssignedIdentities": { 81 | "[variables('msiResourceId')]": {} 82 | } 83 | } 84 | } 85 | }, 86 | "appType": { 87 | "tenantId": "[variables('appTypeDef')[parameters('appType')].tenantId]", 88 | "msiResourceId": "[variables('appTypeDef')[parameters('appType')].msiResourceId]", 89 | "identity": "[variables('appTypeDef')[parameters('appType')].identity]" 90 | } 91 | }, 92 | "resources": [ 93 | { 94 | "type": "Microsoft.Web/serverfarms", 95 | "apiVersion": "2017-08-01", 96 | "name": "[parameters('botName')]", 97 | "kind": "linux", 98 | "location": "[parameters('location')]", 99 | "sku": "[parameters('sku')]", 100 | "properties": { 101 | "name": "[parameters('botName')]", 102 | "reserved": true, 103 | "perSiteScaling": false, 104 | "targetWorkerCount": 0, 105 | "targetWorkerSizeId": 0 106 | } 107 | }, 108 | { 109 | "type": "Microsoft.Web/sites", 110 | "apiVersion": "2016-08-01", 111 | "name": "[parameters('botName')]", 112 | "location": "[parameters('location')]", 113 | "dependsOn": [ 114 | "[resourceId('Microsoft.Web/serverfarms', parameters('botName'))]" 115 | ], 116 | "kind": "app,linux", 117 | "identity": "[variables('appType').identity]", 118 | "properties": { 119 | "enabled": true, 120 | "hostNameSslStates": [ 121 | { 122 | "name": "[concat(parameters('botName'), '.azurewebsites.net')]", 123 | "sslState": "Disabled", 124 | "hostType": "Standard" 125 | }, 126 | { 127 | "name": "[concat(parameters('botName'), '.scm.azurewebsites.net')]", 128 | "sslState": "Disabled", 129 | "hostType": "Repository" 130 | } 131 | ], 132 | "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', parameters('botName'))]", 133 | "siteConfig": { 134 | "linuxFxVersion": "[parameters('linuxFxVersion')]", 135 | "appSettings": [ 136 | { 137 | "name": "WEBSITE_NODE_DEFAULT_VERSION", 138 | "value": "10.14.1" 139 | }, 140 | { 141 | "name": "MicrosoftAppType", 142 | "value": "[parameters('appType')]" 143 | }, 144 | { 145 | "name": "MicrosoftAppId", 146 | "value": "[parameters('appId')]" 147 | }, 148 | { 149 | "name": "MicrosoftAppPassword", 150 | "value": "[parameters('appSecret')]" 151 | }, 152 | { 153 | "name": "MicrosoftAppTenantId", 154 | "value": "[variables('appType').tenantId]" 155 | } 156 | ] 157 | }, 158 | "reserved": true, 159 | "scmSiteAlsoStopped": false, 160 | "clientAffinityEnabled": true, 161 | "clientCertEnabled": false, 162 | "hostNamesDisabled": false, 163 | "containerSize": 0, 164 | "dailyMemoryTimeQuota": 0, 165 | "httpsOnly": false 166 | } 167 | }, 168 | { 169 | "type": "Microsoft.Web/sites/config", 170 | "apiVersion": "2016-08-01", 171 | "name": "[concat(parameters('botName'), '/web')]", 172 | "location": "West US", 173 | "dependsOn": [ 174 | "[resourceId('Microsoft.Web/sites', parameters('botName'))]" 175 | ], 176 | "properties": { 177 | "numberOfWorkers": 1, 178 | "defaultDocuments": [ 179 | "Default.htm", 180 | "Default.html", 181 | "Default.asp", 182 | "index.htm", 183 | "index.html", 184 | "iisstart.htm", 185 | "default.aspx", 186 | "index.php", 187 | "hostingstart.html" 188 | ], 189 | "netFrameworkVersion": "v4.0", 190 | "phpVersion": "", 191 | "pythonVersion": "", 192 | "nodeVersion": "", 193 | "linuxFxVersion": "[parameters('linuxFxVersion')]", 194 | "requestTracingEnabled": false, 195 | "remoteDebuggingEnabled": false, 196 | "httpLoggingEnabled": false, 197 | "logsDirectorySizeLimit": 35, 198 | "detailedErrorLoggingEnabled": false, 199 | "publishingUsername": "parameters('botName')", 200 | "scmType": "LocalGit", 201 | "use32BitWorkerProcess": true, 202 | "webSocketsEnabled": false, 203 | "alwaysOn": true, 204 | "appCommandLine": "", 205 | "managedPipelineMode": "Integrated", 206 | "virtualApplications": [ 207 | { 208 | "virtualPath": "/", 209 | "physicalPath": "site\\wwwroot", 210 | "preloadEnabled": true, 211 | "virtualDirectories": null 212 | } 213 | ], 214 | "winAuthAdminState": 0, 215 | "winAuthTenantState": 0, 216 | "customAppPoolIdentityAdminState": false, 217 | "customAppPoolIdentityTenantState": false, 218 | "loadBalancing": "LeastRequests", 219 | "routingRules": [], 220 | "experiments": { 221 | "rampUpRules": [] 222 | }, 223 | "autoHealEnabled": false, 224 | "vnetName": "", 225 | "siteAuthEnabled": false, 226 | "siteAuthSettings": { 227 | "enabled": null, 228 | "unauthenticatedClientAction": null, 229 | "tokenStoreEnabled": null, 230 | "allowedExternalRedirectUrls": null, 231 | "defaultProvider": null, 232 | "clientId": null, 233 | "clientSecret": null, 234 | "clientSecretCertificateThumbprint": null, 235 | "issuer": null, 236 | "allowedAudiences": null, 237 | "additionalLoginParams": null, 238 | "isAadAutoProvisioned": false, 239 | "googleClientId": null, 240 | "googleClientSecret": null, 241 | "googleOAuthScopes": null, 242 | "facebookAppId": null, 243 | "facebookAppSecret": null, 244 | "facebookOAuthScopes": null, 245 | "twitterConsumerKey": null, 246 | "twitterConsumerSecret": null, 247 | "microsoftAccountClientId": null, 248 | "microsoftAccountClientSecret": null, 249 | "microsoftAccountOAuthScopes": null 250 | }, 251 | "localMySqlEnabled": false, 252 | "http20Enabled": true, 253 | "minTlsVersion": "1.2", 254 | "ftpsState": "AllAllowed", 255 | "reservedInstanceCount": 0 256 | } 257 | }, 258 | { 259 | "apiVersion": "2021-03-01", 260 | "type": "Microsoft.BotService/botServices", 261 | "name": "[parameters('botName')]", 262 | "location": "global", 263 | "kind": "azurebot", 264 | "sku": { 265 | "name": "[parameters('botName')]" 266 | }, 267 | "properties": { 268 | "name": "[parameters('botName')]", 269 | "displayName": "[parameters('botName')]", 270 | "iconUrl": "https://docs.botframework.com/static/devportal/client/images/bot-framework-default.png", 271 | "endpoint": "[variables('botEndpoint')]", 272 | "msaAppId": "[parameters('appId')]", 273 | "msaAppTenantId": "[variables('appType').tenantId]", 274 | "msaAppMSIResourceId": "[variables('appType').msiResourceId]", 275 | "msaAppType": "[parameters('appType')]", 276 | "luisAppIds": [], 277 | "schemaTransformationVersion": "1.3", 278 | "isCmekEnabled": false, 279 | "isIsolated": false 280 | }, 281 | "dependsOn": [ 282 | "[resourceId('Microsoft.Web/sites/', parameters('botName'))]" 283 | ] 284 | }, 285 | { 286 | "type": "Microsoft.Web/sites/hostNameBindings", 287 | "apiVersion": "2016-08-01", 288 | "name": "[concat(parameters('botName'), '/', parameters('botName'), '.azurewebsites.net')]", 289 | "location": "West US", 290 | "dependsOn": [ 291 | "[resourceId('Microsoft.Web/sites', parameters('botName'))]" 292 | ], 293 | "properties": { 294 | "siteName": "parameters('botName')", 295 | "hostNameType": "Verified" 296 | } 297 | } 298 | ] 299 | } 300 | -------------------------------------------------------------------------------- /deploymentTemplates/template-with-new-rg.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "groupLocation": { 6 | "type": "string", 7 | "metadata": { 8 | "description": "Specifies the location of the Resource Group." 9 | } 10 | }, 11 | "groupName": { 12 | "type": "string", 13 | "metadata": { 14 | "description": "Specifies the name of the Resource Group." 15 | } 16 | }, 17 | "appId": { 18 | "type": "string", 19 | "metadata": { 20 | "description": "Active Directory App ID or User-Assigned Managed Identity Client ID, set as MicrosoftAppId in the Web App's Application Settings." 21 | } 22 | }, 23 | "appSecret": { 24 | "type": "string", 25 | "defaultValue": "", 26 | "metadata": { 27 | "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings. Required for MultiTenant and SingleTenant app types. Defaults to \"\"." 28 | } 29 | }, 30 | "appType": { 31 | "type": "string", 32 | "defaultValue": "MultiTenant", 33 | "allowedValues": [ 34 | "MultiTenant", 35 | "SingleTenant", 36 | "UserAssignedMSI" 37 | ], 38 | "metadata": { 39 | "description": "Type of Bot Authentication. set as MicrosoftAppType in the Web App's Application Settings. Allowed values are: MultiTenant, SingleTenant, UserAssignedMSI. Defaults to \"MultiTenant\"." 40 | } 41 | }, 42 | "botId": { 43 | "type": "string", 44 | "metadata": { 45 | "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." 46 | } 47 | }, 48 | "botSku": { 49 | "type": "string", 50 | "metadata": { 51 | "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." 52 | } 53 | }, 54 | "newAppServicePlanName": { 55 | "type": "string", 56 | "metadata": { 57 | "description": "The name of the App Service Plan." 58 | } 59 | }, 60 | "newAppServicePlanSku": { 61 | "type": "object", 62 | "defaultValue": { 63 | "name": "S1", 64 | "tier": "Standard", 65 | "size": "S1", 66 | "family": "S", 67 | "capacity": 1 68 | }, 69 | "metadata": { 70 | "description": "The SKU of the App Service Plan. Defaults to Standard values." 71 | } 72 | }, 73 | "newAppServicePlanLocation": { 74 | "type": "string", 75 | "metadata": { 76 | "description": "The location of the App Service Plan. Defaults to \"westus\"." 77 | } 78 | }, 79 | "newWebAppName": { 80 | "type": "string", 81 | "defaultValue": "", 82 | "metadata": { 83 | "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." 84 | } 85 | }, 86 | "tenantId": { 87 | "type": "string", 88 | "defaultValue": "[subscription().tenantId]", 89 | "metadata": { 90 | "description": "The Azure AD Tenant ID to use as part of the Bot's Authentication. Only used for SingleTenant and UserAssignedMSI app types. Defaults to \"Subscription Tenant ID\"." 91 | } 92 | }, 93 | "existingUserAssignedMSIName": { 94 | "type": "string", 95 | "defaultValue": "", 96 | "metadata": { 97 | "description": "The User-Assigned Managed Identity Resource used for the Bot's Authentication. Defaults to \"\"." 98 | } 99 | }, 100 | "existingUserAssignedMSIResourceGroupName": { 101 | "type": "string", 102 | "defaultValue": "", 103 | "metadata": { 104 | "description": "The User-Assigned Managed Identity Resource Group used for the Bot's Authentication. Defaults to \"\"." 105 | } 106 | } 107 | }, 108 | "variables": { 109 | "appServicePlanName": "[parameters('newAppServicePlanName')]", 110 | "resourcesLocation": "[parameters('newAppServicePlanLocation')]", 111 | "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", 112 | "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", 113 | "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]", 114 | "resourceGroupId": "[concat(subscription().id, '/resourceGroups/', parameters('groupName'))]", 115 | "msiResourceId": "[concat(subscription().id, '/resourceGroups/', parameters('existingUserAssignedMSIResourceGroupName'), '/providers/', 'Microsoft.ManagedIdentity/userAssignedIdentities/', parameters('existingUserAssignedMSIName'))]", 116 | "appTypeDef": { 117 | "MultiTenant": { 118 | "tenantId": "", 119 | "msiResourceId": "", 120 | "identity": { "type": "None" } 121 | }, 122 | "SingleTenant": { 123 | "tenantId": "[parameters('tenantId')]", 124 | "msiResourceId": "", 125 | "identity": { "type": "None" } 126 | }, 127 | "UserAssignedMSI": { 128 | "tenantId": "[parameters('tenantId')]", 129 | "msiResourceId": "[variables('msiResourceId')]", 130 | "identity": { 131 | "type": "UserAssigned", 132 | "userAssignedIdentities": { 133 | "[variables('msiResourceId')]": {} 134 | } 135 | } 136 | } 137 | }, 138 | "appType": { 139 | "tenantId": "[variables('appTypeDef')[parameters('appType')].tenantId]", 140 | "msiResourceId": "[variables('appTypeDef')[parameters('appType')].msiResourceId]", 141 | "identity": "[variables('appTypeDef')[parameters('appType')].identity]" 142 | } 143 | }, 144 | "resources": [ 145 | { 146 | "name": "[parameters('groupName')]", 147 | "type": "Microsoft.Resources/resourceGroups", 148 | "apiVersion": "2018-05-01", 149 | "location": "[parameters('groupLocation')]", 150 | "properties": {} 151 | }, 152 | { 153 | "type": "Microsoft.Resources/deployments", 154 | "apiVersion": "2018-05-01", 155 | "name": "storageDeployment", 156 | "resourceGroup": "[parameters('groupName')]", 157 | "dependsOn": [ 158 | "[resourceId('Microsoft.Resources/resourceGroups/', parameters('groupName'))]" 159 | ], 160 | "properties": { 161 | "mode": "Incremental", 162 | "template": { 163 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", 164 | "contentVersion": "1.0.0.0", 165 | "parameters": {}, 166 | "variables": {}, 167 | "resources": [ 168 | { 169 | "comments": "Create a new App Service Plan", 170 | "type": "Microsoft.Web/serverfarms", 171 | "name": "[variables('appServicePlanName')]", 172 | "apiVersion": "2018-02-01", 173 | "location": "[variables('resourcesLocation')]", 174 | "sku": "[parameters('newAppServicePlanSku')]", 175 | "properties": { 176 | "name": "[variables('appServicePlanName')]" 177 | } 178 | }, 179 | { 180 | "comments": "Create a Web App using the new App Service Plan", 181 | "type": "Microsoft.Web/sites", 182 | "apiVersion": "2015-08-01", 183 | "location": "[variables('resourcesLocation')]", 184 | "kind": "app", 185 | "dependsOn": [ 186 | "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" 187 | ], 188 | "name": "[variables('webAppName')]", 189 | "identity": "[variables('appType').identity]", 190 | "properties": { 191 | "name": "[variables('webAppName')]", 192 | "serverFarmId": "[variables('appServicePlanName')]", 193 | "siteConfig": { 194 | "appSettings": [ 195 | { 196 | "name": "WEBSITE_NODE_DEFAULT_VERSION", 197 | "value": "10.14.1" 198 | }, 199 | { 200 | "name": "MicrosoftAppType", 201 | "value": "[parameters('appType')]" 202 | }, 203 | { 204 | "name": "MicrosoftAppId", 205 | "value": "[parameters('appId')]" 206 | }, 207 | { 208 | "name": "MicrosoftAppPassword", 209 | "value": "[parameters('appSecret')]" 210 | }, 211 | { 212 | "name": "MicrosoftAppTenantId", 213 | "value": "[variables('appType').tenantId]" 214 | } 215 | ], 216 | "cors": { 217 | "allowedOrigins": [ 218 | "https://botservice.hosting.portal.azure.net", 219 | "https://hosting.onecloud.azure-test.net/" 220 | ] 221 | } 222 | } 223 | } 224 | }, 225 | { 226 | "apiVersion": "2021-03-01", 227 | "type": "Microsoft.BotService/botServices", 228 | "name": "[parameters('botId')]", 229 | "location": "global", 230 | "kind": "azurebot", 231 | "sku": { 232 | "name": "[parameters('botSku')]" 233 | }, 234 | "properties": { 235 | "name": "[parameters('botId')]", 236 | "displayName": "[parameters('botId')]", 237 | "iconUrl": "https://docs.botframework.com/static/devportal/client/images/bot-framework-default.png", 238 | "endpoint": "[variables('botEndpoint')]", 239 | "msaAppId": "[parameters('appId')]", 240 | "msaAppTenantId": "[variables('appType').tenantId]", 241 | "msaAppMSIResourceId": "[variables('appType').msiResourceId]", 242 | "msaAppType": "[parameters('appType')]", 243 | "luisAppIds": [], 244 | "schemaTransformationVersion": "1.3", 245 | "isCmekEnabled": false, 246 | "isIsolated": false 247 | }, 248 | "dependsOn": [ 249 | "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" 250 | ] 251 | } 252 | ], 253 | "outputs": {} 254 | } 255 | } 256 | } 257 | ] 258 | } 259 | --------------------------------------------------------------------------------