post-new: image posts

This commit is contained in:
rimu 2024-10-20 20:21:30 +13:00
parent feca5992af
commit 518f165c1f
2 changed files with 50 additions and 22 deletions

View file

@ -1,12 +1,16 @@
import base64
import os
from collections import namedtuple from collections import namedtuple
from io import BytesIO from io import BytesIO
from random import randint from random import randint
import flask import flask
from PIL import Image, ImageOps
from flask import redirect, url_for, flash, request, make_response, session, Markup, current_app, abort, g, json, \ from flask import redirect, url_for, flash, request, make_response, session, Markup, current_app, abort, g, json, \
jsonify jsonify
from flask_login import current_user, login_required from flask_login import current_user, login_required
from flask_babel import _ from flask_babel import _
from pillow_heif import register_heif_opener
from slugify import slugify from slugify import slugify
from sqlalchemy import or_, desc, text from sqlalchemy import or_, desc, text
@ -21,7 +25,8 @@ from app.community.forms import SearchRemoteCommunity, CreateDiscussionForm, Cre
EditCommunityWikiPageForm EditCommunityWikiPageForm
from app.community.util import search_for_community, actor_to_community, \ from app.community.util import search_for_community, actor_to_community, \
save_post, save_icon_file, save_banner_file, send_to_remote_instance, \ 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, \ 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, \ 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 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, \ 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, \ 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_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 feedgen.feed import FeedGenerator
from datetime import timezone, timedelta from datetime import timezone, timedelta
from copy import copy from copy import copy
@ -581,8 +586,8 @@ def add_post(actor, type):
return show_ban_message() return show_ban_message()
community = actor_to_community(actor) community = actor_to_community(actor)
post_type = POST_TYPE_ARTICLE
if type == 'discussion': if type == 'discussion':
post_type = POST_TYPE_ARTICLE
form = CreateDiscussionForm() form = CreateDiscussionForm()
elif type == 'link': elif type == 'link':
post_type = POST_TYPE_LINK post_type = POST_TYPE_LINK
@ -630,6 +635,7 @@ def add_post(actor, type):
'id': None, 'id': None,
'object': { 'object': {
'name': form.title.data, 'name': form.title.data,
'type': 'Page',
'sticky': form.sticky.data, 'sticky': form.sticky.data,
'nsfw': form.nsfw.data, 'nsfw': form.nsfw.data,
'nsfl': form.nsfl.data, 'nsfl': form.nsfl.data,
@ -641,11 +647,42 @@ def add_post(actor, type):
} }
} }
if type == 'link': 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': 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': 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': elif type == 'poll':
request_json['object']['type'] = 'Question' request_json['object']['type'] = 'Question'
choices = [form.choice_1, form.choice_2, form.choice_3, form.choice_4, form.choice_5, 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() choice_data = choice.data.strip()
if choice_data: if choice_data:
request_json['object'][key].append({'name': 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 # todo: add try..except
post = Post.new(current_user, community, request_json) post = Post.new(current_user, community, request_json)
@ -1950,7 +1988,7 @@ def check_url_already_posted():
def upvote_own_post(post): def upvote_own_post(post):
post.score = 1 post.score = 1
post.up_votes = 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) vote = PostVote(user_id=current_user.id, post_id=post.id, author_id=current_user.id, effect=1)
db.session.add(vote) db.session.add(vote)
db.session.commit() db.session.commit()

View file

@ -1130,7 +1130,7 @@ class Post(db.Model):
@classmethod @classmethod
def new(cls, user: User, community: Community, request_json: dict, announce_id=None): 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 make_image_sizes, notify_about_post
from app.utils import allowlist_html, markdown_to_html, html_to_text, microblog_content_to_title, blocked_phrases, \ 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, \ 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), score=instance_weight(user.ap_domain),
instance_id=user.instance_id, instance_id=user.instance_id,
indexable=user.indexable, indexable=user.indexable,
microblog=microblog microblog=microblog,
posted_at=utcnow()
) )
if 'content' in request_json['object'] and request_json['object']['content'] is not None: 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): if is_video_hosting_site(post.url):
post.type = constants.POST_TYPE_VIDEO post.type = constants.POST_TYPE_VIDEO
db.session.add(post) 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.post_count += 1
community.last_active = utcnow() community.last_active = utcnow()
user.post_count += 1 user.post_count += 1
@ -1351,7 +1352,7 @@ class Post(db.Model):
if user.reputation > 100: if user.reputation > 100:
post.up_votes += 1 post.up_votes += 1
post.score += 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() db.session.commit()
return post return post
@ -1364,17 +1365,6 @@ class Post(db.Model):
td = date - self.epoch td = date - self.epoch
return td.days * 86400 + td.seconds + (float(td.microseconds) / 1000000) 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): def delete_dependencies(self):
db.session.query(PostBookmark).filter(PostBookmark.post_id == self.id).delete() db.session.query(PostBookmark).filter(PostBookmark.post_id == self.id).delete()
db.session.query(PollChoiceVote).filter(PollChoiceVote.post_id == self.id).delete() db.session.query(PollChoiceVote).filter(PollChoiceVote.post_id == self.id).delete()