├── .gitignore ├── .vscode └── launch.json ├── README.md ├── package.json ├── scripts ├── monitor.sh └── release.sh ├── src ├── ApiAi.js ├── Glip.ts ├── RedisTokenStore.ts ├── config.ts ├── glip-auth.ts ├── index.ts ├── rc-oauth.ts ├── redis.ts ├── sms.ts ├── weather.js └── webserver │ └── index.ts ├── tsconfig.json └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | build/ 3 | data/ -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible Node.js debug attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "Launch Program", 11 | "program": "${workspaceRoot}/build/index.js", 12 | "sourceMaps": true, 13 | "outFiles": [ 14 | "${workspaceRoot}/build/**/*.js" 15 | ] 16 | }, 17 | { 18 | "type": "node", 19 | "request": "attach", 20 | "name": "Attach to Process", 21 | "address": "localhost", 22 | "port": 5858, 23 | "outFiles": [] 24 | } 25 | ] 26 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Glip AI BOT 2 | 3 | This is an AI Assistant Bot for Glip, using API.AI and RingCentral API 4 | 5 | ## Demo 6 | 7 | Talk in glip with this chat bot: 8 | 9 | ``` 10 | > hi 11 | Good day! 12 | 13 | ``` 14 | 15 | ``` 16 | > What’s the weather like in London 17 | Today in London : Mostly Cloudy, the temperature is 48 F 18 | ``` 19 | 20 | ``` 21 | > Help 22 | Help: Show this help; 23 | Rc Login: Log into your RingCentral account; 24 | Send SMS: sms to 101 say what are you doing; 25 | Receive SMS: Show your sms here; 26 | Disable SMS Notification: Stop showing sms here; 27 | ``` 28 | 29 | ``` 30 | > login to rc 31 | login with oauth 32 | > who am i 33 | > send message to Kevin tell him we won 34 | Send SMS(we won) to Kevin(101) success. 35 | > sms to 101 tell him this is sms message 36 | Send SMS(this is sms message) to Kevin(101) success. 37 | > logout rc account 38 | ``` 39 | 40 | ## Start A Bot 41 | 42 | ### Preinstall 43 | 44 | * nodejs 45 | * yarn 46 | 47 | ``` 48 | git clone https://github.com/embbnux/glip-ai-bot.git 49 | yarn 50 | npm run build 51 | npm start 52 | ``` 53 | 54 | ### Add config file 55 | 56 | to create data/config.json file 57 | ``` 58 | cd project_dir 59 | mkdir data 60 | vim data/config.json 61 | ``` 62 | example of config.json: 63 | ``` 64 | { 65 | "glipApp": { 66 | "server": "https://platform.devtest.ringcentral.com", 67 | "appKey": "ringcentral_glip_app_key", 68 | "appSecret": "ringcentral_glip_app_secret", 69 | "account": { 70 | "username": "rc_phone_number", 71 | "extension": "rc_extension_number", 72 | "password": "rc_account_password" 73 | }, 74 | "tokenCacheFile": "./glip-token-cache.json" 75 | }, 76 | "RcApp": { 77 | "server": "https://platform.devtest.ringcentral.com", 78 | "appKey": "ringcentral_app_key", 79 | "appSecret": "ringcentral_app_secret", 80 | "redirectUri": "http://localhost:8080/rc-oauth-callback" 81 | }, 82 | "ApiAi": { 83 | "token": "api.ai token" 84 | } 85 | } 86 | ``` 87 | 88 | ### start server 89 | 90 | ``` 91 | npm start 92 | ``` 93 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aibot", 3 | "version": "1.0.0", 4 | "description": "Bot for glip", 5 | "main": "build/index.js", 6 | "author": "Kevin Zeng ", 7 | "license": "MIT", 8 | "scripts": { 9 | "build": "tsc", 10 | "start": "node build/index.js", 11 | "postinstall": "tsc" 12 | }, 13 | "dependencies": { 14 | "@types/redis": "^2.6.0", 15 | "apiai": "^4.0.1", 16 | "express": "^4.17.3", 17 | "redis": "^2.7.1", 18 | "ringcentral-ts": "^0.9.1", 19 | "tslib": "^1.6.0", 20 | "typescript": "^2.2.2", 21 | "urllib": "^2.21.2" 22 | }, 23 | "engines": { 24 | "node": "6.9.4" 25 | } 26 | } -------------------------------------------------------------------------------- /scripts/monitor.sh: -------------------------------------------------------------------------------- 1 | while true 2 | do 3 | curl https://glip-bot-ai.herokuapp.com/keep-alive/heart-beat/ 4 | sleep 30 5 | done -------------------------------------------------------------------------------- /scripts/release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | workingBranch=$(git rev-parse --abbrev-ref HEAD) 4 | git fetch heroku 5 | git checkout heroku/master 6 | git merge $workingBranch --no-edit 7 | git push heroku HEAD:master 8 | git checkout $workingBranch -------------------------------------------------------------------------------- /src/ApiAi.js: -------------------------------------------------------------------------------- 1 | import * as ApiAi from 'apiai'; 2 | import config from './config'; 3 | 4 | class ApiAiClient { 5 | constructor() { 6 | this._ai = ApiAi(config.ApiAi.token); 7 | } 8 | 9 | _sendMessage(text, sessionId) { 10 | return new Promise((resolve, reject) => { 11 | const request = this._ai.textRequest(text, { sessionId }); 12 | request.on('response', (response) => { 13 | resolve(response); 14 | }); 15 | 16 | request.on('error', (error) => { 17 | reject(error); 18 | }); 19 | 20 | request.end(); 21 | }); 22 | } 23 | 24 | async send(text, sessionId) { 25 | try { 26 | const response = await this._sendMessage(text, sessionId); 27 | return response; 28 | } catch (e) { 29 | console.log(e); 30 | return null; 31 | } 32 | } 33 | } 34 | 35 | export default ApiAiClient; 36 | -------------------------------------------------------------------------------- /src/Glip.ts: -------------------------------------------------------------------------------- 1 | import RestClient from 'ringcentral-ts/RestClient'; 2 | import Subscription from 'ringcentral-ts/Subscription'; 3 | import PagingResult from 'ringcentral-ts/PagingResult'; 4 | 5 | export default class Glip { 6 | 7 | rest: RestClient; 8 | subscription: Subscription; 9 | person: Person; // Current Persion Id 10 | 11 | constructor(rest: RestClient) { 12 | this.rest = rest; 13 | this.getPerson().then(p => { 14 | this.person = p; 15 | }); 16 | } 17 | 18 | sendMessage(groupId: string, text: string): Promise { 19 | return this.rest.post('/glip/posts', { 20 | groupId, text 21 | }).then(res => res.json()); 22 | } 23 | 24 | /** 25 | * { uuid: '8416043423403629512-8160187281949800354', 26 | event: '/restapi/v1.0/glip/posts', 27 | timestamp: '2017-03-31T07:46:52.013Z', 28 | subscriptionId: 'eaeffeb1-82e2-44db-9db7-946b932448f7', 29 | body: 30 | { id: '37912580', 31 | groupId: '9199618', 32 | type: 'TextMessage', 33 | text: 'Test notifications', 34 | creatorId: '170853004', 35 | addedPersonIds: null, 36 | creationTime: '2017-03-31T07:46:51.706Z', 37 | lastModifiedTime: '2017-03-31T07:46:51.706Z', 38 | eventType: 'PostAdded' } } 39 | * @param cb 40 | */ 41 | async receiveMessage(cb: (msg: GlipMessage, fromSelf: boolean) => void) { 42 | let { subscription } = this; 43 | if (!subscription) { 44 | subscription = new Subscription(this.rest); 45 | subscription.on('error', (err) => { 46 | console.error('Error receiving message from glip', err); 47 | }); 48 | this.subscription = subscription; 49 | await subscription.subscribe(['/account/~/extension/~/glip/posts']); 50 | } 51 | subscription.onMessage(notification => { 52 | let msg: GlipMessage = notification.body; 53 | cb(msg, msg.creatorId === this.person.id); 54 | }); 55 | } 56 | 57 | getGroups(opts?: { type, pageToken, recordCount }): Promise> { 58 | return this.rest.get('/glip/groups', opts).then(res => res.json()); 59 | } 60 | 61 | getPerson(personId = '~'): Promise { 62 | return this.rest.get('/glip/persons/' + personId).then(res => res.json()); 63 | } 64 | } 65 | 66 | interface Person { 67 | // ID of person 68 | id: string; 69 | 70 | // First name of person 71 | firstName: string; 72 | 73 | // Last name of person 74 | lastName: string; 75 | 76 | // Gender of person 77 | gender: string; 78 | 79 | // Email of user 80 | email: string; 81 | 82 | // Current location of person 83 | location: string; 84 | 85 | // ID of company person belongs to 86 | companyId: string; 87 | 88 | // Time of creation (ISO format) 89 | creationTime: string; 90 | 91 | // Time of last modification (ISO format) 92 | lastModifiedTime: string; 93 | 94 | } 95 | 96 | interface Group { 97 | id: string; 98 | type: string; 99 | displayName: string; 100 | members: string[]; 101 | email: string; 102 | isPublic: boolean; 103 | creationTime: string; 104 | lastModifiedTime: string; 105 | 106 | } 107 | 108 | export interface GlipMessage { 109 | id: string; 110 | groupId: string; 111 | type: string; 112 | text: string; 113 | creatorId: string; 114 | addedPersonIds: string[]; 115 | creationTime: string[]; 116 | lastModifiedTime: string[]; 117 | } -------------------------------------------------------------------------------- /src/RedisTokenStore.ts: -------------------------------------------------------------------------------- 1 | import { RedisClient } from 'redis'; 2 | import Token, { TokenStore } from 'ringcentral-ts/Token'; 3 | 4 | export default class RedisTokenStore implements TokenStore { 5 | 6 | redis: RedisClient; 7 | key: string; 8 | 9 | constructor(key: string, redis: RedisClient) { 10 | this.redis = redis; 11 | this.key = key; 12 | } 13 | 14 | async get() { 15 | let tokenData = await new Promise((resolve, reject) => { 16 | this.redis.get(this.key, (err, res) => { 17 | err ? reject(err) : resolve(res); 18 | }); 19 | }); 20 | if (!tokenData) { 21 | throw new Error('Token not exist in redis.'); 22 | } 23 | const t = new Token(); 24 | t.fromCache(JSON.parse(tokenData + '')); 25 | return t; 26 | } 27 | 28 | save(token: Token): Promise { 29 | return new Promise((resolve, reject) => { 30 | this.redis.set(this.key, JSON.stringify(token), (err, res) => { 31 | if (err) { 32 | reject('Fail to save rc token to redis for key ' + this.key + '.' + err); 33 | } else { 34 | resolve(); 35 | } 36 | }); 37 | }); 38 | } 39 | 40 | /** 41 | * Should handle error inside the method 42 | */ 43 | clear() { 44 | return new Promise((resolve, reject) => { 45 | this.redis.del(this.key, (err, res) => { 46 | if (err) { 47 | reject('Fail to delete token in redis.' + err); 48 | } else { 49 | resolve(); 50 | } 51 | }); 52 | }); 53 | } 54 | } -------------------------------------------------------------------------------- /src/config.ts: -------------------------------------------------------------------------------- 1 | // parse config 2 | import * as path from 'path'; 3 | import { readFileSync } from 'fs'; 4 | 5 | export interface Config { 6 | glipApp: { 7 | "server": string; 8 | "appKey": string; 9 | "appSecret": string; 10 | "account": { 11 | "username": string, 12 | "extension": string; 13 | "password": string; 14 | }; 15 | "tokenCacheFile": string; // Resolved absolute path 16 | }; 17 | 18 | RcApp: { 19 | "server": string; 20 | "appKey": string; 21 | "appSecret": string; 22 | redirectUri: string; 23 | }; 24 | } 25 | 26 | let dataDir = process.env.DATA_DIR || './data/'; 27 | let config: Config = JSON.parse(readFileSync(dataDir + 'config.json').toString()); 28 | 29 | config.glipApp.tokenCacheFile = path.join(dataDir, config.glipApp.tokenCacheFile); 30 | export default config; -------------------------------------------------------------------------------- /src/glip-auth.ts: -------------------------------------------------------------------------------- 1 | import RingCentral from 'ringcentral-ts'; 2 | import config from './config'; 3 | import FileTokenStore from 'ringcentral-ts/FileTokenStore'; 4 | 5 | let client = new RingCentral(config.glipApp); // Glip account for bot 6 | 7 | client.tokenStore = new FileTokenStore(config.glipApp.tokenCacheFile); 8 | 9 | export default client.getToken(config.glipApp.account).catch(e => { 10 | return client.auth(config.glipApp.account); 11 | }).then(() => client); -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | // /import RingCentral from 'ringcentral-ts'; 2 | import glipAuth from './glip-auth'; 3 | import Glip, { GlipMessage } from './Glip'; 4 | import * as rcOauth from './rc-oauth'; 5 | import ApiAi from './ApiAi'; 6 | import './webserver'; 7 | import * as sms from './sms'; 8 | import getWeather from './weather'; 9 | 10 | /* 11 | * Sample result returned from api.ai: 12 | * groupId { id: '489f1d39-b9aa-4b40-be96-51417961829d', 13 | timestamp: '2017-04-01T04:28:27.295Z', 14 | lang: 'en', 15 | result: 16 | { source: 'agent', 17 | resolvedQuery: 'send sms to Kevin', 18 | action: 'sendSMS', 19 | actionIncomplete: false, 20 | parameters: { messageText: '', userName: 'Kevin' }, 21 | contexts: [], 22 | metadata: 23 | { intentId: '5ee56a9e-9c63-43da-9980-eb118ce3cb44', 24 | webhookUsed: 'false', 25 | webhookForSlotFillingUsed: 'false', 26 | intentName: 'send sms to Kevin: hi' }, 27 | fulfillment: { speech: 'Message has sent', messages: [Object] }, 28 | score: 0.75 }, 29 | status: { code: 200, errorType: 'success' }, 30 | sessionId: '123456' } 31 | 32 | */ 33 | 34 | 35 | function defaultActionReactor(glip: Glip, msg: GlipMessage, aiResult) { 36 | let text; 37 | if (aiResult.fulfillment.messages) { 38 | text = aiResult.fulfillment.messages.map( 39 | (message) => message.speech 40 | ).join('\n'); 41 | } else { 42 | text = aiResult.fulfillment.speech; 43 | } 44 | glip.sendMessage(msg.groupId, text); 45 | } 46 | 47 | const actions: { [action: string]: (glip: Glip, msg: GlipMessage, aiResult) => any } = { 48 | //help: defaultActionReactor, 49 | receiveSMS: sms.receiveSms, 50 | disableReceiveSMS: sms.disableReceiveSMS, 51 | sendSMS: sms.sendSms, 52 | rcLogin: rcOauth.rcLogin, 53 | rcLogout: rcOauth.rcLogout, 54 | getWeather: getWeather 55 | }; 56 | 57 | async function main() { 58 | const ai = new ApiAi(); 59 | try { 60 | const glip = new Glip((await glipAuth)); 61 | rcOauth.setup(glip); 62 | sms.setup(glip); 63 | glip.receiveMessage(async (msg, fromSelf) => { 64 | if (fromSelf) { 65 | return; 66 | } 67 | const text = msg.text.replace(/]*>/, '').replace('', ' '); 68 | console.log('>>GlipMessage received', msg); 69 | const aiRes = await ai.send(text, msg.groupId); 70 | if (!aiRes) { 71 | console.error('Fail to get Ai response'); 72 | return; 73 | } 74 | const aiResult = aiRes.result; 75 | console.log('>>aiResult', aiResult); 76 | const action = aiResult.action; 77 | let actionFn = actions[action] || defaultActionReactor; 78 | try { 79 | await actionFn(glip, msg, aiResult); 80 | } catch (e) { 81 | glip.sendMessage(msg.groupId, `Perform action function(${actionFn.name}) failed: ${e}. Please contact bot owner.`); 82 | } 83 | }); 84 | } catch (e) { 85 | console.log("Error", e) 86 | } 87 | } 88 | 89 | main(); 90 | -------------------------------------------------------------------------------- /src/rc-oauth.ts: -------------------------------------------------------------------------------- 1 | import RingCentral from 'ringcentral-ts'; 2 | import ExtensionInfo from 'ringcentral-ts/definitions/ExtensionInfo'; 3 | import PhoneNumberInfo from 'ringcentral-ts/definitions/PhoneNumberInfo'; 4 | 5 | import redis from './redis'; 6 | import Glip, { GlipMessage } from './Glip'; 7 | import config from './config'; 8 | import RedisTokenStore from './RedisTokenStore'; 9 | 10 | let glip: Glip; 11 | let rcClients: { [glipUserId: string]: RingCentral } = {}; 12 | let rcExtensions: { [glipUserId: string]: ExtensionInfo } = {}; 13 | let rcExtensionNumbers: { [glipUserId: string]: ExtensionInfo[] } = {}; 14 | let rcPhoneNumbers: { [glipUserId: string]: PhoneNumberInfo[] } = {}; 15 | 16 | export function setup(g: Glip) { 17 | glip = g; 18 | } 19 | /** 20 | * Show logged in RingCentral accout if logged in, else show login url. 21 | * @param glip 22 | * @param msg 23 | * @param aiResult 24 | */ 25 | export async function rcLogin(g: Glip, msg: GlipMessage, aiResult) { 26 | glip = g; 27 | 28 | let rc = getRc(msg.creatorId); 29 | try { 30 | await rc.getToken(); 31 | await showLoggedInRc(glip, msg.groupId, msg.creatorId); 32 | } catch (e) { 33 | glip.sendMessage(msg.groupId, `Please log into RingCentral at \ 34 | [here](${rc.oauthUrl(config.RcApp.redirectUri, { state: msg.creatorId + ':' + msg.groupId, force: true })})`); 35 | } 36 | } 37 | 38 | export async function rcLogout(g: Glip, msg: GlipMessage, aiResult) { 39 | let rc = rcClients[msg.creatorId]; 40 | if (!rc || !rc.getToken()) { 41 | g.sendMessage(msg.groupId, 'You did login.'); 42 | } else { 43 | await rc.logout(); 44 | g.sendMessage(msg.groupId, 'Logout success.'); 45 | } 46 | } 47 | 48 | /** 49 | * 50 | * @param groupId 51 | * @param callbackUrl 52 | */ 53 | export async function loggedIn(query) { 54 | let { state, code, error_description, error } = query; 55 | if (!state || !state.match(/.+:.+/)) { 56 | throw new Error('Invalid state parameter.'); 57 | } 58 | if (!code) { 59 | throw new Error('No auth code, ' + error + ', ' + error_description); 60 | } 61 | let parts = state.split(':'); 62 | let glipUserId = parts[0]; 63 | let groupId = parts[1]; 64 | let rc = getRc(glipUserId); 65 | try { 66 | await rc.oauth(code, config.RcApp.redirectUri); 67 | } catch (e) { 68 | await glip.sendMessage(groupId, 'Login failed:' + e); 69 | throw e; 70 | } 71 | await showLoggedInRc(glip, groupId, glipUserId); 72 | } 73 | 74 | async function showLoggedInRc(glip: Glip, groupId: string, glipUserId: string) { 75 | let ext = await getRcExtension(glipUserId); 76 | glip.sendMessage(groupId, `@${glipUserId} The RingCentral account you logged in is ${ext.name}(${ext.extensionNumber}, ${ext.contact.email}).`); 77 | } 78 | 79 | export function getRc(creatorId: string) { 80 | let rc = rcClients[creatorId]; 81 | if (!rc) { 82 | rc = new RingCentral(config.RcApp); 83 | rc.tokenStore = new RedisTokenStore('rc-token:glip-user:' + creatorId, redis); 84 | rcClients[creatorId] = rc; 85 | } 86 | return rc; 87 | } 88 | 89 | export async function getRcExtension(glipUserId: string) { 90 | let ext = rcExtensions[glipUserId]; 91 | if (!ext) { 92 | let rc = getRc(glipUserId); 93 | ext = await rc.account().extension().get(); 94 | rcExtensions[glipUserId] = ext; 95 | } 96 | return ext; 97 | } 98 | 99 | async function fetchPagingList(fetchPath: any, page = 1) { 100 | const response = await fetchPath.list({ 101 | perPage: 100, 102 | page, 103 | }); 104 | const paging = response.paging; 105 | let records = response.records; 106 | if (paging.totalPages > paging.page) { 107 | records = records.concat(await fetchPagingList(fetchPath, paging.page + 1)); 108 | } 109 | return records; 110 | } 111 | 112 | export async function getRcExtensionList(glipUserId: string) { 113 | let extList = rcExtensionNumbers[glipUserId]; 114 | if (!extList) { 115 | try { 116 | let rc = getRc(glipUserId); 117 | extList = await fetchPagingList(rc.account().extension()); 118 | rcExtensionNumbers[glipUserId] = extList; 119 | } catch (error) { 120 | console.log(error); 121 | extList = []; 122 | } 123 | } 124 | return extList; 125 | } 126 | 127 | export async function searchContacts(glipUserId: string, userName: string) { 128 | let contacts = []; 129 | const extensionNumbers = await getRcExtensionList(glipUserId); 130 | contacts = extensionNumbers.filter((extension) => { 131 | if (extension.name === userName) { 132 | return true; 133 | } 134 | if (extension.contact.firstName === userName) { 135 | return true; 136 | } 137 | return false; 138 | }).map((extension) => ({ 139 | name: extension.name, 140 | firstName: extension.contact.firstName, 141 | lastName: extension.contact.lastName, 142 | phoneNumber: extension.extensionNumber, 143 | })); 144 | try { 145 | const rc = getRc(glipUserId); 146 | const response = await rc.account().extension().addressBook().contact().list({ 147 | startsWith: userName, 148 | }); 149 | const searchResult = response.records.map((record) => ({ 150 | name: `${record.firstName} ${record.lastName}`, 151 | firstName: record.firstName, 152 | lastName: record.lastName, 153 | phoneNumber: record.mobilePhone, 154 | })); 155 | contacts = contacts.concat(searchResult); 156 | } catch (error) { 157 | console.log(error); 158 | } 159 | 160 | return contacts; 161 | }; 162 | 163 | export async function getPhoneNumbers(glipUserId: string) { 164 | let phoneNumbers = rcPhoneNumbers[glipUserId]; 165 | if (!phoneNumbers) { 166 | try { 167 | let rc = getRc(glipUserId); 168 | phoneNumbers = await fetchPagingList(rc.account().extension().phoneNumber()); 169 | rcPhoneNumbers[glipUserId] = phoneNumbers; 170 | } catch (error) { 171 | phoneNumbers = []; 172 | } 173 | } 174 | return phoneNumbers; 175 | } 176 | 177 | export async function getSMSPhoneNumbers(glipUserId: string) { 178 | let phoneNumbers = await getPhoneNumbers(glipUserId); 179 | phoneNumbers = phoneNumbers.filter( 180 | p => (p.features && p.features.indexOf('SmsSender') !== -1) 181 | ); 182 | return phoneNumbers; 183 | } 184 | -------------------------------------------------------------------------------- /src/redis.ts: -------------------------------------------------------------------------------- 1 | import { createClient } from 'redis'; 2 | 3 | const redisClient = createClient(process.env.REDIS_URL); 4 | 5 | redisClient.on('error', err => { 6 | console.error('Redis error', err); 7 | }); 8 | 9 | /* 10 | 11 | ==================== 12 | Sms notification: 13 | 14 | groups-receive-sms:glip-user:{user-id} [{groupId}] 15 | sms-subscription:glip-user:{user-id} {subscriptionId} 16 | 17 | */ 18 | export default redisClient; -------------------------------------------------------------------------------- /src/sms.ts: -------------------------------------------------------------------------------- 1 | import Subscription from 'ringcentral-ts/Subscription'; 2 | import Glip, { GlipMessage } from './Glip'; 3 | import { getRc, rcLogin, getSMSPhoneNumbers, getRcExtension, searchContacts } from './rc-oauth'; 4 | import redis from './redis'; 5 | 6 | export async function receiveSms(glip: Glip, msg: GlipMessage, aiResult) { 7 | let glipUserId = msg.creatorId; 8 | let groupId = msg.groupId; 9 | let key = groupKey(glipUserId); 10 | redis.sadd(key, groupId, (err, addCount) => { 11 | if (err) { 12 | glip.sendMessage(groupId, 'Enable sms notification failed:' + err); 13 | } else if (addCount < 1) { 14 | glip.sendMessage(groupId, 'Sms notification already enabled for this chat.'); 15 | } else { 16 | glip.sendMessage(groupId, 'Future sms of your RingCentral account will be sent here.'); 17 | checkSubcription(glipUserId, glip); 18 | } 19 | }); 20 | } 21 | 22 | export async function disableReceiveSMS(glip: Glip, msg: GlipMessage, aiResult) { 23 | let glipUserId = msg.creatorId; 24 | let groupId = msg.groupId; 25 | let key = groupKey(glipUserId); 26 | redis.srem(key, groupId, (err, remCount) => { 27 | if (err) { 28 | glip.sendMessage(groupId, 'Remove failed:' + err); 29 | } else if (remCount < 1) { 30 | glip.sendMessage(groupId, 'This chat does not receive sms.'); 31 | } else { 32 | glip.sendMessage(groupId, 'Your sms wont show in this chat anymore.'); 33 | checkSubcription(glipUserId, glip); 34 | } 35 | }); 36 | } 37 | 38 | export async function setup(glip: Glip) { 39 | redis.keys('sms-subscription:glip-user:*', async (err, subscriptionKeys) => { 40 | if (err) { 41 | console.error('Fail to get sms subscription redis keys', err); 42 | return; 43 | } 44 | for (let key of subscriptionKeys) { 45 | let glipUserId = key.match(/sms-subscription:glip-user:(.*)$/)[1]; 46 | let rc = getRc(glipUserId); 47 | let sub = new SmsSubscriptionForGlip(rc.createSubscription(), glipUserId, glip); 48 | smsSubscriptions[glipUserId] = sub; 49 | redis.get(key, async (err, subscriptionId) => { 50 | if (err) { 51 | console.error('Get subscription id failed', err); 52 | return; 53 | } 54 | sub.subscription.id = subscriptionId 55 | await sub.subscription.subscribe().catch(e => { 56 | checkSubcription(glipUserId, glip); 57 | }); 58 | }); 59 | } 60 | }); 61 | } 62 | 63 | async function checkSubcription(glipUserId: string, glip: Glip) { 64 | let sub = smsSubscriptions[glipUserId]; 65 | if (!sub) { 66 | let rc = getRc(glipUserId); 67 | sub = new SmsSubscriptionForGlip(rc.createSubscription(), glipUserId, glip); 68 | smsSubscriptions[glipUserId] = sub; 69 | } 70 | sub.checkSubscription(glipUserId); 71 | } 72 | 73 | let smsSubscriptions: { [glipUserId: string]: SmsSubscriptionForGlip } = {}; 74 | 75 | class SmsSubscriptionForGlip { 76 | // recipientGroups: string[] = []; 77 | // ownerGlipUserId: string; 78 | 79 | subscription: Subscription; 80 | glip: Glip; 81 | 82 | constructor(sub: Subscription, glipUserId: string, glip: Glip) { 83 | this.subscription = sub; 84 | this.glip = glip; 85 | let key = groupKey(glipUserId); 86 | /* Sample sms notification: 87 | * { id: '2835129004', 88 | to: 89 | [ { phoneNumber: '+19167582086', 90 | name: 'Kevin Zeng', 91 | location: 'El Dorado Hills / Lincoln / Roseville / Walnut Grove / West Sacramento / Citrus Heights / Antelope / Folsom / Orangevale 92 | / Rancho Cordova / Rio Linda / Rocklin / Clarksburg / Fair Oaks / Loomis / Newcastle / North Highlands / North Sacramento / Mather / Granit 93 | e Bay / Carmichael / Auburn, CA' } ], 94 | from: { phoneNumber: '+13213042353', location: 'Winter Park, FL' }, 95 | type: 'SMS', 96 | creationTime: '2017-04-01T09:02:40.698Z', 97 | lastModifiedTime: '2017-04-01T09:02:40.698Z', 98 | readStatus: 'Unread', 99 | priority: 'Normal', 100 | attachments: [ { id: '2835129004', type: 'Text', contentType: 'text/plain' } ], 101 | direction: 'Inbound', 102 | availability: 'Alive', 103 | subject: 'test sms context', 104 | messageStatus: 'Received' } 105 | */ 106 | sub.onMessage((evt) => { 107 | let smsEvt = evt.body; 108 | let smsContent = smsEvt.subject.split('\n').map(line => '\n> ' + (line || ' ')).join(''); 109 | let smsNotification = `*Sms received from ${smsEvt.from.phoneNumber} to ${smsEvt.to[0].name}(${smsEvt.to[0].phoneNumber})*:\n${smsContent}`; 110 | redis.smembers(key, (err, groups) => { 111 | if (err) { 112 | console.error('Fail to get groups for sms notifications', err); 113 | return; 114 | } 115 | for (let groupId of groups) { 116 | glip.sendMessage(groupId, smsNotification); 117 | } 118 | }); 119 | }); 120 | } 121 | 122 | /** 123 | * Subcribe if not exist 124 | * Cancel the subscription if no groups 125 | */ 126 | async checkSubscription(glipUserId: string) { 127 | let subscriptionKey = 'sms-subscription:glip-user:' + glipUserId; 128 | let key = groupKey(glipUserId); 129 | redis.scard(key, async (err, count) => { 130 | if (err) { 131 | console.error('Get groups count error', err); 132 | } else if (count < 1) { 133 | await this.subscription.cancel(); 134 | redis.del(subscriptionKey); 135 | } else if (!this.subscription.pubnub) { 136 | await this.subscription.subscribe(['/account/~/extension/~/message-store/instant?type=SMS']); 137 | redis.set(subscriptionKey, this.subscription.id, (err, res) => { 138 | if (err) { 139 | console.error('Fail to save subscription id to redis', err); 140 | } 141 | }); 142 | } 143 | }); 144 | } 145 | 146 | } 147 | 148 | function groupKey(glipUserId: string) { 149 | return 'groups-receive-sms:glip-user:' + glipUserId; 150 | } 151 | 152 | const cleanRegex = /[^\d*+#]/g; 153 | const plusRegex = /\+/g; 154 | const extensionDelimiter = /[*#]/g; 155 | 156 | function cleanNumber(phoneNumber: string) { 157 | const cleaned = phoneNumber.replace(cleanRegex, ''); 158 | const hasPlus = cleaned[0] === '+'; 159 | const output = cleaned.replace(plusRegex, '') 160 | .split(extensionDelimiter) 161 | .slice(0, 2) 162 | .join('*'); 163 | return hasPlus ? 164 | `+${output}` : 165 | output; 166 | } 167 | 168 | export async function sendSms(glip: Glip, msg: GlipMessage, aiResult) { 169 | let rc = getRc(msg.creatorId); 170 | if (!rc || !rc.getToken()) { 171 | glip.sendMessage(msg.groupId, `Sorry, You need to login before send sms with bot.`); 172 | rcLogin(glip, msg, aiResult); 173 | } else { 174 | let phoneNumber = aiResult.parameters['phone-number']; 175 | let userName = aiResult.parameters['userName']; 176 | if (!phoneNumber || phoneNumber.length === 0) { 177 | if (userName && userName.length > 0) { 178 | let toUser; 179 | const contacts = await searchContacts(msg.creatorId, userName); 180 | toUser = contacts.find((contact) => contact.name === userName); 181 | if (!toUser) { 182 | toUser = contacts[0]; 183 | } 184 | if (toUser) { 185 | phoneNumber = toUser.phoneNumber; 186 | } else { 187 | glip.sendMessage(msg.groupId, `Sorry, I do not know ${userName}'s phone number.`); 188 | return; 189 | } 190 | } 191 | } 192 | // console.log('start send'); 193 | const text = aiResult.parameters['any']; 194 | if (!phoneNumber || phoneNumber.length === 0) { 195 | glip.sendMessage(msg.groupId, `Sorry, I do not know the phone number.`); 196 | return; 197 | } 198 | if (!text || text.length === 0) { 199 | glip.sendMessage(msg.groupId, `Sorry, I do not get the message text.`); 200 | return; 201 | } 202 | phoneNumber = cleanNumber(phoneNumber); 203 | try { 204 | if (phoneNumber.length > 5) { 205 | const smsPhoneNumbers = await getSMSPhoneNumbers(msg.creatorId); 206 | // console.log(smsPhoneNumbers); 207 | const toUsers = [{ phoneNumber: phoneNumber }]; 208 | const response = await rc.account().extension().sms().post({ 209 | from: { phoneNumber: smsPhoneNumbers[0].phoneNumber }, 210 | to: toUsers, 211 | text, 212 | }); 213 | console.log(response); 214 | glip.sendMessage(msg.groupId, `Send SMS(${text}) to ${phoneNumber} success.`); 215 | } else { 216 | const extensionInfo = await getRcExtension(msg.creatorId); 217 | const from = { extensionNumber: extensionInfo.extensionNumber }; 218 | if (!userName) { 219 | userName = extensionInfo.name; 220 | } 221 | const toUsers = [{ extensionNumber: phoneNumber }]; 222 | const response = await rc.account().extension().companyPager().post({ 223 | from, 224 | to: toUsers, 225 | text, 226 | }); 227 | console.log(response); 228 | glip.sendMessage(msg.groupId, `Send SMS(${text}) to ${userName}(${phoneNumber}) success.`); 229 | } 230 | } catch (error) { 231 | glip.sendMessage(msg.groupId, `Sorry, something wrong happen when send sms.`); 232 | console.log(error); 233 | } 234 | } 235 | } 236 | -------------------------------------------------------------------------------- /src/weather.js: -------------------------------------------------------------------------------- 1 | import { request } from 'urllib'; 2 | import * as querystring from 'querystring'; 3 | const baseUrl = 'https://query.yahooapis.com/v1/public/yql?'; 4 | 5 | function getYqlQuery(city) { 6 | return `select * from weather.forecast where woeid in (select woeid from geo.places(1) where text='${city}')`; 7 | } 8 | 9 | async function requestWeatherForecast(city) { 10 | const options = { dataType: 'json' }; 11 | const yqlQuery = getYqlQuery(city); 12 | const url = baseUrl + querystring.stringify({ q: yqlQuery }); 13 | const response = await request(url, options); 14 | const query = response.data.query; 15 | if (!query) { 16 | return null; 17 | } 18 | const result = query.results; 19 | if (!result) { 20 | return null; 21 | } 22 | const channel = result.channel; 23 | if (!channel ) { 24 | return null; 25 | } 26 | 27 | const item = channel.item; 28 | if (!item ) { 29 | return null; 30 | } 31 | const condition = item.condition; 32 | if (!condition ) { 33 | return null; 34 | } 35 | const location = channel.location; 36 | const units = channel.units; 37 | if (!location || !units) { 38 | return null; 39 | } 40 | return `Today in ${location.city} : ${condition.text}, the temperature is ${condition.temp} ${units.temperature}`; 41 | } 42 | 43 | async function getWeather(glip, msg, aiResult) { 44 | const city = aiResult.parameters['geo-city']; 45 | const result = await requestWeatherForecast(city); 46 | if (result) { 47 | glip.sendMessage(msg.groupId, result); 48 | } 49 | } 50 | 51 | export default getWeather; 52 | -------------------------------------------------------------------------------- /src/webserver/index.ts: -------------------------------------------------------------------------------- 1 | import * as express from 'express'; 2 | import * as rcOauth from '../rc-oauth'; 3 | let app: any = express(); 4 | 5 | app.get('/rc-oauth-callback', async (req, res) => { 6 | try { 7 | await rcOauth.loggedIn(req.query); 8 | res.setHeader('content-type', 'text/html'); 9 | res.end('Login success, close me and go back to glip.'); 10 | } catch (e) { 11 | console.log('Rc oauth error', e); 12 | res.end('Can not log into RingCentral.' + e); 13 | } 14 | }); 15 | 16 | app.listen(process.env.PORT || 8080); -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./build", 4 | "target": "es6", 5 | "module": "commonjs", 6 | "sourceMap": true, 7 | "pretty": true, 8 | "noImplicitThis": true, 9 | "alwaysStrict": true, 10 | "noUnusedLocals": true, 11 | "importHelpers": true, 12 | "allowJs": true 13 | }, 14 | "files": [ 15 | "src/index.ts" 16 | ], 17 | "compileOnSave": true 18 | } -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@types/node@*", "@types/node@^8.0.20": 6 | version "8.0.28" 7 | resolved "https://registry.yarnpkg.com/@types/node/-/node-8.0.28.tgz#86206716f8d9251cf41692e384264cbd7058ad60" 8 | 9 | "@types/node@^7.0.5": 10 | version "7.0.43" 11 | resolved "https://registry.yarnpkg.com/@types/node/-/node-7.0.43.tgz#a187e08495a075f200ca946079c914e1a5fe962c" 12 | 13 | "@types/redis@^2.6.0": 14 | version "2.6.0" 15 | resolved "https://registry.yarnpkg.com/@types/redis/-/redis-2.6.0.tgz#7f01fdec1ba353e9ce7e8439807a43de61760c53" 16 | dependencies: 17 | "@types/node" "*" 18 | 19 | accepts@~1.3.8: 20 | version "1.3.8" 21 | resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" 22 | dependencies: 23 | mime-types "~2.1.34" 24 | negotiator "0.6.3" 25 | 26 | address@>=0.0.1: 27 | version "1.0.3" 28 | resolved "https://registry.yarnpkg.com/address/-/address-1.0.3.tgz#b5f50631f8d6cec8bd20c963963afb55e06cbce9" 29 | 30 | agent-base@2, agent-base@^2.1.1: 31 | version "2.1.1" 32 | resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-2.1.1.tgz#d6de10d5af6132d5bd692427d46fc538539094c7" 33 | dependencies: 34 | extend "~3.0.0" 35 | semver "~5.0.1" 36 | 37 | agent-base@^4.0.1: 38 | version "4.1.1" 39 | resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.1.1.tgz#92d8a4fc2524a3b09b3666a33b6c97960f23d6a4" 40 | dependencies: 41 | es6-promisify "^5.0.0" 42 | 43 | agentkeepalive@^3.1.0: 44 | version "3.3.0" 45 | resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-3.3.0.tgz#6d5de5829afd3be2712201a39275fd11c651857c" 46 | dependencies: 47 | humanize-ms "^1.2.1" 48 | 49 | any-promise@^1.3.0: 50 | version "1.3.0" 51 | resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" 52 | 53 | apiai@^4.0.1: 54 | version "4.0.3" 55 | resolved "https://registry.yarnpkg.com/apiai/-/apiai-4.0.3.tgz#2d1aa3bad1fd9418deaada509369e84e011efaf8" 56 | 57 | array-flatten@1.1.1: 58 | version "1.1.1" 59 | resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" 60 | 61 | ast-types@0.x.x: 62 | version "0.9.12" 63 | resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.9.12.tgz#b136300d67026625ae15326982ca9918e5db73c9" 64 | 65 | async@^1.5.2: 66 | version "1.5.2" 67 | resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" 68 | 69 | async@^2.0.1: 70 | version "2.5.0" 71 | resolved "https://registry.yarnpkg.com/async/-/async-2.5.0.tgz#843190fd6b7357a0b9e1c956edddd5ec8462b54d" 72 | dependencies: 73 | lodash "^4.14.0" 74 | 75 | asynckit@^0.4.0: 76 | version "0.4.0" 77 | resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" 78 | 79 | body-parser@1.19.2: 80 | version "1.19.2" 81 | resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.2.tgz#4714ccd9c157d44797b8b5607d72c0b89952f26e" 82 | dependencies: 83 | bytes "3.1.2" 84 | content-type "~1.0.4" 85 | debug "2.6.9" 86 | depd "~1.1.2" 87 | http-errors "1.8.1" 88 | iconv-lite "0.4.24" 89 | on-finished "~2.3.0" 90 | qs "6.9.7" 91 | raw-body "2.4.3" 92 | type-is "~1.6.18" 93 | 94 | bytes@3.0.0: 95 | version "3.0.0" 96 | resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" 97 | 98 | bytes@3.1.2: 99 | version "3.1.2" 100 | resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" 101 | 102 | co@^4.6.0: 103 | version "4.6.0" 104 | resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" 105 | 106 | combined-stream@^1.0.5: 107 | version "1.0.5" 108 | resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.5.tgz#938370a57b4a51dea2c77c15d5c5fdf895164009" 109 | dependencies: 110 | delayed-stream "~1.0.0" 111 | 112 | component-emitter@^1.2.0: 113 | version "1.2.1" 114 | resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6" 115 | 116 | content-disposition@0.5.4: 117 | version "0.5.4" 118 | resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" 119 | dependencies: 120 | safe-buffer "5.2.1" 121 | 122 | content-type@^1.0.2, content-type@~1.0.4: 123 | version "1.0.4" 124 | resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" 125 | 126 | cookie-signature@1.0.6: 127 | version "1.0.6" 128 | resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" 129 | 130 | cookie@0.4.2: 131 | version "0.4.2" 132 | resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.2.tgz#0e41f24de5ecf317947c82fc789e06a884824432" 133 | 134 | cookiejar@^2.0.6: 135 | version "2.1.1" 136 | resolved "https://registry.yarnpkg.com/cookiejar/-/cookiejar-2.1.1.tgz#41ad57b1b555951ec171412a81942b1e8200d34a" 137 | 138 | copy-to@~2.0.1: 139 | version "2.0.1" 140 | resolved "https://registry.yarnpkg.com/copy-to/-/copy-to-2.0.1.tgz#2680fbb8068a48d08656b6098092bdafc906f4a5" 141 | 142 | core-util-is@~1.0.0: 143 | version "1.0.2" 144 | resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" 145 | 146 | data-uri-to-buffer@1: 147 | version "1.2.0" 148 | resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-1.2.0.tgz#77163ea9c20d8641b4707e8f18abdf9a78f34835" 149 | 150 | debug@2, debug@2.6.9, debug@^2.2.0, debug@^2.6.0, debug@^2.6.8: 151 | version "2.6.9" 152 | resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" 153 | dependencies: 154 | ms "2.0.0" 155 | 156 | deep-is@~0.1.3: 157 | version "0.1.3" 158 | resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" 159 | 160 | default-user-agent@^1.0.0: 161 | version "1.0.0" 162 | resolved "https://registry.yarnpkg.com/default-user-agent/-/default-user-agent-1.0.0.tgz#16c46efdcaba3edc45f24f2bd4868b01b7c2adc6" 163 | dependencies: 164 | os-name "~1.0.3" 165 | 166 | degenerator@^1.0.4: 167 | version "1.0.4" 168 | resolved "https://registry.yarnpkg.com/degenerator/-/degenerator-1.0.4.tgz#fcf490a37ece266464d9cc431ab98c5819ced095" 169 | dependencies: 170 | ast-types "0.x.x" 171 | escodegen "1.x.x" 172 | esprima "3.x.x" 173 | 174 | delay.ts@^1.1.0: 175 | version "1.1.0" 176 | resolved "https://registry.yarnpkg.com/delay.ts/-/delay.ts-1.1.0.tgz#dac5e8e8d4d4b1077970dbf84c03ad9e29b0cfe4" 177 | dependencies: 178 | "@types/node" "^7.0.5" 179 | 180 | delayed-stream@~1.0.0: 181 | version "1.0.0" 182 | resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" 183 | 184 | depd@1.1.1: 185 | version "1.1.1" 186 | resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.1.tgz#5783b4e1c459f06fa5ca27f991f3d06e7a310359" 187 | 188 | depd@~1.1.2: 189 | version "1.1.2" 190 | resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" 191 | 192 | destroy@~1.0.4: 193 | version "1.0.4" 194 | resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" 195 | 196 | digest-header@^0.0.1: 197 | version "0.0.1" 198 | resolved "https://registry.yarnpkg.com/digest-header/-/digest-header-0.0.1.tgz#11ccf6deec5766ac379744d901c12cba49514be6" 199 | dependencies: 200 | utility "0.1.11" 201 | 202 | double-ended-queue@^2.1.0-0: 203 | version "2.1.0-0" 204 | resolved "https://registry.yarnpkg.com/double-ended-queue/-/double-ended-queue-2.1.0-0.tgz#103d3527fd31528f40188130c841efdd78264e5c" 205 | 206 | ee-first@1.1.1, ee-first@~1.1.1: 207 | version "1.1.1" 208 | resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" 209 | 210 | encodeurl@~1.0.2: 211 | version "1.0.2" 212 | resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" 213 | 214 | encoding@^0.1.11: 215 | version "0.1.12" 216 | resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.12.tgz#538b66f3ee62cd1ab51ec323829d1f9480c74beb" 217 | dependencies: 218 | iconv-lite "~0.4.13" 219 | 220 | es6-promise@^4.0.3: 221 | version "4.1.1" 222 | resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.1.1.tgz#8811e90915d9a0dba36274f0b242dbda78f9c92a" 223 | 224 | es6-promisify@^5.0.0: 225 | version "5.0.0" 226 | resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-5.0.0.tgz#5109d62f3e56ea967c4b63505aef08291c8a5203" 227 | dependencies: 228 | es6-promise "^4.0.3" 229 | 230 | escape-html@~1.0.3: 231 | version "1.0.3" 232 | resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" 233 | 234 | escodegen@1.x.x: 235 | version "1.9.0" 236 | resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.9.0.tgz#9811a2f265dc1cd3894420ee3717064b632b8852" 237 | dependencies: 238 | esprima "^3.1.3" 239 | estraverse "^4.2.0" 240 | esutils "^2.0.2" 241 | optionator "^0.8.1" 242 | optionalDependencies: 243 | source-map "~0.5.6" 244 | 245 | esprima@3.x.x, esprima@^3.1.3: 246 | version "3.1.3" 247 | resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633" 248 | 249 | estraverse@^4.2.0: 250 | version "4.2.0" 251 | resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.2.0.tgz#0dee3fed31fcd469618ce7342099fc1afa0bdb13" 252 | 253 | esutils@^2.0.2: 254 | version "2.0.2" 255 | resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b" 256 | 257 | etag@~1.8.1: 258 | version "1.8.1" 259 | resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" 260 | 261 | express@^4.17.3: 262 | version "4.17.3" 263 | resolved "https://registry.yarnpkg.com/express/-/express-4.17.3.tgz#f6c7302194a4fb54271b73a1fe7a06478c8f85a1" 264 | dependencies: 265 | accepts "~1.3.8" 266 | array-flatten "1.1.1" 267 | body-parser "1.19.2" 268 | content-disposition "0.5.4" 269 | content-type "~1.0.4" 270 | cookie "0.4.2" 271 | cookie-signature "1.0.6" 272 | debug "2.6.9" 273 | depd "~1.1.2" 274 | encodeurl "~1.0.2" 275 | escape-html "~1.0.3" 276 | etag "~1.8.1" 277 | finalhandler "~1.1.2" 278 | fresh "0.5.2" 279 | merge-descriptors "1.0.1" 280 | methods "~1.1.2" 281 | on-finished "~2.3.0" 282 | parseurl "~1.3.3" 283 | path-to-regexp "0.1.7" 284 | proxy-addr "~2.0.7" 285 | qs "6.9.7" 286 | range-parser "~1.2.1" 287 | safe-buffer "5.2.1" 288 | send "0.17.2" 289 | serve-static "1.14.2" 290 | setprototypeof "1.2.0" 291 | statuses "~1.5.0" 292 | type-is "~1.6.18" 293 | utils-merge "1.0.1" 294 | vary "~1.1.2" 295 | 296 | extend@3, extend@^3.0.0, extend@~3.0.0: 297 | version "3.0.1" 298 | resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.1.tgz#a755ea7bc1adfcc5a31ce7e762dbaadc5e636444" 299 | 300 | fast-levenshtein@~2.0.4: 301 | version "2.0.6" 302 | resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" 303 | 304 | file-uri-to-path@1: 305 | version "1.0.0" 306 | resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" 307 | 308 | finalhandler@~1.1.2: 309 | version "1.1.2" 310 | resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d" 311 | dependencies: 312 | debug "2.6.9" 313 | encodeurl "~1.0.2" 314 | escape-html "~1.0.3" 315 | on-finished "~2.3.0" 316 | parseurl "~1.3.3" 317 | statuses "~1.5.0" 318 | unpipe "~1.0.0" 319 | 320 | form-data@1.0.0-rc4: 321 | version "1.0.0-rc4" 322 | resolved "https://registry.yarnpkg.com/form-data/-/form-data-1.0.0-rc4.tgz#05ac6bc22227b43e4461f488161554699d4f8b5e" 323 | dependencies: 324 | async "^1.5.2" 325 | combined-stream "^1.0.5" 326 | mime-types "^2.1.10" 327 | 328 | form-data@^1.0.1: 329 | version "1.0.1" 330 | resolved "https://registry.yarnpkg.com/form-data/-/form-data-1.0.1.tgz#ae315db9a4907fa065502304a66d7733475ee37c" 331 | dependencies: 332 | async "^2.0.1" 333 | combined-stream "^1.0.5" 334 | mime-types "^2.1.11" 335 | 336 | form-data@^2.1.2: 337 | version "2.3.1" 338 | resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.1.tgz#6fb94fbd71885306d73d15cc497fe4cc4ecd44bf" 339 | dependencies: 340 | asynckit "^0.4.0" 341 | combined-stream "^1.0.5" 342 | mime-types "^2.1.12" 343 | 344 | formidable@^1.0.17: 345 | version "1.1.1" 346 | resolved "https://registry.yarnpkg.com/formidable/-/formidable-1.1.1.tgz#96b8886f7c3c3508b932d6bd70c4d3a88f35f1a9" 347 | 348 | forwarded@0.2.0: 349 | version "0.2.0" 350 | resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" 351 | 352 | fresh@0.5.2: 353 | version "0.5.2" 354 | resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" 355 | 356 | ftp@~0.3.10: 357 | version "0.3.10" 358 | resolved "https://registry.yarnpkg.com/ftp/-/ftp-0.3.10.tgz#9197d861ad8142f3e63d5a83bfe4c59f7330885d" 359 | dependencies: 360 | readable-stream "1.1.x" 361 | xregexp "2.0.0" 362 | 363 | get-uri@^2.0.0: 364 | version "2.0.1" 365 | resolved "https://registry.yarnpkg.com/get-uri/-/get-uri-2.0.1.tgz#dbdcacacd8c608a38316869368117697a1631c59" 366 | dependencies: 367 | data-uri-to-buffer "1" 368 | debug "2" 369 | extend "3" 370 | file-uri-to-path "1" 371 | ftp "~0.3.10" 372 | readable-stream "2" 373 | 374 | http-errors@1.6.2: 375 | version "1.6.2" 376 | resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.2.tgz#0a002cc85707192a7e7946ceedc11155f60ec736" 377 | dependencies: 378 | depd "1.1.1" 379 | inherits "2.0.3" 380 | setprototypeof "1.0.3" 381 | statuses ">= 1.3.1 < 2" 382 | 383 | http-errors@1.8.1: 384 | version "1.8.1" 385 | resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.8.1.tgz#7c3f28577cbc8a207388455dbd62295ed07bd68c" 386 | dependencies: 387 | depd "~1.1.2" 388 | inherits "2.0.4" 389 | setprototypeof "1.2.0" 390 | statuses ">= 1.5.0 < 2" 391 | toidentifier "1.0.1" 392 | 393 | http-proxy-agent@1, http-proxy-agent@^1.0.0: 394 | version "1.0.0" 395 | resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-1.0.0.tgz#cc1ce38e453bf984a0f7702d2dd59c73d081284a" 396 | dependencies: 397 | agent-base "2" 398 | debug "2" 399 | extend "3" 400 | 401 | http-status@^1.0.1: 402 | version "1.0.1" 403 | resolved "https://registry.yarnpkg.com/http-status/-/http-status-1.0.1.tgz#dc43001a8bfc50ac87d485a892f7578964bc94a2" 404 | 405 | https-proxy-agent@1, https-proxy-agent@^1.0.0: 406 | version "1.0.0" 407 | resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-1.0.0.tgz#35f7da6c48ce4ddbfa264891ac593ee5ff8671e6" 408 | dependencies: 409 | agent-base "2" 410 | debug "2" 411 | extend "3" 412 | 413 | humanize-ms@^1.2.0, humanize-ms@^1.2.1: 414 | version "1.2.1" 415 | resolved "https://registry.yarnpkg.com/humanize-ms/-/humanize-ms-1.2.1.tgz#c46e3159a293f6b896da29316d8b6fe8bb79bbed" 416 | dependencies: 417 | ms "^2.0.0" 418 | 419 | iconv-lite@0.4.18, iconv-lite@^0.4.15, iconv-lite@~0.4.13: 420 | version "0.4.18" 421 | resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.18.tgz#23d8656b16aae6742ac29732ea8f0336a4789cf2" 422 | 423 | iconv-lite@0.4.24: 424 | version "0.4.24" 425 | resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" 426 | dependencies: 427 | safer-buffer ">= 2.1.2 < 3" 428 | 429 | inherits@2.0.3, inherits@~2.0.1, inherits@~2.0.3: 430 | version "2.0.3" 431 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" 432 | 433 | inherits@2.0.4: 434 | version "2.0.4" 435 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" 436 | 437 | ip@^1.1.4, ip@^1.1.5: 438 | version "1.1.5" 439 | resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a" 440 | 441 | ipaddr.js@1.9.1: 442 | version "1.9.1" 443 | resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" 444 | 445 | is-stream@^1.0.1: 446 | version "1.1.0" 447 | resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" 448 | 449 | isarray@0.0.1: 450 | version "0.0.1" 451 | resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" 452 | 453 | isarray@~1.0.0: 454 | version "1.0.0" 455 | resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" 456 | 457 | isomorphic-fetch@^2.2.1: 458 | version "2.2.1" 459 | resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9" 460 | dependencies: 461 | node-fetch "^1.0.1" 462 | whatwg-fetch ">=0.10.0" 463 | 464 | known-fetch-body@^0.1.1: 465 | version "0.1.1" 466 | resolved "https://registry.yarnpkg.com/known-fetch-body/-/known-fetch-body-0.1.1.tgz#931f1ee9d412c7dabeac362a73dbba2b87858792" 467 | dependencies: 468 | form-data "^1.0.1" 469 | 470 | levn@~0.3.0: 471 | version "0.3.0" 472 | resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" 473 | dependencies: 474 | prelude-ls "~1.1.2" 475 | type-check "~0.3.2" 476 | 477 | lodash@^4.14.0: 478 | version "4.17.4" 479 | resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" 480 | 481 | lru-cache@~2.6.5: 482 | version "2.6.5" 483 | resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-2.6.5.tgz#e56d6354148ede8d7707b58d143220fd08df0fd5" 484 | 485 | media-typer@0.3.0: 486 | version "0.3.0" 487 | resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" 488 | 489 | merge-descriptors@1.0.1: 490 | version "1.0.1" 491 | resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" 492 | 493 | methods@^1.1.1, methods@~1.1.2: 494 | version "1.1.2" 495 | resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" 496 | 497 | mime-db@1.52.0: 498 | version "1.52.0" 499 | resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" 500 | 501 | mime-db@~1.30.0: 502 | version "1.30.0" 503 | resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.30.0.tgz#74c643da2dd9d6a45399963465b26d5ca7d71f01" 504 | 505 | mime-types@^2.1.10, mime-types@^2.1.11, mime-types@^2.1.12: 506 | version "2.1.17" 507 | resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.17.tgz#09d7a393f03e995a79f8af857b70a9e0ab16557a" 508 | dependencies: 509 | mime-db "~1.30.0" 510 | 511 | mime-types@~2.1.24, mime-types@~2.1.34: 512 | version "2.1.35" 513 | resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" 514 | dependencies: 515 | mime-db "1.52.0" 516 | 517 | mime@1.6.0: 518 | version "1.6.0" 519 | resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" 520 | 521 | mime@^1.3.4: 522 | version "1.4.0" 523 | resolved "https://registry.yarnpkg.com/mime/-/mime-1.4.0.tgz#69e9e0db51d44f2a3b56e48b7817d7d137f1a343" 524 | 525 | minimist@^1.1.0: 526 | version "1.2.7" 527 | resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.7.tgz#daa1c4d91f507390437c6a8bc01078e7000c4d18" 528 | 529 | ms@2.0.0, ms@^2.0.0: 530 | version "2.0.0" 531 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" 532 | 533 | ms@2.1.3: 534 | version "2.1.3" 535 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" 536 | 537 | negotiator@0.6.3: 538 | version "0.6.3" 539 | resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" 540 | 541 | netmask@^1.0.6: 542 | version "1.0.6" 543 | resolved "https://registry.yarnpkg.com/netmask/-/netmask-1.0.6.tgz#20297e89d86f6f6400f250d9f4f6b4c1945fcd35" 544 | 545 | node-fetch@^1.0.1: 546 | version "1.7.3" 547 | resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef" 548 | dependencies: 549 | encoding "^0.1.11" 550 | is-stream "^1.0.1" 551 | 552 | on-finished@~2.3.0: 553 | version "2.3.0" 554 | resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" 555 | dependencies: 556 | ee-first "1.1.1" 557 | 558 | optionator@^0.8.1: 559 | version "0.8.2" 560 | resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.2.tgz#364c5e409d3f4d6301d6c0b4c05bba50180aeb64" 561 | dependencies: 562 | deep-is "~0.1.3" 563 | fast-levenshtein "~2.0.4" 564 | levn "~0.3.0" 565 | prelude-ls "~1.1.2" 566 | type-check "~0.3.2" 567 | wordwrap "~1.0.0" 568 | 569 | os-name@~1.0.3: 570 | version "1.0.3" 571 | resolved "https://registry.yarnpkg.com/os-name/-/os-name-1.0.3.tgz#1b379f64835af7c5a7f498b357cb95215c159edf" 572 | dependencies: 573 | osx-release "^1.0.0" 574 | win-release "^1.0.0" 575 | 576 | osx-release@^1.0.0: 577 | version "1.1.0" 578 | resolved "https://registry.yarnpkg.com/osx-release/-/osx-release-1.1.0.tgz#f217911a28136949af1bf9308b241e2737d3cd6c" 579 | dependencies: 580 | minimist "^1.1.0" 581 | 582 | pac-proxy-agent@^2.0.0: 583 | version "2.0.0" 584 | resolved "https://registry.yarnpkg.com/pac-proxy-agent/-/pac-proxy-agent-2.0.0.tgz#beb17cd2b06a20b379d57e1b2e2c29be0dfe5f9a" 585 | dependencies: 586 | agent-base "^2.1.1" 587 | debug "^2.6.8" 588 | get-uri "^2.0.0" 589 | http-proxy-agent "^1.0.0" 590 | https-proxy-agent "^1.0.0" 591 | pac-resolver "^3.0.0" 592 | raw-body "^2.2.0" 593 | socks-proxy-agent "^3.0.0" 594 | 595 | pac-resolver@^3.0.0: 596 | version "3.0.0" 597 | resolved "https://registry.yarnpkg.com/pac-resolver/-/pac-resolver-3.0.0.tgz#6aea30787db0a891704deb7800a722a7615a6f26" 598 | dependencies: 599 | co "^4.6.0" 600 | degenerator "^1.0.4" 601 | ip "^1.1.5" 602 | netmask "^1.0.6" 603 | thunkify "^2.1.2" 604 | 605 | parseurl@~1.3.3: 606 | version "1.3.3" 607 | resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" 608 | 609 | path-to-regexp@0.1.7: 610 | version "0.1.7" 611 | resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" 612 | 613 | prelude-ls@~1.1.2: 614 | version "1.1.2" 615 | resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" 616 | 617 | process-nextick-args@~1.0.6: 618 | version "1.0.7" 619 | resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3" 620 | 621 | proxy-addr@~2.0.7: 622 | version "2.0.7" 623 | resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" 624 | dependencies: 625 | forwarded "0.2.0" 626 | ipaddr.js "1.9.1" 627 | 628 | proxy-agent@2, proxy-agent@^2.1.0: 629 | version "2.1.0" 630 | resolved "https://registry.yarnpkg.com/proxy-agent/-/proxy-agent-2.1.0.tgz#a3a2b3866debfeb79bb791f345dc9bc876e7ff86" 631 | dependencies: 632 | agent-base "2" 633 | debug "2" 634 | extend "3" 635 | http-proxy-agent "1" 636 | https-proxy-agent "1" 637 | lru-cache "~2.6.5" 638 | pac-proxy-agent "^2.0.0" 639 | socks-proxy-agent "2" 640 | 641 | pubnub@^4.4.4: 642 | version "4.15.1" 643 | resolved "https://registry.yarnpkg.com/pubnub/-/pubnub-4.15.1.tgz#a5185d42a6c8860746a196b4784302703d7adf69" 644 | dependencies: 645 | agentkeepalive "^3.1.0" 646 | superagent "^2.3.0" 647 | superagent-proxy "^1.0.2" 648 | uuid "^3.0.1" 649 | 650 | qs@6.9.7, qs@^6.1.0, qs@^6.4.0: 651 | version "6.9.7" 652 | resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.7.tgz#4610846871485e1e048f44ae3b94033f0e675afe" 653 | 654 | range-parser@~1.2.1: 655 | version "1.2.1" 656 | resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" 657 | 658 | raw-body@2.4.3: 659 | version "2.4.3" 660 | resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.3.tgz#8f80305d11c2a0a545c2d9d89d7a0286fcead43c" 661 | dependencies: 662 | bytes "3.1.2" 663 | http-errors "1.8.1" 664 | iconv-lite "0.4.24" 665 | unpipe "1.0.0" 666 | 667 | raw-body@^2.2.0: 668 | version "2.3.1" 669 | resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.3.1.tgz#30f95e2a67a14e2e4413d8d51fdd92c877e8f2ed" 670 | dependencies: 671 | bytes "3.0.0" 672 | http-errors "1.6.2" 673 | iconv-lite "0.4.18" 674 | unpipe "1.0.0" 675 | 676 | readable-stream@1.1.x: 677 | version "1.1.14" 678 | resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9" 679 | dependencies: 680 | core-util-is "~1.0.0" 681 | inherits "~2.0.1" 682 | isarray "0.0.1" 683 | string_decoder "~0.10.x" 684 | 685 | readable-stream@2, readable-stream@^2.0.5: 686 | version "2.3.3" 687 | resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.3.tgz#368f2512d79f9d46fdfc71349ae7878bbc1eb95c" 688 | dependencies: 689 | core-util-is "~1.0.0" 690 | inherits "~2.0.3" 691 | isarray "~1.0.0" 692 | process-nextick-args "~1.0.6" 693 | safe-buffer "~5.1.1" 694 | string_decoder "~1.0.3" 695 | util-deprecate "~1.0.1" 696 | 697 | redis-commands@^1.2.0: 698 | version "1.3.1" 699 | resolved "https://registry.yarnpkg.com/redis-commands/-/redis-commands-1.3.1.tgz#81d826f45fa9c8b2011f4cd7a0fe597d241d442b" 700 | 701 | redis-parser@^2.6.0: 702 | version "2.6.0" 703 | resolved "https://registry.yarnpkg.com/redis-parser/-/redis-parser-2.6.0.tgz#52ed09dacac108f1a631c07e9b69941e7a19504b" 704 | 705 | redis@^2.7.1: 706 | version "2.8.0" 707 | resolved "https://registry.yarnpkg.com/redis/-/redis-2.8.0.tgz#202288e3f58c49f6079d97af7a10e1303ae14b02" 708 | dependencies: 709 | double-ended-queue "^2.1.0-0" 710 | redis-commands "^1.2.0" 711 | redis-parser "^2.6.0" 712 | 713 | ringcentral-ts@^0.9.1: 714 | version "0.9.1" 715 | resolved "https://registry.yarnpkg.com/ringcentral-ts/-/ringcentral-ts-0.9.1.tgz#4eb28204f0934fd22c423ff8dd1ed5098ee120eb" 716 | dependencies: 717 | "@types/node" "^8.0.20" 718 | delay.ts "^1.1.0" 719 | form-data "^2.1.2" 720 | http-status "^1.0.1" 721 | isomorphic-fetch "^2.2.1" 722 | known-fetch-body "^0.1.1" 723 | pubnub "^4.4.4" 724 | tslib "^1.6.0" 725 | 726 | safe-buffer@5.2.1: 727 | version "5.2.1" 728 | resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" 729 | 730 | safe-buffer@~5.1.0, safe-buffer@~5.1.1: 731 | version "5.1.1" 732 | resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853" 733 | 734 | "safer-buffer@>= 2.1.2 < 3": 735 | version "2.1.2" 736 | resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" 737 | 738 | semver@^5.0.1: 739 | version "5.4.1" 740 | resolved "https://registry.yarnpkg.com/semver/-/semver-5.4.1.tgz#e059c09d8571f0540823733433505d3a2f00b18e" 741 | 742 | semver@~5.0.1: 743 | version "5.0.3" 744 | resolved "https://registry.yarnpkg.com/semver/-/semver-5.0.3.tgz#77466de589cd5d3c95f138aa78bc569a3cb5d27a" 745 | 746 | send@0.17.2: 747 | version "0.17.2" 748 | resolved "https://registry.yarnpkg.com/send/-/send-0.17.2.tgz#926622f76601c41808012c8bf1688fe3906f7820" 749 | dependencies: 750 | debug "2.6.9" 751 | depd "~1.1.2" 752 | destroy "~1.0.4" 753 | encodeurl "~1.0.2" 754 | escape-html "~1.0.3" 755 | etag "~1.8.1" 756 | fresh "0.5.2" 757 | http-errors "1.8.1" 758 | mime "1.6.0" 759 | ms "2.1.3" 760 | on-finished "~2.3.0" 761 | range-parser "~1.2.1" 762 | statuses "~1.5.0" 763 | 764 | serve-static@1.14.2: 765 | version "1.14.2" 766 | resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.14.2.tgz#722d6294b1d62626d41b43a013ece4598d292bfa" 767 | dependencies: 768 | encodeurl "~1.0.2" 769 | escape-html "~1.0.3" 770 | parseurl "~1.3.3" 771 | send "0.17.2" 772 | 773 | setprototypeof@1.0.3: 774 | version "1.0.3" 775 | resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.0.3.tgz#66567e37043eeb4f04d91bd658c0cbefb55b8e04" 776 | 777 | setprototypeof@1.2.0: 778 | version "1.2.0" 779 | resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" 780 | 781 | smart-buffer@^1.0.13: 782 | version "1.1.15" 783 | resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-1.1.15.tgz#7f114b5b65fab3e2a35aa775bb12f0d1c649bf16" 784 | 785 | socks-proxy-agent@2: 786 | version "2.1.1" 787 | resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-2.1.1.tgz#86ebb07193258637870e13b7bd99f26c663df3d3" 788 | dependencies: 789 | agent-base "2" 790 | extend "3" 791 | socks "~1.1.5" 792 | 793 | socks-proxy-agent@^3.0.0: 794 | version "3.0.0" 795 | resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-3.0.0.tgz#ea23085cd2bde94d084a62448f31139ca7ed6245" 796 | dependencies: 797 | agent-base "^4.0.1" 798 | socks "^1.1.10" 799 | 800 | socks@^1.1.10, socks@~1.1.5: 801 | version "1.1.10" 802 | resolved "https://registry.yarnpkg.com/socks/-/socks-1.1.10.tgz#5b8b7fc7c8f341c53ed056e929b7bf4de8ba7b5a" 803 | dependencies: 804 | ip "^1.1.4" 805 | smart-buffer "^1.0.13" 806 | 807 | source-map@~0.5.6: 808 | version "0.5.7" 809 | resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" 810 | 811 | "statuses@>= 1.3.1 < 2", "statuses@>= 1.5.0 < 2", statuses@^1.3.1, statuses@~1.5.0: 812 | version "1.5.0" 813 | resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" 814 | 815 | string_decoder@~0.10.x: 816 | version "0.10.31" 817 | resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" 818 | 819 | string_decoder@~1.0.3: 820 | version "1.0.3" 821 | resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.0.3.tgz#0fc67d7c141825de94282dd536bec6b9bce860ab" 822 | dependencies: 823 | safe-buffer "~5.1.0" 824 | 825 | superagent-proxy@^1.0.2: 826 | version "1.0.2" 827 | resolved "https://registry.yarnpkg.com/superagent-proxy/-/superagent-proxy-1.0.2.tgz#92d3660578f618ed43a82cf8cac799fe2938ba2d" 828 | dependencies: 829 | debug "2" 830 | proxy-agent "2" 831 | 832 | superagent@^2.3.0: 833 | version "2.3.0" 834 | resolved "https://registry.yarnpkg.com/superagent/-/superagent-2.3.0.tgz#703529a0714e57e123959ddefbce193b2e50d115" 835 | dependencies: 836 | component-emitter "^1.2.0" 837 | cookiejar "^2.0.6" 838 | debug "^2.2.0" 839 | extend "^3.0.0" 840 | form-data "1.0.0-rc4" 841 | formidable "^1.0.17" 842 | methods "^1.1.1" 843 | mime "^1.3.4" 844 | qs "^6.1.0" 845 | readable-stream "^2.0.5" 846 | 847 | thunkify@^2.1.2: 848 | version "2.1.2" 849 | resolved "https://registry.yarnpkg.com/thunkify/-/thunkify-2.1.2.tgz#faa0e9d230c51acc95ca13a361ac05ca7e04553d" 850 | 851 | toidentifier@1.0.1: 852 | version "1.0.1" 853 | resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" 854 | 855 | tslib@^1.6.0: 856 | version "1.7.1" 857 | resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.7.1.tgz#bc8004164691923a79fe8378bbeb3da2017538ec" 858 | 859 | type-check@~0.3.2: 860 | version "0.3.2" 861 | resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" 862 | dependencies: 863 | prelude-ls "~1.1.2" 864 | 865 | type-is@~1.6.18: 866 | version "1.6.18" 867 | resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" 868 | dependencies: 869 | media-typer "0.3.0" 870 | mime-types "~2.1.24" 871 | 872 | typescript@^2.2.2: 873 | version "2.5.2" 874 | resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.5.2.tgz#038a95f7d9bbb420b1bf35ba31d4c5c1dd3ffe34" 875 | 876 | unpipe@1.0.0, unpipe@~1.0.0: 877 | version "1.0.0" 878 | resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" 879 | 880 | urllib@^2.21.2: 881 | version "2.25.0" 882 | resolved "https://registry.yarnpkg.com/urllib/-/urllib-2.25.0.tgz#07af95515102229c221256a443ecc3e5ba04d51e" 883 | dependencies: 884 | any-promise "^1.3.0" 885 | content-type "^1.0.2" 886 | debug "^2.6.0" 887 | default-user-agent "^1.0.0" 888 | digest-header "^0.0.1" 889 | ee-first "~1.1.1" 890 | humanize-ms "^1.2.0" 891 | iconv-lite "^0.4.15" 892 | proxy-agent "^2.1.0" 893 | qs "^6.4.0" 894 | statuses "^1.3.1" 895 | utility "^1.12.0" 896 | 897 | util-deprecate@~1.0.1: 898 | version "1.0.2" 899 | resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" 900 | 901 | utility@0.1.11: 902 | version "0.1.11" 903 | resolved "https://registry.yarnpkg.com/utility/-/utility-0.1.11.tgz#fde60cf9b4e4751947a0cf5d104ce29367226715" 904 | dependencies: 905 | address ">=0.0.1" 906 | 907 | utility@^1.12.0: 908 | version "1.12.0" 909 | resolved "https://registry.yarnpkg.com/utility/-/utility-1.12.0.tgz#bd69307863a3884ee58821251215b9872fb84058" 910 | dependencies: 911 | copy-to "~2.0.1" 912 | escape-html "~1.0.3" 913 | 914 | utils-merge@1.0.1: 915 | version "1.0.1" 916 | resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" 917 | 918 | uuid@^3.0.1: 919 | version "3.1.0" 920 | resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.1.0.tgz#3dd3d3e790abc24d7b0d3a034ffababe28ebbc04" 921 | 922 | vary@~1.1.2: 923 | version "1.1.2" 924 | resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" 925 | 926 | whatwg-fetch@>=0.10.0: 927 | version "2.0.3" 928 | resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz#9c84ec2dcf68187ff00bc64e1274b442176e1c84" 929 | 930 | win-release@^1.0.0: 931 | version "1.1.1" 932 | resolved "https://registry.yarnpkg.com/win-release/-/win-release-1.1.1.tgz#5fa55e02be7ca934edfc12665632e849b72e5209" 933 | dependencies: 934 | semver "^5.0.1" 935 | 936 | wordwrap@~1.0.0: 937 | version "1.0.0" 938 | resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" 939 | 940 | xregexp@2.0.0: 941 | version "2.0.0" 942 | resolved "https://registry.yarnpkg.com/xregexp/-/xregexp-2.0.0.tgz#52a63e56ca0b84a7f3a5f3d61872f126ad7a5943" 943 | --------------------------------------------------------------------------------