mirror of
https://codeberg.org/rimu/pyfedi
synced 2025-02-03 00:31:25 -08:00
Merge remote-tracking branch 'upstream/main'
This commit is contained in:
commit
03d5859ebf
32 changed files with 11866 additions and 1727 deletions
|
@ -374,6 +374,7 @@ def shared_inbox():
|
|||
|
||||
redis_client.set(request_json['id'], 1, ex=90) # Save the activity ID into redis, to avoid duplicate activities that Lemmy sometimes sends
|
||||
activity_log.activity_id = request_json['id']
|
||||
g.site = Site.query.get(1) # g.site is not initialized by @app.before_request when request.path == '/inbox'
|
||||
if g.site.log_activitypub_json:
|
||||
activity_log.activity_json = json.dumps(request_json)
|
||||
activity_log.result = 'processing'
|
||||
|
|
|
@ -223,6 +223,8 @@ def banned_user_agents():
|
|||
|
||||
@cache.memoize(150)
|
||||
def instance_blocked(host: str) -> bool: # see also utils.instance_banned()
|
||||
if host is None or host == '':
|
||||
return True
|
||||
host = host.lower()
|
||||
if 'https://' in host or 'http://' in host:
|
||||
host = urlparse(host).hostname
|
||||
|
@ -232,6 +234,8 @@ def instance_blocked(host: str) -> bool: # see also utils.instance_banned
|
|||
|
||||
@cache.memoize(150)
|
||||
def instance_allowed(host: str) -> bool:
|
||||
if host is None or host == '':
|
||||
return True
|
||||
host = host.lower()
|
||||
if 'https://' in host or 'http://' in host:
|
||||
host = urlparse(host).hostname
|
||||
|
@ -561,11 +565,11 @@ def actor_json_to_model(activity_json, address, server):
|
|||
current_app.logger.error(f'KeyError for {address}@{server} while parsing ' + str(activity_json))
|
||||
return None
|
||||
|
||||
if 'icon' in activity_json:
|
||||
if 'icon' in activity_json and activity_json['icon'] is not None and 'url' in activity_json['icon']:
|
||||
avatar = File(source_url=activity_json['icon']['url'])
|
||||
user.avatar = avatar
|
||||
db.session.add(avatar)
|
||||
if 'image' in activity_json:
|
||||
if 'image' in activity_json and activity_json['image'] is not None and 'url' in activity_json['image']:
|
||||
cover = File(source_url=activity_json['image']['url'])
|
||||
user.cover = cover
|
||||
db.session.add(cover)
|
||||
|
@ -625,11 +629,11 @@ def actor_json_to_model(activity_json, address, server):
|
|||
elif 'content' in activity_json:
|
||||
community.description_html = allowlist_html(activity_json['content'])
|
||||
community.description = ''
|
||||
if 'icon' in activity_json:
|
||||
if 'icon' in activity_json and activity_json['icon'] is not None and 'url' in activity_json['icon']:
|
||||
icon = File(source_url=activity_json['icon']['url'])
|
||||
community.icon = icon
|
||||
db.session.add(icon)
|
||||
if 'image' in activity_json:
|
||||
if 'image' in activity_json and activity_json['image'] is not None and 'url' in activity_json['image']:
|
||||
image = File(source_url=activity_json['image']['url'])
|
||||
community.image = image
|
||||
db.session.add(image)
|
||||
|
@ -702,12 +706,12 @@ def post_json_to_model(activity_log, post_json, user, community) -> Post:
|
|||
if not domain.banned:
|
||||
domain.post_count += 1
|
||||
post.domain = domain
|
||||
if 'image' in post_json and post.image is None:
|
||||
image = File(source_url=post_json['image']['url'])
|
||||
db.session.add(image)
|
||||
post.image = image
|
||||
|
||||
if post is not None:
|
||||
if 'image' in post_json and post.image is None:
|
||||
image = File(source_url=post_json['image']['url'])
|
||||
db.session.add(image)
|
||||
post.image = image
|
||||
db.session.add(post)
|
||||
community.post_count += 1
|
||||
activity_log.result = 'success'
|
||||
|
@ -793,18 +797,19 @@ def make_image_sizes_async(file_id, thumbnail_width, medium_width, directory):
|
|||
db.session.commit()
|
||||
|
||||
# Alert regarding fascist meme content
|
||||
try:
|
||||
image_text = pytesseract.image_to_string(Image.open(BytesIO(source_image)).convert('L'), timeout=30)
|
||||
except FileNotFoundError as e:
|
||||
image_text = ''
|
||||
if 'Anonymous' in image_text and ('No.' in image_text or ' N0' in image_text): # chan posts usually contain the text 'Anonymous' and ' No.12345'
|
||||
post = Post.query.filter_by(image_id=file.id).first()
|
||||
notification = Notification(title='Review this',
|
||||
user_id=1,
|
||||
author_id=post.user_id,
|
||||
url=url_for('activitypub.post_ap', post_id=post.id))
|
||||
db.session.add(notification)
|
||||
db.session.commit()
|
||||
if img_width < 2000: # images > 2000px tend to be real photos instead of 4chan screenshots.
|
||||
try:
|
||||
image_text = pytesseract.image_to_string(Image.open(BytesIO(source_image)).convert('L'), timeout=30)
|
||||
except FileNotFoundError as e:
|
||||
image_text = ''
|
||||
if 'Anonymous' in image_text and ('No.' in image_text or ' N0' in image_text): # chan posts usually contain the text 'Anonymous' and ' No.12345'
|
||||
post = Post.query.filter_by(image_id=file.id).first()
|
||||
notification = Notification(title='Review this',
|
||||
user_id=1,
|
||||
author_id=post.user_id,
|
||||
url=url_for('activitypub.post_ap', post_id=post.id))
|
||||
db.session.add(notification)
|
||||
db.session.commit()
|
||||
|
||||
|
||||
# create a summary from markdown if present, otherwise use html if available
|
||||
|
@ -1548,7 +1553,7 @@ def update_post_from_activity(post: Post, request_json: dict):
|
|||
old_cross_posts = Post.query.filter(Post.id.in_(post.cross_posts)).all()
|
||||
post.cross_posts.clear()
|
||||
for ocp in old_cross_posts:
|
||||
if ocp.cross_posts is not None:
|
||||
if ocp.cross_posts is not None and post.id in ocp.cross_posts:
|
||||
ocp.cross_posts.remove(post.id)
|
||||
|
||||
if post is not None:
|
||||
|
|
|
@ -458,6 +458,7 @@ def admin_users_trash():
|
|||
page = request.args.get('page', 1, type=int)
|
||||
search = request.args.get('search', '')
|
||||
local_remote = request.args.get('local_remote', '')
|
||||
type = request.args.get('type', 'bad_rep')
|
||||
|
||||
users = User.query.filter_by(deleted=False)
|
||||
if local_remote == 'local':
|
||||
|
@ -466,14 +467,19 @@ def admin_users_trash():
|
|||
users = users.filter(User.ap_id != None)
|
||||
if search:
|
||||
users = users.filter(User.email.ilike(f"%{search}%"))
|
||||
users = users.filter(User.reputation < -10)
|
||||
users = users.order_by(User.reputation).paginate(page=page, per_page=1000, error_out=False)
|
||||
|
||||
if type == '' or type == 'bad_rep':
|
||||
users = users.filter(User.reputation < -10)
|
||||
users = users.order_by(User.reputation).paginate(page=page, per_page=1000, error_out=False)
|
||||
elif type == 'bad_attitude':
|
||||
users = users.filter(User.attitude < 0.0)
|
||||
users = users.order_by(-User.attitude).paginate(page=page, per_page=1000, error_out=False)
|
||||
|
||||
next_url = url_for('admin.admin_users_trash', page=users.next_num) if users.has_next else None
|
||||
prev_url = url_for('admin.admin_users_trash', page=users.prev_num) if users.has_prev and page != 1 else None
|
||||
|
||||
return render_template('admin/users.html', title=_('Problematic users'), next_url=next_url, prev_url=prev_url, users=users,
|
||||
local_remote=local_remote, search=search,
|
||||
local_remote=local_remote, search=search, type=type,
|
||||
moderating_communities=moderating_communities(current_user.get_id()),
|
||||
joined_communities=joined_communities(current_user.get_id()),
|
||||
site=g.site
|
||||
|
|
|
@ -105,7 +105,7 @@ def empty():
|
|||
@login_required
|
||||
def chat_options(conversation_id):
|
||||
conversation = Conversation.query.get_or_404(conversation_id)
|
||||
if current_user.is_admin() or current_user.is_member(current_user):
|
||||
if current_user.is_admin() or conversation.is_member(current_user):
|
||||
return render_template('chat/chat_options.html', conversation=conversation,
|
||||
moderating_communities=moderating_communities(current_user.get_id()),
|
||||
joined_communities=joined_communities(current_user.get_id()),
|
||||
|
|
|
@ -88,7 +88,7 @@ class BanUserCommunityForm(FlaskForm):
|
|||
class CreateDiscussionForm(FlaskForm):
|
||||
communities = SelectField(_l('Community'), validators=[DataRequired()], coerce=int)
|
||||
discussion_title = StringField(_l('Title'), validators=[DataRequired(), Length(min=3, max=255)])
|
||||
discussion_body = TextAreaField(_l('Body'), validators=[Optional(), Length(min=3, max=5000)])
|
||||
discussion_body = TextAreaField(_l('Body'), validators=[Optional(), Length(min=3, max=5000)], render_kw={'rows': 5})
|
||||
sticky = BooleanField(_l('Sticky'))
|
||||
nsfw = BooleanField(_l('NSFW'))
|
||||
nsfl = BooleanField(_l('Gore/gross'))
|
||||
|
@ -99,7 +99,7 @@ class CreateDiscussionForm(FlaskForm):
|
|||
class CreateLinkForm(FlaskForm):
|
||||
communities = SelectField(_l('Community'), validators=[DataRequired()], coerce=int)
|
||||
link_title = StringField(_l('Title'), validators=[DataRequired(), Length(min=3, max=255)])
|
||||
link_body = TextAreaField(_l('Body'), validators=[Optional(), Length(min=3, max=5000)])
|
||||
link_body = TextAreaField(_l('Body'), validators=[Optional(), Length(min=3, max=5000)], render_kw={'rows': 5})
|
||||
link_url = StringField(_l('URL'), validators=[DataRequired(), Regexp(r'^https?://', message='Submitted links need to start with "http://"" or "https://"')],
|
||||
render_kw={'placeholder': 'https://...'})
|
||||
sticky = BooleanField(_l('Sticky'))
|
||||
|
@ -120,7 +120,7 @@ class CreateImageForm(FlaskForm):
|
|||
communities = SelectField(_l('Community'), validators=[DataRequired()], coerce=int)
|
||||
image_title = StringField(_l('Title'), validators=[DataRequired(), Length(min=3, max=255)])
|
||||
image_alt_text = StringField(_l('Alt text'), validators=[Optional(), Length(min=3, max=255)])
|
||||
image_body = TextAreaField(_l('Body'), validators=[Optional(), Length(min=3, max=5000)])
|
||||
image_body = TextAreaField(_l('Body'), validators=[Optional(), Length(min=3, max=5000)], render_kw={'rows': 5})
|
||||
image_file = FileField(_('Image'), validators=[DataRequired()])
|
||||
sticky = BooleanField(_l('Sticky'))
|
||||
nsfw = BooleanField(_l('NSFW'))
|
||||
|
|
|
@ -30,7 +30,7 @@ from app.utils import get_setting, render_template, allowlist_html, markdown_to_
|
|||
shorten_string, gibberish, community_membership, ap_datetime, \
|
||||
request_etag_matches, return_304, instance_banned, can_create_post, can_upvote, can_downvote, user_filters_posts, \
|
||||
joined_communities, moderating_communities, blocked_domains, mimetype_from_url, blocked_instances, \
|
||||
community_moderators, communities_banned_from, show_ban_message
|
||||
community_moderators, communities_banned_from, show_ban_message, recently_upvoted_posts, recently_downvoted_posts
|
||||
from feedgen.feed import FeedGenerator
|
||||
from datetime import timezone, timedelta
|
||||
|
||||
|
@ -241,12 +241,21 @@ def show_community(community: Community):
|
|||
prev_url = url_for('activitypub.community_profile', actor=community.ap_id if community.ap_id is not None else community.name,
|
||||
page=posts.prev_num, sort=sort, layout=post_layout) if posts.has_prev and page != 1 else None
|
||||
|
||||
# Voting history
|
||||
if current_user.is_authenticated:
|
||||
recently_upvoted = recently_upvoted_posts(current_user.id)
|
||||
recently_downvoted = recently_downvoted_posts(current_user.id)
|
||||
else:
|
||||
recently_upvoted = []
|
||||
recently_downvoted = []
|
||||
|
||||
return render_template('community/community.html', community=community, title=community.title, breadcrumbs=breadcrumbs,
|
||||
is_moderator=is_moderator, is_owner=is_owner, is_admin=is_admin, 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, SUBSCRIPTION_OWNER=SUBSCRIPTION_OWNER, SUBSCRIPTION_MODERATOR=SUBSCRIPTION_MODERATOR,
|
||||
etag=f"{community.id}{sort}{post_layout}_{hash(community.last_active)}", related_communities=related_communities,
|
||||
next_url=next_url, prev_url=prev_url, low_bandwidth=low_bandwidth,
|
||||
recently_upvoted=recently_upvoted, recently_downvoted=recently_downvoted,
|
||||
rss_feed=f"https://{current_app.config['SERVER_NAME']}/community/{community.link()}/feed", rss_feed_name=f"{community.title} on PieFed",
|
||||
content_filters=content_filters, moderating_communities=moderating_communities(current_user.get_id()),
|
||||
joined_communities=joined_communities(current_user.get_id()), sort=sort,
|
||||
|
@ -436,7 +445,7 @@ def join_then_add(actor):
|
|||
db.session.commit()
|
||||
flash('You joined ' + community.title)
|
||||
if not community.user_is_banned(current_user):
|
||||
return redirect(url_for('community.add_post', actor=community.link()))
|
||||
return redirect(url_for('community.add_discussion_post', actor=community.link()))
|
||||
else:
|
||||
abort(401)
|
||||
|
||||
|
|
|
@ -3,9 +3,11 @@ from app import db
|
|||
from app.errors import bp
|
||||
|
||||
|
||||
@bp.app_errorhandler(404)
|
||||
def not_found_error(error):
|
||||
return render_template('errors/404.html'), 404
|
||||
# 404 error handler removed because a lot of 404s are just images in /static/* and it doesn't make sense to waste cpu cycles presenting a nice page.
|
||||
# Also rendering a page requires populating g.site which means hitting the DB.
|
||||
# @bp.app_errorhandler(404)
|
||||
# def not_found_error(error):
|
||||
# return render_template('errors/404.html'), 404
|
||||
|
||||
|
||||
@bp.app_errorhandler(500)
|
||||
|
|
|
@ -25,7 +25,7 @@ from sqlalchemy_searchable import search
|
|||
from app.utils import render_template, get_setting, gibberish, request_etag_matches, return_304, blocked_domains, \
|
||||
ap_datetime, ip_address, retrieve_block_list, shorten_string, markdown_to_text, user_filters_home, \
|
||||
joined_communities, moderating_communities, parse_page, theme_list, get_request, markdown_to_html, allowlist_html, \
|
||||
blocked_instances, communities_banned_from, topic_tree
|
||||
blocked_instances, communities_banned_from, topic_tree, recently_upvoted_posts, recently_downvoted_posts
|
||||
from app.models import Community, CommunityMember, Post, Site, User, utcnow, Domain, Topic, File, Instance, \
|
||||
InstanceRole, Notification
|
||||
from PIL import Image
|
||||
|
@ -139,9 +139,18 @@ def home_page(type, sort):
|
|||
active_communities = active_communities.filter(Community.id.not_in(banned_from))
|
||||
active_communities = active_communities.order_by(desc(Community.last_active)).limit(5).all()
|
||||
|
||||
# Voting history
|
||||
if current_user.is_authenticated:
|
||||
recently_upvoted = recently_upvoted_posts(current_user.id)
|
||||
recently_downvoted = recently_downvoted_posts(current_user.id)
|
||||
else:
|
||||
recently_upvoted = []
|
||||
recently_downvoted = []
|
||||
|
||||
return render_template('index.html', posts=posts, active_communities=active_communities, show_post_community=True,
|
||||
POST_TYPE_IMAGE=POST_TYPE_IMAGE, POST_TYPE_LINK=POST_TYPE_LINK,
|
||||
low_bandwidth=low_bandwidth,
|
||||
low_bandwidth=low_bandwidth, recently_upvoted=recently_upvoted,
|
||||
recently_downvoted=recently_downvoted,
|
||||
SUBSCRIPTION_PENDING=SUBSCRIPTION_PENDING, SUBSCRIPTION_MEMBER=SUBSCRIPTION_MEMBER,
|
||||
etag=f"{type}_{sort}_{hash(str(g.site.last_active))}", next_url=next_url, prev_url=prev_url,
|
||||
#rss_feed=f"https://{current_app.config['SERVER_NAME']}/feed",
|
||||
|
@ -291,7 +300,9 @@ def list_files(directory):
|
|||
|
||||
@bp.route('/test')
|
||||
def test():
|
||||
return ''
|
||||
md = "::: spoiler I'm all for ya having fun and your right to hurt yourself.\n\nI am a former racer, commuter, and professional Buyer for a chain of bike shops. I'm also disabled from the crash involving the 6th and 7th cars that have hit me in the last 170k+ miles of riding. I only barely survived what I simplify as a \"broken neck and back.\" Cars making U-turns are what will get you if you ride long enough, \n\nespecially commuting. It will look like just another person turning in front of you, you'll compensate like usual, and before your brain can even register what is really happening, what was your normal escape route will close and you're going to crash really hard. It is the only kind of crash that your intuition is useless against.\n:::"
|
||||
|
||||
return markdown_to_html(md)
|
||||
|
||||
users_to_notify = User.query.join(Notification, User.id == Notification.user_id).filter(
|
||||
User.ap_id == None,
|
||||
|
@ -346,6 +357,36 @@ def test_email():
|
|||
return f'Email sent to {current_user.email}.'
|
||||
|
||||
|
||||
@bp.route('/find_voters')
|
||||
def find_voters():
|
||||
user_ids = db.session.execute(text('SELECT id from "user" ORDER BY last_seen DESC LIMIT 5000')).scalars()
|
||||
voters = {}
|
||||
for user_id in user_ids:
|
||||
recently_downvoted = recently_downvoted_posts(user_id)
|
||||
if len(recently_downvoted) > 10:
|
||||
voters[user_id] = str(recently_downvoted)
|
||||
|
||||
return str(find_duplicate_values(voters))
|
||||
|
||||
|
||||
def find_duplicate_values(dictionary):
|
||||
# Create a dictionary to store the keys for each value
|
||||
value_to_keys = {}
|
||||
|
||||
# Iterate through the input dictionary
|
||||
for key, value in dictionary.items():
|
||||
# If the value is not already in the dictionary, add it
|
||||
if value not in value_to_keys:
|
||||
value_to_keys[value] = [key]
|
||||
else:
|
||||
# If the value is already in the dictionary, append the key to the list
|
||||
value_to_keys[value].append(key)
|
||||
|
||||
# Filter out the values that have only one key (i.e., unique values)
|
||||
duplicates = {value: keys for value, keys in value_to_keys.items() if len(keys) > 1}
|
||||
|
||||
return duplicates
|
||||
|
||||
def verification_warning():
|
||||
if hasattr(current_user, 'verified') and current_user.verified is False:
|
||||
flash(_('Please click the link in your email inbox to verify your account.'), 'warning')
|
||||
|
|
|
@ -1278,6 +1278,7 @@ class Site(db.Model):
|
|||
last_active = db.Column(db.DateTime, default=utcnow)
|
||||
log_activitypub_json = db.Column(db.Boolean, default=False)
|
||||
default_theme = db.Column(db.String(20), default='')
|
||||
contact_email = db.Column(db.String(255), default='')
|
||||
|
||||
@staticmethod
|
||||
def admins() -> List[User]:
|
||||
|
|
|
@ -7,7 +7,7 @@ from app.utils import MultiCheckboxField
|
|||
|
||||
|
||||
class NewReplyForm(FlaskForm):
|
||||
body = TextAreaField(_l('Body'), render_kw={'placeholder': 'What are your thoughts?', 'rows': 3}, validators={DataRequired(), Length(min=3, max=5000)})
|
||||
body = TextAreaField(_l('Body'), render_kw={'placeholder': 'What are your thoughts?', 'rows': 5}, validators={DataRequired(), Length(min=3, max=5000)})
|
||||
notify_author = BooleanField(_l('Notify about replies'))
|
||||
submit = SubmitField(_l('Comment'))
|
||||
|
||||
|
|
|
@ -24,7 +24,8 @@ from app.utils import get_setting, render_template, allowlist_html, markdown_to_
|
|||
shorten_string, markdown_to_text, gibberish, ap_datetime, return_304, \
|
||||
request_etag_matches, ip_address, user_ip_banned, instance_banned, can_downvote, can_upvote, post_ranking, \
|
||||
reply_already_exists, reply_is_just_link_to_gif_reaction, confidence, moderating_communities, joined_communities, \
|
||||
blocked_instances, blocked_domains, community_moderators, blocked_phrases, show_ban_message
|
||||
blocked_instances, blocked_domains, community_moderators, blocked_phrases, show_ban_message, recently_upvoted_posts, \
|
||||
recently_downvoted_posts, recently_upvoted_post_replies, recently_downvoted_post_replies
|
||||
|
||||
|
||||
def show_post(post_id: int):
|
||||
|
@ -239,12 +240,26 @@ def show_post(post_id: int):
|
|||
breadcrumb.url = '/communities'
|
||||
breadcrumbs.append(breadcrumb)
|
||||
|
||||
# Voting history
|
||||
if current_user.is_authenticated:
|
||||
recently_upvoted = recently_upvoted_posts(current_user.id)
|
||||
recently_downvoted = recently_downvoted_posts(current_user.id)
|
||||
recently_upvoted_replies = recently_upvoted_post_replies(current_user.id)
|
||||
recently_downvoted_replies = recently_downvoted_post_replies(current_user.id)
|
||||
else:
|
||||
recently_upvoted = []
|
||||
recently_downvoted = []
|
||||
recently_upvoted_replies = []
|
||||
recently_downvoted_replies = []
|
||||
|
||||
response = render_template('post/post.html', title=post.title, post=post, is_moderator=is_moderator, community=post.community,
|
||||
breadcrumbs=breadcrumbs, related_communities=related_communities, mods=mod_list,
|
||||
canonical=post.ap_id, form=form, replies=replies, THREAD_CUTOFF_DEPTH=constants.THREAD_CUTOFF_DEPTH,
|
||||
description=description, og_image=og_image, POST_TYPE_IMAGE=constants.POST_TYPE_IMAGE,
|
||||
POST_TYPE_LINK=constants.POST_TYPE_LINK, POST_TYPE_ARTICLE=constants.POST_TYPE_ARTICLE,
|
||||
noindex=not post.author.indexable,
|
||||
recently_upvoted=recently_upvoted, recently_downvoted=recently_downvoted,
|
||||
recently_upvoted_replies=recently_upvoted_replies, recently_downvoted_replies=recently_downvoted_replies,
|
||||
etag=f"{post.id}{sort}_{hash(post.last_active)}", markdown_editor=current_user.is_authenticated and current_user.markdown_editor,
|
||||
low_bandwidth=request.cookies.get('low_bandwidth', '0') == '1', SUBSCRIPTION_MEMBER=SUBSCRIPTION_MEMBER,
|
||||
moderating_communities=moderating_communities(current_user.get_id()),
|
||||
|
@ -259,7 +274,6 @@ def show_post(post_id: int):
|
|||
@login_required
|
||||
@validation_required
|
||||
def post_vote(post_id: int, vote_direction):
|
||||
upvoted_class = downvoted_class = ''
|
||||
post = Post.query.get_or_404(post_id)
|
||||
existing_vote = PostVote.query.filter_by(user_id=current_user.id, post_id=post.id).first()
|
||||
if existing_vote:
|
||||
|
@ -275,7 +289,6 @@ def post_vote(post_id: int, vote_direction):
|
|||
post.up_votes -= 1
|
||||
post.down_votes += 1
|
||||
post.score -= 2
|
||||
downvoted_class = 'voted_down'
|
||||
else: # previous vote was down
|
||||
if vote_direction == 'downvote': # new vote is also down, so remove it
|
||||
db.session.delete(existing_vote)
|
||||
|
@ -286,18 +299,15 @@ def post_vote(post_id: int, vote_direction):
|
|||
post.up_votes += 1
|
||||
post.down_votes -= 1
|
||||
post.score += 2
|
||||
upvoted_class = 'voted_up'
|
||||
else:
|
||||
if vote_direction == 'upvote':
|
||||
effect = 1
|
||||
post.up_votes += 1
|
||||
post.score += 1
|
||||
upvoted_class = 'voted_up'
|
||||
else:
|
||||
effect = -1
|
||||
post.down_votes += 1
|
||||
post.score -= 1
|
||||
downvoted_class = 'voted_down'
|
||||
vote = PostVote(user_id=current_user.id, post_id=post.id, author_id=post.author.id,
|
||||
effect=effect)
|
||||
# upvotes do not increase reputation in low quality communities
|
||||
|
@ -346,17 +356,25 @@ def post_vote(post_id: int, vote_direction):
|
|||
current_user.recalculate_attitude()
|
||||
db.session.commit()
|
||||
post.flush_cache()
|
||||
|
||||
recently_upvoted = []
|
||||
recently_downvoted = []
|
||||
if vote_direction == 'upvote':
|
||||
recently_upvoted = [post_id]
|
||||
elif vote_direction == 'downvote':
|
||||
recently_downvoted = [post_id]
|
||||
cache.delete_memoized(recently_upvoted_posts, current_user.id)
|
||||
cache.delete_memoized(recently_downvoted_posts, current_user.id)
|
||||
|
||||
template = 'post/_post_voting_buttons.html' if request.args.get('style', '') == '' else 'post/_post_voting_buttons_masonry.html'
|
||||
return render_template(template, post=post, community=post.community,
|
||||
upvoted_class=upvoted_class,
|
||||
downvoted_class=downvoted_class)
|
||||
return render_template(template, post=post, community=post.community, recently_upvoted=recently_upvoted,
|
||||
recently_downvoted=recently_downvoted)
|
||||
|
||||
|
||||
@bp.route('/comment/<int:comment_id>/<vote_direction>', methods=['POST'])
|
||||
@login_required
|
||||
@validation_required
|
||||
def comment_vote(comment_id, vote_direction):
|
||||
upvoted_class = downvoted_class = ''
|
||||
comment = PostReply.query.get_or_404(comment_id)
|
||||
existing_vote = PostReplyVote.query.filter_by(user_id=current_user.id, post_reply_id=comment.id).first()
|
||||
if existing_vote:
|
||||
|
@ -423,9 +441,20 @@ def comment_vote(comment_id, vote_direction):
|
|||
db.session.commit()
|
||||
|
||||
comment.post.flush_cache()
|
||||
|
||||
recently_upvoted = []
|
||||
recently_downvoted = []
|
||||
if vote_direction == 'upvote':
|
||||
recently_upvoted = [comment_id]
|
||||
elif vote_direction == 'downvote':
|
||||
recently_downvoted = [comment_id]
|
||||
cache.delete_memoized(recently_upvoted_post_replies, current_user.id)
|
||||
cache.delete_memoized(recently_downvoted_post_replies, current_user.id)
|
||||
|
||||
return render_template('post/_comment_voting_buttons.html', comment=comment,
|
||||
upvoted_class=upvoted_class,
|
||||
downvoted_class=downvoted_class, community=comment.community)
|
||||
recently_upvoted_replies=recently_upvoted,
|
||||
recently_downvoted_replies=recently_downvoted,
|
||||
community=comment.community)
|
||||
|
||||
|
||||
@bp.route('/post/<int:post_id>/comment/<int:comment_id>')
|
||||
|
|
|
@ -6,7 +6,7 @@ from sqlalchemy import or_
|
|||
from app.models import Post
|
||||
from app.search import bp
|
||||
from app.utils import moderating_communities, joined_communities, render_template, blocked_domains, blocked_instances, \
|
||||
communities_banned_from
|
||||
communities_banned_from, recently_upvoted_posts, recently_downvoted_posts
|
||||
|
||||
|
||||
@bp.route('/search', methods=['GET', 'POST'])
|
||||
|
@ -46,8 +46,18 @@ def run_search():
|
|||
next_url = url_for('search.run_search', page=posts.next_num, q=q) if posts.has_next else None
|
||||
prev_url = url_for('search.run_search', page=posts.prev_num, q=q) if posts.has_prev and page != 1 else None
|
||||
|
||||
# Voting history
|
||||
if current_user.is_authenticated:
|
||||
recently_upvoted = recently_upvoted_posts(current_user.id)
|
||||
recently_downvoted = recently_downvoted_posts(current_user.id)
|
||||
else:
|
||||
recently_upvoted = []
|
||||
recently_downvoted = []
|
||||
|
||||
return render_template('search/results.html', title=_('Search results for %(q)s', q=q), posts=posts, q=q,
|
||||
next_url=next_url, prev_url=prev_url, show_post_community=True,
|
||||
recently_upvoted=recently_upvoted,
|
||||
recently_downvoted=recently_downvoted,
|
||||
moderating_communities=moderating_communities(current_user.get_id()),
|
||||
joined_communities=joined_communities(current_user.get_id()),
|
||||
site=g.site)
|
||||
|
|
|
@ -19,6 +19,8 @@
|
|||
<input type="search" name="search" value="{{ search }}">
|
||||
<input type="radio" name="local_remote" value="local" id="local_remote_local" {{ 'checked' if local_remote == 'local' }}><label for="local_remote_local"> Local</label>
|
||||
<input type="radio" name="local_remote" value="remote" id="local_remote_remote" {{ 'checked' if local_remote == 'remote' }}><label for="local_remote_remote"> Remote</label>
|
||||
<input type="radio" name="type" value="bad_rep" id="type_bad_rep" {{ 'checked' if type == 'bad_rep' }}><label for="type_bad_rep"> Bad rep</label>
|
||||
<input type="radio" name="type" value="bad_attitude" id="type_bad_attitude" {{ 'checked' if type == 'bad_attitude' }}><label for="type_bad_attitude"> Bad attitude</label>
|
||||
<input type="submit" name="submit" value="Search" class="btn btn-primary">
|
||||
</form>
|
||||
<table class="table table-striped mt-1">
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{% if current_user.is_authenticated and current_user.verified %}
|
||||
{% if can_upvote(current_user, community) %}
|
||||
<div class="upvote_button {{ upvoted_class }}" role="button" aria-label="{{ _('UpVote button.') }}" aria-live="assertive"
|
||||
<div class="upvote_button {{ 'voted_up' if in_sorted_list(recently_upvoted_replies, comment.id) }}" role="button" aria-label="{{ _('UpVote button.') }}" aria-live="assertive"
|
||||
hx-post="/comment/{{ comment.id }}/upvote" hx-trigger="click throttle:1s" hx-target="closest .voting_buttons_new" tabindex="0">
|
||||
<span class="fe fe-arrow-up"></span>
|
||||
<img class="htmx-indicator" src="/static/images/spinner.svg" alt="" style="opacity: 0;">
|
||||
|
@ -8,7 +8,7 @@
|
|||
{% endif %}
|
||||
<span title="{{ comment.up_votes }}, {{ comment.down_votes }}" aria-live="assertive" aria-label="{{ _('Score: ') }}{{ comment.up_votes - comment.down_votes }}.">{{ comment.up_votes - comment.down_votes }}</span>
|
||||
{% if can_downvote(current_user, community) %}
|
||||
<div class="downvote_button {{ downvoted_class }}" role="button" aria-label="{{ _('DownVote button.') }}" aria-live="assertive"
|
||||
<div class="downvote_button {{ 'voted_down' if in_sorted_list(recently_downvoted_replies, comment.id) }}" role="button" aria-label="{{ _('DownVote button.') }}" aria-live="assertive"
|
||||
hx-post="/comment/{{ comment.id }}/downvote" hx-trigger="click throttle:1s" hx-target="closest .voting_buttons_new" tabindex="0">
|
||||
<span class="fe fe-arrow-down"></span>
|
||||
<img class="htmx-indicator" src="/static/images/spinner.svg" alt="" style="opacity: 0;">
|
||||
|
|
|
@ -85,6 +85,7 @@
|
|||
<p><audio controls preload="{{ 'none' if low_bandwidth else 'metadata' }}" src="{{ post.url }}"></audio></p>
|
||||
{% endif %}
|
||||
{% if 'youtube.com' in post.url %}
|
||||
<p><a href="https://piped.video/watch?v={{ post.youtube_embed() }}">{{ _('Watch on piped.video') }} <span class="fe fe-external"></span></a></p>
|
||||
<div style="padding-bottom: 56.25%; position: relative;"><iframe style="position: absolute; top: 0px; left: 0px; width: 100%; height: 100%;" src="https://www.youtube.com/embed/{{ post.youtube_embed() }}?rel=0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture; fullscreen" width="100%" height="100%" frameborder="0"></iframe></div>
|
||||
{% endif %}
|
||||
{% elif post.type == POST_TYPE_IMAGE %}
|
||||
|
|
|
@ -8,9 +8,11 @@
|
|||
<div class="col-12">
|
||||
<div class="row main_row">
|
||||
<div class="col">
|
||||
{% if not hide_vote_buttons %}
|
||||
<div class="voting_buttons" aria-hidden="true">
|
||||
{% include "post/_post_voting_buttons.html" %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if post.image_id %}
|
||||
<div class="thumbnail{{ ' lbw' if low_bandwidth }}" aria-hidden="true">
|
||||
{% if low_bandwidth %}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{% if current_user.is_authenticated and current_user.verified %}
|
||||
{% if can_upvote(current_user, post.community) %}
|
||||
<div class="upvote_button {{ upvoted_class }}" role="button" aria-label="{{ _('UpVote button, %(count)d upvotes so far.', count=post.up_votes) }}" aria-live="assertive"
|
||||
<div class="upvote_button {{ 'voted_up' if in_sorted_list(recently_upvoted, post.id) }}" role="button" aria-label="{{ _('UpVote button, %(count)d upvotes so far.', count=post.up_votes) }}" aria-live="assertive"
|
||||
hx-post="/post/{{ post.id }}/upvote" hx-trigger="click throttle:1s" hx-target="closest .voting_buttons" tabindex="0">
|
||||
<span class="fe fe-arrow-up"></span>
|
||||
{{ shorten_number(post.up_votes) }}
|
||||
|
@ -8,7 +8,7 @@
|
|||
</div>
|
||||
{% endif %}
|
||||
{% if can_downvote(current_user, post.community) %}
|
||||
<div class="downvote_button {{ downvoted_class }}" role="button" aria-label="{{ _('DownVote button, %(count)d downvotes so far.', count=post.down_votes) }}" aria-live="assertive"
|
||||
<div class="downvote_button {{ 'voted_down' if in_sorted_list(recently_downvoted, post.id) }}" role="button" aria-label="{{ _('DownVote button, %(count)d downvotes so far.', count=post.down_votes) }}" aria-live="assertive"
|
||||
hx-post="/post/{{ post.id }}/downvote" hx-trigger="click throttle:1s" hx-target="closest .voting_buttons" tabindex="0">
|
||||
<span class="fe fe-arrow-down"></span>
|
||||
{{ shorten_number(post.down_votes) }}
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
{% if current_user.is_authenticated and current_user.verified %}
|
||||
{% if can_upvote(current_user, post.community) %}
|
||||
<div class="upvote_button {{ upvoted_class }}" role="button" aria-label="{{ _('UpVote') }}" aria-live="assertive"
|
||||
<div class="upvote_button {{ 'voted_up' if in_sorted_list(recently_upvoted, post.id) }}" role="button" aria-label="{{ _('UpVote') }}" aria-live="assertive"
|
||||
hx-post="/post/{{ post.id }}/upvote?style=masonry" hx-trigger="click throttle:1s" hx-target="closest .voting_buttons_masonry" tabindex="0" title="{{ post.up_votes }} upvotes">
|
||||
<span class="fe fe-arrow-up"></span>
|
||||
<img class="htmx-indicator" src="/static/images/spinner.svg" alt="" style="opacity: 0;">
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if can_downvote(current_user, post.community) %}
|
||||
<div class="downvote_button {{ downvoted_class }}" role="button" aria-label="{{ _('DownVote') }}" aria-live="assertive"
|
||||
<div class="downvote_button {{ 'voted_down' if in_sorted_list(recently_downvoted, post.id) }}" role="button" aria-label="{{ _('DownVote') }}" aria-live="assertive"
|
||||
hx-post="/post/{{ post.id }}/downvote?style=masonry" hx-trigger="click throttle:1s" hx-target="closest .voting_buttons_masonry" tabindex="0" title="{{ post.down_votes }} downvotes">
|
||||
<span class="fe fe-arrow-down"></span>
|
||||
<img class="htmx-indicator" src="/static/images/spinner.svg" alt="" style="opacity: 0;">
|
||||
|
|
|
@ -12,3 +12,18 @@ User-Agent: *
|
|||
Disallow: /d/
|
||||
Disallow: /static/media/users/
|
||||
Disallow: /post/*/options
|
||||
|
||||
User-agent: GPTBot
|
||||
Disallow: /
|
||||
|
||||
User-agent: AhrefsBot
|
||||
Disallow: /
|
||||
|
||||
User-agent: SemrushBot
|
||||
Disallow: /
|
||||
|
||||
User-agent: DotBot
|
||||
Disallow: /
|
||||
|
||||
User-agent: SeznamBot
|
||||
Disallow: /
|
||||
|
|
2962
app/translations/ca/LC_MESSAGES/messages.po
Normal file
2962
app/translations/ca/LC_MESSAGES/messages.po
Normal file
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
2962
app/translations/es/LC_MESSAGES/messages.po
Normal file
2962
app/translations/es/LC_MESSAGES/messages.po
Normal file
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
2607
app/translations/lt/LC_MESSAGES/messages.po
Normal file
2607
app/translations/lt/LC_MESSAGES/messages.po
Normal file
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -14,7 +14,7 @@ class ProfileForm(FlaskForm):
|
|||
email = EmailField(_l('Email address'), validators=[Email(), DataRequired(), Length(min=5, max=255)])
|
||||
password_field = PasswordField(_l('Set new password'), validators=[Optional(), Length(min=1, max=50)],
|
||||
render_kw={"autocomplete": 'new-password'})
|
||||
about = TextAreaField(_l('Bio'), validators=[Optional(), Length(min=3, max=5000)])
|
||||
about = TextAreaField(_l('Bio'), validators=[Optional(), Length(min=3, max=5000)], render_kw={'rows': 5})
|
||||
matrixuserid = StringField(_l('Matrix User ID'), validators=[Optional(), Length(max=255)], render_kw={'autocomplete': 'off'})
|
||||
profile_file = FileField(_('Avatar image'))
|
||||
banner_file = FileField(_('Top banner image'))
|
||||
|
|
|
@ -19,7 +19,7 @@ from app.user.utils import purge_user_then_delete
|
|||
from app.utils import get_setting, render_template, markdown_to_html, user_access, markdown_to_text, shorten_string, \
|
||||
is_image_url, ensure_directory_exists, gibberish, file_get_contents, community_membership, user_filters_home, \
|
||||
user_filters_posts, user_filters_replies, moderating_communities, joined_communities, theme_list, blocked_instances, \
|
||||
allowlist_html
|
||||
allowlist_html, recently_upvoted_posts, recently_downvoted_posts
|
||||
from sqlalchemy import desc, or_, text
|
||||
import os
|
||||
|
||||
|
@ -85,7 +85,7 @@ def show_profile(user):
|
|||
description=description, subscribed=subscribed, upvoted=upvoted,
|
||||
post_next_url=post_next_url, post_prev_url=post_prev_url,
|
||||
replies_next_url=replies_next_url, replies_prev_url=replies_prev_url,
|
||||
noindex=not user.indexable, show_post_community=True,
|
||||
noindex=not user.indexable, show_post_community=True, hide_vote_buttons=True,
|
||||
moderating_communities=moderating_communities(current_user.get_id()),
|
||||
joined_communities=joined_communities(current_user.get_id())
|
||||
)
|
||||
|
|
51
app/utils.py
51
app/utils.py
|
@ -1,5 +1,6 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import bisect
|
||||
import hashlib
|
||||
import mimetypes
|
||||
import random
|
||||
|
@ -220,8 +221,8 @@ def markdown_to_html(markdown_text) -> str:
|
|||
if markdown_text:
|
||||
raw_html = markdown2.markdown(markdown_text, safe_mode=True, extras={'middle-word-em': False, 'tables': True, 'fenced-code-blocks': True, 'strike': True})
|
||||
# replace lemmy spoiler tokens with appropriate html tags instead. (until possibly added as extra to markdown2)
|
||||
re_spoiler = re.compile(r':{3} spoiler\s+?(\S.+?\n)(.+?)\n:{3}', re.S)
|
||||
raw_html = re_spoiler.sub(r'<details><summary>\1</summary>\2</details>', raw_html)
|
||||
re_spoiler = re.compile(r':{3} spoiler\s+?(\S.+?)(?:\n|</p>)(.+?)(?:\n|<p>):{3}', re.S)
|
||||
raw_html = re_spoiler.sub(r'<details><summary>\1</summary><p>\2</p></details>', raw_html)
|
||||
return allowlist_html(raw_html)
|
||||
else:
|
||||
return ''
|
||||
|
@ -244,6 +245,8 @@ def microblog_content_to_title(html: str) -> str:
|
|||
continue
|
||||
else:
|
||||
tag = tag.extract()
|
||||
else:
|
||||
tag = tag.extract()
|
||||
|
||||
if title_found:
|
||||
result = soup.text
|
||||
|
@ -451,6 +454,8 @@ def user_ip_banned() -> bool:
|
|||
|
||||
@cache.memoize(timeout=30)
|
||||
def instance_banned(domain: str) -> bool: # see also activitypub.util.instance_blocked()
|
||||
if domain is None or domain == '':
|
||||
return False
|
||||
banned = BannedInstances.query.filter_by(domain=domain).first()
|
||||
return banned is not None
|
||||
|
||||
|
@ -794,9 +799,13 @@ def current_theme():
|
|||
if current_user.theme is not None and current_user.theme != '':
|
||||
return current_user.theme
|
||||
else:
|
||||
return g.site.default_theme if g.site.default_theme is not None else ''
|
||||
if hasattr(g, 'site'):
|
||||
site = g.site
|
||||
else:
|
||||
site = Site.query.get(1)
|
||||
return site.default_theme if site.default_theme is not None else ''
|
||||
else:
|
||||
return g.site.default_theme if g.site.default_theme is not None else ''
|
||||
return ''
|
||||
|
||||
|
||||
def theme_list():
|
||||
|
@ -855,3 +864,37 @@ def show_ban_message():
|
|||
resp = make_response(redirect(url_for('main.index')))
|
||||
resp.set_cookie('sesion', '17489047567495', expires=datetime(year=2099, month=12, day=30))
|
||||
return resp
|
||||
|
||||
|
||||
# search a sorted list using a binary search. Faster than using 'in' with a unsorted list.
|
||||
def in_sorted_list(arr, target):
|
||||
index = bisect.bisect_left(arr, target)
|
||||
return index < len(arr) and arr[index] == target
|
||||
|
||||
|
||||
@cache.memoize(timeout=600)
|
||||
def recently_upvoted_posts(user_id) -> List[int]:
|
||||
post_ids = db.session.execute(text('SELECT post_id FROM "post_vote" WHERE user_id = :user_id AND effect > 0 ORDER BY id DESC LIMIT 1000'),
|
||||
{'user_id': user_id}).scalars()
|
||||
return sorted(post_ids) # sorted so that in_sorted_list can be used
|
||||
|
||||
|
||||
@cache.memoize(timeout=600)
|
||||
def recently_downvoted_posts(user_id) -> List[int]:
|
||||
post_ids = db.session.execute(text('SELECT post_id FROM "post_vote" WHERE user_id = :user_id AND effect < 0 ORDER BY id DESC LIMIT 1000'),
|
||||
{'user_id': user_id}).scalars()
|
||||
return sorted(post_ids)
|
||||
|
||||
|
||||
@cache.memoize(timeout=600)
|
||||
def recently_upvoted_post_replies(user_id) -> List[int]:
|
||||
reply_ids = db.session.execute(text('SELECT post_reply_id FROM "post_reply_vote" WHERE user_id = :user_id AND effect > 0 ORDER BY id DESC LIMIT 1000'),
|
||||
{'user_id': user_id}).scalars()
|
||||
return sorted(reply_ids) # sorted so that in_sorted_list can be used
|
||||
|
||||
|
||||
@cache.memoize(timeout=600)
|
||||
def recently_downvoted_post_replies(user_id) -> List[int]:
|
||||
reply_ids = db.session.execute(text('SELECT post_reply_id FROM "post_reply_vote" WHERE user_id = :user_id AND effect < 0 ORDER BY id DESC LIMIT 1000'),
|
||||
{'user_id': user_id}).scalars()
|
||||
return sorted(reply_ids)
|
||||
|
|
|
@ -2,3 +2,5 @@
|
|||
|
||||
This document helps prevent rearguing decisions after they have been made. It can also help new contributors come up to
|
||||
speed by providing a summary of how we got to the present state.
|
||||
|
||||
[Switch to using HTML while federating content instead of Markdown](https://codeberg.org/rimu/pyfedi/issues/133#issuecomment-1756067)
|
||||
|
|
32
migrations/versions/91a931afd6d9_contact_email.py
Normal file
32
migrations/versions/91a931afd6d9_contact_email.py
Normal file
|
@ -0,0 +1,32 @@
|
|||
"""contact email
|
||||
|
||||
Revision ID: 91a931afd6d9
|
||||
Revises: 08b3f718df5d
|
||||
Create Date: 2024-04-12 16:22:32.137053
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '91a931afd6d9'
|
||||
down_revision = '08b3f718df5d'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
with op.batch_alter_table('site', schema=None) as batch_op:
|
||||
batch_op.add_column(sa.Column('contact_email', sa.String(length=255), nullable=True))
|
||||
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
with op.batch_alter_table('site', schema=None) as batch_op:
|
||||
batch_op.drop_column('contact_email')
|
||||
|
||||
# ### end Alembic commands ###
|
|
@ -11,7 +11,8 @@ from flask import session, g, json, request, current_app
|
|||
from app.constants import POST_TYPE_LINK, POST_TYPE_IMAGE, POST_TYPE_ARTICLE
|
||||
from app.models import Site
|
||||
from app.utils import getmtime, gibberish, shorten_string, shorten_url, digits, user_access, community_membership, \
|
||||
can_create_post, can_upvote, can_downvote, shorten_number, ap_datetime, current_theme, community_link_to_href
|
||||
can_create_post, can_upvote, can_downvote, shorten_number, ap_datetime, current_theme, community_link_to_href, \
|
||||
in_sorted_list
|
||||
|
||||
app = create_app()
|
||||
cli.register(app)
|
||||
|
@ -42,6 +43,7 @@ with app.app_context():
|
|||
app.jinja_env.globals['can_create'] = can_create_post
|
||||
app.jinja_env.globals['can_upvote'] = can_upvote
|
||||
app.jinja_env.globals['can_downvote'] = can_downvote
|
||||
app.jinja_env.globals['in_sorted_list'] = in_sorted_list
|
||||
app.jinja_env.globals['theme'] = current_theme
|
||||
app.jinja_env.globals['file_exists'] = os.path.exists
|
||||
app.jinja_env.filters['community_links'] = community_link_to_href
|
||||
|
@ -53,7 +55,8 @@ with app.app_context():
|
|||
def before_request():
|
||||
session['nonce'] = gibberish()
|
||||
g.locale = str(get_locale())
|
||||
g.site = Site.query.get(1)
|
||||
if request.path != '/inbox' and not request.path.startswith('/static/'): # do not load g.site on shared inbox, to increase chance of duplicate detection working properly
|
||||
g.site = Site.query.get(1)
|
||||
if current_user.is_authenticated:
|
||||
current_user.last_seen = datetime.utcnow()
|
||||
current_user.email_unread_sent = False
|
||||
|
|
Loading…
Add table
Reference in a new issue