Auto post via RSS urls. Fixes #453

This commit is contained in:
Delgermurun 2014-12-13 22:55:10 +08:00
parent 512cc7707d
commit c5e813909a
19 changed files with 614 additions and 0 deletions

View file

@ -78,4 +78,5 @@ telescope-invites
useraccounts:unstyled useraccounts:unstyled
telescope-datetimepicker telescope-datetimepicker
tsega:bootstrap3-datetimepicker@3.1.3_1 tsega:bootstrap3-datetimepicker@3.1.3_1
telescope-post-by-rss

View file

@ -123,6 +123,7 @@ telescope-lib@0.2.9
telescope-module-share@0.0.0 telescope-module-share@0.0.0
telescope-newsletter@0.1.0 telescope-newsletter@0.1.0
telescope-notifications@0.1.0 telescope-notifications@0.1.0
telescope-post-by-rss@0.0.1
telescope-rss@0.0.0 telescope-rss@0.0.0
telescope-search@0.0.0 telescope-search@0.0.0
telescope-singleday@0.1.0 telescope-singleday@0.1.0

View file

@ -0,0 +1 @@
.build*

View file

@ -0,0 +1 @@
node_modules

View file

@ -0,0 +1,7 @@
This directory and the files immediately inside it are automatically generated
when you change this package's NPM dependencies. Commit the files in this
directory (npm-shrinkwrap.json, .gitignore, and this README) to source control
so that others run the same versions of sub-dependencies.
You should NOT check in the node_modules directory that Meteor automatically
creates; if you are using git, the .gitignore file tells git to ignore it.

View file

@ -0,0 +1,63 @@
{
"dependencies": {
"html-to-text": {
"version": "0.1.0",
"dependencies": {
"htmlparser": {
"version": "1.7.7"
},
"underscore": {
"version": "1.7.0"
},
"underscore.string": {
"version": "2.4.0"
},
"optimist": {
"version": "0.6.1",
"dependencies": {
"wordwrap": {
"version": "0.0.2"
},
"minimist": {
"version": "0.0.10"
}
}
}
}
},
"htmlparser2": {
"version": "3.8.2",
"dependencies": {
"domhandler": {
"version": "2.3.0"
},
"domutils": {
"version": "1.5.0"
},
"domelementtype": {
"version": "1.1.3"
},
"readable-stream": {
"version": "1.1.13",
"dependencies": {
"core-util-is": {
"version": "1.0.1"
},
"isarray": {
"version": "0.0.1"
},
"string_decoder": {
"version": "0.10.31"
},
"inherits": {
"version": "2.0.1"
}
}
},
"entities": {
"version": "1.0.0"
}
}
}
}
}

View file

@ -0,0 +1,3 @@
{
"rss_url_is_already_exists": "Url is already exists."
}

View file

@ -0,0 +1,20 @@
adminNav.push({
route: 'rssUrls',
label: 'RSS Urls'
});
Meteor.startup(function () {
Router.onBeforeAction(Router._filters.isAdmin, {only: ['rssUrls']});
// RSS Urls Admin
Router.route('/rss-urls', {
name: 'rssUrls',
waitOn: function() {
return Meteor.subscribe('rssUrls');
},
template: getTemplate('rssUrls')
});
});

View file

@ -0,0 +1,6 @@
<template name="rssUrlItem">
<li>
<span>Url: {{url}}</span>
<a class="delete-url" href="#">Delete</a>
</li>
</template>

View file

@ -0,0 +1,10 @@
Meteor.startup(function () {
Template[getTemplate('rssUrlItem')].events({
'click .delete-url': function(e, instance){
e.preventDefault();
if (confirm(i18n.t('are_you_sure')))
RssUrls.remove(instance.data._id);
}
});
});

View file

@ -0,0 +1,21 @@
<template name="rssUrls">
<div class="grid-small grid-module dialog admin">
<h2>RSS Urls</h2>
<form class="form-block">
<h3>Add new RSS Url</h3>
<div class="control-group">
<label>Url</label>
<div class="controls"><input id="url" type="text" value="" /></div>
</div>
<div class="form-actions">
<input type="submit" class="button" value="Submit" />
</div>
</form>
<ul>
{{#each rssUrls}}
{{> UI.dynamic template=rssUrlItem}}
{{/each}}
</ul>
</div>
</template>

View file

@ -0,0 +1,28 @@
Meteor.startup(function () {
Template[getTemplate('rssUrls')].helpers({
rssUrls: function(){
return RssUrls.find({}, {sort: {url: 1}});
},
rssUrlItem: function () {
return getTemplate('rssUrlItem');
}
});
Template[getTemplate('rssUrls')].events({
'click input[type=submit]': function(e){
e.preventDefault();
var url = $('#url').val();
Meteor.call('insertRssUrl', {url: url}, function(error, result) {
if(error){
console.log(error);
flashMessage(error.reason, "error");
clearSeenMessages();
}else{
$('#url').val('');
}
});
}
});
});

View file

@ -0,0 +1,35 @@
// rssUrl schema
rssUrlSchema = new SimpleSchema({
_id: {
type: String,
optional: true
},
url: {
type: String,
regEx: SimpleSchema.RegEx.Url
},
});
RssUrls = new Meteor.Collection("rss_urls", {
schema: rssUrlSchema
});
Meteor.startup(function () {
RssUrls.allow({
remove: isAdminById
});
Meteor.methods({
insertRssUrl: function(rssUrl){
check(rssUrl, rssUrlSchema);
if (RssUrls.findOne({url: rssUrl.url}))
throw new Meteor.Error('already-exists', i18n.t('rss_url_is_already_exists'));
if (!Meteor.user() || !isAdmin(Meteor.user()))
throw new Meteor.Error('login-required', i18n.t('you_need_to_login_and_be_an_admin_to_add_a_new_rss_url'));
return RssUrls.insert(rssUrl);
}
});
});

View file

@ -0,0 +1,13 @@
SyncedCron.add({
name: 'Post by RSS url',
schedule: function(parser) {
return parser.text('every 10 mins');
},
job: function() {
fetchUrls();
}
});
Meteor.startup(function() {
SyncedCron.start();
});

View file

@ -0,0 +1,6 @@
Meteor.publish('rssUrls', function() {
if(isAdminById(this.userId)){
return RssUrls.find();
}
return [];
});

View file

@ -0,0 +1,98 @@
var htmlParser = Npm.require('htmlparser2');
var htmlToText = Npm.require('html-to-text');
var getFirstAdminUser = function() {
return Meteor.users.findOne({isAdmin: true}, {sort: {createdAt: 1}});
}
var insertPost = function(feedItem) {
var post = {
title: feedItem.title,
body: htmlToText.fromString(feedItem.description),
url: feedItem.link
};
// check that there are no posts with the same URL
if (!!post.url) {
var sixMonthsAgo = moment().subtract(6, 'months').toDate();
var postWithSameLink = Posts.findOne({url: post.url, postedAt: {$gte: sixMonthsAgo}});
if (typeof postWithSameLink !== 'undefined') {
return;
}
}
var title = cleanUp(post.title),
body = post.body,
user = getFirstAdminUser(),
postId = '';
// ------------------------------ Checks ------------------------------ //
// check that user provided a title
if(!post.title)
post.title = 'Untitled';
// ------------------------------ Properties ------------------------------ //
// Basic Properties
properties = {
title: title,
body: body,
userId: user._id,
author: getDisplayNameById(user._id),
upvotes: 0,
downvotes: 0,
commentCount: 0,
clickCount: 0,
viewCount: 0,
baseScore: 0,
score: 0,
status: 2,
inactive: false,
createdAt: new Date(),
postedAt: new Date()
};
post = _.extend(post, properties);
// ------------------------------ Insert ------------------------------ //
post._id = Posts.insert(post);
// ------------------------------ After Insert ------------------------------ //
// increment posts count
Meteor.users.update({_id: user._id}, {$inc: {postCount: 1}});
Meteor.call('upvotePost', post, user);
return post;
};
var handleFeed = function(error, feed) {
if (error) return;
feed.items.forEach(function(item, index, array) {
insertPost(item);
});
};
fetchUrls = function() {
var content;
RssUrls.find().forEach(function(rssUrl) {
try {
content = HTTP.get(rssUrl.url).content;
} catch (e) {
// just go to next url
return true;
}
var feedHandler = new htmlParser.FeedHandler(handleFeed);
var parser = new htmlParser.Parser(feedHandler, {xmlMode: true});
parser.write(content);
parser.end()
});
}

View file

@ -0,0 +1,5 @@
{
"translation_function_name": "__",
"helper_name": "_",
"namespace": "project"
}

View file

@ -0,0 +1,56 @@
Package.describe({
summary: 'Auto post via RSS to Telescope',
version: '0.0.1',
name: 'telescope-post-by-rss'
});
Npm.depends({
'htmlparser2': '3.8.2',
'html-to-text': '0.1.0',
});
Package.onUse(function(api) {
api.use([
'telescope-base',
'aldeed:simple-schema',
'tap:i18n'
], ['client', 'server']);
api.use([
'iron:router',
'templating'
], 'client');
api.use([
'http',
'mrt:moment',
'percolatestudio:synced-cron'
], 'server');
api.add_files([
'lib/rssUrls.js'
], ['client', 'server']);
api.add_files([
'lib/client/routes.js',
'lib/client/templates/rss_urls.js',
'lib/client/templates/rss_urls.html',
'lib/client/templates/rss_url_item.js',
'lib/client/templates/rss_url_item.html',
], 'client');
api.add_files([
'lib/server/utils.js',
'lib/server/cron.js',
'lib/server/publications.js'
], ['server']);
api.add_files([
"i18n/en.i18n.json"
], ["client", "server"]);
});
Package.onTest(function(api) {
api.use('tinytest');
});

View file

@ -0,0 +1,239 @@
{
"dependencies": [
[
"aldeed:simple-schema",
"1.1.0"
],
[
"application-configuration",
"1.0.3"
],
[
"base64",
"1.0.1"
],
[
"binary-heap",
"1.0.1"
],
[
"blaze",
"2.0.3"
],
[
"blaze-tools",
"1.0.1"
],
[
"boilerplate-generator",
"1.0.1"
],
[
"callback-hook",
"1.0.1"
],
[
"check",
"1.0.2"
],
[
"coffeescript",
"1.0.4"
],
[
"ddp",
"1.0.12"
],
[
"deps",
"1.0.5"
],
[
"ejson",
"1.0.4"
],
[
"follower-livedata",
"1.0.2"
],
[
"geojson-utils",
"1.0.1"
],
[
"html-tools",
"1.0.2"
],
[
"htmljs",
"1.0.2"
],
[
"http",
"1.0.8"
],
[
"id-map",
"1.0.1"
],
[
"iron:controller",
"1.0.3"
],
[
"iron:core",
"1.0.3"
],
[
"iron:dynamic-template",
"1.0.3"
],
[
"iron:layout",
"1.0.3"
],
[
"iron:location",
"1.0.3"
],
[
"iron:middleware-stack",
"1.0.3"
],
[
"iron:router",
"1.0.3"
],
[
"iron:url",
"1.0.3"
],
[
"jquery",
"1.0.1"
],
[
"json",
"1.0.1"
],
[
"logging",
"1.0.5"
],
[
"meteor",
"1.1.3"
],
[
"minifiers",
"1.1.2"
],
[
"minimongo",
"1.0.5"
],
[
"mongo",
"1.0.9"
],
[
"mrt:moment",
"2.8.1"
],
[
"observe-sequence",
"1.0.3"
],
[
"ordered-dict",
"1.0.1"
],
[
"percolatestudio:synced-cron",
"1.1.0"
],
[
"random",
"1.0.1"
],
[
"reactive-dict",
"1.0.4"
],
[
"reactive-var",
"1.0.3"
],
[
"retry",
"1.0.1"
],
[
"routepolicy",
"1.0.2"
],
[
"session",
"1.0.4"
],
[
"spacebars",
"1.0.3"
],
[
"spacebars-compiler",
"1.0.3"
],
[
"tap:http-methods",
"0.0.23"
],
[
"tap:i18n",
"1.2.1"
],
[
"telescope-base",
"0.0.0"
],
[
"telescope-i18n",
"0.0.0"
],
[
"telescope-lib",
"0.2.9"
],
[
"templating",
"1.0.9"
],
[
"tracker",
"1.0.3"
],
[
"ui",
"1.0.4"
],
[
"underscore",
"1.0.1"
],
[
"url",
"1.0.2"
],
[
"webapp",
"1.1.4"
],
[
"webapp-hashing",
"1.0.1"
]
],
"pluginDependencies": [],
"toolVersion": "meteor-tool@1.0.36",
"format": "1.0"
}