]> git.gir.st - subscriptionfeed.git/blob - app/reddit/__init__.py
paper over reddit exceptions
[subscriptionfeed.git] / app / reddit / __init__.py
1 import re
2 import sqlite3
3 from flask_login import current_user, login_required
4 from flask import Blueprint, render_template, request, redirect, flash
5
6 from ..common.common import *
7
8 frontend = Blueprint('reddit', __name__,
9 template_folder='templates',
10 static_folder='static',
11 static_url_path='/static/rd')
12
13 @frontend.route('/feed/subreddits')
14 @frontend.route('/r/<subreddit>')
15 def reddit(subreddit=None):
16 token = getattr(current_user, 'token', 'guest')
17 count = int(request.args.get('count', 0))
18 before = request.args.get('before')
19 after = request.args.get('after')
20
21 sortorder = request.args.get('s', "hot") # TODO: verify!
22 timerange = request.args.get('t', None) # TODO: verify!
23
24 all_subreddits = get_subreddits(token)
25 subreddits = [subreddit] if subreddit else all_subreddits
26
27 try:
28 data = fetch_reddit(subreddits,
29 sorted_by=sortorder, time=timerange, limit=36,
30 count=count, before=before, after=after)
31 videos = parse_reddit_videos(data)
32 before = data['data']['before']
33 after = data['data']['after']
34 except RedditException as e:
35 return f"error retrieving reddit data: <xmp>{e}</xmp>", 502 # TODO: better
36
37 # set pin/hide stati of retrieved videos
38 with sqlite3.connect(cf['global']['database']) as conn:
39 c = conn.cursor()
40 c.execute("""
41 SELECT post_id,display,video_id,title,subreddit
42 FROM reddit_flags
43 LEFT JOIN reddit_posts ON reddit_posts.id = reddit_flags.post_id
44 WHERE user = ?
45 AND display IS NOT NULL
46 """, (token,))
47 flags = c.fetchall()
48
49 # on user's feed: show all pinned videos
50 # on /r/...: only show pinned videos that are on the page anyways
51 if not subreddit: # only on /feed/reddit
52 videos = [ # prepend pinned posts
53 {
54 'video_id': video_id,
55 'title': title,
56 'url': f"/r/{sr}/comments/{post_id}/",
57 'n_comments': '?',
58 'n_karma': '?',
59 'subreddit': sr,
60 'post_id': post_id,
61 'pinned': True,
62 }
63 for post_id,display,video_id,title,sr in flags
64 if display == 'pinned'
65 ] + [ # followed by non-pinned, non-hidden videos
66 v for v in videos
67 if v['post_id'] not in [post for post,_,_,_,_ in flags]
68 ]
69 else: # on /r/...
70 pinned = [post for post,disp,_,_,_ in flags if disp == 'pinned']
71 hidden = [post for post,disp,_,_,_ in flags if disp == 'hidden']
72 videos = [
73 {**v, 'pinned': v['post_id'] in pinned}
74 for v in videos
75 if v['post_id'] not in hidden
76 ]
77
78 title = f"/r/{subreddit}" if subreddit else "my subreddits"
79 return render_template('reddit.html.j2', title=title, rows=videos,
80 subreddits=all_subreddits, before=before, after=after, count=count)
81
82 @frontend.route('/manage/subreddits')
83 # disabled for guest user: @login_required
84 def subscription_manager():
85 token = getattr(current_user, 'token', 'guest')
86 subreddits = get_subreddits(token)
87 return render_template('subreddit_manager.html.j2', subreddits=subreddits)
88
89 @frontend.route('/feed/subreddits', methods=['POST'])
90 @frontend.route('/r/<subreddit>', methods=['POST'])
91 @login_required
92 def feed_post(subreddit=None):
93 token = current_user.token
94
95 action = next(request.form.keys(), None)
96 if action in ['pin', 'unpin', 'hide']:
97 post_id = request.form.get(action)
98 if not re.match(r"^[a-z0-9]+$", post_id):
99 return "invalid post id", 400
100
101 display = {
102 'pin': 'pinned',
103 'unpin': None,
104 'hide': 'hidden',
105 }[action]
106 with sqlite3.connect(cf['global']['database']) as conn:
107 c = conn.cursor()
108 # if the post was pinned, we need to be able to retrieve it
109 # independently from if it is shown in the feed. so we check if we
110 # know about it, or download and store it.
111 if action == "pin":
112 c.execute("""
113 SELECT count(*) FROM reddit_posts WHERE id = ?
114 """, (post_id,))
115 if c.fetchone()[0] < 1:
116 post = parse_reddit_videos(fetch_reddit_post(post_id))[0] # TODO: if exception, abort pinning
117 c.execute("""
118 INSERT INTO reddit_posts (id,subreddit,title,video_id)
119 VALUES (?,?,?,?)
120 """, (
121 post['post_id'],
122 post['subreddit'],
123 html.unescape(post['title']),
124 post['video_id']
125 ))
126
127 c.execute("""
128 INSERT OR REPLACE INTO reddit_flags (user, post_id, display)
129 VALUES (?, ?, ?)
130 """, (token, post_id, display))
131 else:
132 flash("unsupported action", "error")
133 return redirect(request.url, code=303)
134
135 @frontend.route('/manage/subreddits', methods=['POST'])
136 @login_required
137 def manage_subscriptions():
138 token = current_user.token
139 if 'subscribe' in request.form:
140 subreddit = request.form.get("subscribe")
141 match = re.search(r"(?:(?:https?://)?(?:old.|www.|\w\w.)?reddit.com)?(?:/?r/)?([-+_0-9A-Za-z]{2,21})", subreddit)
142 if match:
143 subreddit = match.group(1)
144 else:
145 flash("invalid subreddit", "error")
146 return redirect(request.url, code=303)
147 with sqlite3.connect(cf['global']['database']) as conn:
148 c = conn.cursor()
149 c.execute("""
150 INSERT OR IGNORE INTO subreddits (user, subreddit)
151 VALUES (?, ?)
152 """, (token, subreddit))
153
154 elif 'unsubscribe' in request.form:
155 subreddit = request.form.get("unsubscribe")
156 with sqlite3.connect(cf['global']['database']) as conn:
157 c = conn.cursor()
158 c.execute("""
159 DELETE FROM subreddits
160 WHERE user = ? AND subreddit = ?
161 """, (token, subreddit))
162 # TODO: sql-error-handling, report success
163
164 else:
165 flash("unsupported action", "error")
166
167 return redirect(request.url, code=303)
168
169 def get_subreddits(token):
170 with sqlite3.connect(cf['global']['database']) as conn:
171 c = conn.cursor()
172 c.execute("""
173 SELECT subreddit
174 FROM subreddits
175 WHERE user = ?
176 ORDER BY subreddit COLLATE NOCASE ASC
177 """, (token,))
178 subreddits = [sr for (sr,) in c.fetchall()]
179 return subreddits
180
181 @frontend.app_template_filter('trim3')
182 def trim3(n):
183 if type(n) != int:
184 return n # not a number
185 elif round(n, 1) >= 10_000:
186 return "%.0fk" % (n/1000)
187 elif n >= 1_000:
188 return "%.1fk" % (n/1000)
189 else:
190 return "%d" % n
Imprint / Impressum