twilio_如何使用Twilio构建SMS约会提醒应用程序

twilio

本文由Marc TowlerBruno Mota进行同行评审。 感谢所有SitePoint的同行评审员使SitePoint内容达到最佳状态!

在本教程中,我们将使用Node.js构建SMS提醒应用程序。 我们将使用用户的Google日历获取约会,然后使用Twilio发送短信。

与以往一样,您可以从github repo中找到本教程中使用的代码。

设置事情

首先,您需要拥有一个Google帐户和一个Twilio帐户。 如果您还没有这些,可以继续注册。 这里是链接:

您无需担心Twilio,可以免费尝试。

Google控制台项目

拥有Google帐户后,请转到Google控制台并创建一个新应用。 默认情况下,Google控制台页面会向您显示您使用过的最新应用程序的仪表板。 但是,如果您还没有从事任何项目,它将显示以下内容:

Google控制台屏幕的屏幕截图

在这里,您可以单击右上角的“ select project菜单,然后选择“ create a project 。 这将打开一个模态窗口,允许您输入项目的标题。

新项目表单的屏幕截图

创建项目后,将显示仪表板。 您可以从那里单击use Google APIs ,搜索Google Calendar API并启用它。

Google Calendar API概述屏幕的屏幕截图

启用API后,它将要求您创建凭据。 单击Go to Credentials以开始设置。 这将显示以下内容:

API凭证屏幕的屏幕截图

单击Add credentials按钮,然后选择OAuth 2.0 client ID

这将要求您首先配置同意屏幕。 单击“ configure consent screen

输入Product name shown to usersProduct name shown to users文本字段的值,然后单击save

OAuth同意屏幕的屏幕截图

配置完成后,您现在可以创建客户端ID。 选择Web application作为应用程序类型,保留默认名称(如果需要),输入http://localhost:3000/login作为Authorized redirect URIs然后单击create

创建客户端ID表单的屏幕截图

免费学习PHP!

全面介绍PHP和MySQL,从而实现服务器端编程的飞跃。

原价$ 11.95 您的完全免费

这将打开一个显示客户端ID和客户端密码的模式。 请暂时注意这些内容,我们稍后将使用它们。

特威里奥

创建Twilio帐户后,请转到设置页面 ,并AuthTokenLive API CredentialsAccountSIDAuthToken的值。

接下来转到programmable voice dashboard 。 在这里您可以看到沙箱编号。 您可以使用此数字来测试twilio。 但是稍后您将需要购买电话号码,以便twilio发送的短信不会添加到“从twilio沙箱发送”中 。 Twilio沙盒编号的另一个限制是只能与经过验证的编号一起使用。 这意味着您必须向twilio注册电话号码才能向其发送消息。 您可以从“ manage caller IDs page执行此操作。

构建应用

现在我们准备构建该应用程序。 在继续之前,我想简要介绍一下我们将如何实现该应用程序。 将有三个主要文件:一个用于服务器,一个用于缓存Google日历中的事件,另一个用于提醒用户。 该服务器用于允许用户登录并获得访问令牌。 事件将保存在MySQL数据库中,全局应用程序配置将添加到.json文件中。 Node的cron实现将用于执行用于缓存事件和提醒用户的任务。

安装依赖项

在您的工作目录上,创建一个package.json文件并添加以下内容:

{
  "name": "google-calendar-twilio",
  "version": "0.0.1",
  "dependencies": {
    "config": "^1.17.1",
    "cron": "^1.1.0",
    "express": "^4.13.3",
    "googleapis": "^2.1.6",
    "moment": "^2.10.6",
    "moment-timezone": "^0.4.1",
    "mysql": "felixge/node-mysql",
    "twilio": "^2.6.0"
  }
}

在此文件中,我们指定应用程序所依赖的库的名称和版本。 以下是每个库的用法分类:

  • config –用于存储和检索全局应用程序配置。
  • cron –用于在一天的特定时间执行特定任务。 在此应用中,我们使用它来运行任务以缓存用户Google日历中的事件并发送文本提醒。
  • express – Node.js的事实上的Web框架。 我们正在使用它来提供登录页面。
  • googleapis – Google API的官方Node.js客户端。
  • moment –日期和时间库。 我们正在使用它来轻松格式化从Google Calendar API获得的日期。
  • moment-timezone –时区插件。 这将设置应用程序的默认时区。
  • mysql – Node.jsMySQL客户端。
  • twilio – Node.js的官方Twilio客户端。 这使我们可以发送文本提醒。

从终端执行npm install以安装所有依赖项。

数据库

如前所述,我们将为此应用程序使用MySQL数据库。 继续使用您选择的数据库管理工具创建一个新数据库。 然后,使用以下SQL转储文件创建表: appointment-notifier.sql

数据库中有两个表: usersappointmentsusers表用于存储用户数据。 对于此应用程序,我们将只存储一个用户,并且仅存储访问令牌。
appointments表用于存储我们从Google Calendar API获取的事件。 请注意,其中没有user_id字段,因为我们只有一个用户。 然后,我们将获取所有零行,作为notified字段的值。

应用程式设定

在您的工作目录上,创建一个config文件夹,然后在其中创建default.json文件。 这是我们放置全局应用程序配置的地方。 这包括时区,我们将向其发送提醒的电话号码,数据库,google app和Twilio设置。

这是模板,请务必填写所有字段。

{
    "app": {
        "timezone": "Asia/Manila"
    },

    "me": {
        "phone_number": ""
    },

    "db": {
        "host": "localhost",
        "user": "root",
        "password": "secret",
        "database": "calendar_notifier"
    },

    "google":{
        "client_id": "THE CLIENT ID OF YOUR GOOGLE APP",
        "client_secret": "THE CLIENT SECRET OF YOUR GOOGLE APP",
        "redirect_uri": "http://localhost:3000/login",
        "access_type": "offline",
        "scopes": [
            "https://www.googleapis.com/auth/plus.me",
            "https://www.googleapis.com/auth/calendar"
        ]
    },

    "twilio": {
        "sid": "YOUR TWILIO SID",
        "secret": "YOUR TWILIO SECRET",
        "phone_number": "+YOUR TWILIO PHONE NUMBER / SANDBOX NUMBER"
    }

}

共同文件

作为优秀的开发人员,我们需要尽可能避免代码重复。 这就是为什么我们需要将我前面提到的三个主要文件(服务器,缓存,通知)所需的代码放入单独的文件中。 创建一个common的工作目录文件夹。 这是我们要添加公用文件的地方。

数据库

common目录中创建一个db.js文件,然后添加以下内容:

var config = require('config');

var db_config = config.get('db');
var mysql = require('mysql');
var connection = mysql.createConnection({
    host: db_config.host,
    user: db_config.user,
    password: db_config.password,
    database: db_config.database
});

exports.db = connection;

这使用config库获取我们之前在config/default.json文件中添加的配置值。 具体来说,我们正在获取数据库配置,以便我们可以连接到数据库。 然后,我们将导出此模块,以便以后可以从另一个文件中使用它。

时间

time.js文件用于通过moment-timezone库设置默认时moment-timezone 。 我们还导出了时区的值,因为稍后将在运行两个cron任务(缓存事件和通知用户)时使用该值。

var config = require('config');
var app_timezone = config.get('app.timezone');
var moment = require('moment-timezone');
moment.tz.setDefault(app_timezone);

exports.config = {
    timezone: app_timezone
};

exports.moment = moment;
谷歌

google.js文件用于初始化Google客户端和OAuth2客户端。 为了初始化OAuth2客户端,我们需要传入客户端ID,客户端密码和我们先前在配置文件中添加的重定向URL。 然后,我们初始化Google日历服务。 最后,我们导出OAuth2客户端,日历和Google配置。

var config = require('config');
var google_config = config.get('google');

var google = require('googleapis');
var OAuth2 = google.auth.OAuth2;
var oauth2Client = new OAuth2(google_config.client_id, google_config.client_secret, google_config.redirect_uri);

var calendar = google.calendar('v3');

exports.oauth2Client = oauth2Client; 
exports.calendar = calendar;
exports.config = google_config;

创建服务器

现在我们准备在服务器上工作。 服务器负责获取访问令牌。 无需用户当前登录即可用于与Google Calendar API通话。首先创建一个server.js文件并添加以下内容:

var google = require('./common/google');
var connection = require('./common/db');

var express = require('express');
var app = express();

var server = app.listen(3000, function () {
  var host = server.address().address;
  var port = server.address().port;

  console.log('Example app listening at http://%s:%s', host, port);
});


function updateAccessToken(tokens, response){

    connection.db.query(
        "UPDATE users SET access_token = ? WHERE id = 1", 
        [JSON.stringify(tokens)],
        function(err, rows, fields){
            if(!err){
                console.log('updated!');
                response.send('connected!');
            }else{
                console.log('error updating table');
                console.log(err);
                response.send('error occured, please try again');
            }
        }
    );

}

app.get('/', function(req, res){
    var url = google.oauth2Client.generateAuthUrl({
      access_type: google.config.access_type,
      scope: google.config.scopes
    });

    res.send('<a href="' + url + '">login to google</a>');
});


app.get('/login', function(req, res){
    var code = req.query.code;
    console.log('login');

    google.oauth2Client.getToken(code, function(err, tokens){

      if(!err){
        console.log('tokens');
        console.log(tokens);

        updateAccessToken(tokens, res);

      }else{
        res.send('error getting token');
        console.log('error getting token');
      }
    });

});

分解:

首先,我们导入之前创建的googledb模块。

var google = require('./common/google');
var connection = require('./common/db');

创建在本地主机的端口3000上运行的Express服务器。 这就是为什么我们之前在应用程序配置和Google的重定向URI中添加了http://localhost:3000/login

var express = require('express');
var app = express();

var server = app.listen(3000, function () {
  var host = server.address().address;
  var port = server.address().port;

  console.log('Example app listening at http://%s:%s', host, port);
});

定义一个updateAccessToken函数。 这接受两个参数: tokensresponse 。 令牌是用户授予必要权限后从Google获得的访问令牌。 response是来自Express的响应对象。 我们将其传递给此函数,以便我们可以向用户发送响应。 在函数内部,我们更新第一行的access_token 。 如前所述,此应用仅适用于单个用户。 一旦access_token更新,我们将发送响应。

function updateAccessToken(tokens, response){

    connection.db.query(
        "UPDATE users SET access_token = ? WHERE id = 1", 
        [JSON.stringify(tokens)],
        function(err, rows, fields){
            if(!err){
                console.log('updated!');
                response.send('connected!');
            }else{
                console.log('error updating table');
                console.log(err);
                response.send('error occured, please try again');
            }
        }
    );

}

添加主页的路由。 当访问http://localhost:3000时,将执行此命令。 从这里我们生成身份验证URL。 这使用来自oauth2ClientgenerateAuthUrl方法。 它接受包含access_typescope的对象。 我们将从先前创建的应用程序配置文件中获取这些值。 最后,我们发送用户可以单击的实际链接。 注意,您应该始终在视图内执行此操作,但是为了简化操作,我们将直接返回链接。

app.get('/', function(req, res){
    var url = google.oauth2Client.generateAuthUrl({
      access_type: google.config.access_type,
      scope: google.config.scopes
    });

    res.send('<a href="' + url + '">login to google</a>');
});

添加登录路径。 这是在为用户赋予必要的权限后重定向用户的途径。 Google传递了一个名为code的查询参数。 我们通过请求中的query对象来获取它。 然后,我们调用getToken方法并将code作为参数传递。 这将为我们提供访问令牌。 因此,我们调用updateAccessToken函数将其保存到数据库中。

app.get('/login', function(req, res){
    var code = req.query.code;
    console.log('login');

    google.oauth2Client.getToken(code, function(err, tokens){

      if(!err){
        console.log('tokens');
        console.log(tokens);

        updateAccessToken(tokens, res);

      }else{
        res.send('error getting token');
        console.log('error getting token');
      }
    });

});

创建Cacher

缓存器负责将用户约会保存到数据库中。 这样可以避免我们每次发送提醒时都必须查询Google Calendar API目录。 创建一个cache.js文件并添加以下内容:

var google = require('./common/google');
var connection = require('./common/db');
var time = require('./common/time');

var CronJob = require('cron').CronJob;


function addAppointment(event_id, summary, start, end){

    connection.db.query(
        "INSERT INTO appointments SET id = ?, summary = ?, datetime_start = ?, datetime_end = ?, notified = 0",
        [event_id, summary, start, end],
        function(err, rows, fields){
            if(!err){
                console.log('added!');
            }else{
                console.log('error adding to table');
            }
        }
    );

}

function getEvents(err, response){

    console.log('response');
    console.log(response);
    if(err){
      console.log('The API returned an error: ' + err);
    }

    var events = response.items;

    if(events.length == 0){
        console.log('No upcoming events found.');
    }else{
      console.log('Upcoming 10 events:');
      for(var i = 0; i < events.length; i++){
        var event = events[i];
        var event_id = event.id;
        var summary = event.summary;
        var start = event.start.dateTime || event.start.date;
        var end = event.end.dateTime || event.end.date;

        addAppointment(event_id, summary, start, end);

      }

    }

}

function cache(){

    var current_datetime = time.moment().toISOString();

    google.calendar.events.list({
        auth: google.oauth2Client,
        calendarId: 'primary',
        timeMin: current_datetime,
        maxResults: 10,
        singleEvents: true,
        orderBy: 'startTime'
    }, getEvents);      

}

connection.db.query('SELECT access_token FROM users WHERE id = 1', function(error, results, fields){
    if(!error){
        var tokens = JSON.parse(results[0].access_token);

        google.oauth2Client.setCredentials({
          'access_token': tokens.access_token,
          'refresh_token': tokens.refresh_token
        });

        new CronJob('0 0 * * *', cache, null, true, time.config.timezone);
        //cache(); //for testing
    }
});

分解:

首先,我们导入所需的所有模块。

var google = require('./common/google');
var connection = require('./common/db');
var time = require('./common/time');

var CronJob = require('cron').CronJob;

addAppointment函数负责将约会保存到appointments表中。 这接受约会的event_idsummarystartend日期时间。 event_id基本上是Google日历中特定约会的ID。 我们将其用作主键的值,这意味着重复项不会插入到appointments表中。 这里缺少的是比较数据库中已有的约会和API返回的约会的方法。 如果由于某种原因约会的时间表发生了变化,那么数据库将不会被更新,因为我们在这里所做的就是将其插入到表中。 我将其留给您的待办事项清单。

function addAppointment(event_id, summary, start, end){

    connection.db.query(
        "INSERT INTO appointments SET id = ?, summary = ?, datetime_start = ?, datetime_end = ?, notified = 0",
        [event_id, summary, start, end],
        function(err, rows, fields){
            if(!err){
                console.log('added!');
            }else{
                console.log('error adding to table');
            }
        }
    );

}

getEvents函数负责遍历API返回的所有约会。 这使用addAppointment方法保存循环的每次迭代的约会。

function getEvents(err, response){

    console.log('response');
    console.log(response);
    if(err){
      console.log('The API returned an error: ' + err);
    }

    var events = response.items;

    if(events.length == 0){
        console.log('No upcoming events found.');
    }else{

      for(var i = 0; i < events.length; i++){
        var event = events[i];
        var event_id = event.id;
        var summary = event.summary;
        var start = event.start.dateTime || event.start.date;
        var end = event.end.dateTime || event.end.date;

        addAppointment(event_id, summary, start, end);

      }

    }

}

cache方法是实际调用Google Calendar API的方法。 这是通过使用Google客户端来实现的。 在这里,我们在calendar.events对象上调用list方法。 它接受两个参数:第一个是包含查询选项的对象,第二个是返回结果后要执行的函数。

function cache(){

    var current_datetime = time.moment().toISOString();

    google.calendar.events.list({
        auth: google.oauth2Client,
        calendarId: 'primary',
        timeMin: current_datetime,
        maxResults: 10,
        singleEvents: true,
        orderBy: 'startTime'
    }, getEvents);      

}

在包含选项的对象中,我们具有以下内容:

  • auth –这是oauth2Client 。 这用于验证请求。
  • calendarId –我们将在其中获取约会的日历的ID。 在这种情况下,我们使用的是主日历。 Google日历实际上允许您创建许多日历。 其他人也可以与您共享日历。 每个日历都有自己的ID。 这就是我们在此处指定的内容。 如果您有兴趣访问其他日历,请务必查看Calendars上API文档
  • timeMin –查询中要使用的基本日期时间。 在这种情况下,我们使用的是当前日期时间。 因为谁想知道过去发生的事件? 请注意,尽管这使用ISO 8601标准来表示时间。 值得庆幸的是,现在有一个名为toISOString的方法可以用来获取该方法。
  • maxResults –您要返回的结果总数。
  • singleEvents –允许您指定是否仅返回单个一次性事件。 在这里,我们使用true表示重复事件不会被返回。
  • orderBy –允许您指定返回结果的顺序。 在这种情况下,我们使用了startTime ,它根据结果的开始时间以升序对结果进行排序。 仅当singleEvents选项设置为true singleEvents选项才可用。

所有这些选项以及许多其他选项都可以在“ Events: list文档中找到。

从数据库获取access_token并将其用于设置oauth2Client客户端的凭据。 完成后,创建一个新的cron作业,它将每天在午夜12点运行cache方法。

connection.db.query('SELECT access_token FROM users WHERE id = 1', function(error, results, fields){
    if(!error){
        var tokens = JSON.parse(results[0].access_token);

        google.oauth2Client.setCredentials({
          'access_token': tokens.access_token,
          'refresh_token': tokens.refresh_token
        });

        new CronJob('0 0 * * *', cache, null, true, time.config.timezone);
        //cache(); //for testing
    }
});

创建通知程序

最后但并非最不重要的一点是,我们有通知程序( notify.js )。 这负责从数据库中获取约会并确定约会是否成熟。 如果是,那么我们将其发送。

var config = require('config');

var twilio_config = config.get('twilio');
var twilio = require('twilio')(twilio_config.sid, twilio_config.secret);

var connection = require('./common/db');    
var time = require('./common/time');

var CronJob = require('cron').CronJob;

function updateAppointment(id){

    //update appointment to notified=1
    connection.db.query(
        "UPDATE appointments SET notified = 1 WHERE id = ?", 
        [id], 
        function(error, results, fields){
            if(!error){
                console.log('updated appointment with ID of ' + id);
            }
        }
    );
}

function sendNotifications(error, results, fields){

    var phone_number = config.get('me.phone_number');
    console.log(phone_number);

    console.log('results');
    console.log(results);

    if(!error){ 
        for(var x in results){

            var id = results[x].id;
            var datetime_start = results[x].datetime_start;
            var datetime_end = results[x].datetime_end;

            var appointment_start = time.moment(datetime_start);
            var summary = results[x].summary + " is fast approaching on " + appointment_start.format('MMM DD, YYYY hh:mm a'); 

            var hour_diff = appointment_start.diff(time.moment(), 'hours');

            console.log('hour diff:');
            console.log(hour_diff);

            if(hour_diff <= 24){

                twilio.sendMessage({
                    to: phone_number, 
                    from: twilio_config.phone_number,
                    body: summary
                }, function(err, responseData){ 

                    if(!err){ 
                        console.log('message sent!');
                        console.log(responseData.from); 
                        console.log(responseData.body); 
                    }else{
                        console.log('error:');
                        console.log(err);
                    }
                });

                updateAppointment(id);
            }


        }
    }

}


function startTask(){

    connection.db.query('SELECT * FROM appointments WHERE notified = 0', sendNotifications);

}

new CronJob('0 12 * * *', startTask, null, true, time.config.timezone);

分解:

导入所有必需的模块。

var config = require('config');

var twilio_config = config.get('twilio');
var twilio = require('twilio')(twilio_config.sid, twilio_config.secret);

var connection = require('./common/db');    
var time = require('./common/time');

var CronJob = require('cron').CronJob;

创建一个updateAppointment函数。 这接受约会的ID作为其参数。 它所做的全部工作就是将“ notified字段的值设置为1,这意味着特定约会的通知已经发送。

function updateAppointment(id){

    //update appointment to notified=1
    connection.db.query(
        "UPDATE appointments SET notified = 1 WHERE id = ?", 
        [id], 
        function(error, results, fields){
            if(!error){
                console.log('updated appointment with ID of ' + id);
            }
        }
    );
}

接下来,我们有sendNotifications函数。 这负责与Twilio实际发送文本提醒。 从数据库中获取约会后,将调用此函数。 这就是为什么将errorresultsfields参数传递给它的原因。 该error包含来自数据库的任何错误。 results包含从数据库返回的行。 并且这些fields包含有关返回结果字段的信息。

function sendNotifications(error, results, fields){

    var phone_number = config.get('me.phone_number');
    console.log(phone_number);

    console.log('results');
    console.log(results);

    if(!error){ 
        for(var x in results){

            var id = results[x].id;
            var datetime_start = results[x].datetime_start;
            var datetime_end = results[x].datetime_end;

            var appointment_start = time.moment(datetime_start);
            var summary = results[x].summary + " is fast approaching on " + appointment_start.format('MMM DD, YYYY hh:mm a'); 

            var hour_diff = appointment_start.diff(time.moment(), 'hours');

            console.log('hour diff:');
            console.log(hour_diff);

            if(hour_diff <= 24){

                twilio.sendMessage({
                    to: phone_number, 
                    from: twilio_config.phone_number,
                    body: summary
                }, function(err, responseData){ 

                    if(!err){ 
                        console.log('message sent!');
                        console.log(responseData.from); 
                        console.log(responseData.body); 
                        updateAppointment(id);
                    }else{
                        console.log('error:');
                        console.log(err);
                    }
                });

            }


        }
    }

}

在该函数内部,我们从应用程序配置中获取用户的电话号码。

var phone_number = config.get('me.phone_number');
console.log(phone_number);

检查是否有任何错误,是否没有错误,然后继续遍历返回的所有结果。

if(!error){ 
    for(var x in results){
        ...
    }
}

在循环内部,我们提取所需的所有值并构造要发送的实际消息。 我们还将获得当前时间与约会开始时间之间的小时差。 我们检查时差是否小于或等于24小时。

var id = results[x].id;
var datetime_start = results[x].datetime_start;
var datetime_end = results[x].datetime_end;

var appointment_start = time.moment(datetime_start);
var summary = results[x].summary + " is fast approaching on " + appointment_start.format('MMM DD, YYYY hh:mm a'); 

var hour_diff = appointment_start.diff(time.moment(), 'hours');

console.log('hour diff:');
console.log(hour_diff);

if(hour_diff <= 24){
    ...
}

如果少于或等于24小时,我们将发送通知。 这是通过使用Twilio客户端来完成的。 我们呼吁sendMessage ,并通过在包含对象to (用户电话号码), from (Twilio的sandobox号码或电话号码,你从Twilio买),以及body包含文本消息。 如果没有返回任何错误,则假定通知已发送。 所以我们称之为updateAppointment功能所设置的notified字段设置为1,所以它不会在下一次选择的任务运行。

twilio.sendMessage({
    to: phone_number, 
    from: twilio_config.phone_number,
    body: summary
}, function(err, responseData){ 

    if(!err){ 
        console.log('message sent!');
        console.log(responseData.from); 
        console.log(responseData.body); 

        updateAppointment(id);
    }else{
        console.log('error:');
        console.log(err);
    }
});

最后,我们有了startTask方法。 它所做的就是从appointments表中选择所有尚未发送通知的appointments 。 该函数每中午12点和下午6点执行一次。

function startTask(){

    connection.db.query('SELECT * FROM appointments WHERE notified = 0', sendNotifications);

}

new CronJob('0 12,18 * * *', startTask, null, true, time.config.timezone);

结论

而已! 在本教程中,您学习了如何使用Twilio创建SMS提醒应用程序。 具体来说,我们已经研究了如何通过Google Calendar API获取用户的约会。 我们已经将它们保存在数据库中,并通过Twilio通知了用户。 您可以从github repo中找到本教程中使用的代码。

翻译自: https://www.sitepoint.com/build-sms-appointment-reminder-app-with-twilio/

twilio

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值