├── .gitignore ├── LICENSE ├── README.md ├── index.js ├── ironcat.jpg ├── lib ├── config.js ├── itemToMessage.js └── pollFeed.js ├── package.json └── pm2.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.log 3 | *.pid 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Yaniv Kessler 4 | 5 | <<<<<<< HEAD 6 | Permission is hereby granted, free of charge, to any person obtaining a copy of 7 | this software and associated documentation files (the "Software"), to deal in 8 | the Software without restriction, including without limitation the rights to 9 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 10 | the Software, and to permit persons to whom the Software is furnished to do so, 11 | subject to the following conditions: 12 | ======= 13 | Permission is hereby granted, free of charge, to any person obtaining a copy 14 | of this software and associated documentation files (the "Software"), to deal 15 | in the Software without restriction, including without limitation the rights 16 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 | copies of the Software, and to permit persons to whom the Software is 18 | furnished to do so, subject to the following conditions: 19 | >>>>>>> 1233135fa29027ee8cd8a65718f46211423c83d2 20 | 21 | The above copyright notice and this permission notice shall be included in all 22 | copies or substantial portions of the Software. 23 | 24 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 25 | <<<<<<< HEAD 26 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 27 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 28 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 29 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 30 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 31 | ======= 32 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 33 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 34 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 35 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 36 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 37 | SOFTWARE. 38 | >>>>>>> 1233135fa29027ee8cd8a65718f46211423c83d2 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # octofications 2 | github feed as notifications in your desktop 3 | ### install 4 | just clone and do 5 | ``` 6 | node index 7 | ``` 8 | or 9 | 10 | ``` 11 | pm2 start pm2.json 12 | ``` 13 | _requires [node.js](http://nodejs.org) to be installed, obviously_ 14 | ### configuration 15 | of course some configuration is needed, naturally its using [rc](https://github.com/dominictarr/rc): 16 | ``` 17 | { 18 | "authenticationToken": "yourAuthToken", 19 | "user": "yourGithubUser", 20 | "pollInterval": { 21 | "second": 60 22 | } 23 | } 24 | ``` 25 | There more information [here](https://github.com/kessler/tempus-fugit#the-interval-object) on how to customize pollInterval 26 | ### misc. 27 | if its not working then check that you set the proper permissions for the token 28 | 29 | octofications is based on [node-notifier](https://github.com/mikaelbr/node-notifier) so out of the box it works on linux and mac, windows requires [growl](http://growl.info/). 30 | 31 | ### debug 32 | 33 | set the DEBUG environment variable to "octofications*" 34 | 35 | ### todo 36 | - publish to npm 37 | - change icon ? -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var config = require('./lib/config.js'); 2 | var pollFeed = require('./lib/pollFeed.js'); 3 | var FeedParser = require('feedparser'); 4 | var notifier = require('node-notifier'); 5 | var lruCache = require('lru-cache'); 6 | var debug = require('debug')('octofications'); 7 | var schedule = require('tempus-fugit').schedule; 8 | var inspect = require('util').inspect; 9 | var itemToMessage = require('./lib/itemToMessage.js'); 10 | 11 | var EventEmitter = require('events').EventEmitter; 12 | 13 | var options = { 14 | maxAge: 1000 * 60 * 60 * 24, 15 | max: 1000 16 | }; 17 | 18 | var cache = lruCache(options); 19 | var feedEmitter = new EventEmitter(); 20 | var firstLaunch = true; 21 | 22 | feedEmitter.on('item', function(item, count) { 23 | if (!cache.get(item.title)) { 24 | debug('feed item %s, %s, fl: %s', item.title, item.link, firstLaunch); 25 | 26 | // avoid the swamp of old feed items when launching 1st time 27 | if (!firstLaunch) { 28 | setTimeout(function() { 29 | notifier.notify(itemToMessage(item)); 30 | }, count * 5000); 31 | } 32 | 33 | cache.set(item.title, true); 34 | } 35 | }); 36 | 37 | feedEmitter.on('end', function() { 38 | 39 | firstLaunch = false; 40 | }); 41 | 42 | var job = schedule(config.pollInterval, pollFeed(feedEmitter)); 43 | 44 | debug('feed job scheduled, %s', inspect(job)); 45 | -------------------------------------------------------------------------------- /ironcat.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kessler/node-octofications/ba7e30640b2c257dc04787ac0c2f0fe873495456/ironcat.jpg -------------------------------------------------------------------------------- /lib/config.js: -------------------------------------------------------------------------------- 1 | var rc = require('rc'); 2 | var defaults = { 3 | authenticationToken: 'yourAuthToken', 4 | user: 'yourGithubUser', 5 | pollInterval: { 6 | second: 60 7 | } 8 | }; 9 | 10 | module.exports = rc('octofications', defaults); 11 | -------------------------------------------------------------------------------- /lib/itemToMessage.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var isos = require('isos') 3 | 4 | if (isos('osx')) { 5 | module.exports = itemToMessageMac 6 | } else { 7 | module.exports = itemToMessage 8 | } 9 | 10 | var icon = path.join(__dirname, 'ironcat.jpg') 11 | 12 | function itemToMessageMac(item) { 13 | return { title: item.author, subtitle: item.title.replace(item.author, ''), message: item.link.replace('https://github.com/', '') + '\n' + item.title, open: item.link } 14 | } 15 | 16 | function itemToMessage(item) { 17 | return { title: item.title, message: item.link, icon: icon} 18 | } -------------------------------------------------------------------------------- /lib/pollFeed.js: -------------------------------------------------------------------------------- 1 | var FeedParser = require('feedparser') 2 | var request = require('hyperquest') 3 | var https = require('https') 4 | var config = require('./config') 5 | var debug = require('debug')('octofications:feed') 6 | var util = require('util') 7 | 8 | module.exports = function(emitter) { 9 | 10 | return function pollFeed(job) { 11 | 12 | debug('executing feed job') 13 | 14 | var parser = new FeedParser() 15 | 16 | var options = { 17 | uri: 'https://github.com/' + config.user + '.private.atom', 18 | auth: config.authenticationToken + ':x-oauth-basic' 19 | } 20 | 21 | var req = request(options) 22 | 23 | req.on('response', function(res) { 24 | 25 | if (res.statusCode === 200) { 26 | debug('response from github ok.') 27 | res.pipe(parser) 28 | } else { 29 | debug('error getting feed') 30 | job.done() 31 | } 32 | }) 33 | 34 | req.on('error', function(e) { 35 | debug('request error: %s', e) 36 | job.done() 37 | }) 38 | 39 | parser.on('error', function(error) { 40 | debug('parser error: %s', error.toString()) 41 | job.done() 42 | }); 43 | 44 | var count = 0 45 | 46 | parser.on('readable', function() { 47 | // This is where the action is! 48 | var stream = this 49 | var meta = this.meta // **NOTE** the "meta" is always available in the context of the parser instance 50 | var item 51 | 52 | while (item = stream.read()) { 53 | emitter.emit('item', item, count++) 54 | } 55 | }) 56 | 57 | parser.on('end', function () { 58 | emitter.emit('end') 59 | console.log(job) 60 | job.done() 61 | }) 62 | } 63 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "octofications", 3 | "version": "1.0.0", 4 | "description": "github feed as notifications in your desktop", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "mocha" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/kessler/node-octofications" 12 | }, 13 | "keywords": [ 14 | "github", 15 | "notifications" 16 | ], 17 | "author": "Yaniv Kessler", 18 | "license": "MIT", 19 | "bugs": { 20 | "url": "https://github.com/kessler/node-octofications/issues" 21 | }, 22 | "homepage": "https://github.com/kessler/node-octofications", 23 | "dependencies": { 24 | "debug": "^0.8.1", 25 | "feedparser": "^0.16.6", 26 | "hyperquest": "^1.2.0", 27 | "isos": "0.0.3", 28 | "lru-cache": "^2.5.0", 29 | "node-notifier": "^4.2.3", 30 | "rc": "^0.3.5", 31 | "tempus-fugit": "^2.2.0" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /pm2.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "octofications", 3 | "script": "index.js", 4 | "watch": false, 5 | "log": "./node-octofications.log" 6 | } 7 | --------------------------------------------------------------------------------