diff --git a/.gitignore b/.gitignore index ff71dd8..ba56510 100644 --- a/.gitignore +++ b/.gitignore @@ -5,5 +5,4 @@ env.yml bin/ .nyc_output/ private/ -.env -.env-dev +.env* diff --git a/CHANGELOG.md b/CHANGELOG.md index 5fb3a08..9aadc28 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,4 +24,6 @@ v0.0.4 - improved error handling in Lambda functions - added unit tests for twitter.js - updated node modules -- updated README \ No newline at end of file +- updated README +- add additional setup command scripts +- add user locale support diff --git a/README.md b/README.md index aae08d8..54916c3 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![License: GPL v3](https://img.shields.io/badge/License-GPL%20v3-blue.svg)](http://www.gnu.org/licenses/gpl-3.0) [![Build Status](https://travis-ci.org/urbanriskmap/cognicity-reports-twitter-dm.svg?branch=master)](https://travis-ci.org/urbanriskmap/cognicity-reports-twitter-dm) [![Coverage Status](https://coveralls.io/repos/github/urbanriskmap/cognicity-reports-twitter-dm/badge.svg?branch=master)](https://coveralls.io/github/urbanriskmap/cognicity-reports-twitter-dm?branch=master) +[![Build Status](https://travis-ci.org/urbanriskmap/cognicity-reports-twitter-dm.svg?branch=master)](https://travis-ci.org/urbanriskmap/cognicity-reports-twitter-dm) [![Coverage Status](https://coveralls.io/repos/github/urbanriskmap/cognicity-reports-twitter-dm/badge.svg?branch=master)](https://coveralls.io/github/urbanriskmap/cognicity-reports-twitter-dm?branch=master) [![License: GPL v3](https://img.shields.io/badge/License-GPL%20v3-blue.svg)](http://www.gnu.org/licenses/gpl-3.0) ## cognicity-reports-twitter-dm @@ -34,6 +34,7 @@ Configuration variables are as follows (these should be set in the Lambda enviro * `CARDS_API` - the endpoint to get new report cards * `CARDS_API_KEY` - the api key for the cards endpoint * `CARDS_URL` - the URL for the card resources to be sent to the user +* `DEFAULT_INSTANCE_COUNTRY_CODE` - default country code for deployment (e.g. 'us') * `DEFAULT_INSTANCE_REGION_CODE` - in case a report is submitted outside the city, the code that the bot should fall back on for geographic reference * `DEFAULT_LANGUAGE` - default language for user interactions * `MAP_URL` - the risk map URL diff --git a/commands/add-welcome-message-rule.js b/commands/add-welcome-message-rule.js new file mode 100644 index 0000000..52a22e1 --- /dev/null +++ b/commands/add-welcome-message-rule.js @@ -0,0 +1,38 @@ +// Add a webhook +// npx babel-node commands/add-welcome-message.js +import config from '../src/config'; +import request from 'request'; + +const ENV = 'production' + +const run = function(){ + console.log('running'); + const oauth = { + consumer_key: config.TWITTER_CONSUMER_KEY, + consumer_secret: config.TWITTER_CONSUMER_SECRET, + token: config.TWITTER_TOKEN, + token_secret: config.TWITTER_TOKEN_SECRET, + } + + console.log(oauth); + + const opts = { + url: config.TWITTER_ENDPOINT + 'direct_messages/welcome_messages/rules/new.json', + oauth: oauth, + json: true, + headers: { + 'content-type': 'application/json', + }, + body: { + "welcome_message_rule" : { + "welcome_message_id": '', + }, + }, + } + request.post(opts, function(err, response, body){ + console.log('returned') + console.log(err); + console.log(body); + }); +} +run(); diff --git a/commands/add-welcome-message.js b/commands/add-welcome-message.js new file mode 100644 index 0000000..6f4c0da --- /dev/null +++ b/commands/add-welcome-message.js @@ -0,0 +1,51 @@ +// Add a webhook +// npx babel-node commands/add-welcome-message.js +import config from '../src/config'; +import buttons from '../src/lib/buttons.json'; +import request from 'request'; + +const ENV = 'production' + +const run = function(){ + console.log('running'); + const oauth = { + consumer_key: config.TWITTER_CONSUMER_KEY, + consumer_secret: config.TWITTER_CONSUMER_SECRET, + token: config.TWITTER_TOKEN, + token_secret: config.TWITTER_TOKEN_SECRET, + } + + console.log(oauth); + + const opts = { + url: config.TWITTER_ENDPOINT + 'direct_messages/welcome_messages/new', + oauth: oauth, + json: true, + headers: { + 'content-type': 'application/json', + }, + body: { + "welcome_message" : { + "name": "simple_welcome-message 03", + "message_data": { + "text": buttons[config.DEFAULT_LANGUAGE].text.welcome, + "quick_reply": { + "type": "options", + "options": [ + { + "label": buttons[config.DEFAULT_LANGUAGE].text.start, + "metadata": "start", + }, + ] + } + } + } + } + } + request.post(opts, function(err, response, body){ + console.log('returned') + console.log(err); + console.log(body); + }); +} +run(); diff --git a/commands/delete-subscriptions.js b/commands/delete-subscriptions.js new file mode 100644 index 0000000..43afff4 --- /dev/null +++ b/commands/delete-subscriptions.js @@ -0,0 +1,35 @@ +// Delete a subscription +// npx babel-node commands/delete-subscription.js +import config from '../src/config'; +import request from 'request'; + +const ENV = 'production' + +const run = function(){ + console.log('running'); + const oauth = { + consumer_key: config.TWITTER_CONSUMER_KEY, + consumer_secret: config.TWITTER_CONSUMER_SECRET, + token: config.TWITTER_TOKEN, + token_secret: config.TWITTER_TOKEN_SECRET, + } + + console.log(oauth); + + const opts = { + url: config.TWITTER_ENDPOINT + 'account_activity/all/' + ENV + '/subscriptions.json', + oauth: oauth, + json: true, + headers: { + 'content-type': 'application/json', + }, + body: {} + } + + request.delete(opts, function(err, response, body){ + console.log('returned') + console.log(err); + console.log(body); + }); +} +run(); diff --git a/commands/delete-welcome-message-rule.js b/commands/delete-welcome-message-rule.js new file mode 100644 index 0000000..2807141 --- /dev/null +++ b/commands/delete-welcome-message-rule.js @@ -0,0 +1,33 @@ +// Delete a webhook message rule +// npx babel-node commands/delete-welcome-message-rule.js +import config from '../src/config'; +import request from 'request'; + +const ENV = 'production' + +const run = function(){ + console.log('running'); + const oauth = { + consumer_key: config.TWITTER_CONSUMER_KEY, + consumer_secret: config.TWITTER_CONSUMER_SECRET, + token: config.TWITTER_TOKEN, + token_secret: config.TWITTER_TOKEN_SECRET, + } + + console.log(oauth); + + const opts = { + url: config.TWITTER_ENDPOINT + 'direct_messages/welcome_messages/rules/destroy.json?id=', + oauth: oauth, + json: true, + headers: { + 'content-type': 'application/json', + }, + } + request.delete(opts, function(err, response, body){ + console.log('returned') + console.log(err); + console.log(body); + }); +} +run(); diff --git a/commands/list-subscriptions.js b/commands/list-subscriptions.js new file mode 100644 index 0000000..9d2c77a --- /dev/null +++ b/commands/list-subscriptions.js @@ -0,0 +1,36 @@ +// Add a subscription +// npx babel-node commands/delete-subscription.js +import config from '../src/config'; +import request from 'request'; + +const ENV = 'production' + +const run = function(){ + console.log('running'); + const oauth = { + consumer_key: config.TWITTER_CONSUMER_KEY, + consumer_secret: config.TWITTER_CONSUMER_SECRET, + token: config.TWITTER_TOKEN, + token_secret: config.TWITTER_TOKEN_SECRET, + } + + console.log(oauth); + + const opts = { + url: config.TWITTER_ENDPOINT + 'account_activity/all/' + ENV + '/subscriptions.json', + oauth: oauth, + json: true, + headers: { + 'content-type': 'application/json', + }, + body: {} + } + + request.get(opts, function(err, response, body){ + console.log('returned') + console.log(response.statusCode); + console.log(err); + console.log(body); + }); +} +run(); diff --git a/commands/list-welcome-message-rules.js b/commands/list-welcome-message-rules.js new file mode 100644 index 0000000..b9268e3 --- /dev/null +++ b/commands/list-welcome-message-rules.js @@ -0,0 +1,33 @@ +// Add a webhook +// npx babel-node commands/add-welcome-message.js +import config from '../src/config'; +import request from 'request'; + +const ENV = 'production' + +const run = function(){ + console.log('running'); + const oauth = { + consumer_key: config.TWITTER_CONSUMER_KEY, + consumer_secret: config.TWITTER_CONSUMER_SECRET, + token: config.TWITTER_TOKEN, + token_secret: config.TWITTER_TOKEN_SECRET, + } + + console.log(oauth); + + const opts = { + url: config.TWITTER_ENDPOINT + 'direct_messages/welcome_messages/rules/list', + oauth: oauth, + json: true, + headers: { + 'content-type': 'application/json', + }, + } + request.get(opts, function(err, response, body){ + console.log('returned') + console.log(err); + console.log(body); + }); +} +run(); diff --git a/src/config.js b/src/config.js index 7736b10..fb69425 100644 --- a/src/config.js +++ b/src/config.js @@ -1,3 +1,5 @@ +/* eslint-disable max-len */ + require('dotenv').config({silent: true}); export default { @@ -6,6 +8,7 @@ export default { CARDS_API: process.env.CARDS_API || 'https://data.riskmap.us/cards/', CARDS_API_KEY: process.env.CARDS_API_KEY, CARDS_URL: process.env.CARDS_URL || 'https://cards.riskmap.us/flood/', + DEFAULT_INSTANCE_COUNTRY_CODE: process.env.DEFAULT_INSTANCE_COUNTRY_CODE || 'us', DEFAULT_INSTANCE_REGION_CODE: process.env.DEFAULT_INSTANCE_REGION_CODE || 'brw', DEFAULT_LANGUAGE: process.env.DEFAULT_LANGUAGE || 'en', diff --git a/src/lib/buttons.json b/src/lib/buttons.json index 19b3d4d..0b851b7 100644 --- a/src/lib/buttons.json +++ b/src/lib/buttons.json @@ -11,12 +11,22 @@ }, "es": { "text": { - "welcome": "[ES} Welcome to RiskMap bot. Click 'Get Started' or send me a message.", - "start": "[ES] Get Started", - "report": "[ES] Report flooding", - "map": "[ES] View live reports", - "view": "[ES] View your report", - "add": "[ES] Add another report" + "welcome": "Bienvenido a RiskMap bot. Haga clic en 'Comenzar' o envíeme un mensaje.", + "start": "> Comienzo", + "report": "Inportar inundaciones", + "map": "Ver el mapa de inundación en vivo", + "view": "Ver su informe", + "add": "Agregar otro informe" + } + }, + "in": { + "text": { + "welcome": "Welcome to RiskMap bot. Click 'Get Started' or send me a message.", + "start": "> Get Started", + "report": "Report flooding", + "map": "View live flood map", + "view": "View your report", + "add": "Add another report" } } } \ No newline at end of file diff --git a/src/lib/locale.js b/src/lib/locale.js new file mode 100644 index 0000000..229c43d --- /dev/null +++ b/src/lib/locale.js @@ -0,0 +1,60 @@ +import request from 'request'; + +// Define global +let DEFAULT_LANGUAGE = 'en'; + +/** + * Class for getting Twitter user's locale + * @class Locale + * @param {Object} config - module configuration (config.js) + */ +export default class Locale { + /** + * constructor for class Locale + * @param {Object} config - module configuration (config.js) + */ + constructor(config) { + this.config = config; + this.request = request; + + // Update the global with the config + DEFAULT_LANGUAGE = this.config.DEFAULT_LANGUAGE; + } + + /** + * Get a user's locale by user ID + * @method get + * @param {String} userId - Twitter user ID + * @return {Promise} - response + */ + get(userId) { + return new Promise((resolve, reject) => { + const oauth = { + consumer_key: this.config.TWITTER_CONSUMER_KEY, + consumer_secret: this.config.TWITTER_CONSUMER_SECRET, + token: this.config.TWITTER_TOKEN, + token_secret: this.config.TWITTER_TOKEN_SECRET, + }; + const opts = { + url: this.config.TWITTER_ENDPOINT + + 'users/show.json?include_entities=false&user_id=' + + String(userId), + oauth: oauth, + json: true, + headers: { + 'content-type': 'application/json', + }, + }; + + request.get(opts, function(err, response, body) { + if (err) { + console.log('Error getting user locale. ' + + err.message); + resolve(DEFAULT_LANGUAGE); // Acess the global default + } else { + resolve(body.lang); + } + }); + }); + } +} diff --git a/src/lib/messages.json b/src/lib/messages-id.json similarity index 100% rename from src/lib/messages.json rename to src/lib/messages-id.json diff --git a/src/lib/messages-in.json b/src/lib/messages-in.json new file mode 100644 index 0000000..d451d02 --- /dev/null +++ b/src/lib/messages-in.json @@ -0,0 +1,16 @@ +{ + "en": { + "texts": { + "default": "RiskMap bot helps you report flooding in realtime. In life-threatening situations call 911.", + "card": "RiskMap bot helps you report flooding in realtime. In life-threatening situations call 911.", + "thanks": "Thank you for your report. Click to see your report on the map." + } + }, + "es": { + "texts": { + "default": "[ES] RiskMap bot helps you report flooding in realtime. In life-threatening situations call 911.", + "card": "[ES] RiskMap bot helps you report flooding in realtime. In life-threatening situations call 911.", + "thanks": "[ES] Thank you for your report. Click to see your report on the map." + } + } +} \ No newline at end of file diff --git a/src/lib/messages-us.json b/src/lib/messages-us.json new file mode 100644 index 0000000..d451d02 --- /dev/null +++ b/src/lib/messages-us.json @@ -0,0 +1,16 @@ +{ + "en": { + "texts": { + "default": "RiskMap bot helps you report flooding in realtime. In life-threatening situations call 911.", + "card": "RiskMap bot helps you report flooding in realtime. In life-threatening situations call 911.", + "thanks": "Thank you for your report. Click to see your report on the map." + } + }, + "es": { + "texts": { + "default": "[ES] RiskMap bot helps you report flooding in realtime. In life-threatening situations call 911.", + "card": "[ES] RiskMap bot helps you report flooding in realtime. In life-threatening situations call 911.", + "thanks": "[ES] Thank you for your report. Click to see your report on the map." + } + } +} \ No newline at end of file diff --git a/src/lib/twitter.js b/src/lib/twitter.js index 4c3b16b..f987f64 100644 --- a/src/lib/twitter.js +++ b/src/lib/twitter.js @@ -3,8 +3,8 @@ import request from 'request'; // Locals import Bot from '@urbanriskmap/cognicity-bot-core'; -import messages from './messages.json'; import buttons from './buttons.json'; +import Locale from './locale'; /** * Class for sending CogniCity messages via Twitter DM @@ -19,9 +19,12 @@ export default class Twitter { */ constructor(config) { this.config = config; - this.config.MESSAGES = messages; + this.config.MESSAGES = require('./messages-' + + this.config.DEFAULT_INSTANCE_COUNTRY_CODE + + '.json'); this.bot = new Bot(this.config); this.request = request; + this.locale = new Locale(this.config); } /** @@ -244,13 +247,19 @@ export default class Twitter { **/ sendReply(dmEvent) { console.log('send reply is fired'); + console.log(JSON.stringify(dmEvent)); return new Promise(async (resolve, reject) => { - const properties = { - userId: dmEvent.message_create.sender_id, - language: this.config.DEFAULT_LANGUAGE, // TODO - use msg lang - network: 'twitter', - }; try { + // Get user locale + const locale = await this.locale.get( + dmEvent.message_create.sender_id); + + const properties = { + userId: dmEvent.message_create.sender_id, + language: locale, + network: 'twitter', + }; + const message = await this.bot.card(properties); const response = this._prepareCardResponse({ userId: properties.userId, diff --git a/src/test/index.js b/src/test/index.js index 96cbc6c..fc0aec1 100644 --- a/src/test/index.js +++ b/src/test/index.js @@ -9,5 +9,7 @@ // Unit tests import testTwitter from './testTwitter'; +import testLocale from './testLocale'; testTwitter(); +testLocale(); diff --git a/src/test/testLocale.js b/src/test/testLocale.js new file mode 100644 index 0000000..0d0f454 --- /dev/null +++ b/src/test/testLocale.js @@ -0,0 +1,54 @@ +import * as test from 'unit.js'; + +import Locale from '../lib/locale'; +import config from '../config'; + +/** + * Twitter locale class testing harness + */ +export default function() { + describe('Twitter locale class testing', function() { + const locale = new Locale(config); + const oldRequest = locale.request; + let requestError = false; + + before(function() { + const mockRequest = function(properties, callback) { + if (requestError === false) { + callback(null, 200, {lang: 'id'}); + } else { + callback(new Error(`Request error`)); + } + }; + + locale.request.get = mockRequest; + }); + + it('Creates class', function(done) { + test.value(locale instanceof Locale).is(true); + done(); + }); + + it('Gets mocked locale', function(done) { + locale.get(1) + .then((res) => { + test.value(res).is('id'); + done(); + }); + }); + + it('Gets default locale', function(done) { + requestError = true; + locale.get(1) + .then((res) => { + test.value(res).is(config.DEFAULT_LANGUAGE); + done(); + }); + }); + + + after(function() { + locale.request = oldRequest; + }); + }); +} diff --git a/src/test/testTwitter.js b/src/test/testTwitter.js index d4245d4..ca17cc4 100644 --- a/src/test/testTwitter.js +++ b/src/test/testTwitter.js @@ -34,6 +34,13 @@ export default function() { callback(new Error('1')); } }; + + // Mock locale lib + twitter.locale.get = function(userId) { + return new Promise((resolve, reject) => { + resolve('en'); + }); + }; }); it('Creates class', function(done) {