├── .gitignore ├── .npm └── package │ ├── .gitignore │ ├── README │ └── npm-shrinkwrap.json ├── .travis.yml ├── .versions ├── LICENSE.md ├── README.md ├── danielqiu ├── danielqiu:feed.js ├── feed-reader ├── collections.js ├── createFeed.js ├── feedTypeEnum.js ├── fetchFeedAtom.js ├── fetchTweets.js ├── processFeed.js └── saveFeed.js ├── package.js ├── test ├── danielqiu └── danielqiu:feed-tests.js └── versions.json /.gitignore: -------------------------------------------------------------------------------- 1 | .build* 2 | -------------------------------------------------------------------------------- /.npm/package/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.npm/package/README: -------------------------------------------------------------------------------- 1 | This directory and the files immediately inside it are automatically generated 2 | when you change this package's NPM dependencies. Commit the files in this 3 | directory (npm-shrinkwrap.json, .gitignore, and this README) to source control 4 | so that others run the same versions of sub-dependencies. 5 | 6 | You should NOT check in the node_modules directory that Meteor automatically 7 | creates; if you are using git, the .gitignore file tells git to ignore it. 8 | -------------------------------------------------------------------------------- /.npm/package/npm-shrinkwrap.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "request": { 4 | "version": "2.51.0", 5 | "dependencies": { 6 | "bl": { 7 | "version": "0.9.3", 8 | "dependencies": { 9 | "readable-stream": { 10 | "version": "1.0.33", 11 | "dependencies": { 12 | "core-util-is": { 13 | "version": "1.0.1" 14 | }, 15 | "isarray": { 16 | "version": "0.0.1" 17 | }, 18 | "string_decoder": { 19 | "version": "0.10.31" 20 | }, 21 | "inherits": { 22 | "version": "2.0.1" 23 | } 24 | } 25 | } 26 | } 27 | }, 28 | "caseless": { 29 | "version": "0.8.0" 30 | }, 31 | "forever-agent": { 32 | "version": "0.5.2" 33 | }, 34 | "form-data": { 35 | "version": "0.2.0", 36 | "dependencies": { 37 | "async": { 38 | "version": "0.9.0" 39 | }, 40 | "mime-types": { 41 | "version": "2.0.4", 42 | "dependencies": { 43 | "mime-db": { 44 | "version": "1.3.1" 45 | } 46 | } 47 | } 48 | } 49 | }, 50 | "json-stringify-safe": { 51 | "version": "5.0.0" 52 | }, 53 | "mime-types": { 54 | "version": "1.0.2" 55 | }, 56 | "node-uuid": { 57 | "version": "1.4.2" 58 | }, 59 | "qs": { 60 | "version": "2.3.3" 61 | }, 62 | "tunnel-agent": { 63 | "version": "0.4.0" 64 | }, 65 | "tough-cookie": { 66 | "version": "0.12.1", 67 | "dependencies": { 68 | "punycode": { 69 | "version": "1.3.2" 70 | } 71 | } 72 | }, 73 | "http-signature": { 74 | "version": "0.10.0", 75 | "dependencies": { 76 | "assert-plus": { 77 | "version": "0.1.2" 78 | }, 79 | "asn1": { 80 | "version": "0.1.11" 81 | }, 82 | "ctype": { 83 | "version": "0.5.2" 84 | } 85 | } 86 | }, 87 | "oauth-sign": { 88 | "version": "0.5.0" 89 | }, 90 | "hawk": { 91 | "version": "1.1.1", 92 | "dependencies": { 93 | "hoek": { 94 | "version": "0.9.1" 95 | }, 96 | "boom": { 97 | "version": "0.4.2" 98 | }, 99 | "cryptiles": { 100 | "version": "0.2.2" 101 | }, 102 | "sntp": { 103 | "version": "0.2.4" 104 | } 105 | } 106 | }, 107 | "aws-sign2": { 108 | "version": "0.5.0" 109 | }, 110 | "stringstream": { 111 | "version": "0.0.4" 112 | }, 113 | "combined-stream": { 114 | "version": "0.0.7", 115 | "dependencies": { 116 | "delayed-stream": { 117 | "version": "0.0.5" 118 | } 119 | } 120 | } 121 | } 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # The language specific base vm to use for this repository 2 | language: node_js 3 | 4 | # The node.js version to install 5 | node_js: "0.10.33" 6 | 7 | # All installation prerequisites before running your build / tests go here. 8 | install: 9 | # Install meteor 10 | - "curl https://install.meteor.com | /bin/sh" 11 | # Install spacejam 12 | - "npm install -g spacejam" 13 | 14 | # All build / test commands / scripts go here. 15 | # For testing meteor packages, that's all that is needed. 16 | script: 17 | - "spacejam test-packages ./" 18 | -------------------------------------------------------------------------------- /.versions: -------------------------------------------------------------------------------- 1 | application-configuration@1.0.4 2 | base64@1.0.2 3 | binary-heap@1.0.2 4 | callback-hook@1.0.2 5 | check@1.0.3 6 | danielqiu:feed@0.0.4 7 | dbeath:feedparser@0.16.6 8 | ddp@1.0.13 9 | ejson@1.0.5 10 | follower-livedata@1.0.3 11 | geojson-utils@1.0.2 12 | id-map@1.0.2 13 | json@1.0.2 14 | local-test:danielqiu:feed@0.0.4 15 | logging@1.0.6 16 | meteor@1.1.4 17 | minimongo@1.0.6 18 | mongo@1.0.10 19 | mrt:twit@0.2.0 20 | ordered-dict@1.0.2 21 | random@1.0.2 22 | retry@1.0.2 23 | tinytest@1.0.4 24 | tracker@1.0.4 25 | underscore@1.0.2 26 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Daniel Qiu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/danielqiu/feed.png?branch=master)](https://travis-ci.org/danielqiu/feed) 2 | 3 | Feed 4 | ====== 5 | Feed reader is a Meteor package for reading feeds, such as RSS, ATOM and Twitter streaming. 6 | 7 | #Usage 8 | 9 | Install package from Meteor: 10 | ``` 11 | meteor add danielqiu:feed 12 | ``` 13 | ##Collection 14 | Feed, such as Github, Twitter, Stackoverflow, is stored in the Feeds collection. Feed entries of each feed is stored in the FeedEntries collection. Create the two collections in the /lib directory which is shared by the server and client. 15 | 16 | ```javascript 17 | Feeds = new Meteor.Collection("feeds"); 18 | FeedEntries = new Meteor.Collection("feed_entries"); 19 | ``` 20 | 21 | ##Create Feed, init Feed and invoke Feed.read() 22 | ```javascript 23 | function feedReader() { 24 | 25 | // pass the created collections to Feed.collections() 26 | var collections = { 27 | feeds: Feeds, 28 | feed_entries: FeedEntries 29 | } 30 | 31 | Feed.collections(collections); 32 | 33 | var github_feed = { 34 | _id: Meteor.settings.github_id, 35 | category: Meteor.settings.github_category, 36 | link: Meteor.settings.github_link, 37 | refresh_interval: Meteor.settings.github_refresh_interval 38 | }; 39 | 40 | Feed.createAtomFeed(github_feed); 41 | 42 | var stackoverflow_feed = { 43 | _id: Meteor.settings.stackoverflow_id, 44 | category: Meteor.settings.stackoverflow_category, 45 | link: Meteor.settings.stackoverflow_link, 46 | refresh_interval: Meteor.settings.stackoverflow_refresh_interval 47 | }; 48 | 49 | 50 | Feed.createAtomFeed(stackoverflow_feed); 51 | 52 | var twitter_feed = { 53 | _id: Meteor.settings.twitter_id, 54 | category: Meteor.settings.twitter_category, 55 | link: Meteor.settings.twitter_link, 56 | refresh_interval: Meteor.settings.twitter_refresh_interval 57 | }; 58 | 59 | Feed.createTwitterFeed(twitter_feed); 60 | 61 | var twitter_parameters = { 62 | consumer_key: Meteor.settings.twitter_consumer_key, 63 | consumer_secret: Meteor.settings.twitter_consumer_secret, 64 | access_token: Meteor.settings.twitter_access_token, 65 | access_token_secret: Meteor.settings.twitter_access_token_secret, 66 | screen_name: Meteor.settings.twitter_screen_name 67 | }; 68 | 69 | Feed.initTwitterFeed(twitter_parameters); 70 | 71 | // invoke Feed.read() to get real-time reactive social stream 72 | Feed.read(); 73 | } 74 | ``` 75 | ##Settings.json 76 | ```json 77 | { 78 | "twitter_consumer_key": "Your twitter consumer key here", 79 | "twitter_consumer_secret": "Your twitter consumer secret here", 80 | "twitter_access_token": "Your twitter access token here", 81 | "twitter_access_token_secret": "Your twitter access token aecret here", 82 | 83 | "github_id": "daniel's github", 84 | "github_category": "Github", 85 | "github_link": "https://github.com/danielqiu.atom", 86 | "github_refresh_interval": 5000, 87 | 88 | "stackoverflow_id": "daniel's stackoverflow", 89 | "stackoverflow_category": "Stackoverflow", 90 | "stackoverflow_link": "http://stackoverflow.com/feeds/user/1871293", 91 | "stackoverflow_refresh_interval": 5000, 92 | 93 | "twitter_id": "daniel's Twitter", 94 | "twitter_category": "Twitter", 95 | "twitter_link": "https://twitter.com/danqiu", 96 | "twitter_refresh_interval": 5000, 97 | 98 | "twitter_screen_name": "danqiu" 99 | } 100 | ``` 101 | 102 | ##Example 103 | Please visit [Feed-demo](https://github.com/danielqiu/feed-demo) to see the example. 104 | -------------------------------------------------------------------------------- /danielqiu: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielqiu/feed/619ba04e3d8876f572b8e63591881dffd9f33555/danielqiu -------------------------------------------------------------------------------- /danielqiu:feed.js: -------------------------------------------------------------------------------- 1 | Feed = function(options) {}; 2 | 3 | Feed.collections = function(collections) { 4 | Feeds = collections.feeds; 5 | FeedEntries = collections.feed_entries; 6 | } 7 | 8 | Feed.createTwitterFeed = function(feed) { 9 | 10 | feed.type = FeedType.TWITTER; 11 | 12 | createFeed(feed); 13 | }; 14 | 15 | Feed.createAtomFeed = function(feed) { 16 | 17 | feed.type = FeedType.ATOM; 18 | 19 | createFeed(feed); 20 | }; 21 | 22 | Feed.createRssFeed = function(feed) { 23 | 24 | feed.type = FeedType.RSS; 25 | 26 | createFeed(feed); 27 | }; 28 | 29 | // Variable to take values of Twitter parameters, 30 | // such as consumer key, consumer secret, access token , access token secret 31 | // and screen name 32 | Twitter = {}; 33 | 34 | Feed.initTwitterFeed = function(arguments) { 35 | 36 | Twitter.consumer_key = arguments.consumer_key; 37 | Twitter.consumer_secret = arguments.consumer_secret; 38 | Twitter.access_token = arguments.access_token; 39 | Twitter.access_token_secret = arguments.access_token_secret; 40 | 41 | Twitter.screen_name = arguments.screen_name; 42 | }; 43 | 44 | Feed.refreshIntervalHandles = {}; 45 | 46 | Feed.read = function() { 47 | 48 | console.log("Reading feed..."); 49 | 50 | function fetchEntries(feed) { 51 | feed.latest_date = null; 52 | if (feed.type === FeedType.TWITTER) { 53 | fetchTweets(feed); 54 | } else if (feed.type === FeedType.ATOM || feed.type === FeedType.RSS) { 55 | fetchAtomRss(feed); 56 | } 57 | } 58 | 59 | // get all the feeds details 60 | var feeds = Feeds.find().fetch(); 61 | 62 | _.each(feeds, function(feed) { 63 | 64 | fetchEntries(feed); 65 | 66 | // get the refresh interval from feed setting 67 | // if the refresh_interval is not set, set to default 10 seconds 68 | var refresh_interval = feed.refresh_interval || 10000 ; 69 | var intervalHandle = Meteor.setInterval(function() { 70 | fetchEntries(feed); 71 | }, refresh_interval); 72 | 73 | Feed.refreshIntervalHandles[feed._id] = intervalHandle; 74 | 75 | }); 76 | 77 | }; 78 | 79 | Feed.stopReading = function() { 80 | if (!_.isEmpty(Feed.refreshIntervalHandles)) { 81 | _.each(_.values(Feed.refreshIntervalHandles), function(intervalHandle) { 82 | Meteor.clearInterval(intervalHandle); 83 | }); 84 | console.log( 85 | "Stopped " + _.keys(Feed.refreshIntervalHandles).length + " feeds" 86 | ); 87 | Feed.refreshIntervalHandles = {}; 88 | } 89 | }; 90 | -------------------------------------------------------------------------------- /feed-reader/collections.js: -------------------------------------------------------------------------------- 1 | Feeds = {}; 2 | FeedEntries = {}; -------------------------------------------------------------------------------- /feed-reader/createFeed.js: -------------------------------------------------------------------------------- 1 | createFeed = function(feed) { 2 | 3 | Feeds.insert(feed, function (error, result) { 4 | if (error) { 5 | //console.log("Inserting Exception: ", error); 6 | } 7 | }); 8 | }; -------------------------------------------------------------------------------- /feed-reader/feedTypeEnum.js: -------------------------------------------------------------------------------- 1 | // enum 2 | FeedType = { 3 | ATOM: "Atom", 4 | RSS: "Rss", 5 | TWITTER: "Twitter" 6 | } -------------------------------------------------------------------------------- /feed-reader/fetchFeedAtom.js: -------------------------------------------------------------------------------- 1 | var request = Npm.require('request'); 2 | 3 | /* 4 | Ref: https://github.com/danmactough/node-feedparser/blob/master/README.md#usage 5 | */ 6 | fetchAtomRss = function (feed) { 7 | 8 | // Define our streams 9 | var req = request(feed.link), 10 | feedparser = new Feedparser(); 11 | 12 | req.on('error', function (error) { 13 | // handle any request errors 14 | console.log("req error: " + error); 15 | }); 16 | 17 | // Define our handlers 18 | req.on('response', function (res) { 19 | 20 | var stream = this; 21 | 22 | if (res.statusCode != 200) { 23 | console.log("res.statusCode:" + res.statusCode); 24 | 25 | return; // Run into some problem, ignore it 26 | } 27 | 28 | stream.pipe(feedparser); 29 | }); 30 | 31 | feedparser.on('error', function (error) { 32 | // always handle errors 33 | console.log("feedparser error: " + error); 34 | }); 35 | 36 | feedparser.on('readable', function () { 37 | // This is where the action is! 38 | var stream = this, 39 | meta = this.meta, // **NOTE** the "meta" is always available in the context of the feedparser instance 40 | item; 41 | 42 | while (item = this.read()) { 43 | processFeed(item, feed); 44 | } 45 | 46 | }); 47 | } 48 | -------------------------------------------------------------------------------- /feed-reader/fetchTweets.js: -------------------------------------------------------------------------------- 1 | fetchTweets = function (feed) { 2 | 3 | var Twit = new TwitMaker({ 4 | consumer_key: Twitter.consumer_key, 5 | consumer_secret: Twitter.consumer_secret, 6 | access_token: Twitter.access_token, 7 | access_token_secret: Twitter.access_token_secret 8 | }); 9 | 10 | Twit.get('statuses/user_timeline', { 11 | screen_name: Twitter.screen_name 12 | }, function (err, tweets) { 13 | _.each(tweets, function (tweet) { 14 | processFeed(tweet, feed); 15 | }) 16 | }) 17 | } 18 | 19 | -------------------------------------------------------------------------------- /feed-reader/processFeed.js: -------------------------------------------------------------------------------- 1 | var Fiber = Npm.require('fibers'); 2 | 3 | processFeed = function (source, feed) { 4 | Fiber(function () { 5 | var entry = null; 6 | if (feed.type === FeedType.TWITTER) { 7 | entry = convertTweetToFeedEntry(source, feed) 8 | } else if (feed.type === FeedType.ATOM || feed.type === FeedType.RSS) { 9 | entry = convertAtomFeedToFeedEntry(source, feed); 10 | } else { 11 | return; 12 | } 13 | 14 | // Ignore old entry 15 | if (entry.pubdate <= feed.last_updated) { 16 | return; 17 | } 18 | 19 | console.log("entry.pubdate: " + entry.pubdate); 20 | console.log("feed.last_updated: " + feed.last_updated); 21 | insertFeedEntry(entry, feed); 22 | 23 | }).run(); 24 | } 25 | 26 | convertTweetToFeedEntry = function (tweet, feed) { 27 | 28 | var tweet_date = parseTwitterDate(tweet.created_at); 29 | 30 | var entry = {}; 31 | entry._id = tweet.id_str; 32 | entry.feed_id = feed._id; 33 | entry.feed_category = feed.category; 34 | entry.pubdate = tweet_date; 35 | entry.date = tweet_date; 36 | entry.summary = tweet.text; 37 | entry.title = tweet.user.name; 38 | entry.description = tweet.text; 39 | entry.author = tweet.user.name; 40 | entry.link = feed.link; 41 | 42 | return entry; 43 | } 44 | 45 | // convert the created_at to a date and then to a friendly string 46 | parseTwitterDate = function (text) { 47 | return new Date(Date.parse(text.replace(/( +)/, ' UTC$1'))); 48 | } 49 | 50 | convertAtomFeedToFeedEntry = function (post, feed) { 51 | var entry = {}; 52 | 53 | entry._id = post.guid; 54 | entry.feed_id = feed._id; 55 | entry.feed_category = feed.category; 56 | entry.pubdate = post.pubdate; 57 | entry.date = post.date; 58 | entry.summary = post.summary; 59 | entry.title = post.title; 60 | entry.description = post.description; 61 | entry.author = post.author; 62 | entry.link = post.link; 63 | 64 | return entry; 65 | } 66 | -------------------------------------------------------------------------------- /feed-reader/saveFeed.js: -------------------------------------------------------------------------------- 1 | insertFeedEntry = function (entry, feed) { 2 | console.log("inserting ", entry._id); 3 | 4 | try { 5 | FeedEntries.insert(entry); 6 | console.log("inserted"); 7 | 8 | } catch (e) { 9 | console.log("Exception: " + e); 10 | }; 11 | 12 | // store the latest feed date 13 | console.log("typeof feed.latest_date: " + typeof feed.latest_date); 14 | console.log("feed.latest_date: " + feed.latest_date); 15 | if (typeof feed.latest_date === 'undefined' || feed.latest_date == null 16 | || feed.latest_date < entry.pubdate) { 17 | 18 | feed.latest_date = entry.pubdate; 19 | // update database 20 | console.log("updating feed of " + feed._id + " with latest_date: " + entry.pubdate); 21 | Feeds.update({ 22 | _id: feed._id 23 | }, { 24 | $set: { 25 | last_updated: entry.pubdate, 26 | } 27 | }); 28 | 29 | //update in-memory variable of feed 30 | feed.last_updated = entry.pubdate; 31 | 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /package.js: -------------------------------------------------------------------------------- 1 | Package.describe({ 2 | name: 'danielqiu:feed', 3 | summary: 'Feed reader for RSS, ATOM and Twitter streaming.', 4 | version: '0.0.5', 5 | author: "Daniel Qiu", 6 | git: 'https://github.com/danielqiu/feed.git' 7 | }); 8 | 9 | /* This defines the actual package */ 10 | Package.onUse(function(api) { 11 | api.versionsFrom('1.0'); 12 | 13 | api.use('dbeath:feedparser@0.16.6'); 14 | api.use('mrt:twit@0.2.0'); 15 | 16 | // Specify the source code for the package. 17 | api.addFiles(['danielqiu:feed.js', 18 | 'feed-reader/collections.js', 19 | 'feed-reader/feedTypeEnum.js', 20 | 'feed-reader/createFeed.js', 21 | 'feed-reader/fetchFeedAtom.js', 22 | 'feed-reader/fetchTweets.js', 23 | 'feed-reader/processFeed.js', 24 | 'feed-reader/saveFeed.js'], 'server'); 25 | 26 | // Export the object 'Feed' to packages or apps that use this package. 27 | api.export('Feed', 'server'); 28 | }); 29 | 30 | /* This defines the tests for the package */ 31 | Package.onTest(function(api) { 32 | // Sets up a dependency on this package 33 | api.use('danielqiu:feed'); 34 | 35 | // Allows to use the 'tinytest' framework 36 | api.use('tinytest', 'server'); 37 | 38 | // Specify the source code for the package tests 39 | api.addFiles('test/danielqiu:feed-tests.js'); 40 | }); 41 | 42 | /* This lets you use npm packages in your package*/ 43 | Npm.depends({ 44 | "request": "2.51.0" 45 | }); 46 | -------------------------------------------------------------------------------- /test/danielqiu: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielqiu/feed/619ba04e3d8876f572b8e63591881dffd9f33555/test/danielqiu -------------------------------------------------------------------------------- /test/danielqiu:feed-tests.js: -------------------------------------------------------------------------------- 1 | Tinytest.add( 2 | 'Feed - Test github Feed fetching and cancelling', 3 | function (test) { 4 | 5 | var Feeds = new Meteor.Collection("feeds_test"); 6 | var FeedEntries = new Meteor.Collection("feed_entries_test"); 7 | 8 | var collections = { 9 | feeds: Feeds, 10 | feed_entries: FeedEntries 11 | } 12 | 13 | Feed.collections(collections); 14 | 15 | var github_feed = { 16 | _id: "daniel's github test", 17 | category: "Github", 18 | link: "https://github.com/danielqiu.atom", 19 | refresh_interval: 1000 20 | }; 21 | 22 | Feed.createAtomFeed(github_feed); 23 | 24 | // invoke feedReader to get real-time reactive social stream 25 | Feed.read(); 26 | 27 | var feed_created = Feeds.find(); 28 | test.isNotNull(feed_created, "feed wasn't created"); 29 | 30 | var feed_entries_created = FeedEntries.find(); 31 | test.isNotNull(feed_entries_created, "feed entries weren't created"); 32 | 33 | test.isFalse( 34 | _.isEmpty(Feed.refreshIntervalHandles), 35 | "Feed refresh interval handles should exist" 36 | ); 37 | 38 | var clearIntervalCount = 0; 39 | Meteor.clearInterval = function () { 40 | clearIntervalCount += 1; 41 | }; 42 | 43 | test.equal( 44 | clearIntervalCount, 45 | 0, 46 | "clearInterval should not have been called yet" 47 | ); 48 | 49 | Feed.stopReading(); 50 | 51 | test.equal(clearIntervalCount, 1, "clearInterval should have been called"); 52 | test.isTrue( 53 | _.isEmpty(Feed.refreshIntervalHandles), 54 | "Feed refresh interval handles should not exist" 55 | ); 56 | 57 | }); 58 | -------------------------------------------------------------------------------- /versions.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": [ 3 | [ 4 | "dbeath:feedparser", 5 | "0.16.6" 6 | ], 7 | [ 8 | "meteor", 9 | "1.1.3" 10 | ], 11 | [ 12 | "mrt:twit", 13 | "0.2.0" 14 | ], 15 | [ 16 | "underscore", 17 | "1.0.1" 18 | ] 19 | ], 20 | "pluginDependencies": [], 21 | "toolVersion": "meteor-tool@1.0.36", 22 | "format": "1.0" 23 | } --------------------------------------------------------------------------------