Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change the adaptor to use the new Bot Builder SDK #1

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 22 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# hubot-skype-bot

A Hubot adapter for the official [Skype Bots API (Preview)][skypebots].
A Hubot adapter for the official [Microsoft Bot Framework (Preview)][botframework].

This adapter relies on **Skype NodeJS SDK**.
This adapter relies on [**Bot Framework NodeJS SDK**][botframeworknodejs].

Refer to Skype Bots [documentation][] for more information.

Expand All @@ -11,32 +11,36 @@ See [`src/skype.coffee`](src/skype.coffee) for full documentation.

## Getting Started

You need 3 pieces of _credentials_ before getting started with `hubot-skype-bot`: a Skype bot ID, a Microsoft application ID, and a secret associated with the application ID.
You need 2 pieces of _credentials_ before getting started with `hubot-skype-bot`: a Microsoft application ID, and a password associated with the application ID.

### 1. Create Skype Bot

To obtain the Skype bot ID, start by [creating a new Skype bot][createbot] (https://developer.microsoft.com/en-us/skype/bots/manage/Create).
To obtain a new bot, start by [registering a new one][createbot].

Tick both _"Send and receive messages and content in 1:1 chat"_, and _"Send and receive messages and content in a group chat (limited preview for developer accounts only)"_.

For the _"Messaging Webhook"_, set it to the URL that your bot will be hosted, and accessible from, followed by a `/skype/` path. For example, if you are using [`ngrok`][ngrok] to expose your locally hosted bot, you will be entering something like: `https://unique-id.ngrok.io/skype/`
For the _"Messaging Endpoint"_, set it to the URL (HTTPS) that your bot will be hosted, and accessible from, followed by a `/skype/` path. For example, if you are using [`ngrok`][ngrok] to expose your locally hosted bot, you will be entering something like: `https://unique-id.ngrok.io/skype/`

During the creation process, you will be asked for a _Microsoft Application ID_.

After bot is created, in _Skype Channel_, click on _Edit_ and enable _Group messaging_.

To start speaking with it click in the _Add to Skype_ button (in Linux, open [Skype Web](https://web.skype.com) before clicking the add button).

### 2. Create Microsoft Application

There should be a link to the [Microsoft Application Registration Portal][appportal] (https://apps.dev.microsoft.com/). Once you create an application, you will be given an application ID, and a secret associated with the application ID.
There should be a link to the [Create Microsoft App ID and password][appportal]. Once you create an application, you will be given an application ID, and a secret associated with the application ID.

### 3. Set Environment Variables

You should now have the 3 aforementioned pieces of _credentials_. Expose them to your bot environment:
You should now have the 2 aforementioned pieces of _credentials_. Expose them to your bot environment:

```bash
export SKYPE_BOT_ID="BOT ID HERE"
export MICROSOFT_APP_ID="APP ID HERE"
export MICROSOFT_APP_SECRET="APP SECRET HERE"
export MICROSOFT_APP_PASSWORD="APP PASSWORD HERE"
```

One Hubot is running, click in _Test connection to your bot_ in [your bot page][botframeworkbots].
This will send a POST to your endpoint that will be answered with a HTTP 100.


## Installation via NPM

Expand All @@ -55,13 +59,14 @@ Now, run Hubot with the `skype-bot` adapter:

Variable | Default | Description
--- | --- | ---
`MICROSOFT_APP_ID` | N/A | Your bot's unique ID (https://developer.microsoft.com/en-us/skype/bots/manage)
`MICROSOFT_APP_SECRET` | N/A | A Microsoft application ID to authenticate your bot (https://apps.dev.microsoft.com/)
`SKYPE_BOT_ID` | N/A | A Microsoft application secret associated with your application ID (https://apps.dev.microsoft.com/)
`MICROSOFT_APP_ID` | N/A | Your bot's unique ID (https://dev.botframework.com/bots)
`MICROSOFT_APP_PASSWORD` | N/A | A Microsoft application ID to authenticate your bot (https://apps.dev.microsoft.com/)


[skypebots]: https://developer.microsoft.com/skype/bots
[documentation]: https://developer.microsoft.com/en-us/skype/bots/docs
[createbot]: https://developer.microsoft.com/en-us/skype/bots/manage/Create
[botframework]: https://dev.botframework.com/
[botframeworkbots]: https://dev.botframework.com/bots
[botframeworknodejs]: https://docs.botframework.com/en-us/node/builder/chat-reference/modules/_botbuilder_d_.html
[documentation]: https://docs.botframework.com/en-us/skype/getting-started
[createbot]: https://dev.botframework.com/bots/new
[appportal]: https://apps.dev.microsoft.com/
[ngrok]: https://ngrok.com/
9 changes: 6 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
{
"name": "hubot-skype-bot",
"description": "A Hubot adapter for the official Skype Bots API (Preview).",
"version": "1.0.1",
"description": "A Hubot adapter for the official Microsoft Bot Framework (Preview).",
"version": "2.0.0",
"author": "Ian Lai <[email protected]>",
"contributors": [
"Adrian Lopez <[email protected]>"
],
"license": "MIT",
"keywords": [
"hubot",
Expand All @@ -21,7 +24,7 @@
"url": "https://github.com/ClaudeBot/hubot-skype-bot/issues"
},
"dependencies": {
"skype-sdk": "https://devportalassets.azureedge.net/files/skype-sdk.tar.gz"
"botbuilder": "~3.2.2"
},
"peerDependencies": {},
"main": "./src/skype.coffee",
Expand Down
173 changes: 115 additions & 58 deletions src/skype.coffee
Original file line number Diff line number Diff line change
@@ -1,82 +1,139 @@
{Robot, Adapter, TextMessage} = require "hubot"
skype = require "skype-sdk";
builder = require "botbuilder"; # Microsoft botframework

# Skype adaptator
class Skype extends Adapter
constructor: (@robot) ->
# @robot is hubot
super @robot
@botService = null
# @bot is botframework
@bot = null
@intents = null

# Env vars to configure the botframework
@appID = process.env.MICROSOFT_APP_ID
@appSecret = process.env.MICROSOFT_APP_SECRET
@botID = process.env.SKYPE_BOT_ID
@apiURL = "https://apis.skype.com"
@apiTimeout = 15000
@robot.logger.info "hubot-skype-bot: Adapter loaded."
@appPassword = process.env.MICROSOFT_APP_PASSWORD

_nameFromId: (userId) ->
parts = userId.split(":")
parts[parts.length - 1]
@robot.logger.info "hubot-skype-bot: Adapter loaded."

_createUser: (userId, roomId = false, displayName = "") ->
_createUser: (userId, address) ->
user = @robot.brain.userForId(userId)
user.room = roomId if roomId
user.name = displayName
if displayName.length < 1
user.name = @_nameFromId(userId)
@robot.logger.info("hubot-skype-bot: new user : ", user)
if typeof address != 'undefined'
user.address = address
@robot.logger.debug("hubot-skype-bot: new user : ", user)
user

_processMsg: (msg) ->
retun unless msg.from? and msg.content?
user = @_createUser msg.from, msg.to
_msg = msg.content.trim()

# Format for PMs
_msg = @robot.name + " " + _msg if msg.to is @botID

message = new TextMessage user, _msg, msg.messageId
@receive(message) if message?

_sendMsg: (context, msg) ->
# TODO: add, and test support for rooms ...
target = context.user.id
target = context.user.room if context.user.room isnt @botID
@botService.send(target, msg, true, (err) =>
@robot.logger.error("hubot-skype-bot: error sending message : ", err) if err?
)

_sendMsg: (address, text) =>
@robot.logger.debug "Bot msg: #{text}"
msg = new builder.Message()
msg.textFormat("plain") # By default is markdown
msg.address(address)
msg.text(text)
@bot.send msg, (err) =>
if typeof err == 'undefined'
@robot.logger.error "Sending msg to Skype #{err}"
else
@robot.logger.debug "Msg to Skype sended correctly"
return

# Function used by Hubot to answer
send: (envelope, strings...) ->
@_sendMsg envelope, strings.join "\n"
@robot.logger.debug "Send"
@_sendMsg envelope.user.address, strings.join "\n"

reply: (envelope, strings...) ->
@_sendMsg envelope, envelope.user.name + ": " + strings.join "\n #{envelope.user.name}: "
@robot.logger.debug "Reply"
@_sendMsg envelope.user.address, strings.join "\n"

# Pass the msg to Hubot, appending the bot name at the beggining
_processMsg: (msg) ->
user = @_createUser msg.user.id, msg.address
# Remove <at id="28:...">name</at>. This is received by the bot when called from a group
# Append robot name at the beggining
text = @robot.name + " " + msg.text.replace /.*<\/at>\s+(.*)$/, "$1"
message = new TextMessage user, text, msg.address.id
# @receive pass the message to Hubot internals
@receive(message) if message?

# Adapter start
run: ->
unless @appID
@emit "error", new Error "You must configure the MICROSOFT_APP_ID environment variable."
unless @appSecret
@emit "error", new Error "You must configure the MICROSOFT_APP_SECRET environment variable."
unless @botID
@emit "error", new Error "You must configure the SKYPE_BOT_ID environment variable."

@botID = "28:#{@botID}"
@botService = new skype.BotService
messaging:
botId: @botID,
serverUrl: @apiURL,
requestTimeout: @apiTimeout,
appId: @appID,
appSecret: @appSecret

@robot.router.post "/skype/", skype.messagingHandler(@botService)

@botService.on("message", (bot, data) =>
@_processMsg data
)
unless @appPassword
@emit "error", new Error "You must configure the MICROSOFT_APP_PASSWORD environment variable."

@botService.on("contactAdded", (bot, data) =>
@_createUser data.from, false, data.fromDisplayName
@connector = new (builder.ChatConnector)(
appId: @appID
appPassword: @appPassword
)

# Creating bot of botframework
@bot = new (builder.UniversalBot)(@connector)

# HTTP POST to /skype/ are passed to botframework (by default port 8080)
@robot.router.post "/skype/", @connector.listen()

# Anything received by the bot is parsed by the defined intents
# If nothing is matched, pass the msg to Hubot
@intents = new (builder.IntentDialog)
@bot.dialog '/', @intents

# Intents regex starts with .* to also match callings from groups, that appends <at id=...>...</at>
# The matches function needs a regexp for the first arguments, then an array of anonymous funcs
# Those anonymous functions receive session param, which we could use to answer, store values, etc.
# https://docs.botframework.com/en-us/node/builder/chat-reference/classes/_botbuilder_d_.session.html
@intents.matches /.*example$/i, [
(session) =>
session.send "This bot is a mixture between BotFramwork de Microsoft y Hubot.
If you write 'example', this message is shown.\n\n
If you write 'chat', an example dialog is started.\n\n
Otherwise, the message is passed to hubot (write for example 'ping').\n\n\n\n
In group chats, bot only will receive messages send to it. Eg.: @botname example"
return
]

# This example intent has two anonymous funcs. First one start a dialog (botframework function) with the user.
# Second one is executed after and receive the values written by the user
@intents.matches /.*chat$/i, [
(session) =>
session.beginDialog '/chat'
return
(session, results) =>
console.log "Dialog results: #{results}"
return
]

# Si ninguno de los otros intents ha matcheado, entra en este, que pasa el mensaje a Hubot
# Esta seria la conexion entre los mensajes de Skype y Hubot
@intents.onDefault [
(session, args, next) =>
@robot.logger.debug "Msg from the user: #{session.message.text}"
@_processMsg session.message
return
]

# If user wants to exit from any dialog at any moment it can write "goodbye"
@bot.endConversationAction('goodbye', 'Closing dialog', { matches: /.*goodbye/i });

# This dialog receives as first argument a name (to be called from intents) and an array of anonymous functions (as seen with intents)
# Normally you send answers and receive responses in the next func
@bot.dialog '/chat', [
(session) ->
builder.Prompts.text session, "Tell me someting"
return
(session, results) ->
# This is to remove <at...> from the response of a user in case of group chats
resp = results.response.replace /.*<\/at>\s+(.*)$/, "$1"
session.send "You said: #{resp}"

# We can use session.userData to store values
# Eg.: session.userData.channel_name = resp

# To finish the dialog
session.endDialog()
return
]

@robot.logger.info "hubot-skype-bot: Adapter running."
@emit "connected"

Expand Down