diff options
Diffstat (limited to 'source/pyAggr3g470r.py')
-rwxr-xr-x | source/pyAggr3g470r.py | 715 |
1 files changed, 0 insertions, 715 deletions
diff --git a/source/pyAggr3g470r.py b/source/pyAggr3g470r.py deleted file mode 100755 index 922e7114..00000000 --- a/source/pyAggr3g470r.py +++ /dev/null @@ -1,715 +0,0 @@ -#! /usr/bin/env python -#-*- coding: utf-8 -*- - -# pyAggr3g470r - A Web based news aggregator. -# Copyright (C) 2010-2013 Cédric Bonhomme - http://cedricbonhomme.org/ -# -# For more information : http://bitbucket.org/cedricbonhomme/pyaggr3g470r/ -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/> - -__author__ = "Cedric Bonhomme" -__version__ = "$Revision: 4.1 $" -__date__ = "$Date: 2010/01/29 $" -__revision__ = "$Date: 2013/09/09 $" -__copyright__ = "Copyright (c) Cedric Bonhomme" -__license__ = "GPLv3" - -# -# This file contains the "Root" class which describes -# all pages (views) of pyAggr3g470r. These pages are: -# - main page; -# - management; -# - history; -# - favorites; -# - notifications; -# - unread; -# - feed summary; -# - inactives; -# - languages. -# Templates are described in ./templates with the Mako -# template library. -# - -import os -import re -import time -import datetime - -from collections import defaultdict -from whoosh.index import EmptyIndexError - -import cherrypy -from mako.template import Template -from mako.lookup import TemplateLookup -lookup = TemplateLookup(directories=['templates']) - -import conf -import utils -import export -import mongodb -import search -import feedgetter -import auth - -def error_404(status, message, traceback, version): - """ - Display an error if the page does not exist. - """ - message = "<p>Error %s - This page does not exist.</p>" % status - tmpl = lookup.get_template("error.html") - return tmpl.render(message=message) - -def handle_error(): - """ - Handle different type of errors. - """ - message = "<p>Sorry, an error occured.</p>" - cherrypy.response.status = 500 - cherrypy.response.body = [message] - -class RestrictedArea(object): - """ - All methods in this controller (and subcontrollers) is - open only to members of the admin group - """ - _cp_config = { - 'auth.auth.require': [auth.member_of('admin')] - } - - @cherrypy.expose - def index(self): - message = "<p>This is the admin only area.</p>" - tmpl = lookup.get_template("error.html") - return tmpl.render(message=message) - -class pyAggr3g470r(object): - """ - Main class. - All pages of pyAggr3g470r are described in this class. - """ - _cp_config = {'request.error_response': handle_error, \ - 'tools.sessions.on': True, \ - 'tools.auth.on': True} - - def __init__(self): - """ - """ - self.auth = auth.AuthController() - restricted = RestrictedArea() - - self.mongo = mongodb.Articles(conf.MONGODB_ADDRESS, conf.MONGODB_PORT, \ - conf.MONGODB_DBNAME, conf.MONGODB_USER, conf.MONGODB_PASSWORD) - @auth.require() - def index(self): - """ - Main page containing the list of feeds and articles. - """ - feeds = self.mongo.get_all_feeds() - nb_unread_articles = self.mongo.nb_unread_articles() - nb_favorites = self.mongo.nb_favorites() - nb_mail_notifications = self.mongo.nb_mail_notifications() - tmpl = lookup.get_template("index.html") - return tmpl.render(feeds=feeds, nb_feeds=len(feeds), mongo=self.mongo, \ - nb_favorites=nb_favorites, nb_unread_articles=nb_unread_articles, \ - nb_mail_notifications=nb_mail_notifications, header_text=nb_unread_articles) - - index.exposed = True - - @auth.require() - def management(self): - """ - Management page. - Allows adding and deleting feeds. Export functions of the MongoDB data base - and display some statistics. - """ - feeds = self.mongo.get_all_feeds() - nb_mail_notifications = self.mongo.nb_mail_notifications() - nb_favorites = self.mongo.nb_favorites() - nb_articles = format(self.mongo.nb_articles(), ",d") - nb_unread_articles = format(self.mongo.nb_unread_articles(), ",d") - nb_indexed_documents = format(search.nb_documents(), ",d") - tmpl = lookup.get_template("management.html") - return tmpl.render(feeds=feeds, nb_mail_notifications=nb_mail_notifications, \ - nb_favorites=nb_favorites, nb_articles=nb_articles, \ - nb_unread_articles=nb_unread_articles, \ - mail_notification_enabled=conf.MAIL_ENABLED, \ - nb_indexed_documents=nb_indexed_documents) - - management.exposed = True - - @auth.require() - def statistics(self, word_size=6): - """ - More advanced statistics. - """ - articles = self.mongo.get_articles() - top_words = utils.top_words(articles, n=50, size=int(word_size)) - tag_cloud = utils.tag_cloud(top_words) - tmpl = lookup.get_template("statistics.html") - return tmpl.render(articles=articles, word_size=word_size, tag_cloud=tag_cloud) - - statistics.exposed = True - - @auth.require() - def search(self, query=None): - """ - Simply search for the string 'query' - in the description of the article. - """ - param, _, value = query.partition(':') - feed_id = None - if param == "Feed": - feed_id, _, query = value.partition(':') - search_result = defaultdict(list) - try: - results = search.search(param) - except EmptyIndexError as e: - return self.error('<p>The database has not been <a href="/index_base">indexed</a>.</p>') - for result in results: - article = self.mongo.get_articles(result[0], result[1]) - if article != []: - search_result[result[0]].append(article) - sorted_search_result = {feed_id: sorted(articles, key=lambda t: t['article_date'], reverse=True) \ - for feed_id, articles in search_result.items()} - tmpl = lookup.get_template("search.html") - return tmpl.render(search_result=sorted_search_result, query=query, feed_id=feed_id, mongo=self.mongo) - - search.exposed = True - - @auth.require() - def fetch(self, param=None): - """ - Fetch all feeds. - """ - feed_link = None - if None != param: - # Fetch only the feed specified in parameter - feed_link = self.mongo.get_feed(param)["feed_link"] - feed_getter = feedgetter.FeedGetter() - feed_getter.retrieve_feed(feed_url=feed_link) - return self.index() - - fetch.exposed = True - - @auth.require() - def article(self, param, plain_text=0): - """ - Display the article in parameter in a new Web page. - """ - try: - feed_id, article_id = param.split(':') - article = self.mongo.get_articles(feed_id, article_id) - if article == []: - return self.error("<p>This article do not exists.</p>") - feed = self.mongo.get_feed(feed_id) - articles = self.mongo.get_articles(feed_id) - except: - return self.error("<p>Bad URL. This article do not exists.</p>") - - if article["article_readed"] == False: - # if the current article is not yet readed, update the database - self.mark_as_read("Article:"+article["article_id"]+":"+feed["feed_id"]) - - # Description (full content) of the article - if plain_text == "1": - description = "<p>" + utils.clear_string(article["article_content"]) + "</p>" - else: - description = article["article_content"] - if description == "": - description = "<p>No description available.</p>" - - # Generation of the QR Code for the current article - utils.generate_qr_code(article) - - # Previous and following articles - previous, following = None, None - liste = self.mongo.get_articles(feed_id) - for current_article in self.mongo.get_articles(feed_id): - next(articles) - if current_article["article_id"] == article_id: - break - following = current_article - if following is None: - following = liste[liste.count()-1] - try: - previous = next(articles) - except StopIteration: - previous = liste[0] - - tmpl = lookup.get_template("article.html") - return tmpl.render(header_text=article["article_title"], article=article, previous=previous, following=following, \ - diaspora=conf.DIASPORA_POD, feed=feed, description=description, plain_text=plain_text) - - article.exposed = True - - @auth.require() - def feed(self, feed_id, word_size=6): - """ - This page gives summary informations about a feed (number of articles, - unread articles, average activity, tag cloud, e-mail notification and - favourite articles for the current feed. - """ - try: - feed = self.mongo.get_feed(feed_id) - if feed == None: - return self.error("<p>This feed do not exists.</p>") - articles = self.mongo.get_articles(feed_id, limit=10) - nb_articles_feed = self.mongo.nb_articles(feed_id) - nb_articles_total = self.mongo.nb_articles() - nb_unread_articles_feed = self.mongo.nb_unread_articles(feed_id) - favorites = self.mongo.get_favorites(feed_id) - nb_favorites = self.mongo.nb_favorites(feed_id) - except KeyError: - return self.error("<p>This feed do not exists.</p>") - - - if articles.count() != 0: - today = datetime.datetime.now() - last_article = articles[0]["article_date"] - first_article = articles[self.mongo.nb_articles(feed_id)-2]["article_date"] - delta = last_article - first_article - elapsed = today - last_article - average = round(nb_articles_feed / abs(delta.days), 2) - - top_words = utils.top_words(articles = self.mongo.get_articles(feed_id), n=50, size=int(word_size)) - tag_cloud = utils.tag_cloud(top_words) - - tmpl = lookup.get_template("feed.html") - return tmpl.render(feed=feed, articles=articles, favorites=favorites, \ - nb_articles_feed=nb_articles_feed, nb_articles_total=nb_articles_total, nb_unread_articles_feed=nb_unread_articles_feed, \ - nb_favorites = nb_favorites, first_post_date=first_article, end_post_date=last_article, \ - average=average, delta=delta, elapsed=elapsed, \ - tag_cloud=tag_cloud, word_size=word_size, \ - mail_to=conf.mail_to, mail_notification_enabled=conf.MAIL_ENABLED) - - tmpl = lookup.get_template("feed.html") - return tmpl.render(feed=feed, articles=[]) - - feed.exposed = True - - @auth.require() - def articles(self, feed_id): - """ - This page displays all articles of a feed. - """ - try: - feed = self.mongo.get_feed(feed_id) - articles = self.mongo.get_articles(feed_id) - except KeyError: - return self.error("<p>This feed do not exists.</p>") - tmpl = lookup.get_template("articles.html") - return tmpl.render(articles=articles, feed=feed) - - articles.exposed = True - - @auth.require() - def unread(self, feed_id=""): - """ - This page displays all unread articles of a feed. - """ - feeds = self.mongo.get_all_feeds() - tmpl = lookup.get_template("unread.html") - return tmpl.render(feeds=feeds, feed_id=feed_id, mongo=self.mongo) - unread.exposed = True - - @auth.require() - def history(self, query="all", m=""): - """ - This page enables to browse articles chronologically. - """ - feeds = self.mongo.get_all_feeds() - tmpl = lookup.get_template("history.html") - return tmpl.render(feeds=feeds, mongo=self.mongo, query=query, m=m) - - history.exposed = True - - @auth.require() - def error(self, message): - """ - Display a message (bad feed id, bad article id, etc.) - """ - tmpl = lookup.get_template("error.html") - return tmpl.render(message=message) - - error.exposed = True - - @auth.require() - def mark_as_read(self, target=""): - """ - Mark one (or more) article(s) as read by setting the value of the field - 'article_readed' of the MongoDB database to 'True'. - """ - param, _, identifiant = target.partition(':') - - # Mark all articles as read. - if param == "": - self.mongo.mark_as_read(True, None, None) - # Mark all articles from a feed as read. - elif param == "Feed" or param == "Feed_FromMainPage": - self.mongo.mark_as_read(True, identifiant, None) - # Mark an article as read. - elif param == "Article": - self.mongo.mark_as_read(True, identifiant.split(':')[1], identifiant.split(':')[0]) - return self.index() - - mark_as_read.exposed = True - - @auth.require() - def notifications(self): - """ - List all active e-mail notifications. - """ - feeds = self.mongo.get_all_feeds(condition=("mail",True)) - tmpl = lookup.get_template("notifications.html") - return tmpl.render(feeds=feeds, mail_to=conf.mail_to, mail_notification_enabled=conf.MAIL_ENABLED) - - notifications.exposed = True - - @auth.require() - def mail_notification(self, param): - """ - Enable or disable to notifications of news for a feed. - """ - try: - action, feed_id = param.split(':') - new_value = 1 == int(action) - self.mongo.update_feed(feed_id, {"mail":new_value}) - except: - return self.error("<p>Bad URL. This feed do not exists.</p>") - return self.index() - - mail_notification.exposed = True - - @auth.require() - def like(self, param): - """ - Mark or unmark an article as favorites. - """ - try: - like, feed_id, article_id = param.split(':') - articles = self.mongo.get_articles(feed_id, article_id) - except: - return self.error("<p>Bad URL. This article do not exists.</p>") - self.mongo.like_article("1"==like, feed_id, article_id) - return self.article(feed_id+":"+article_id) - - like.exposed = True - - @auth.require() - def subscriptions(self): - """ - List all active e-mail notifications. - """ - feeds = self.mongo.get_all_feeds() - tmpl = lookup.get_template("subscriptions.html") - return tmpl.render(feeds=feeds) - - subscriptions.exposed = True - - @auth.require() - def favorites(self): - """ - List of favorites articles - """ - feeds = self.mongo.get_all_feeds() - articles = {} - for feed in feeds: - articles[feed["feed_id"]] = self.mongo.get_favorites(feed["feed_id"]) - tmpl = lookup.get_template("favorites.html") - return tmpl.render(feeds=feeds, \ - articles=articles) - - favorites.exposed = True - - @auth.require() - def inactives(self, nb_days=365): - """ - List of favorites articles - """ - feeds = self.mongo.get_all_feeds() - today = datetime.datetime.now() - inactives = [] - for feed in feeds: - more_recent_article = self.mongo.get_articles(feed["feed_id"], limit=1) - if more_recent_article.count() == 0: - last_post = datetime.datetime.fromtimestamp(time.mktime(time.gmtime(0))) - else: - last_post = next(more_recent_article)["article_date"] - elapsed = today - last_post - if elapsed > datetime.timedelta(days=int(nb_days)): - inactives.append((feed, elapsed)) - tmpl = lookup.get_template("inactives.html") - return tmpl.render(inactives=inactives, nb_days=int(nb_days)) - - inactives.exposed = True - - @auth.require() - def languages(self): - """ - Filter by languages. - """ - try: - from guess_language import guess_language_name - except: - tmpl = lookup.get_template("error.html") - return tmpl.render(message='<p>Module <i><a href="https://bitbucket.org/spirit/guess_language/">guess_language</a></i> not installed.</p>') - result = {} - feeds = self.mongo.get_all_feeds() - for feed in feeds: - for article in self.mongo.get_articles(feed["feed_id"]): - language = guess_language_name(utils.clear_string(article["article_content"])) - result.setdefault(language, defaultdict(list)) - result[language][feed["feed_id"]].append(article) - tmpl = lookup.get_template("languages.html") - return tmpl.render(articles_sorted_by_languages=result, mongo=self.mongo) - - languages.exposed = True - - @auth.require() - def add_feed(self, url): - """ - Add a new feed with the URL of a page. - """ - # search the feed in the HTML page with BeautifulSoup - feed_url = utils.search_feed(url) - if feed_url is None: - return self.error("<p>Impossible to find a feed at this URL.</p>") - # if a feed exists - else: - result = utils.add_feed(feed_url) - # if the feed is not in the file feed.lst - import hashlib - sha1_hash = hashlib.sha1() - sha1_hash.update(feed_url.encode('utf-8')) - feed_id = sha1_hash.hexdigest() - if result is False: - message = """<p>You are already following <a href="/feed/%s">this feed</a>!</p>""" % (feed_id,) - else: - message = """<p><a href="/feed/%s">Feed added</a>. You can now <a href="/fetch/">fetch your feeds</a>.</p>""" % (feed_id,) - tmpl = lookup.get_template("confirmation.html") - return tmpl.render(message=message) - - add_feed.exposed = True - - @auth.require() - def remove_feed(self, feed_id): - """ - Remove a feed from the file feed.lst and from the MongoDB database. - """ - feed = self.mongo.get_feed(feed_id) - self.mongo.delete_feed(feed_id) - utils.remove_feed(feed["feed_link"]) - message = """<p>All articles from the feed <i>%s</i> are now removed from the base.</p>""" % (feed["feed_title"],) - tmpl = lookup.get_template("confirmation.html") - return tmpl.render(message=message) - - remove_feed.exposed = True - - @auth.require() - def change_site_url(self, feed_id, old_site_url, new_site_url): - """ - Enables to change the URL of a site present in the database. - """ - try: - self.mongo.update_feed(feed_id, {"site_link":new_site_url}) - tmpl = lookup.get_template("confirmation.html") - return tmpl.render(message="<p>The URL of the site has been changed.</p>") - except: - return self.error("<p>Error when changing the URL of the site.</p>") - - change_site_url.exposed = True - - @auth.require() - def change_feed_url(self, feed_id, old_feed_url, new_feed_url): - """ - Enables to change the URL of a feed already present in the database. - """ - import hashlib - sha1_hash = hashlib.sha1() - sha1_hash.update(new_feed_url.encode('utf-8')) - new_feed_id = sha1_hash.hexdigest() - self.mongo.update_feed(feed_id, {"feed_id":new_feed_id, - "feed_link":new_feed_url}) - result = utils.change_feed_url(old_feed_url, new_feed_url) - if result: - tmpl = lookup.get_template("confirmation.html") - return tmpl.render(message="<p>The URL of the feed has been changed.</p>") - else: - return self.error("<p>Error when changing the URL of the feed.</p>") - - change_feed_url.exposed = True - - @auth.require() - def change_feed_name(self, feed_id, new_feed_name): - """ - Enables to change the name of a feed. - """ - try: - self.mongo.update_feed(feed_id, {"feed_title":new_feed_name}) - tmpl = lookup.get_template("confirmation.html") - return tmpl.render(message="<p>The name of the feed has been changed.</p>") - except: - return self.error("<p>Error when changing the name of the feed.</p>") - - change_feed_name.exposed = True - - @auth.require() - def change_feed_logo(self, feed_id, new_feed_logo): - """ - Enables to change the name of a feed. - """ - try: - self.mongo.update_feed(feed_id, {"feed_image":new_feed_logo}) - tmpl = lookup.get_template("confirmation.html") - return tmpl.render(message="<p>The logo of the feed has been changed.</p>") - except: - return self.error("<p>Error when changing the logo of the feed.</p>") - - change_feed_logo.exposed = True - - @auth.require() - def change_username(self, new_username): - """ - Enables to change the username of a user. - """ - result = auth.change_username(self.auth.username, new_username) - if result: - self.auth.username = new_username - tmpl = lookup.get_template("confirmation.html") - return tmpl.render(message="<p>Your username has been changed.</p>") - else: - return self.error("<p>Impossible to change the username.</p>") - - change_username.exposed = True - - @auth.require() - def change_password(self, new_password): - """ - Enables to change the password of a user. - """ - result = auth.change_password(self.auth.username, new_password) - if result: - tmpl = lookup.get_template("confirmation.html") - return tmpl.render(message="<p>Your password has been changed.</p>") - else: - return self.error("<p>Impossible to change the password.</p>") - - change_password.exposed = True - - @auth.require() - def delete_article(self, param): - """ - Delete an article. - """ - try: - feed_id, article_id = param.split(':') - # Delete from the MonfoDB database - self.mongo.delete_article(feed_id, article_id) - # Delete from the Whoosh index - search.delete_article(feed_id, article_id) - except: - return self.error("<p>Bad URL. This article do not exists.</p>") - - return self.index() - - delete_article.exposed = True - - @auth.require() - def logout(self): - """ - Close the session. - """ - return self.auth.logout() - - logout.exposed = True - - @auth.require() - def drop_base(self): - """ - Delete all articles. - """ - self.mongo.drop_database() - return self.index() - - drop_base.exposed = True - - @auth.require() - def index_base(self): - """ - Launches the indexing of the database. - """ - search.create_index() - return self.index() - - index_base.exposed = True - - @auth.require() - def export(self, export_method): - """ - Export articles currently loaded from the MongoDB database with - the appropriate function of the 'export' module. - """ - getattr(export, export_method)(self.mongo) - try: - getattr(export, export_method)(self.mongo) - except Exception as e: - return self.error(e) - tmpl = lookup.get_template("confirmation.html") - return tmpl.render(message="<p>Export successfully terminated.<br />Check the folder: <b>" + conf.path + "/var/export/</b>.</p>") - - export.exposed = True - - @auth.require() - def epub(self, param): - """ - Export an article to EPUB. - """ - try: - from epub import ez_epub - except Exception as e: - return self.error(e) - try: - feed_id, article_id = param.split(':') - except: - return self.error("Bad URL.") - try: - feed_id, article_id = param.split(':') - feed = self.mongo.get_feed(feed_id) - articles = self.mongo.get_articles(feed_id) - article = self.mongo.get_articles(feed_id, article_id) - except: - self.error("<p>This article do not exists.</p>") - try: - folder = conf.path + "/var/export/epub/" - os.makedirs(folder) - except OSError: - # directories already exists (not a problem) - pass - section = ez_epub.Section() - section.title = article["article_title"] - section.paragraphs = [utils.clear_string(article["article_content"])] - ez_epub.makeBook(article["article_title"], [feed["feed_title"]], [section], \ - os.path.normpath(folder) + "article.epub", lang='en-US', cover=None) - return self.article(param) - - epub.exposed = True - - -if __name__ == '__main__': - # Point of entry in execution mode - root = pyAggr3g470r() - root.favicon_ico = cherrypy.tools.staticfile.handler(filename=os.path.join(conf.path + "/static/img/favicon.png")) - cherrypy.config.update({'error_page.404': error_404}) - cherrypy.quickstart(root, "/" ,config=conf.path + "/cfg/cherrypy.cfg") |