├── index.js
├── package.json
├── .gitignore
├── README.md
└── viber-connector.js
/index.js:
--------------------------------------------------------------------------------
1 | var viberConnector = require('./viber-connector.js')
2 |
3 | exports.ViberEnabledConnector = viberConnector.ViberEnabledConnector;
4 | exports.ViberChannelId = viberConnector.ViberChannelId
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "botbuilder-viber",
3 | "version": "0.0.3",
4 | "description": "Viber connector for Microsoft BotBuilder framework",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "keywords": [
10 | "botbuilder",
11 | "viber",
12 | "microsoft",
13 | "chatbot"
14 | ],
15 | "author": "DreamTeam Mobile",
16 | "license": "ISC",
17 | "devDependencies": {
18 | "botbuilder": "3.9.0",
19 | "viber-bot": "1.0.12",
20 | "winston": "2.3.0",
21 | "winston-console-formatter": "0.3.1"
22 | },
23 | "dependencies": {
24 | "async": "2.1.5",
25 | "botbuilder": "^3.9.0"
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 |
6 | # Runtime data
7 | pids
8 | *.pid
9 | *.seed
10 |
11 | # Directory for instrumented libs generated by jscoverage/JSCover
12 | lib-cov
13 |
14 | # Coverage directory used by tools like istanbul
15 | coverage
16 |
17 | # nyc test coverage
18 | .nyc_output
19 |
20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
21 | .grunt
22 |
23 | # node-waf configuration
24 | .lock-wscript
25 |
26 | # Compiled binary addons (http://nodejs.org/api/addons.html)
27 | build/Release
28 |
29 | # Dependency directories
30 | node_modules
31 | jspm_packages
32 |
33 | # Optional npm cache directory
34 | .npm
35 |
36 | # Optional REPL history
37 | .node_repl_history
38 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | botbuilder-viber
2 | =======
3 |
4 |
5 |
6 | A node module for Microsoft [BotBuilder Framework](https://github.com/Microsoft/BotBuilder).
7 |
8 | This module provides plug-in Viber connector for Microsoft BotBuilder framework.
9 |
10 | `botbuilder-viber` currently supports the following tracking features:
11 |
12 | * sending text, image, buttons, stickers messages
13 | * receiving text messages
14 | * receiving images
15 | * receiving other messages as text message `[json]` with `message.attachments[0]` filled with raw Viber message:
16 | ```
17 | {
18 | payload: rawMessage, /* json from viber directly */
19 | contentType: 'object',
20 | }
21 | ```
22 |
23 | ## Getting started
24 |
25 | `botbuilder-viber` is installed just like any other node module:
26 |
27 | ```
28 | $ npm i botbuilder-viber
29 | ```
30 | In your bot's app.js:
31 | ```
32 | var viber = require('botbuilder-viber')
33 | ...
34 | var viberOptions = {
35 | Token: process.env.VIBER_TOKEN,
36 | Name: 'ViberBotName',
37 | AvatarUrl: 'http://url.to/pngfile'
38 | }
39 | var viberChannel = new viber.ViberEnabledConnector(viberOptions)
40 | //after initialising your bot and existing connectors
41 | bot.connector(viber.ViberChannelId, viberChannel)
42 | app.use('/viber/webhook', viberChannel.listen())
43 | ```
44 |
45 | When the bot starts, you need to [register your webhook with Viber](https://developers.viber.com/api/rest-bot-api/index.html#webhooks).
46 |
47 | Url of the webhook will be the url of your bot appended `/viber/webhook`.
48 |
49 | Example: `https://botappservice.azurewebsites.net/viber/webhook`
50 |
51 | `botbuilder-viber` requires `express` instead of `restify` like most `BotBuilder` samples do.
52 |
53 | ## Sending stickers
54 | ```
55 | var viberPayload = {}
56 | viberPayload[viber.ViberChannelId] = {
57 | "type": "sticker",
58 | "sticker_id": 114406
59 | }
60 | var stickerMessage = new builder.Message(session).sourceEvent(viberPayload)
61 | session.send(stickerMessage)
62 | ```
63 | [How to find out Viber's sticker ids?](https://developers.viber.com/tools/sticker-ids/index.html)
64 |
65 |
66 | ### Viber API documentation
67 | [Viber REST API](https://developers.viber.com/api/rest-bot-api/index.html)
68 |
69 | ### Other information
70 | * This BotBuilder is working on top of `viber-node` module: https://github.com/Viber/viber-bot-node
71 | * It can probably send whole set of different messages Viber supports (Text, Url, Contact, Picture, Video, Location, Sticker, File)
72 | * Avatar is recommended to be 720x720, and no more than 100kb (otherwise will not show on mobile clients). Use tinypng to make it smaller.
--------------------------------------------------------------------------------
/viber-connector.js:
--------------------------------------------------------------------------------
1 | const builder = require('botbuilder');
2 | const ViberBot = require('viber-bot').Bot;
3 | const BotEvents = require('viber-bot').Events;
4 | const UserProfile = require('viber-bot').UserProfile;
5 | const VTextMessage = require('viber-bot').Message.Text
6 | const VPictureMessage = require('viber-bot').Message.Picture
7 | const VLocationMessage = require('viber-bot').Message.Location
8 | const VContactMessage = require('viber-bot').Message.Contact
9 | const VStickerMessage = require('viber-bot').Message.Sticker
10 | const winston = require('winston');
11 | const toYAML = require('winston-console-formatter'); // makes the output more friendly
12 | const async = require("async");
13 | /*
14 | Until BotBuilder supports custom channels,
15 | we have to use Kik's channelId to make BotBuilder play nice with user data.
16 | We can use any other channel which supports buttons instead of Kik here.
17 | */
18 | const ViberChannelId = 'kik'
19 |
20 | const logger = function() {
21 | const logger = new winston.Logger({ level: "debug" }); // We recommend DEBUG for development
22 | logger.add(winston.transports.Console, toYAML.config());
23 | return logger;
24 | }();
25 |
26 | var ViberEnabledConnector = (function() {
27 | function ViberEnabledConnector(opts) {
28 | var self = this;
29 | this.options = opts || {};
30 | this.viberBot = new ViberBot({
31 | authToken: this.options.Token,
32 | name: this.options.Name,
33 | // It is recommended to be 720x720, and no more than 100kb.
34 | avatar: this.options.AvatarUrl,
35 | logger: logger
36 | });
37 |
38 | this.viberBot.on(BotEvents.MESSAGE_RECEIVED, (message, response) => {
39 | self.processMessage(message, response);
40 | });
41 |
42 | this.viberBot.on(BotEvents.CONVERSATION_STARTED, (response, onFinish) => {
43 | // onFinish(new TextMessage(`Hi, ${userProfile.name}! Nice to meet you.`))
44 | var self = this;
45 | var userProfile = response.userProfile;
46 | var addr = {
47 | channelId: ViberChannelId,
48 | user: { id: encodeURIComponent(userProfile.id), name: userProfile.name },
49 | bot: { id: 'viberbot', name: self.options.Name },
50 | conversation: { id: 'ViberConversationId' }
51 | };
52 |
53 | var msg = new builder.Message()
54 | .address(addr)
55 | .timestamp(convertTimestamp(new Date()))
56 | .entities();
57 | msg.type = msg.data.type = 'contactRelationUpdate';
58 | msg.data.action = 'add';
59 | this.handler([msg.toMessage()]);
60 | });
61 | }
62 |
63 | function convertTimestamp(ts) {
64 | return ts;
65 | }
66 |
67 | ViberEnabledConnector.prototype.processMessage = function(message, response) {
68 | var self = this;
69 | var userProfile = response.userProfile;
70 | var addr = {
71 | channelId: ViberChannelId,
72 | user: { id: encodeURIComponent(userProfile.id), name: userProfile.name },
73 | bot: { id: 'viberbot', name: self.options.Name },
74 | conversation: { id: 'ViberConversationId' }
75 | };
76 | var msg = new builder.Message()
77 | .address(addr)
78 | .timestamp(convertTimestamp(message.timestamp))
79 | .entities();
80 |
81 | var rawMessage = message.toJson();
82 | if (rawMessage.type === 'text') {
83 | msg = msg.text(message.text);
84 | } else if (rawMessage.type === 'picture'){
85 | msg.text(message.text || 'picture').addAttachment({
86 | contentUrl: rawMessage.media,
87 | contentType: 'image/jpeg',
88 | name: 'viberimage.jpeg'
89 | })
90 | } else {
91 | msg = msg.text(message.text || '[json]').addAttachment({
92 | payload: rawMessage,
93 | contentType: 'object',
94 | });
95 | }
96 | this.handler([msg.toMessage()]);
97 | return this;
98 | }
99 |
100 | ViberEnabledConnector.prototype.onEvent = function(handler) {
101 | this.handler = handler;
102 | }
103 |
104 | ViberEnabledConnector.prototype.listen = function() {
105 | return this.viberBot.middleware();
106 | }
107 |
108 | ViberEnabledConnector.prototype.send = function(messages, done) {
109 | var _this = this;
110 | async.eachSeries(messages, function(msg, cb) {
111 | try {
112 | if (msg.address) {
113 | _this.postMessage(msg, cb);
114 | } else {
115 | logger.error('ViberEnabledConnector: send - message is missing address.');
116 | cb(new Error('Message missing address.'));
117 | }
118 | } catch (e) {
119 | cb(e);
120 | }
121 | }, done);
122 | }
123 |
124 | ViberEnabledConnector.prototype.convertToViberMessage = function(message) {
125 | var viberKb = null;
126 | var pictureMessage = null;
127 | if (message.sourceEvent && message.sourceEvent.type) {
128 | switch (message.sourceEvent.type) {
129 | case 'sticker':
130 | return new VStickerMessage(message.sourceEvent.sticker_id, null, null, new Date(), '')
131 | break;
132 | }
133 | }
134 | if (message.attachments && message.attachments.length) {
135 | var attachment = message.attachments[0];
136 | switch (attachment.contentType) {
137 | case 'application/vnd.microsoft.keyboard':
138 | var a = attachment;
139 | if (a.content.buttons && a.content.buttons.length) {
140 | viberKb = {
141 | "Type": "keyboard",
142 | "DefaultHeight": true,
143 | "Buttons": []
144 | };
145 | for (var j = 0; j < a.content.buttons.length; j++) {
146 | var sourceB = a.content.buttons[j];
147 | var b = {
148 | "ActionType": "reply",
149 | "ActionBody": sourceB.value,
150 | "Text": sourceB.title,
151 | "TextSize": "regular"
152 | };
153 | viberKb.Buttons.push(b);
154 | }
155 | }
156 | break;
157 | case 'image/jpeg':
158 | var p = attachment;
159 | pictureMessage = new VPictureMessage(p.contentUrl, message.text, null, null, null, new Date(), '');
160 | break;
161 | }
162 | }
163 | if (pictureMessage) {
164 | return pictureMessage;
165 | } else {
166 | return new VTextMessage(message.text, viberKb, null, new Date(), '')
167 | }
168 | }
169 |
170 | ViberEnabledConnector.prototype.postMessage = function(message, cb) {
171 | var self = this,
172 | addr = message.address,
173 | user = addr.user;
174 | var realUserId = decodeURIComponent(addr.user.id)
175 | var profile = new UserProfile(realUserId, addr.user.name, '', '', '');
176 | if (message.type === 'typing') {
177 | // since Viber doesn't support "typing" notifications via API
178 | // this.viberBot.sendMessage(profile, [new VTextMessage('...', null, null, new Date(), '')]).then(function(x) { cb()}, function(y) {cb()})
179 | cb()
180 | } else {
181 | var viberMessages = [self.convertToViberMessage(message)];
182 | this.viberBot.sendMessage(profile, viberMessages).then(function(x) { cb() }, function(y) { cb() })
183 | }
184 | }
185 |
186 | ViberEnabledConnector.prototype.startConversation = function(address, done) {
187 | var addr = address
188 | address.conversation = { id: 'ViberConversationId' }
189 | done(null, addr);
190 | }
191 |
192 | return ViberEnabledConnector;
193 | })();
194 |
195 | exports.ViberEnabledConnector = ViberEnabledConnector;
196 | exports.ViberChannelId = ViberChannelId
--------------------------------------------------------------------------------