Tropo and Node.js Automated Voice Mail

May 31, 2018

Introduction

I use Tropo to answer my phone. Tropo is cloud software which creates a phone number and provides the web API to answer calls and record voice messages. I used the Node.js API library because of its simplicity and ease of setup.

The Tropo Node API client can be downloaded here. I will let you install Node.js yourself and just show the important components of the script.

Install Tropo

If you have Node.js installed, and are using the Node Package Manager (npm), just do the following command as root:

# npm install tropo-webapi -g

This will install Tropo globally on your machine.

Node.js code

Now create a Node.js server to record voicemail messages. You will need to have a server.js file with the express Node module and complete setup. The call to tropo.record will post the multipart wav file to https://test.me/tel/voice/rec. I used Multer to get the file from the HTTP Post endpoint. I had difficulties with this code until I found out that the name supplied to Post was called ‘filename’. Without this string, the Posted multi-part will fail.

//tropo functions
var tropowebapi = require('tropo-webapi');
var sys = require('sys');
//Install multi part to access files >npm install --save multer
var multer = require('multer');
var upload = multer({ dest: 'uploads/' });
var fs = require('fs');

//telephone functions
app.post('/tel/voice', function(req, res){
	var tropo = new tropowebapi.TropoWebAPI();

	if(req.body['session']['from']['channel'] == "TEXT") {
        tropo.say("This application is voice only.  Please call in using a regular phone or SIP phone.");
        tropo.on("continue", null, null, true);
        res.send(TropoJSON(tropo));
    }
	else{
		tropo.say("Hello!");
		var say = new Say("Please ree cord your message after the beep and do not hang up until further instructions.");
		var choices = new Choices(null, null, "#");
		tropo.record(3, false, true, true, choices, 'audio/wav', 3, 60, null, null, "recording", null, say, 5, null, "https://test.me/tel/voice/rec", null, null);
		tropo.on("continue", null, "/tel/voice/answer", true);
		tropo.on("incomplete", null, "/tel/voice/timeout", true);
		tropo.on("error", null, "/tel/voice/error", true);
	}
	res.send(tropowebapi.TropoJSON(tropo));
});

app.post('/tel/voice/answer', function(req, res){
	var tropo = new tropowebapi.TropoWebAPI();
	tropo.say("Perfecto! Your recording saved and we will contact you shortly! Thank you! Have a great day! Goodbye!");
	res.send(tropowebapi.TropoJSON(tropo));
});

app.post('/tel/voice/timeout', function(req, res){
    var tropo = new tropowebapi.TropoWebAPI();
    tropo.say("Sorry, I didn't hear anything. Please call back and try again.");
    res.send(tropowebapi.TropoJSON(tropo));
});

app.post('/tel/voice/error', function(req, res){
    // Create a new instance of the TropoWebAPI object.
    var tropo = new tropowebapi.TropoWebAPI();
    tropo.say("Recording failed. Please call back and try again.");
    res.send(tropowebapi.TropoJSON(tropo));
});

//must be named filename to upload
app.post('/tel/voice/rec', upload.single('filename'), function (req, res, next) {
	console.log(req.file.filename);
	var tmp_path = req.file.path;
	var target_path = 'uploads/' + req.file.filename + '.wav';
	var src = fs.createReadStream(tmp_path);
	var dest = fs.createWriteStream(target_path);
	src.pipe(dest);
    var tropo = new tropowebapi.TropoWebAPI();
    res.send(tropowebapi.TropoJSON(tropo));
});
Cron Job

I set up a cron job to run every minute to email me the voice messages. It is possible to email directly from Node, however, I found it not as straight forward.

Crontab -e run every minute.

* * * * * cd /home/dev/ && /usr/bin/python3 /home/dev/cronemail.py > /tmp/cron.log 2>&1

Here is the cronemail.py Python code.

#!/usr/bin/env python3
import os
import sys
import smtplib
from email.mime.text import MIMEText
from email.mime.image import MIMEImage
from email.mime.audio import MIMEAudio
from email.mime.multipart import MIMEMultipart
from email.header import Header
from email.utils import formataddr
from os import listdir
from os.path import isfile, join
import mimetypes

# send voice recordings to email and delete old recordings.

class Recording():
	def __init__(self):
		self.emailSent = False
		
	def sendMultipartEmail(self, sender, recipient, subject, attachFiles, body, bodyFormat):
		msg = MIMEMultipart()
		msg['Subject'] = subject
		#msg['From'] = formataddr((str(Header('MyWebsite', 'utf-8')), 'from@mywebsite.com'))
		msg['From'] = sender
		msg['To'] = recipient
		#msg.preamble = body
		part = None
		if(bodyFormat == 'html'):
			part = MIMEText(body, 'html')
		else:
			part = MIMEText(body, 'plain')

		for f in attachFiles:
			ctype, encoding = mimetypes.guess_type(f)
			if ctype is None or encoding is not None:
				# No guess could be made, or the file is encoded (compressed), so
				# use a generic bag-of-bits type.
				ctype = 'application/octet-stream'
			maintype, subtype = ctype.split('/', 1)
			with open(f, 'rb') as fp:
				img = MIMEAudio(fp.read(), _subtype=subtype)
				msg.add_header('Content-Disposition', 'attachment', filename=os.path.basename(fp.name))
				fp.close()
			msg.attach(img)
		s = smtplib.SMTP('mail.server.com', 587)
		#s.connect('mail.server.com', 587) #this hangs on Debian, so I removed it.
		s.starttls()
		s.login('username', 'password')
		msg.attach(part)
		s.send_message(msg)
		s.quit()
#

def main():
	app = Recording()
	EmailSender = "test@mail.server.com"
	EmailRecipient = "test@gmail.com"
	EmailSubject = "Voice Recordings"
	
	bodyFormat = "html" #or "plain"
	body = """\
		<html>
		<head></head>
		<body>
			<p>Hi!<br>
			Here is new voice recordings!<br>
			%s<br>
			</p>
			<p>
			More automated communication features at <a href="%s">test.me</a>.
			</p>
		</body>
		</html>
	"""

	body = body % ("", 'https://test.me')
	#/uploads/recording.wav
	#gather recordings
	recordings = [f for f in listdir('/home/dev/breve/uploads') if isfile(join('/home/dev/breve/uploads', f))]
	i = 0
	for name in recordings:
		recordings[i] = "/home/dev/breve/uploads/" + name
		i += 1

	if(len(recordings) > 0):
		app.sendMultipartEmail(EmailSender, EmailRecipient, EmailSubject, recordings, body, bodyFormat)
		#delete the recordings
		print(recordings)
		for name1 in recordings:
			os.remove(name1)
	print("Sent and removed")
#

if __name__ == "__main__":
	main()

comments powered by Disqus