├── .gitignore ├── main.js └── tests ├── basiclistener.js ├── emailer.js ├── imageresize.js ├── mimetypes.js └── smtp.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | var request = require('request') 2 | , events = require('events') 3 | , querystring = require('querystring') 4 | ; 5 | 6 | var createCouchDBEmitter = function (uri) { 7 | if (uri[uri.length - 1] !== '/') uri += '/' 8 | var changesStream = new events.EventEmitter(); 9 | changesStream.since = 0; 10 | 11 | changesStream.on('change', function (c) { 12 | if (c.seq) changesStream.since = c.seq; 13 | if (c.last_seq) changesStream.since = c.last_seq; 14 | }) 15 | 16 | changesStream.buffer = ''; 17 | changesStream.writable = true; 18 | changesStream.write = function (chunk) { 19 | 20 | var line 21 | , change 22 | ; 23 | changesStream.buffer += chunk.toString(); 24 | 25 | while (changesStream.buffer.indexOf('\n') !== -1) { 26 | line = changesStream.buffer.slice(0, changesStream.buffer.indexOf('\n')); 27 | if (line.length > 1) { 28 | change = JSON.parse(line); 29 | if (change.last_seq) changesStream.since = change.last_seq; 30 | else changesStream.emit('change', change); 31 | } 32 | changesStream.buffer = changesStream.buffer.slice(changesStream.buffer.indexOf('\n') + 1) 33 | } 34 | }; 35 | changesStream.end = function () {}; 36 | 37 | var connect = function () { 38 | var qs = querystring.stringify( 39 | {include_docs: "true" 40 | , heartbeat: 5 * 1000 41 | , feed: 'continuous' 42 | , since: changesStream.since 43 | }) 44 | var r = request( 45 | { uri: uri+'_changes?'+qs 46 | , headers: {'content-type':'application/json', connection:'keep-alive'} 47 | }) 48 | r.pipe(changesStream); 49 | r.on('end', function () { 50 | connect(); 51 | changesStream.emit('close'); 52 | }) 53 | } 54 | connect(); 55 | 56 | // request({ uri: uri+'_changes' 57 | // , headers: {'content-type':'application/json'} 58 | // }, function (err, resp, body) { 59 | // if (resp.statusCode !== 200) throw new Error('Request did not return 200.\n'+body.buffer); 60 | // changesStream.since = JSON.parse(body).last_seq; 61 | // connect(); 62 | // }) 63 | 64 | return changesStream; 65 | } 66 | 67 | exports.createCouchDBEmitter = createCouchDBEmitter; -------------------------------------------------------------------------------- /tests/basiclistener.js: -------------------------------------------------------------------------------- 1 | var dbemitter = require('../main') 2 | , request = require('request') 3 | , child_process = require('child_process') 4 | ; 5 | 6 | var db = 'http://mikeal.couchone.com/hoodies' 7 | , h = {'content-type':'application/json', 'accept':'application/json'} 8 | ; 9 | 10 | var emitter = dbemitter.createCouchDBEmitter(db); 11 | 12 | emitter.on('change', function (change) { 13 | var doc = change.doc; 14 | console.log(JSON.stringify(change.seq)); 15 | }) -------------------------------------------------------------------------------- /tests/emailer.js: -------------------------------------------------------------------------------- 1 | var dbemitter = require('../main') 2 | , request = require('request') 3 | , child_process = require('child_process') 4 | ; 5 | 6 | var db = 'http://localhost:5984/testemailer' 7 | , h = {'content-type':'application/json', 'accept':'application/json'} 8 | ; 9 | 10 | // to:recipient@somewhere.com 11 | // from:you@yourdomain.com 12 | // subject:Testing 123 13 | // 14 | // This is my message. 15 | 16 | var sendEmail = function (from, to, subject, body, callback) { 17 | var text = 18 | 'to:' + to + '\n' + 19 | 'from:' + from + '\n' + 20 | 'subject:' + subject + '\n' + 21 | '\n' + 22 | body + 23 | '\r\n\r\n' 24 | ; 25 | var sendmail = child_process.spawn('sendmail', ['-v', '-q', '-t']) 26 | , stdout = '' 27 | , stderr = '' 28 | ; 29 | 30 | sendmail.stdout.on('data', function (chunk) {stdout += chunk}); 31 | sendmail.stderr.on('data', function (chunk) {stderr += chunk}); 32 | 33 | sendmail.stdin.write(text) 34 | sendmail.stdin.end(); 35 | sendmail.on('exit', function (code) {callback(code, stdout, stderr)}); 36 | } 37 | 38 | // sendEmail('mikeal.rogers@gmail.com', 'mikeal.rogers@gmail.com', 'Test', "Test Body", function (code, stdout, stderr) { 39 | // console.log(code); console.log(stdout); console.log(stderr); 40 | // }); 41 | 42 | var c = dbemitter.createCouchDBEmitter(db) 43 | c.on('change', function (change) { 44 | console.log('change '+change.seq) 45 | var doc = change.doc; 46 | if (doc.type === 'email' && doc.status === 'pending') { 47 | doc.status = 'sending'; 48 | doc.statusChangeTimestamp = new Date(); 49 | request({uri:db, body:JSON.stringify(doc), headers:h, method:'POST'}, function (err, resp, body) { 50 | if (err) throw err; 51 | if (resp.statusCode !== 201) throw new Error('Could not update document.'); 52 | doc._rev = JSON.parse(body).rev; 53 | console.log('sending email...') 54 | sendEmail(doc.from, doc.to, doc.subject, doc.body, function (code, stdout, stderr) { 55 | if (code === 0) { 56 | doc.status = 'sent' 57 | doc.result = {code:code, stdout:stdout, stderr:stderr} 58 | } else { 59 | doc.status = 'error'; 60 | doc.error = {code:code, stdout:stdout, stderr:stderr} 61 | } 62 | request({uri:db, body:JSON.stringify(doc), headers:h, method:'POST'}, function (err, resp, body) { 63 | if (err) throw err; 64 | if (resp.statusCode !== 201) throw new Error('Could not update document.') 65 | 66 | if (!doc.error) console.log('email sent to '+doc.to) 67 | else console.log('error sending email.') 68 | }) 69 | }) 70 | }) 71 | } 72 | }) 73 | -------------------------------------------------------------------------------- /tests/imageresize.js: -------------------------------------------------------------------------------- 1 | /** Resizes images attachments in place on a remote CouchDB. 2 | * Usage: node imageresize.js http://yourcouch/db 3 | * Author: Max Ogden (@maxogden) 4 | **/ 5 | 6 | var dbemitter = require('../main') 7 | , request = require('request') 8 | , im = require('imagemagick') 9 | , sys = require("sys") 10 | , url = require("url") 11 | , path = require("path") 12 | , fs = require("fs") 13 | , mimetypes = require('./mimetypes') 14 | ; 15 | 16 | var db = process.argv[2] 17 | , h = {'content-type':'application/json', 'accept':'application/json'} 18 | , converted = [] 19 | ; 20 | 21 | var emitter = dbemitter.createCouchDBEmitter(db); 22 | 23 | emitter.on('change', function (change) { 24 | var doc = change.doc 25 | , attachments = doc._attachments 26 | ; 27 | 28 | if ( ( doc._id.substr(0,7) === "_design" ) || ( ! attachments ) ) return; 29 | 30 | for ( var name in attachments ) { 31 | var uniqueName = doc._id + unescape(name); 32 | if ( ( typeof(doc.message) !== "undefined" ) && ( attachments[name].length > 1000000 ) && ( name.match(/jpe?g|png/) ) ) { 33 | if ( converted.indexOf(uniqueName) === -1 ) { 34 | converted.push(uniqueName); 35 | ensureCommit(function(uri, doc) { 36 | return function() { 37 | resize(uri, doc); 38 | } 39 | }(db + "/" + doc._id + "/" + escape(unescape(name)), doc)) 40 | } 41 | } 42 | } 43 | }) 44 | 45 | function ensureCommit(callback) { 46 | request({uri:db + "/_ensure_full_commit", method:'POST', headers:h}, function (err, resp, body) { 47 | if (err) throw err; 48 | if (resp.statusCode > 299) throw new Error("Could not check commited status\n"+body); 49 | var status = JSON.parse(body); 50 | if (status.ok) { 51 | callback(); 52 | } else { 53 | setTimeout( function() { 54 | ensureCommit( callback ); 55 | }, 1000 ); 56 | } 57 | }); 58 | } 59 | 60 | function download(uri, callback) { 61 | var filename = unescape(url.parse(uri).pathname.split("/").pop()) 62 | ; 63 | request({ 64 | uri: uri, 65 | encoding: "binary" 66 | }, function(err, resp, body) { 67 | if (err || resp.statusCode > 299) { 68 | setTimeout(function() { 69 | download(uri, callback) 70 | }, 1000) 71 | } else { 72 | fs.writeFileSync(filename, body, 'binary'); 73 | callback(filename); 74 | } 75 | }) 76 | } 77 | 78 | function resize(uri, doc) { 79 | download(uri, function(filename) { 80 | im.convert([filename, '-resize', '700', filename], 81 | function(err, stdout, stderr) { 82 | if (err) throw err; 83 | upload(filename, db + "/" + doc._id, doc); 84 | }) 85 | }) 86 | } 87 | 88 | function upload(filename, uri, doc) { 89 | fs.readFile(filename, 'binary', function (er, data) { 90 | var mime = mimetypes.lookup(path.extname(filename).slice(1)); 91 | data = new Buffer(data, 'binary').toString('base64'); 92 | doc._attachments[filename] = {data:data, content_type:mime}; 93 | var body = JSON.stringify(doc); 94 | request({uri:uri, method:'PUT', body:body, headers:h}, function (err, resp, body) { 95 | if (err) throw err; 96 | if (resp.statusCode > 299) throw new Error("Could not upload converted photo\n"+body); 97 | sys.puts('Resized ' + filename + " from doc " + doc._id); 98 | }); 99 | }) 100 | } 101 | 102 | // TODO binary version (doesnt work yet -- incorrect encoding?): 103 | // var data = fs.readFileSync(filename, 'binary'); 104 | // request({uri:uri + "/" + filename + "?rev=" + doc._rev, method: 'PUT', encoding: "binary", body:data, headers:{"content-type": mime}}, function (err, resp, body) { 105 | // if (err) throw err; 106 | // if (resp.statusCode !== 201) throw new Error("Could not push document\n"+body); 107 | // }); -------------------------------------------------------------------------------- /tests/mimetypes.js: -------------------------------------------------------------------------------- 1 | // from http://github.com/felixge/node-paperboy 2 | exports.types = { 3 | "aiff":"audio/x-aiff", 4 | "arj":"application/x-arj-compressed", 5 | "asf":"video/x-ms-asf", 6 | "asx":"video/x-ms-asx", 7 | "au":"audio/ulaw", 8 | "avi":"video/x-msvideo", 9 | "bcpio":"application/x-bcpio", 10 | "ccad":"application/clariscad", 11 | "cod":"application/vnd.rim.cod", 12 | "com":"application/x-msdos-program", 13 | "cpio":"application/x-cpio", 14 | "cpt":"application/mac-compactpro", 15 | "csh":"application/x-csh", 16 | "css":"text/css", 17 | "deb":"application/x-debian-package", 18 | "dl":"video/dl", 19 | "doc":"application/msword", 20 | "drw":"application/drafting", 21 | "dvi":"application/x-dvi", 22 | "dwg":"application/acad", 23 | "dxf":"application/dxf", 24 | "dxr":"application/x-director", 25 | "etx":"text/x-setext", 26 | "ez":"application/andrew-inset", 27 | "fli":"video/x-fli", 28 | "flv":"video/x-flv", 29 | "gif":"image/gif", 30 | "gl":"video/gl", 31 | "gtar":"application/x-gtar", 32 | "gz":"application/x-gzip", 33 | "hdf":"application/x-hdf", 34 | "hqx":"application/mac-binhex40", 35 | "html":"text/html", 36 | "ice":"x-conference/x-cooltalk", 37 | "ico":"image/x-icon", 38 | "ief":"image/ief", 39 | "igs":"model/iges", 40 | "ips":"application/x-ipscript", 41 | "ipx":"application/x-ipix", 42 | "jad":"text/vnd.sun.j2me.app-descriptor", 43 | "jar":"application/java-archive", 44 | "jpeg":"image/jpeg", 45 | "jpg":"image/jpeg", 46 | "js":"text/javascript", 47 | "json":"application/json", 48 | "latex":"application/x-latex", 49 | "lsp":"application/x-lisp", 50 | "lzh":"application/octet-stream", 51 | "m":"text/plain", 52 | "m3u":"audio/x-mpegurl", 53 | "man":"application/x-troff-man", 54 | "me":"application/x-troff-me", 55 | "midi":"audio/midi", 56 | "mif":"application/x-mif", 57 | "mime":"www/mime", 58 | "movie":"video/x-sgi-movie", 59 | "mustache":"text/plain", 60 | "mp4":"video/mp4", 61 | "mpg":"video/mpeg", 62 | "mpga":"audio/mpeg", 63 | "ms":"application/x-troff-ms", 64 | "nc":"application/x-netcdf", 65 | "oda":"application/oda", 66 | "ogm":"application/ogg", 67 | "pbm":"image/x-portable-bitmap", 68 | "pdf":"application/pdf", 69 | "pgm":"image/x-portable-graymap", 70 | "pgn":"application/x-chess-pgn", 71 | "pgp":"application/pgp", 72 | "pm":"application/x-perl", 73 | "png":"image/png", 74 | "pnm":"image/x-portable-anymap", 75 | "ppm":"image/x-portable-pixmap", 76 | "ppz":"application/vnd.ms-powerpoint", 77 | "pre":"application/x-freelance", 78 | "prt":"application/pro_eng", 79 | "ps":"application/postscript", 80 | "qt":"video/quicktime", 81 | "ra":"audio/x-realaudio", 82 | "rar":"application/x-rar-compressed", 83 | "ras":"image/x-cmu-raster", 84 | "rgb":"image/x-rgb", 85 | "rm":"audio/x-pn-realaudio", 86 | "rpm":"audio/x-pn-realaudio-plugin", 87 | "rtf":"text/rtf", 88 | "rtx":"text/richtext", 89 | "scm":"application/x-lotusscreencam", 90 | "set":"application/set", 91 | "sgml":"text/sgml", 92 | "sh":"application/x-sh", 93 | "shar":"application/x-shar", 94 | "silo":"model/mesh", 95 | "sit":"application/x-stuffit", 96 | "skt":"application/x-koan", 97 | "smil":"application/smil", 98 | "snd":"audio/basic", 99 | "sol":"application/solids", 100 | "spl":"application/x-futuresplash", 101 | "src":"application/x-wais-source", 102 | "stl":"application/SLA", 103 | "stp":"application/STEP", 104 | "sv4cpio":"application/x-sv4cpio", 105 | "sv4crc":"application/x-sv4crc", 106 | "svg":"image/svg+xml", 107 | "swf":"application/x-shockwave-flash", 108 | "tar":"application/x-tar", 109 | "tcl":"application/x-tcl", 110 | "tex":"application/x-tex", 111 | "texinfo":"application/x-texinfo", 112 | "tgz":"application/x-tar-gz", 113 | "tiff":"image/tiff", 114 | "tr":"application/x-troff", 115 | "tsi":"audio/TSP-audio", 116 | "tsp":"application/dsptype", 117 | "tsv":"text/tab-separated-values", 118 | "unv":"application/i-deas", 119 | "ustar":"application/x-ustar", 120 | "vcd":"application/x-cdlink", 121 | "vda":"application/vda", 122 | "vivo":"video/vnd.vivo", 123 | "vrm":"x-world/x-vrml", 124 | "wav":"audio/x-wav", 125 | "wax":"audio/x-ms-wax", 126 | "wma":"audio/x-ms-wma", 127 | "wmv":"video/x-ms-wmv", 128 | "wmx":"video/x-ms-wmx", 129 | "wrl":"model/vrml", 130 | "wvx":"video/x-ms-wvx", 131 | "xbm":"image/x-xbitmap", 132 | "xlw":"application/vnd.ms-excel", 133 | "xml":"text/xml", 134 | "xpm":"image/x-xpixmap", 135 | "xwd":"image/x-xwindowdump", 136 | "xyz":"chemical/x-pdb", 137 | "zip":"application/zip", 138 | }; 139 | 140 | exports.lookup = function(ext, defaultType) { 141 | defaultType = defaultType || 'application/octet-stream'; 142 | 143 | return (ext in exports.types) 144 | ? exports.types[ext] 145 | : defaultType; 146 | }; -------------------------------------------------------------------------------- /tests/smtp.js: -------------------------------------------------------------------------------- 1 | var dns = require('dns') 2 | , net = require('net') 3 | , events = require('events') 4 | ; 5 | 6 | function getexchange (domain, cb) { 7 | dns.resolveMx(domain, function (err, addresses) { 8 | if (err) return cb(err); 9 | var resolver = {} 10 | addresses.reverse(); 11 | for (var i=0;i 299) return cb(new Error({code:code, message:line})); 45 | client.kick(); 46 | } 47 | client.kick = function () { 48 | var c = client.queue.shift(); 49 | client.stream.write(c[0]) 50 | client.currentCallback = function (code, line) { 51 | if (c[1]) c[1](null, code, line); 52 | client.kick(); 53 | } 54 | } 55 | }) 56 | } 57 | 58 | --------------------------------------------------------------------------------