From eda06e15f851eceb79eef97f2cda3022fc27314e Mon Sep 17 00:00:00 2001 From: girst Date: Wed, 1 May 2024 20:28:51 +0000 Subject: [PATCH] reddit: add client side reddit client helps when the instance's ip is banned by reddit. this is a very hacky implementation, but good enough for this godforsaken website. --- app/reddit/__init__.py | 7 ++- app/reddit/static/client.js | 78 +++++++++++++++++++++++++++++ app/reddit/templates/reddit.html.j2 | 32 ++++++++++++ config/config.ini | 8 +++ 4 files changed, 123 insertions(+), 2 deletions(-) create mode 100644 app/reddit/static/client.js diff --git a/app/reddit/__init__.py b/app/reddit/__init__.py index 4b2eb77..236b3a2 100644 --- a/app/reddit/__init__.py +++ b/app/reddit/__init__.py @@ -17,6 +17,7 @@ frontend = Blueprint('reddit', __name__, def reddit(subreddit=None): token = getattr(current_user, 'token', 'guest') after = request.args.get('after') + use_client_side_client = cf.getboolean('reddit', 'use_client_side_client', fallback=False) sortorder = request.args.get('s', "hot") timerange = request.args.get('t', None) @@ -32,7 +33,7 @@ def reddit(subreddit=None): subreddits = subreddit.split("+") if subreddit else all_subreddits title = f"/r/{subreddit}" if subreddit else "my subreddits" - if subreddits: + if not use_client_side_client and subreddits: try: data = fetch_reddit(subreddits, sorted_by=sortorder, time=timerange, limit=36, after=after) @@ -49,10 +50,12 @@ def reddit(subreddit=None): for v in videos if v['video_id'] not in hidden and v['n_karma']>0 ], key=lambda v:v['pinned'], reverse=True) - else: # not subscribed to anything + else: # not subscribed to anything (or client side client) videos = [] + next_page = None return render_template('reddit.html.j2', title=title, rows=videos, + client_side_client=use_client_side_client, subreddit=subreddits, subreddits=all_subreddits, next_page=next_page) @frontend.route('/manage/subreddits') diff --git a/app/reddit/static/client.js b/app/reddit/static/client.js new file mode 100644 index 0000000..60064ea --- /dev/null +++ b/app/reddit/static/client.js @@ -0,0 +1,78 @@ +function template(tpl, replacements) { + function safetify(s) { + // decodes and re-encodes html entities + let e = document.createElement("textarea"); + e.innerHTML = s; + e.innerText = e.value; + return e.innerHTML; + } + + for (let key in replacements) { + tpl = tpl.replaceAll("{{"+key+"}}", safetify(replacements[key])); + } + return tpl; +} + +function load_page(subreddits, {sorted_by='hot', time='', limit=36, count='', before='', after=''}) { + + let multireddit = subreddits.join("+"); + let query = new URLSearchParams({ + count, before, after, limit, + t: time, // hour,week,month,year,all + }).toString(); + + fetch_jsonp(`https://www.reddit.com/r/${multireddit}/${sorted_by}.json?${query}`).then(json => { + for (entry of json.data.children) { + let e = entry.data; + if (e.score == 0) continue; /*more downvotes than upvotes, ignore*/ + if (!['youtube.com', 'youtu.be', 'youtube-nocookie.com'].includes(e.domain)) continue; + let match = e.url.match(/^https?:\/\/(?:www.|m.)?(?:youtube.com\/watch\?(?:.*&)?v=|youtu.be\/|youtube(?:-nocookie)?.com\/(?:embed|shorts|live)\/|youtube.com\/)([-_0-9A-Za-z]+)(?:[?&#]t=([0-9hms:]+))?/); + if (!match) continue; + let video_id = match[1], timestamp = match[2], maybe_length=null; + match = e.title.match(/.*[\[(](?:00:)?(\d\d?(?::\d\d){1,2})[\])]/); + if (match) { + maybe_length = match[1]; + // 20:59:00 => 20:59 (we're assuming no video is >10h) + maybe_length = maybe_length.replace(/([1-9]\d:\d\d):00/, "$1") + } + document.querySelector('#cards').innerHTML += template(window.card.innerHTML, { + karma: e.score, + title: e.title, + url: e.permalink, + videoid: video_id, + length: maybe_length||'', + timestamp: timestamp||'', + comments: e.num_comments, + subreddit: e.subreddit, + }); + } + document.querySelector('#cards').innerHTML += window.dummycards.innerHTML; + document.querySelector('.pagination-container').innerHTML = window.pagination.innerHTML + .replaceAll(/%7B%7Bafter%7D%7D/g, json.data.after); + }).catch(e=>{ + console.error(e); + document.querySelector('#cards').innerHTML = ` + + `; + }); +} + +/* the following code has been adapted from https://xkcd.wtf (AGPLv3) */ +let __fetch_jsonp_index = 0; +let abort = new AbortController(); +function fetch_jsonp(url, cbparam = 'jsonp') { + abort.abort(); //cancel previous request + abort = new AbortController(); //re-init aborter + return new Promise((resolve, reject) => { + let tmp = '__fetch_jsonp_'+__fetch_jsonp_index++; + let s = document.createElement("script"); + s.src = url + '&' + cbparam + '=' + tmp; + s.onerror = err => reject(err); + window[tmp] = json => resolve(json); + abort.signal.addEventListener('abort', ()=> { + s.src=""; + reject(new DOMException('Aborted', 'AbortError')) + }); + document.body.appendChild(s); + }); +} diff --git a/app/reddit/templates/reddit.html.j2 b/app/reddit/templates/reddit.html.j2 index c7c598b..c3fed14 100644 --- a/app/reddit/templates/reddit.html.j2 +++ b/app/reddit/templates/reddit.html.j2 @@ -24,6 +24,9 @@ top (
+{% if client_side_client %} + +{% else %} {% for row in rows %} {% set karma = '↑' ~ row.n_karma|trim3 %} {% set alt = row.n_karma~' karma, '~row.n_comments~' comments' %} @@ -31,13 +34,42 @@ top ( /r/{{ row.subreddit | lower | e }} + {% endcall %} {% endfor %} {{ macros.dummycard() }} +{% endif %}
{{ macros.more({'after':next_page}) if next_page }}
+ +{% if client_side_client %} + + + + + + +{% endif %} + {% endblock %} {% block footer %} diff --git a/config/config.ini b/config/config.ini index 2342eed..b49b0c4 100644 --- a/config/config.ini +++ b/config/config.ini @@ -36,6 +36,14 @@ public_uri = http://delta.gir.st:8801 # modules in the [frontend] section): require_auth = yes +[reddit] +# there are two ways the reddit client can work. by default, reddit posts are +# fetched by the server itself. this has the advantage that we can honor pinned +# and hidden videos, when they appear in the feed. however, making a lot of +# requests to reddit will probably get your IP banned by reddit. hence, the +# plugin can also fetch posts via javascript on the user's browser: +use_client_side_client = false + [websub] # real-time updates of subscriptions. -- 2.39.3