]> git.r.bdr.sh - rbdr/dead-drop/blob - lib/controllers/recordings.js
Handle Menus and Store / Recall recordings (#2)
[rbdr/dead-drop] / lib / controllers / recordings.js
1 'use strict';
2
3 const Joi = require('joi');
4 const Pify = require('pify');
5 const Redis = require('redis');
6 const Twilio = require('twilio');
7
8 const internals = {};
9
10 internals.kContentType = 'application/xml'; // The content type used to respond
11 internals.kLanguage = 'es-mx'; // the language to use
12 internals.kMaxMessageLength = 30; // max message length in seconds
13 internals.kIdDateFormat = 'YYMMDDHHmmssSSS'; // derive ids from current date. 15 digits.
14 internals.kRecordingsSet = 'recordings';
15 internals.kRecordMessage = 'Graba tu mensaje despues del bip. ' +
16 'Presiona cualquier tecla para finalizar tu mensaje. '; // the recording message
17 internals.kConfirmationMessage = 'Gracias. Tu mensaje es el número: ';
18 internals.kNotFoundMessage = 'Mensaje no encontrado. Adiós!';
19
20 internals.kRecordingSchema = Joi.object().keys({
21 url: Joi.string().required()
22 });
23
24 /**
25 * Handles the HTTP requests for the recording menu
26 *
27 * @class RecordingsController
28 * @param {DeadDrop.tConfiguration} config The configuration to
29 * initialize.
30 */
31 module.exports = internals.RecordingsController = class RecordingsController {
32 constructor(config) {
33
34 this._redis = Redis.createClient(config.redis);
35
36 // Log an error if it happens.
37 this._redis.on('error', (err) => {
38
39 console.error(err);
40 });
41 }
42
43 /**
44 * Start recording process
45 *
46 * @function startRecording
47 * @memberof RecordingsController
48 * @instance
49 * @return {generator} a koa compatible handler generator function
50 */
51 startRecording() {
52
53 return function * () {
54
55 const response = new Twilio.TwimlResponse();
56
57 // the action will default to post in the same URL, so no change
58 // required there.
59 response.say(internals.kRecordMessage, { language: internals.kLanguage })
60 .record({
61 maxLength: internals.kMaxMessageLength
62 });
63
64 this.type = internals.kContentType;
65 this.body = response.toString();
66 };
67 }
68
69 /**
70 * Saves the recording for later use
71 *
72 * @function saveRecording
73 * @memberof RecordingsController
74 * @instance
75 * @return {generator} a koa compatible handler generator function
76 */
77 saveRecording() {
78
79 const self = this;
80
81 return function * () {
82
83 const zadd = Pify(self._redis.zadd.bind(self._redis));
84
85 const response = new Twilio.TwimlResponse();
86
87 const id = Date.now().toString().substr(2,10);
88 const url = this.request.body.RecordingUrl;
89 const separatedId = id.split('').join('. ');
90 const recording = {
91 url
92 };
93
94 yield self._validate(recording).catch((err) => {
95
96 this.throw(err.message, 422);
97 });
98
99 // Add to ordered set for quick fetches, and set for random fetches
100 yield zadd(internals.kRecordingsSet, parseInt(id), url);
101
102 response.say(`${internals.kConfirmationMessage}${separatedId}`, { language: internals.kLanguage });
103
104 this.type = internals.kContentType;
105 this.body = response.toString();
106 };
107 }
108
109 /**
110 * Gets a recording.
111 *
112 * @function getRecording
113 * @memberof RecordingsController
114 * @instance
115 * @return {generator} a koa compatible handler generator function
116 */
117 getRecording() {
118
119 const self = this;
120
121 return function * (id) {
122
123 id = parseInt(id);
124
125 const zcard = Pify(self._redis.zcard.bind(self._redis));
126 const zrange = Pify(self._redis.zrange.bind(self._redis));
127 const zscore = Pify(self._redis.zscore.bind(self._redis));
128 const zrangebyscore = Pify(self._redis.zrangebyscore.bind(self._redis));
129
130 const response = new Twilio.TwimlResponse();
131 let location = null;
132
133 if (!id) {
134 const maxNumber = yield zcard(internals.kRecordingsSet);
135 const index = Math.floor(Math.random() * maxNumber); // get random between 0 and cardinality
136 location = yield zrange(internals.kRecordingsSet, index, index);
137 }
138 else {
139 location = yield zrangebyscore(internals.kRecordingsSet, id, id);
140 }
141
142 if (location && location.length > 0) {
143 if (!id) {
144 id = yield zscore(internals.kRecordingsSet, location[0]);
145 }
146
147 const separatedId = id.toString().split('').join('. ');
148 response.play(location[0]).say(separatedId, { language: internals.kLanguage });
149 }
150 else {
151 response.say(internals.kNotFoundMessage, { language: internals.kLanguage });
152 }
153
154 this.type = internals.kContentType;
155 this.body = response.toString();
156 };
157 }
158
159 // Validates the post schema
160
161 _validate(post) {
162
163 const validate = Pify(Joi.validate.bind(Joi));
164 return validate(post, internals.kRecordingSchema);
165 }
166 };