mirror of
https://codeberg.org/rimu/pyfedi
synced 2025-01-23 11:26:56 -08:00
rss feeds on communities
This commit is contained in:
parent
5b6c9f39b2
commit
ac8a229475
9 changed files with 79 additions and 5 deletions
|
@ -199,7 +199,7 @@ def community_profile(actor):
|
|||
if '@' in actor:
|
||||
# don't provide activitypub info for remote communities
|
||||
if 'application/ld+json' in request.headers.get('Accept', '') or 'application/activity+json' in request.headers.get('Accept', ''):
|
||||
abort(404)
|
||||
abort(400)
|
||||
community: Community = Community.query.filter_by(ap_id=actor, banned=False).first()
|
||||
else:
|
||||
community: Community = Community.query.filter_by(name=actor, banned=False, ap_id=None).first()
|
||||
|
|
|
@ -19,8 +19,8 @@ from app.utils import get_setting, render_template, allowlist_html, markdown_to_
|
|||
shorten_string, markdown_to_text, domain_from_url, validate_image, gibberish, community_membership, ap_datetime, \
|
||||
request_etag_matches, return_304
|
||||
import os
|
||||
from PIL import Image, ImageOps
|
||||
from datetime import datetime
|
||||
from feedgen.feed import FeedGenerator
|
||||
from datetime import timezone
|
||||
|
||||
|
||||
@bp.route('/add_local', methods=['GET', 'POST'])
|
||||
|
@ -117,7 +117,59 @@ def show_community(community: Community):
|
|||
return render_template('community/community.html', community=community, title=community.title,
|
||||
is_moderator=is_moderator, is_owner=is_owner, mods=mod_list, posts=posts, description=description,
|
||||
og_image=og_image, POST_TYPE_IMAGE=POST_TYPE_IMAGE, POST_TYPE_LINK=POST_TYPE_LINK, SUBSCRIPTION_PENDING=SUBSCRIPTION_PENDING,
|
||||
SUBSCRIPTION_MEMBER=SUBSCRIPTION_MEMBER, etag=f"{community.id}_{hash(community.last_active)}")
|
||||
SUBSCRIPTION_MEMBER=SUBSCRIPTION_MEMBER, etag=f"{community.id}_{hash(community.last_active)}",
|
||||
rss_feed=f"https://{current_app.config['SERVER_NAME']}/community/{community.link()}/feed", rss_feed_name=f"{community.title} posts on PieFed")
|
||||
|
||||
|
||||
# RSS feed of the community
|
||||
@bp.route('/<actor>/feed', methods=['GET'])
|
||||
@cache.cached(timeout=600)
|
||||
def show_community_rss(actor):
|
||||
actor = actor.strip()
|
||||
if '@' in actor:
|
||||
community: Community = Community.query.filter_by(ap_id=actor, banned=False).first()
|
||||
else:
|
||||
community: Community = Community.query.filter_by(name=actor, banned=False, ap_id=None).first()
|
||||
if community is not None:
|
||||
# If nothing has changed since their last visit, return HTTP 304
|
||||
current_etag = f"{community.id}_{hash(community.last_active)}"
|
||||
if request_etag_matches(current_etag):
|
||||
return return_304(current_etag, 'application/rss+xml')
|
||||
|
||||
posts = community.posts.filter(Post.from_bot == False).order_by(desc(Post.created_at)).limit(100).all()
|
||||
description = shorten_string(community.description, 150) if community.description else None
|
||||
og_image = community.image.source_url if community.image_id else None
|
||||
fg = FeedGenerator()
|
||||
fg.id(f"https://{current_app.config['SERVER_NAME']}/c/{actor}")
|
||||
fg.title(community.title)
|
||||
fg.link(href=f"https://{current_app.config['SERVER_NAME']}/c/{actor}", rel='alternate')
|
||||
if og_image:
|
||||
fg.logo(og_image)
|
||||
else:
|
||||
fg.logo(f"https://{current_app.config['SERVER_NAME']}/static/images/apple-touch-icon.png")
|
||||
if description:
|
||||
fg.subtitle(description)
|
||||
else:
|
||||
fg.subtitle(' ')
|
||||
fg.link(href=f"https://{current_app.config['SERVER_NAME']}/c/{actor}/feed", rel='self')
|
||||
fg.language('en')
|
||||
|
||||
for post in posts:
|
||||
fe = fg.add_entry()
|
||||
fe.title(post.title)
|
||||
fe.link(href=f"https://{current_app.config['SERVER_NAME']}/post/{post.id}")
|
||||
fe.description(post.body_html)
|
||||
fe.guid(post.profile_id(), permalink=True)
|
||||
fe.author(name=post.author.user_name)
|
||||
fe.pubDate(post.created_at.replace(tzinfo=timezone.utc))
|
||||
|
||||
response = make_response(fg.rss_str())
|
||||
response.headers.set('Content-Type', 'application/rss+xml')
|
||||
response.headers.add_header('ETag', f"{community.id}_{hash(community.last_active)}")
|
||||
response.headers.add_header('Cache-Control', 'no-cache, max-age=600, must-revalidate')
|
||||
return response
|
||||
else:
|
||||
abort(404)
|
||||
|
||||
|
||||
@bp.route('/<actor>/subscribe', methods=['GET'])
|
||||
|
|
|
@ -186,6 +186,10 @@
|
|||
content: "\e91e";
|
||||
}
|
||||
|
||||
.fe-rss::before {
|
||||
content: "\e9be";
|
||||
}
|
||||
|
||||
.fe-image {
|
||||
position: relative;
|
||||
top: 2px;
|
||||
|
|
|
@ -189,6 +189,10 @@ nav, etc which are used site-wide */
|
|||
content: "\e91e";
|
||||
}
|
||||
|
||||
.fe-rss::before {
|
||||
content: "\e9be";
|
||||
}
|
||||
|
||||
.fe-image {
|
||||
position: relative;
|
||||
top: 2px;
|
||||
|
|
|
@ -188,6 +188,10 @@
|
|||
content: "\e91e";
|
||||
}
|
||||
|
||||
.fe-rss::before {
|
||||
content: "\e9be";
|
||||
}
|
||||
|
||||
.fe-image {
|
||||
position: relative;
|
||||
top: 2px;
|
||||
|
|
|
@ -48,6 +48,9 @@
|
|||
{% if og_image %}
|
||||
<meta property="og:image" content="{{ og_image }}" />
|
||||
{% endif %}
|
||||
{% if rss_feed %}
|
||||
<link rel="alternate" type="application/rss+xml" title="{{ rss_feed_name }}" href="{{ rss_feed }}" />
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
</head>
|
||||
<body class="d-flex flex-column" style="padding-top: 78px;">
|
||||
|
|
|
@ -81,6 +81,9 @@
|
|||
{% endfor %}
|
||||
</ol>
|
||||
{% endif %}
|
||||
<p class="mt-4">
|
||||
<a class="no-underline" href="{{ rss_feed }}" rel="nofollow"><span class="fe fe-rss"></span> RSS feed</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{% if is_moderator %}
|
||||
|
|
|
@ -37,16 +37,19 @@ def render_template(template_name: str, **context) -> Response:
|
|||
|
||||
|
||||
def request_etag_matches(etag):
|
||||
print(str(request.headers))
|
||||
if 'If-None-Match' in request.headers:
|
||||
old_etag = request.headers['If-None-Match']
|
||||
return old_etag == etag
|
||||
return False
|
||||
|
||||
|
||||
def return_304(etag):
|
||||
def return_304(etag, content_type=None):
|
||||
resp = make_response('', 304)
|
||||
resp.headers.add_header('ETag', request.headers['If-None-Match'])
|
||||
resp.headers.add_header('Cache-Control', 'no-cache, max-age=600, must-revalidate')
|
||||
if content_type:
|
||||
resp.headers.set('Content-Type', content_type)
|
||||
return resp
|
||||
|
||||
|
||||
|
|
|
@ -25,3 +25,4 @@ flask-caching==2.0.2
|
|||
Pillow
|
||||
pillow-heif
|
||||
opengraph-parse=0.0.6
|
||||
feedgen==0.9.0
|
||||
|
|
Loading…
Reference in a new issue