summaryrefslogtreecommitdiff
path: root/app.py
diff options
context:
space:
mode:
Diffstat (limited to 'app.py')
-rwxr-xr-xapp.py209
1 files changed, 209 insertions, 0 deletions
diff --git a/app.py b/app.py
new file mode 100755
index 0000000..755d55c
--- /dev/null
+++ b/app.py
@@ -0,0 +1,209 @@
1#!/usr/bin/env python3
2
3import sqlite3
4import base64
5from copy import copy
6from datetime import datetime
7from collections import defaultdict
8
9from flask import Flask, render_template, jsonify, request, make_response
10
11app = Flask(__name__)
12
13
14class DataObject(object):
15
16 @classmethod
17 def from_db_row(cls, cursor, row):
18 return cls(*row)
19
20 def _update_json(self, data):
21 return
22
23 def to_json(self):
24 data = copy(self.__dict__)
25 self._update_json(data)
26 data['self'] = self.self_link
27 return data
28
29 def __repr__(self):
30 return '{self.__class__.__name__}({self.id!r}, {self.title!r})'.format(
31 self=self)
32
33 @property
34 def self_link(self):
35 return self.URL.format(self=self)
36
37
38class RSSItem(DataObject):
39
40 URL = '/item/{self.id}'
41
42 def __init__(self, id, title, author, feed_title, feed_url, url,
43 publish_date, content, read):
44 self.id = id
45 self.feed_url = RSSFeed(feed_url, None, None).self_link
46 self.feed_title = feed_title
47 self.title = title
48 self.author = author
49 self.url = url
50 self.publish_date = datetime.fromtimestamp(publish_date)
51 self.content = content
52 self.read = not bool(read)
53 self.no_content = len(content) < 100
54
55
56class RSSFeed(DataObject):
57
58 URL = '/feed/{self.id}'
59
60 def __init__(self, feed_url, url, title):
61 self.feed_url = feed_url
62 self.url = url
63 self.title = title
64
65 def _update_json(self, data):
66 data['items'] = '/feed/{}/items'.format(self.id)
67
68 @property
69 def id(self):
70 return base64.b64encode(
71 self.feed_url.encode('utf-8')).decode('utf-8').strip('=')
72
73 @staticmethod
74 def parse_token(token):
75 return base64.b64decode(
76 '{}========'.format(token).encode('utf-8')).decode('utf-8')
77
78
79
80class DBReader(object):
81
82 BASE_QUERY = '''
83 SELECT
84 i.id, i.title, i.author, f.title,
85 f.rssurl, i.url, i.pubDate, i.content,
86 i.unread
87 FROM
88 rss_item i
89 INNER JOIN rss_feed f
90 ON f.rssurl = i.feedurl
91 {where}
92 {where_cond}
93 ORDER BY
94 i.pubDate desc
95 '''
96
97 def __init__(self, db_path):
98 self.db_path = db_path
99
100 def _get_connection(self):
101 conn = sqlite3.connect(self.db_path)
102 conn.row_factory = RSSItem.from_db_row
103 return conn
104
105 def _fetch(self, where=None, params=()):
106 with self._get_connection() as con:
107 curs = con.cursor()
108 args = { 'where': '', 'where_cond': '' }
109
110 if where:
111 args.update({ 'where': 'WHERE', 'where_cond': where })
112
113 curs.execute(self.BASE_QUERY.format(**args), params)
114 return curs.fetchall()
115
116 def update_unread(self, id, unread=True):
117 with self._get_connection() as con:
118 con.execute(
119 'UPDATE rss_item SET unread = ? WHERE id = ?',
120 [1 if unread else 0, id])
121 con.commit()
122
123 def get_entry(self, id):
124 return self._fetch("i.id = ?", [id])[0]
125
126 def get_unread(self):
127 data = defaultdict(list)
128 unread = self._fetch("i.unread = ?", [1])
129
130 for record in unread:
131 data[record.feed_title].append(record)
132
133 return sorted(data.items())
134
135 def get_unread_for_feed(self, token, only_unread=False):
136 if only_unread:
137 return self._fetch("i.unread = ? AND i.feedurl = ?", [1, RSSFeed.parse_token(token)])
138 else:
139 return self._fetch("i.feedurl = ?", [RSSFeed.parse_token(token)])
140
141 def get_feeds(self):
142 with self._get_connection() as con:
143 con.row_factory = RSSFeed.from_db_row
144 curs = con.cursor()
145 curs.execute('SELECT rssurl, url, title FROM rss_feed')
146 return curs.fetchall()
147
148 def get_feed(self, token):
149 with self._get_connection() as con:
150 con.row_factory = RSSFeed.from_db_row
151 curs = con.cursor()
152 curs.execute('SELECT rssurl, url, title FROM rss_feed WHERE rssurl = ?', [RSSFeed.parse_token(token)])
153 return curs.fetchall()[0]
154
155
156def json_list(data):
157 return [i.to_json() for i in data]
158
159
160@app.route('/')
161def index():
162 reader = DBReader('../../cache.db')
163 return render_template('items.html', items=reader.get_unread())
164
165
166@app.route('/feed/')
167def feed_list():
168 reader = DBReader('../../cache.db')
169 return jsonify({ 'feeds': json_list(reader.get_feeds()) })
170
171
172@app.route('/feed/<token>')
173def feed(token):
174 reader = DBReader('../../cache.db')
175 return jsonify(reader.get_feed(token).to_json())
176
177
178@app.route('/feed/<token>/items')
179def feed_items(token):
180 reader = DBReader('../../cache.db')
181 return jsonify({ 'items': json_list(reader.get_unread_for_feed(token)) })
182
183
184@app.route('/feed/<token>/items/unread')
185def unread_feed_items(token):
186 reader = DBReader('../../cache.db')
187 return jsonify({ 'items': json_list(reader.get_unread_for_feed(token, True)) })
188
189
190@app.route("/item/<int:entry_id>", methods=["GET", "POST"])
191def item(entry_id):
192 #post read=1
193 reader = DBReader('../../cache.db')
194
195 if request.method == 'POST':
196 try:
197 read = bool(int(request.form.get('read')))
198 except:
199 return make_response('', 400)
200
201 reader.update_unread(entry_id, not read)
202 return make_response('', 204)
203 else:
204 return jsonify(reader.get_entry(entry_id).to_json())
205
206
207if __name__ == '__main__':
208 app.debug = True
209 app.run()