Refactor server and lib into separate folders.
This commit is contained in:
231
server/app.js
Normal file
231
server/app.js
Normal file
@@ -0,0 +1,231 @@
|
||||
var express = require('express')
|
||||
, app = express()
|
||||
, _ = require('underscore')
|
||||
, fs = require('fs')
|
||||
, mixpanel = require('mixpanel')
|
||||
, exec = require('child_process').exec
|
||||
, spawn = require('child_process').spawn
|
||||
, Stream = require('stream')
|
||||
, providers = require('../lib/providers.js')
|
||||
, redis = require('redis-url').connect()
|
||||
|
||||
// Optional modules
|
||||
var banned_numbers;
|
||||
try {
|
||||
banned_numbers = require('./banned_numbers.js')
|
||||
} catch(e) {
|
||||
banned_numbers = {BLACKLIST: {}};
|
||||
}
|
||||
|
||||
var mpq;
|
||||
try {
|
||||
mixpanel_config = require('./mixpanel_config.js')
|
||||
mpq = new mixpanel.Client(mixpanel_config.api_key);
|
||||
} catch(e) {
|
||||
mpq = {track: function() {}};
|
||||
}
|
||||
|
||||
var access_keys;
|
||||
try {
|
||||
// Optionally, you may specify special access keys in a keys.json file.
|
||||
// These access keys are not rate-limited.
|
||||
// See example_keys.json for format.
|
||||
access_keys = require('./keys.json');
|
||||
} catch (e) {
|
||||
access_keys = {};
|
||||
}
|
||||
|
||||
// Express config
|
||||
app.set('views', __dirname + '/views');
|
||||
app.set('view engine', 'jade');
|
||||
|
||||
app.use(express.cookieParser());
|
||||
app.use(express.static(__dirname + '/public'));
|
||||
app.use(express.bodyParser());
|
||||
|
||||
// App routes
|
||||
app.get('/', function(req, res) {
|
||||
fs.readFile(__dirname + '/views/index.html', 'utf8', function(err, text){
|
||||
res.send(text);
|
||||
});
|
||||
});
|
||||
|
||||
app.get('/providers/:region', function(req, res) {
|
||||
// Utility function, just to check the providers currently loaded
|
||||
res.send(providers[req.params.region]);
|
||||
});
|
||||
|
||||
app.post('/text', function(req, res) {
|
||||
var number = stripPhone(req.body.number);
|
||||
if (number.length < 9 || number.length > 10) {
|
||||
res.send({success:false, message:'Invalid phone number.'});
|
||||
return;
|
||||
}
|
||||
textRequestHandler(req, res, number, 'us', req.query.key);
|
||||
});
|
||||
|
||||
app.post('/canada', function(req, res) {
|
||||
textRequestHandler(req, res, stripPhone(req.body.number), 'canada', req.query.key);
|
||||
});
|
||||
|
||||
app.post('/intl', function(req, res) {
|
||||
textRequestHandler(req, res, stripPhone(req.body.number), 'intl', req.query.key);
|
||||
});
|
||||
|
||||
// App helper functions
|
||||
|
||||
function textRequestHandler(req, res, number, region, key) {
|
||||
if (!number || !req.body.message) {
|
||||
mpq.track('incomplete request');
|
||||
res.send({success:false, message:'Number and message parameters are required.'});
|
||||
return;
|
||||
}
|
||||
if (banned_numbers.BLACKLIST[number]) {
|
||||
mpq.track('banned number');
|
||||
res.send({success:false,message:'Sorry, texts to this number are disabled.'});
|
||||
return;
|
||||
}
|
||||
var ip = req.header('X-Real-IP') || req.connection.remoteAddress;
|
||||
|
||||
var message = req.body.message;
|
||||
if (message.indexOf(':') > -1) {
|
||||
// Handle problem with vtext where message would not get sent properly if it
|
||||
// contains a colon
|
||||
message = ' ' + message;
|
||||
}
|
||||
|
||||
var tracking_details = {
|
||||
number: number,
|
||||
message: req.body.message,
|
||||
ip: ip
|
||||
};
|
||||
|
||||
var doSendText = function(response_obj) {
|
||||
response_obj = response_obj || {};
|
||||
|
||||
// Time to actually send the message
|
||||
sendText(number, message, region, function(err) {
|
||||
if (err) {
|
||||
mpq.track('sendText failed', tracking_details);
|
||||
res.send(_.extend(response_obj,
|
||||
{
|
||||
success:false,
|
||||
message:'Communication with SMS gateway failed.'
|
||||
}));
|
||||
}
|
||||
else {
|
||||
mpq.track('sendText success', tracking_details);
|
||||
res.send(_.extend(response_obj, {success:true}));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// Do they have a valid access key?
|
||||
if (key && key in access_keys) {
|
||||
console.log('Got valid key', key, '... not applying limits.');
|
||||
// Skip verification
|
||||
mpq.track('sendText skipping verification', _.extend(tracking_details, {
|
||||
key: key,
|
||||
}));
|
||||
doSendText({used_key: key});
|
||||
return;
|
||||
}
|
||||
|
||||
// If they don't have a special key, apply rate limiting and verification
|
||||
var ipkey = 'textbelt:ip:' + ip + '_' + dateStr();
|
||||
var phonekey = 'textbelt:phone:' + number;
|
||||
|
||||
redis.incr(phonekey, function(err, num) {
|
||||
if (err) {
|
||||
mpq.track('redis fail');
|
||||
res.send({success:false, message:'Could not validate phone# quota.'});
|
||||
return;
|
||||
}
|
||||
|
||||
setTimeout(function() {
|
||||
redis.decr(phonekey, function(err, num) {
|
||||
if (err) {
|
||||
mpq.track('failed to decr phone quota', {number: number});
|
||||
console.log('*** WARNING failed to decr ' + number);
|
||||
}
|
||||
});
|
||||
}, 1000*60*3);
|
||||
if (num > 3) {
|
||||
mpq.track('exceeded phone quota');
|
||||
res.send({success:false, message:'Exceeded quota for this phone number. ' + number});
|
||||
return;
|
||||
}
|
||||
|
||||
// now check against ip quota
|
||||
redis.incr(ipkey, function(err, num) {
|
||||
if (err) {
|
||||
mpq.track('redis fail');
|
||||
res.send({success:false, message:'Could not validate IP quota.'});
|
||||
return;
|
||||
}
|
||||
if (num > 75) {
|
||||
mpq.track('exceeded ip quota');
|
||||
res.send({success:false, message:'Exceeded quota for this IP address. ' + ip});
|
||||
return;
|
||||
}
|
||||
setTimeout(function() {
|
||||
redis.decr(ipkey, function(err, num) {
|
||||
if (err) {
|
||||
mpq.track('failed to decr ip key', {ipkey: ipkey});
|
||||
console.log('*** WARNING failed to decr ' + ipkey);
|
||||
}
|
||||
});
|
||||
}, 1000*60*60*24);
|
||||
|
||||
// Cleared to send now
|
||||
doSendText();
|
||||
}); // end redis ipkey incr
|
||||
}); // end redis phonekey incr
|
||||
} // end textRequestHandler
|
||||
|
||||
function dateStr() {
|
||||
var today = new Date();
|
||||
var dd = today.getDate();
|
||||
var mm = today.getMonth()+1;
|
||||
var yyyy = today.getFullYear();
|
||||
return mm + '/' + dd + '/' + yyyy;
|
||||
}
|
||||
|
||||
function stripPhone(phone) {
|
||||
return (phone+'').replace(/\D/g, '');
|
||||
}
|
||||
|
||||
function sendText(phone, message, region, cb) {
|
||||
console.log('txting phone', phone, ':', message);
|
||||
|
||||
region = region || 'us';
|
||||
|
||||
var providers_list = providers[region];
|
||||
|
||||
var done = _.after(providers_list.length, function() {
|
||||
cb(false);
|
||||
});
|
||||
|
||||
_.each(providers_list, function(provider) {
|
||||
var email = provider.replace('%s', phone);
|
||||
email = 'Subject: Text\r\n\r\n' + email;
|
||||
var child = spawn('sendmail', ['-f', 'txt2@textbelt.com', email]);
|
||||
child.stdout.on('data', console.log);
|
||||
child.stderr.on('data', console.log);
|
||||
child.on('error', function(data) {
|
||||
mpq.track('sendmail failed', {email: email, data: data});
|
||||
done();
|
||||
});
|
||||
child.on('exit', function(code, signal) {
|
||||
done();
|
||||
});
|
||||
child.stdin.write(message + '\n.');
|
||||
child.stdin.end();
|
||||
});
|
||||
}
|
||||
|
||||
// Start server
|
||||
var port = process.env.PORT || 9090;
|
||||
app.listen(port, function() {
|
||||
console.log('Listening on', port);
|
||||
});
|
8
server/banned_numbers_example.js
Normal file
8
server/banned_numbers_example.js
Normal file
@@ -0,0 +1,8 @@
|
||||
// People are added to this list if they contact Textbelt when someone is
|
||||
// abusing the service.
|
||||
|
||||
exports = module.exports = {
|
||||
BLACKLIST: {
|
||||
"9145555555": true,
|
||||
}
|
||||
}
|
3
server/keys_example.json
Normal file
3
server/keys_example.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"specialkey": -1
|
||||
}
|
3
server/mixpanel_config_example.js
Normal file
3
server/mixpanel_config_example.js
Normal file
@@ -0,0 +1,3 @@
|
||||
exports = module.exports = {
|
||||
api_key: 'abc123123',
|
||||
}
|
110
server/views/index.html
Normal file
110
server/views/index.html
Normal file
@@ -0,0 +1,110 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-type" content="text/html;charset=UTF-8">
|
||||
<title>TextBelt - Free Texting API - No Ads</title>
|
||||
<link href="http://ianww.com/bootstrap.min.css" rel="stylesheet" />
|
||||
<link href="http://ianww.com/main.css" rel="stylesheet" />
|
||||
<style>
|
||||
body {
|
||||
background-image: none;
|
||||
background-color: #2C3A50;
|
||||
}
|
||||
.mute {
|
||||
color: #909090;
|
||||
font-size: 10px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div>
|
||||
<div class="content drop-shadow lifted">
|
||||
<div class="name_container">
|
||||
<h1>TextBelt</h1>
|
||||
<h5 style="margin-left:2px">A free, open source API for outgoing texts.</h5>
|
||||
<p style="margin-left:2px; ">Maintained by <a href="http://ianww.com">Ian Webster</a>. Open source on <a href="http://github.com/typpo/textbelt">github</a>.</p>
|
||||
<div class="wrapper" style="float:right; margin-top:-33px">
|
||||
<div id="title">
|
||||
<p>
|
||||
<span class='st_facebook'></span>
|
||||
<span class='st_twitter'></span>
|
||||
<span class='st_email'></span>
|
||||
</p>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p>TextBelt is an outgoing SMS API that uses carrier-specific gateways to deliver your text messages for free, and without ads. The service is fairly reliable and has sent over 100,000 texts.</p>
|
||||
|
||||
<p>Send a text with a simple POST request:</p>
|
||||
|
||||
<pre><code>$ curl -X POST <a href="http://textbelt.com/text">http://textbelt.com/text</a> \
|
||||
-d number=5551234567 \
|
||||
-d "message=I sent this message for free with textbelt.com"
|
||||
</code></pre>
|
||||
|
||||
<p> <code>number</code> and <code>message</code> parameters are required.</p>
|
||||
|
||||
<h3>Success and Failure</h3>
|
||||
|
||||
<p>Sample success:</p>
|
||||
|
||||
<pre><code>{"success":true}
|
||||
</code></pre>
|
||||
|
||||
<p>Sample failure:</p>
|
||||
|
||||
<pre><code>{"success":false,"message":"Exceeded quota for this phone number."}
|
||||
</code></pre>
|
||||
|
||||
<h3>Canadian and International endpoints</h3>
|
||||
|
||||
<p>
|
||||
The /text endpoint supports U.S. phone numbers (and parts of Canada).
|
||||
</p>
|
||||
|
||||
<p>
|
||||
For Canadian texts, curl <pre><code>http://textbelt.com/canada</pre></code>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
For international texts, curl <pre><code>http://textbelt.com/intl</pre></code>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Canadian and international support may not be complete. Please refer to the list of supported carriers.
|
||||
</p>
|
||||
|
||||
<h3>Notes and Limitations</h3>
|
||||
|
||||
<ul>
|
||||
<li><p>IP addresses are limited to 75 texts per day. Phone numbers are limited to 3 texts every 3 minutes. To report abuse or request increased limits, please contact ianw_textbelt at ianww.com.</p></li>
|
||||
<li><p>Some carriers may deliver text messages from "txt@textbelt.com"</p></li>
|
||||
<li><p>Supported U.S. carriers: <span class="mute">Alltel, Ameritech, AT&T Wireless, Boost, CellularOne, Cingular, Edge Wireless, Sprint PCS, Telus Mobility, T-Mobile, Metro PCS, Nextel, O2, Orange, Qwest, Rogers Wireless, US Cellular, Verizon, Virgin Mobile.</span></p></li>
|
||||
<li><p>Supported U.S. and Canadian carriers (/canada): <span class="mute">3 River Wireless, ACS Wireless, AT&T, Alltel, BPL Mobile, Bell Canada, Bell Mobility, Bell Mobility (Canada), Blue Sky Frog, Bluegrass Cellular, Boost Mobile, Carolina West Wireless, Cellular One, Cellular South, Centennial Wireless, CenturyTel, Cingular (Now AT&T), Clearnet, Comcast, Corr Wireless Communications, Dobson, Edge Wireless, Fido, Golden Telecom, Helio, Houston Cellular, Idea Cellular, Illinois Valley Cellular, Inland Cellular Telephone, MCI, MTS, Metro PCS, Metrocall, Metrocall 2-way, Microcell, Midwest Wireless, Mobilcomm, Nextel, OnlineBeep, PCS One, President's Choice, Public Service Cellular, Qwest, Rogers AT&T Wireless, Rogers Canada, Satellink, Solo Mobile, Southwestern Bell, Sprint, Sumcom, Surewest Communicaitons, T-Mobile, Telus, Tracfone, Triton, US Cellular, US West, Unicel, Verizon, Virgin Mobile, Virgin Mobile Canada, West Central Wireless, Western Wireless</span></p></li>
|
||||
<li><p>Supported international carriers (/intl): <span class="mute">Chennai RPG Cellular, Chennai Skycell / Airtel, Comviq, DT T-Mobile, Delhi Aritel, Delhi Hutch, Dutchtone / Orange-NL, EMT, Escotel, German T-Mobile, Goa BPLMobil, Golden Telecom, Gujarat Celforce, Iusacell Mexico, JSM Tele-Page, Kerala Escotel, Kolkata Airtel, Kyivstar, LMT, Lauttamus Communication, Maharashtra BPL Mobile, Maharashtra Idea Cellular, Manitoba Telecom Systems, Meteor, MiWorld, Mobileone, Mobilfone, Mobility Bermuda, Mobistar Belgium, Mobitel Tanzania, Mobtel Srbija, Movistar, Mumbai BPL Mobile, Netcom, Nextel Mexico, Ntelos, O2, O2 (M-mail), One Connect Austria, OnlineBeep, Optus Mobile, Orange, Orange Mumbai, Orange NL / Dutchtone, Oskar, P&T Luxembourg, Personal Communication, Pondicherry BPL Mobile, Primtel, SCS-900, SFR France, Safaricom, Satelindo GSM, Simple Freedom, Smart Telecom, Southern LINC, Sunrise Mobile, Surewest Communications, Swisscom, Telcel Mexico, T-Mobile Austria, T-Mobile Germany, T-Mobile UK, TIM, TSR Wireless, Tamil Nadu BPL Mobile, Tele2 Latvia, Telefonica Movistar, Telenor, Teletouch, Telia Denmark, UMC, Uraltel, Uttar Pradesh Escotel, Vessotel, Vodafone Italy, Vodafone Japan, Vodafone UK, Wyndtell</span></p></li>
|
||||
</ul>
|
||||
|
||||
<h3>About</h3>
|
||||
|
||||
<p>
|
||||
This project is maintained by <a href="http://www.ianww.com/">Ian Webster</a> and available on <a href="http://github.com/typpo/textbelt">Github</a>.
|
||||
<div>
|
||||
<a href="https://mixpanel.com/f/partner"><img src="https://mixpanel.com/site_media/images/partner/badge_blue.png" alt="Mobile and Web Analytics" /></a>
|
||||
</div>
|
||||
</p>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
|
||||
</div></div>
|
||||
<!-- start Mixpanel --><script type="text/javascript">(function(d,c){var a,b,g,e;a=d.createElement("script");a.type="text/javascript";a.async=!0;a.src=("https:"===d.location.protocol?"https:":"http:")+'//api.mixpanel.com/site_media/js/api/mixpanel.2.js';b=d.getElementsByTagName("script")[0];b.parentNode.insertBefore(a,b);c._i=[];c.init=function(a,d,f){var b=c;"undefined"!==typeof f?b=c[f]=[]:f="mixpanel";g="disable track track_pageview track_links track_forms register register_once unregister identify name_tag set_config".split(" ");
|
||||
for(e=0;e<g.length;e++)(function(a){b[a]=function(){b.push([a].concat(Array.prototype.slice.call(arguments,0)))}})(g[e]);c._i.push([a,d,f])};window.mixpanel=c})(document,[]);
|
||||
mixpanel.init("6e6e6b71ed5ada4504c52d915388d73d");</script><!-- end Mixpanel -->
|
||||
<script>mixpanel.track('main');</script>
|
||||
<script type="text/javascript">var switchTo5x=true;</script>
|
||||
<script type="text/javascript" src="http://w.sharethis.com/button/buttons.js"></script>
|
||||
<script type="text/javascript">stLight.options({publisher: "ur-e52063a3-f5a7-2078-bc73-7c684f5a6cdf"}); </script>
|
||||
</body>
|
||||
</html>
|
Reference in New Issue
Block a user