From 518f165c1f2363b7c9b8c9d41ef7339dd1e4c0db Mon Sep 17 00:00:00 2001 From: rimu <3310831+rimu@users.noreply.github.com> Date: Sun, 20 Oct 2024 20:21:30 +1300 Subject: [PATCH] post-new: image posts --- app/community/routes.py | 52 +++++++++++++++++++++++++++++++++++------ app/models.py | 20 ++++------------ 2 files changed, 50 insertions(+), 22 deletions(-) diff --git a/app/community/routes.py b/app/community/routes.py index 0e4811e3..b037e408 100644 --- a/app/community/routes.py +++ b/app/community/routes.py @@ -1,12 +1,16 @@ +import base64 +import os from collections import namedtuple from io import BytesIO from random import randint import flask +from PIL import Image, ImageOps from flask import redirect, url_for, flash, request, make_response, session, Markup, current_app, abort, g, json, \ jsonify from flask_login import current_user, login_required from flask_babel import _ +from pillow_heif import register_heif_opener from slugify import slugify from sqlalchemy import or_, desc, text @@ -21,7 +25,8 @@ from app.community.forms import SearchRemoteCommunity, CreateDiscussionForm, Cre EditCommunityWikiPageForm from app.community.util import search_for_community, actor_to_community, \ save_post, save_icon_file, save_banner_file, send_to_remote_instance, \ - delete_post_from_community, delete_post_reply_from_community, community_in_list, find_local_users, tags_from_string + delete_post_from_community, delete_post_reply_from_community, community_in_list, find_local_users, tags_from_string, \ + allowed_extensions, end_poll_date from app.constants import SUBSCRIPTION_MEMBER, SUBSCRIPTION_OWNER, POST_TYPE_LINK, POST_TYPE_ARTICLE, POST_TYPE_IMAGE, \ SUBSCRIPTION_PENDING, SUBSCRIPTION_MODERATOR, REPORT_STATE_NEW, REPORT_STATE_ESCALATED, REPORT_STATE_RESOLVED, \ REPORT_STATE_DISCARDED, POST_TYPE_VIDEO, NOTIF_COMMUNITY, POST_TYPE_POLL, MICROBLOG_APPS @@ -38,7 +43,7 @@ from app.utils import get_setting, render_template, allowlist_html, markdown_to_ joined_communities, moderating_communities, blocked_domains, mimetype_from_url, blocked_instances, \ community_moderators, communities_banned_from, show_ban_message, recently_upvoted_posts, recently_downvoted_posts, \ blocked_users, post_ranking, languages_for_form, english_language_id, menu_topics, add_to_modlog, \ - blocked_communities, remove_tracking_from_link, piefed_markdown_to_lemmy_markdown + blocked_communities, remove_tracking_from_link, piefed_markdown_to_lemmy_markdown, ensure_directory_exists from feedgen.feed import FeedGenerator from datetime import timezone, timedelta from copy import copy @@ -581,8 +586,8 @@ def add_post(actor, type): return show_ban_message() community = actor_to_community(actor) + post_type = POST_TYPE_ARTICLE if type == 'discussion': - post_type = POST_TYPE_ARTICLE form = CreateDiscussionForm() elif type == 'link': post_type = POST_TYPE_LINK @@ -630,6 +635,7 @@ def add_post(actor, type): 'id': None, 'object': { 'name': form.title.data, + 'type': 'Page', 'sticky': form.sticky.data, 'nsfw': form.nsfw.data, 'nsfl': form.nsfl.data, @@ -641,11 +647,42 @@ def add_post(actor, type): } } if type == 'link': - request_json['object']['attachment'] = {'type': 'Link', 'href': form.link_url.data} + request_json['object']['attachment'] = [{'type': 'Link', 'href': form.link_url.data}] elif type == 'image': - request_json['object']['attachment'] = {'type': 'Image', 'url': image_url, 'name': form.image_alt_text} + uploaded_file = request.files['image_file'] + if uploaded_file and uploaded_file.filename != '': + # check if this is an allowed type of file + file_ext = os.path.splitext(uploaded_file.filename)[1] + if file_ext.lower() not in allowed_extensions: + abort(400, description="Invalid image type.") + + new_filename = gibberish(15) + # set up the storage directory + directory = 'app/static/media/posts/' + new_filename[0:2] + '/' + new_filename[2:4] + ensure_directory_exists(directory) + + final_place = os.path.join(directory, new_filename + file_ext) + uploaded_file.seek(0) + uploaded_file.save(final_place) + + if file_ext.lower() == '.heic': + register_heif_opener() + + Image.MAX_IMAGE_PIXELS = 89478485 + + # resize if necessary + img = Image.open(final_place) + if '.' + img.format.lower() in allowed_extensions: + img = ImageOps.exif_transpose(img) + + # limit full sized version to 2000px + img.thumbnail((2000, 2000)) + img.save(final_place) + + request_json['object']['attachment'] = [{'type': 'Image', 'url': f'https://{current_app.config["SERVER_NAME"]}/{final_place.replace("app/", "")}', + 'name': form.image_alt_text.data}] elif type == 'video': - request_json['object']['attachment'] = {'type': 'Document', 'url': form.video_url.data} + request_json['object']['attachment'] = [{'type': 'Document', 'url': form.video_url.data}] elif type == 'poll': request_json['object']['type'] = 'Question' choices = [form.choice_1, form.choice_2, form.choice_3, form.choice_4, form.choice_5, @@ -656,6 +693,7 @@ def add_post(actor, type): choice_data = choice.data.strip() if choice_data: request_json['object'][key].append({'name': choice_data}) + request_json['object']['endTime'] = end_poll_date(form.finish_in.data) # todo: add try..except post = Post.new(current_user, community, request_json) @@ -1950,7 +1988,7 @@ def check_url_already_posted(): def upvote_own_post(post): post.score = 1 post.up_votes = 1 - post.ranking = post_ranking(post.score, utcnow()) + post.ranking = post.post_ranking(post.score, utcnow()) vote = PostVote(user_id=current_user.id, post_id=post.id, author_id=current_user.id, effect=1) db.session.add(vote) db.session.commit() diff --git a/app/models.py b/app/models.py index 8587e190..76d27aca 100644 --- a/app/models.py +++ b/app/models.py @@ -1130,7 +1130,7 @@ class Post(db.Model): @classmethod def new(cls, user: User, community: Community, request_json: dict, announce_id=None): - from activitypub.util import instance_weight, find_language_or_create, find_language, find_hashtag_or_create, \ + from app.activitypub.util import instance_weight, find_language_or_create, find_language, find_hashtag_or_create, \ make_image_sizes, notify_about_post from app.utils import allowlist_html, markdown_to_html, html_to_text, microblog_content_to_title, blocked_phrases, \ is_image_url, is_video_url, domain_from_url, opengraph_parse, shorten_string, remove_tracking_from_link, \ @@ -1160,7 +1160,8 @@ class Post(db.Model): score=instance_weight(user.ap_domain), instance_id=user.instance_id, indexable=user.indexable, - microblog=microblog + microblog=microblog, + posted_at=utcnow() ) if 'content' in request_json['object'] and request_json['object']['content'] is not None: @@ -1305,7 +1306,7 @@ class Post(db.Model): if is_video_hosting_site(post.url): post.type = constants.POST_TYPE_VIDEO db.session.add(post) - post.ranking = post_ranking(post.score, post.posted_at) + post.ranking = post.post_ranking(post.score, post.posted_at) community.post_count += 1 community.last_active = utcnow() user.post_count += 1 @@ -1351,7 +1352,7 @@ class Post(db.Model): if user.reputation > 100: post.up_votes += 1 post.score += 1 - post.ranking = Post.post_ranking(post.score, post.posted_at) + post.ranking = post.post_ranking(post.score, post.posted_at) db.session.commit() return post @@ -1364,17 +1365,6 @@ class Post(db.Model): td = date - self.epoch return td.days * 86400 + td.seconds + (float(td.microseconds) / 1000000) - @classmethod - def post_ranking(cls, score, date: datetime): - if date is None: - date = datetime.utcnow() - if score is None: - score = 1 - order = math.log(max(abs(score), 1), 10) - sign = 1 if score > 0 else -1 if score < 0 else 0 - seconds = Post.epoch_seconds(date) - 1685766018 - return round(sign * order + seconds / 45000, 7) - def delete_dependencies(self): db.session.query(PostBookmark).filter(PostBookmark.post_id == self.id).delete() db.session.query(PollChoiceVote).filter(PollChoiceVote.post_id == self.id).delete()