#!/usr/bin/env python3 import sqlite3 import base64 from copy import copy from datetime import datetime from collections import defaultdict from flask import Flask, render_template, jsonify, request, make_response DB_PATH = "/home/mcrute/.newsbeuter/cache.db" app = Flask(__name__) class DataObject(object): @classmethod def from_db_row(cls, cursor, row): return cls(*row) def _update_json(self, data): return def to_json(self): data = copy(self.__dict__) self._update_json(data) data['self'] = self.self_link return data def __repr__(self): return '{self.__class__.__name__}({self.id!r}, {self.title!r})'.format( self=self) @property def self_link(self): return self.URL.format(self=self) class RSSItem(DataObject): URL = '/item/{self.id}' def __init__(self, id, title, author, feed_title, feed_url, url, publish_date, content, read): self.id = id self.feed = RSSFeed(feed_url, None, feed_title) self.title = title self.author = author self.url = url self.publish_date = datetime.fromtimestamp(publish_date) self.content = content self.read = not bool(read) self.no_content = len(content) < 100 def _update_json(self, data): data['feed'] = self.feed.self_link return data class RSSFeed(DataObject): URL = '/feed/{self.id}' def __init__(self, feed_url, url, title): self.feed_url = feed_url self.url = url self.title = title def _update_json(self, data): data['items'] = self.items data['unread_items'] = self.unread_items @property def items(self): return '/feed/{}/items'.format(self.id) @property def unread_items(self): return '/feed/{}/items/unread'.format(self.id) @property def id(self): return base64.b64encode( self.feed_url.encode('utf-8')).decode('utf-8').strip('=') @staticmethod def parse_token(token): return base64.b64decode( '{}========'.format(token).encode('utf-8')).decode('utf-8') class UnreadRSSFeed(RSSFeed): def __init__(self, feed_url, url, title, unread_count): super(UnreadRSSFeed, self).__init__(feed_url, url, title) self.unread_count = unread_count class DBReader(object): BASE_QUERY = ''' SELECT i.id, i.title, i.author, f.title, f.rssurl, i.url, i.pubDate, i.content, i.unread FROM rss_item i INNER JOIN rss_feed f ON f.rssurl = i.feedurl {where} {where_cond} ORDER BY i.pubDate desc ''' UNREAD_FEEDS = ''' SELECT f.rssurl, f.url, f.title, count(*) as unread_count FROM rss_item i LEFT JOIN rss_feed f ON f.rssurl = i.feedurl WHERE i.unread = 1 GROUP BY i.feedurl ''' def __init__(self, db_path): self.con = sqlite3.connect(db_path) self.con.row_factory = RSSItem.from_db_row def close(self): return self.con.close() def _fetch(self, where=None, params=()): with self.con as con: curs = con.cursor() args = { 'where': '', 'where_cond': '' } if where: args.update({ 'where': 'WHERE', 'where_cond': where }) curs.execute(self.BASE_QUERY.format(**args), params) return curs.fetchall() def update_unread(self, id, unread=True): with self.con as con: con.execute( 'UPDATE rss_item SET unread = ? WHERE id = ?', [1 if unread else 0, id]) con.commit() def update_feed_unread(self, token, unread=True): with self.con as con: con.execute( 'UPDATE rss_item SET unread = ? WHERE feedurl = ?', [1 if unread else 0, RSSFeed.parse_token(token)]) con.commit() def get_entry(self, id): return self._fetch("i.id = ?", [id])[0] def get_unread(self): data = defaultdict(list) return self._fetch("i.unread = ?", [1]) def get_unread_feeds(self): with self.con as con: con.row_factory = UnreadRSSFeed.from_db_row curs = con.cursor() curs.execute(self.UNREAD_FEEDS) return curs.fetchall() def get_unread_for_feed(self, token, only_unread=False): if only_unread: return self._fetch("i.unread = ? AND i.feedurl = ?", [1, RSSFeed.parse_token(token)]) else: return self._fetch("i.feedurl = ?", [RSSFeed.parse_token(token)]) def get_feeds(self): with self.con as con: con.row_factory = RSSFeed.from_db_row curs = con.cursor() curs.execute('SELECT rssurl, url, title FROM rss_feed') return curs.fetchall() def get_feed(self, token): with self.con as con: con.row_factory = RSSFeed.from_db_row curs = con.cursor() curs.execute('SELECT rssurl, url, title FROM rss_feed WHERE rssurl = ?', [RSSFeed.parse_token(token)]) return curs.fetchall()[0] def json_list(data): return [i.to_json() for i in data] @app.route('/') def index(): return render_template('items.html') @app.route('/feed') def feed_list(): reader = DBReader(DB_PATH) return jsonify({ 'feeds': json_list(reader.get_feeds()) }) @app.route('/feed/unread') def unread_feed_list(): reader = DBReader(DB_PATH) return jsonify({ 'feeds': json_list(reader.get_unread_feeds()) }) @app.route('/feed/') def feed(token): reader = DBReader(DB_PATH) return jsonify(reader.get_feed(token).to_json()) @app.route('/feed//items') def feed_items(token): reader = DBReader(DB_PATH) unread = reader.get_unread_for_feed(token) return jsonify({ 'items': json_list(unread), "count": len(unread) }) @app.route('/feed//items/unread', methods=["GET", "POST"]) def unread_feed_items(token): reader = DBReader(DB_PATH) if request.method == 'POST': try: read = bool(int(request.form.get('read'))) except: return make_response('', 400) reader.update_feed_unread(token, not read) unread = reader.get_unread_for_feed(token, True) return jsonify({ 'items': json_list(unread), "count": len(unread) }) @app.route('/item/unread') def unread_item_list(): reader = DBReader(DB_PATH) return jsonify({ 'items': json_list(reader.get_unread()) }) @app.route("/item/", methods=["GET", "POST"]) def item(entry_id): #post read=1 reader = DBReader(DB_PATH) if request.method == 'POST': try: read = bool(int(request.form.get('read'))) except: return make_response('', 400) reader.update_unread(entry_id, not read) return make_response('', 204) else: return jsonify(reader.get_entry(entry_id).to_json()) if __name__ == '__main__': app.debug = True app.run()