mirror of
https://codeberg.org/rimu/pyfedi
synced 2025-01-23 11:26:56 -08:00
Merge remote-tracking branch 'origin/main'
This commit is contained in:
commit
b90f4feb3c
16 changed files with 558 additions and 233 deletions
|
@ -25,7 +25,7 @@ from app.activitypub.util import public_key, users_total, active_half_year, acti
|
|||
update_post_from_activity, undo_vote, undo_downvote, post_to_page, get_redis_connection, find_reported_object, \
|
||||
process_report, ensure_domains_match, can_edit, can_delete, remove_data_from_banned_user, resolve_remote_post, \
|
||||
inform_followers_of_post_update, comment_model_to_json, restore_post_or_comment, ban_user, unban_user, \
|
||||
log_incoming_ap, find_community_ap_id, site_ban_remove_data, community_ban_remove_data
|
||||
log_incoming_ap, find_community, site_ban_remove_data, community_ban_remove_data, verify_object_from_source
|
||||
from app.utils import gibberish, get_setting, render_template, \
|
||||
community_membership, ap_datetime, ip_address, can_downvote, \
|
||||
can_upvote, can_create_post, awaken_dormant_instance, shorten_string, can_create_post_reply, sha256_digest, \
|
||||
|
@ -465,14 +465,19 @@ def shared_inbox():
|
|||
HttpSignature.verify_request(request, actor.public_key, skip_date=True)
|
||||
except VerificationError as e:
|
||||
bounced = True
|
||||
if not 'signature' in request_json:
|
||||
log_incoming_ap(id, APLOG_NOTYPE, APLOG_FAILURE, request_json if store_ap_json else None, 'Could not verify HTTP signature: ' + str(e))
|
||||
return '', 400
|
||||
# HTTP sig will fail if a.gup.pe or PeerTube have bounced a request, so check LD sig instead
|
||||
try:
|
||||
LDSignature.verify_signature(request_json, actor.public_key)
|
||||
except VerificationError as e:
|
||||
log_incoming_ap(id, APLOG_NOTYPE, APLOG_FAILURE, request_json if store_ap_json else None, 'Could not verify LD signature: ' + str(e))
|
||||
if 'signature' in request_json:
|
||||
try:
|
||||
LDSignature.verify_signature(request_json, actor.public_key)
|
||||
except VerificationError as e:
|
||||
log_incoming_ap(id, APLOG_NOTYPE, APLOG_FAILURE, request_json if store_ap_json else None, 'Could not verify LD signature: ' + str(e))
|
||||
return '', 400
|
||||
# not HTTP sig, and no LD sig, so reduce the inner object to just its remote ID, and then fetch it and check it in process_inbox_request()
|
||||
elif ((request_json['type'] == 'Create' or request_json['type'] == 'Update') and
|
||||
isinstance(request_json['object'], dict) and 'id' in request_json['object'] and isinstance(request_json['object']['id'], str)):
|
||||
request_json['object'] = request_json['object']['id']
|
||||
else:
|
||||
log_incoming_ap(id, APLOG_NOTYPE, APLOG_FAILURE, request_json if store_ap_json else None, 'Could not verify HTTP signature: ' + str(e))
|
||||
return '', 400
|
||||
|
||||
actor.instance.last_seen = utcnow()
|
||||
|
@ -599,6 +604,40 @@ def process_inbox_request(request_json, store_ap_json):
|
|||
db.session.commit()
|
||||
community = None # found as needed
|
||||
|
||||
# Announce: take care of inner objects that are just a URL (PeerTube, a.gup.pe), or find the user if the inner object is a dict
|
||||
if request_json['type'] == 'Announce':
|
||||
if isinstance(request_json['object'], str):
|
||||
if request_json['object'].startswith('https://' + current_app.config['SERVER_NAME']):
|
||||
log_incoming_ap(id, APLOG_DUPLICATE, APLOG_IGNORED, request_json if store_ap_json else None, 'Activity about local content which is already present')
|
||||
return
|
||||
post = resolve_remote_post(request_json['object'], community.id, announce_actor=community.ap_profile_id, store_ap_json=store_ap_json)
|
||||
if post:
|
||||
log_incoming_ap(id, APLOG_ANNOUNCE, APLOG_SUCCESS, request_json)
|
||||
else:
|
||||
log_incoming_ap(id, APLOG_ANNOUNCE, APLOG_FAILURE, request_json, 'Could not resolve post')
|
||||
return
|
||||
|
||||
user_ap_id = request_json['object']['actor']
|
||||
user = find_actor_or_create(user_ap_id)
|
||||
if not user or not isinstance(user, User):
|
||||
log_incoming_ap(id, APLOG_ANNOUNCE, APLOG_FAILURE, request_json if store_ap_json else None, 'Blocked or unfound user for Announce object actor ' + user_ap_id)
|
||||
return
|
||||
|
||||
user.last_seen = site.last_active = utcnow()
|
||||
user.instance.last_seen = utcnow()
|
||||
user.instance.dormant = False
|
||||
user.instance.gone_forever = False
|
||||
user.instance.failures = 0
|
||||
db.session.commit()
|
||||
|
||||
# Now that we have the community and the user from an Announce, we can save repeating code by removing it
|
||||
# core_activity is checked for its Type, but request_json is passed to any other functions
|
||||
announced = True
|
||||
core_activity = request_json['object']
|
||||
else:
|
||||
announced = False
|
||||
core_activity = request_json
|
||||
|
||||
# Follow: remote user wants to join/follow one of our users or communities
|
||||
if request_json['type'] == 'Follow':
|
||||
target_ap_id = request_json['object']
|
||||
|
@ -706,6 +745,12 @@ def process_inbox_request(request_json, store_ap_json):
|
|||
|
||||
# Create is new content. Update is often an edit, but Updates from Lemmy can also be new content
|
||||
if request_json['type'] == 'Create' or request_json['type'] == 'Update':
|
||||
if isinstance(request_json['object'], str):
|
||||
request_json = verify_object_from_source(request_json) # change request_json['object'] from str to dict, then process normally
|
||||
if not request_json:
|
||||
log_incoming_ap(id, APLOG_CREATE, APLOG_FAILURE, request_json if store_ap_json else None, 'Could not verify unsigned request from source')
|
||||
return
|
||||
|
||||
if request_json['object']['type'] == 'ChatMessage':
|
||||
sender = user
|
||||
recipient_ap_id = request_json['object']['to'][0]
|
||||
|
@ -761,11 +806,10 @@ def process_inbox_request(request_json, store_ap_json):
|
|||
if post_being_replied_to.author.is_local():
|
||||
inform_followers_of_post_update(post_being_replied_to.id, user.instance_id)
|
||||
return
|
||||
community_ap_id = find_community_ap_id(request_json)
|
||||
community = find_community(request_json)
|
||||
if not ensure_domains_match(request_json['object']):
|
||||
log_incoming_ap(id, APLOG_CREATE, APLOG_FAILURE, request_json if store_ap_json else None, 'Domains do not match')
|
||||
return
|
||||
community = find_actor_or_create(community_ap_id, community_only=True, create_if_not_found=False) if community_ap_id else None
|
||||
if community and community.local_only:
|
||||
log_incoming_ap(id, APLOG_CREATE, APLOG_FAILURE, request_json if store_ap_json else None, 'Remote Create in local_only community')
|
||||
return
|
||||
|
@ -812,40 +856,69 @@ def process_inbox_request(request_json, store_ap_json):
|
|||
log_incoming_ap(id, APLOG_DELETE, APLOG_FAILURE, request_json if store_ap_json else None, 'Delete: cannot find ' + ap_id)
|
||||
return
|
||||
|
||||
if request_json['type'] == 'Like' or request_json['type'] == 'EmojiReact': # Upvote
|
||||
process_upvote(user, store_ap_json, request_json, announced=False)
|
||||
if core_activity['type'] == 'Like' or core_activity['type'] == 'EmojiReact': # Upvote
|
||||
process_upvote(user, store_ap_json, request_json, announced)
|
||||
return
|
||||
|
||||
if request_json['type'] == 'Dislike': # Downvote
|
||||
if core_activity['type'] == 'Dislike': # Downvote
|
||||
if site.enable_downvotes is False:
|
||||
log_incoming_ap(id, APLOG_DISLIKE, APLOG_IGNORED, request_json if store_ap_json else None, 'Dislike ignored because of allow_dislike setting')
|
||||
return
|
||||
process_downvote(user, store_ap_json, request_json, announced=False)
|
||||
process_downvote(user, store_ap_json, request_json, announced)
|
||||
return
|
||||
|
||||
if request_json['type'] == 'Flag': # Reported content
|
||||
reported = find_reported_object(request_json['object'])
|
||||
if core_activity['type'] == 'Flag': # Reported content
|
||||
reported = find_reported_object(core_activity['object'])
|
||||
if reported:
|
||||
process_report(user, reported, request_json)
|
||||
process_report(user, reported, core_activity)
|
||||
log_incoming_ap(id, APLOG_REPORT, APLOG_SUCCESS, request_json if store_ap_json else None)
|
||||
announce_activity_to_followers(reported.community, user, request_json)
|
||||
else:
|
||||
log_incoming_ap(id, APLOG_REPORT, APLOG_IGNORED, request_json if store_ap_json else None, 'Report ignored due to missing content')
|
||||
return
|
||||
|
||||
if request_json['type'] == 'Add': # remote site is adding a local user as a moderator, and is sending directly rather than announcing (happens if not subscribed)
|
||||
if core_activity['type'] == 'Lock': # Post lock
|
||||
mod = user
|
||||
community_ap_id = find_community_ap_id(request_json)
|
||||
community = find_actor_or_create(community_ap_id, community_only=True, create_if_not_found=False) if community_ap_id else None
|
||||
post_id = core_activity['object']
|
||||
post = Post.query.filter_by(ap_id=post_id).first()
|
||||
reason = core_activity['summary'] if 'summary' in core_activity else ''
|
||||
if post:
|
||||
if post.community.is_moderator(mod) or post.community.is_instance_admin(mod):
|
||||
post.comments_enabled = False
|
||||
db.session.commit()
|
||||
add_to_modlog_activitypub('lock_post', mod, community_id=post.community.id,
|
||||
link_text=shorten_string(post.title), link=f'post/{post.id}',
|
||||
reason=reason)
|
||||
log_incoming_ap(id, APLOG_LOCK, APLOG_SUCCESS, request_json if store_ap_json else None)
|
||||
else:
|
||||
log_incoming_ap(id, APLOG_LOCK, APLOG_FAILURE, request_json if store_ap_json else None, 'Lock: Does not have permission')
|
||||
else:
|
||||
log_incoming_ap(id, APLOG_LOCK, APLOG_FAILURE, request_json if store_ap_json else None, 'Lock: post not found')
|
||||
return
|
||||
|
||||
if core_activity['type'] == 'Add': # Add mods, or sticky a post
|
||||
mod = user
|
||||
if not announced:
|
||||
community = find_community(core_activity)
|
||||
if community:
|
||||
if not community.is_moderator(mod) and not community.is_instance_admin(mod):
|
||||
log_incoming_ap(id, APLOG_ADD, APLOG_FAILURE, request_json if store_ap_json else None, 'Does not have permission')
|
||||
return
|
||||
target = request_json['target']
|
||||
target = core_activity['target']
|
||||
featured_url = community.ap_featured_url
|
||||
moderators_url = community.ap_moderators_url
|
||||
if target == featured_url:
|
||||
post = Post.query.filter_by(ap_id=core_activity['object']).first()
|
||||
if post:
|
||||
post.sticky = True
|
||||
db.session.commit()
|
||||
log_incoming_ap(id, APLOG_ADD, APLOG_SUCCESS, request_json if store_ap_json else None)
|
||||
else:
|
||||
log_incoming_ap(id, APLOG_ADD, APLOG_FAILURE, request_json if store_ap_json else None, 'Cannot find: ' + core_activity['object'])
|
||||
return
|
||||
if target == moderators_url:
|
||||
new_mod = find_actor_or_create(request_json['object'], create_if_not_found=False)
|
||||
if new_mod and new_mod.is_local():
|
||||
new_mod = find_actor_or_create(core_activity['object'])
|
||||
if new_mod:
|
||||
existing_membership = CommunityMember.query.filter_by(community_id=community.id, user_id=new_mod.id).first()
|
||||
if existing_membership:
|
||||
existing_membership.is_moderator = True
|
||||
|
@ -855,39 +928,45 @@ def process_inbox_request(request_json, store_ap_json):
|
|||
db.session.commit()
|
||||
log_incoming_ap(id, APLOG_ADD, APLOG_SUCCESS, request_json if store_ap_json else None)
|
||||
else:
|
||||
log_incoming_ap(id, APLOG_ADD, APLOG_FAILURE, request_json if store_ap_json else None, 'Cannot find: ' + request_json['object'])
|
||||
log_incoming_ap(id, APLOG_ADD, APLOG_FAILURE, request_json if store_ap_json else None, 'Cannot find: ' + core_activity['object'])
|
||||
return
|
||||
else:
|
||||
# Lemmy might not send anything directly to sticky a post if no-one is subscribed (could not get it to generate the activity)
|
||||
log_incoming_ap(id, APLOG_ADD, APLOG_FAILURE, request_json if store_ap_json else None, 'Unknown target for Add')
|
||||
log_incoming_ap(id, APLOG_ADD, APLOG_FAILURE, request_json if store_ap_json else None, 'Unknown target for Add')
|
||||
else:
|
||||
log_incoming_ap(id, APLOG_ADD, APLOG_FAILURE, request_json if store_ap_json else None, 'Add: cannot find community')
|
||||
return
|
||||
|
||||
if request_json['type'] == 'Remove': # remote site is removing a local user as a moderator, and is sending directly rather than announcing (happens if not subscribed)
|
||||
if core_activity['type'] == 'Remove': # Remove mods, or unsticky a post
|
||||
mod = user
|
||||
community_ap_id = find_community_ap_id(request_json)
|
||||
community = find_actor_or_create(community_ap_id, community_only=True, create_if_not_found=False) if community_ap_id else None
|
||||
if not announced:
|
||||
community = find_community(core_activity)
|
||||
if community:
|
||||
if not community.is_moderator(mod) and not community.is_instance_admin(mod):
|
||||
log_incoming_ap(id, APLOG_ADD, APLOG_FAILURE, request_json if store_ap_json else None, 'Does not have permission')
|
||||
return
|
||||
target = request_json['target']
|
||||
target = core_activity['target']
|
||||
featured_url = community.ap_featured_url
|
||||
moderators_url = community.ap_moderators_url
|
||||
if target == featured_url:
|
||||
post = Post.query.filter_by(ap_id=core_activity['object']).first()
|
||||
if post:
|
||||
post.sticky = False
|
||||
db.session.commit()
|
||||
log_incoming_ap(id, APLOG_REMOVE, APLOG_SUCCESS, request_json if store_ap_json else None)
|
||||
else:
|
||||
log_incoming_ap(id, APLOG_REMOVE, APLOG_FAILURE, request_json if store_ap_json else None, 'Cannot find: ' + core_activity['object'])
|
||||
return
|
||||
if target == moderators_url:
|
||||
old_mod = find_actor_or_create(request_json['object'], create_if_not_found=False)
|
||||
if old_mod and old_mod.is_local():
|
||||
old_mod = find_actor_or_create(core_activity['object'])
|
||||
if old_mod:
|
||||
existing_membership = CommunityMember.query.filter_by(community_id=community.id, user_id=old_mod.id).first()
|
||||
if existing_membership:
|
||||
existing_membership.is_moderator = False
|
||||
db.session.commit()
|
||||
log_incoming_ap(id, APLOG_REMOVE, APLOG_SUCCESS, request_json if store_ap_json else None)
|
||||
else:
|
||||
log_incoming_ap(id, APLOG_ADD, APLOG_FAILURE, request_json if store_ap_json else None, 'Cannot find: ' + request_json['object'])
|
||||
log_incoming_ap(id, APLOG_ADD, APLOG_FAILURE, request_json if store_ap_json else None, 'Cannot find: ' + core_activity['object'])
|
||||
return
|
||||
else:
|
||||
# Lemmy might not send anything directly to unsticky a post if no-one is subscribed (could not get it to generate the activity)
|
||||
log_incoming_ap(id, APLOG_ADD, APLOG_FAILURE, request_json if store_ap_json else None, 'Unknown target for Remove')
|
||||
log_incoming_ap(id, APLOG_ADD, APLOG_FAILURE, request_json if store_ap_json else None, 'Unknown target for Remove')
|
||||
else:
|
||||
log_incoming_ap(id, APLOG_ADD, APLOG_FAILURE, request_json if store_ap_json else None, 'Remove: cannot find community')
|
||||
return
|
||||
|
@ -1024,29 +1103,8 @@ def process_inbox_request(request_json, store_ap_json):
|
|||
|
||||
# Announce is new content and votes that happened on a remote server.
|
||||
if request_json['type'] == 'Announce':
|
||||
if isinstance(request_json['object'], str): # Mastodon, PeerTube, A.gup.pe
|
||||
if request_json['object'].startswith('https://' + current_app.config['SERVER_NAME']):
|
||||
log_incoming_ap(id, APLOG_DUPLICATE, APLOG_IGNORED, request_json if store_ap_json else None, 'Activity about local content which is already present')
|
||||
return
|
||||
post = resolve_remote_post(request_json['object'], community.id, announce_actor=community.ap_profile_id, store_ap_json=store_ap_json)
|
||||
if post:
|
||||
log_incoming_ap(id, APLOG_ANNOUNCE, APLOG_SUCCESS, request_json)
|
||||
else:
|
||||
log_incoming_ap(id, APLOG_ANNOUNCE, APLOG_FAILURE, request_json, 'Could not resolve post')
|
||||
return
|
||||
|
||||
user_ap_id = request_json['object']['actor']
|
||||
user = find_actor_or_create(user_ap_id)
|
||||
if not user or not isinstance(user, User):
|
||||
log_incoming_ap(id, APLOG_ANNOUNCE, APLOG_FAILURE, request_json if store_ap_json else None, 'Blocked or unfound user for Announce object actor ' + user_ap_id)
|
||||
return
|
||||
|
||||
user.last_seen = site.last_active = utcnow()
|
||||
user.instance.last_seen = utcnow()
|
||||
user.instance.dormant = False
|
||||
user.instance.gone_forever = False
|
||||
user.instance.failures = 0
|
||||
db.session.commit()
|
||||
# should be able to remove the rest of this, and process the activities above.
|
||||
# Then for any activities that are both sent direct and Announced, one can be dropped when shared_inbox() checks for dupes
|
||||
|
||||
if request_json['object']['type'] == 'Create' or request_json['object']['type'] == 'Update':
|
||||
object_type = request_json['object']['object']['type']
|
||||
|
@ -1078,101 +1136,101 @@ def process_inbox_request(request_json, store_ap_json):
|
|||
log_incoming_ap(id, APLOG_DELETE, APLOG_FAILURE, request_json if store_ap_json else None, 'Delete: cannot find ' + ap_id)
|
||||
return
|
||||
|
||||
if request_json['object']['type'] == 'Like' or request_json['object']['type'] == 'EmojiReact': # Announced Upvote
|
||||
process_upvote(user, store_ap_json, request_json)
|
||||
return
|
||||
#if request_json['object']['type'] == 'Like' or request_json['object']['type'] == 'EmojiReact': # Announced Upvote
|
||||
# process_upvote(user, store_ap_json, request_json)
|
||||
# return
|
||||
|
||||
if request_json['object']['type'] == 'Dislike': # Announced Downvote
|
||||
if site.enable_downvotes is False:
|
||||
log_incoming_ap(id, APLOG_DISLIKE, APLOG_IGNORED, request_json if store_ap_json else None, 'Dislike ignored because of allow_dislike setting')
|
||||
return
|
||||
process_downvote(user, store_ap_json, request_json)
|
||||
return
|
||||
#if request_json['object']['type'] == 'Dislike': # Announced Downvote
|
||||
# if site.enable_downvotes is False:
|
||||
# log_incoming_ap(id, APLOG_DISLIKE, APLOG_IGNORED, request_json if store_ap_json else None, 'Dislike ignored because of allow_dislike setting')
|
||||
# return
|
||||
# process_downvote(user, store_ap_json, request_json)
|
||||
# return
|
||||
|
||||
if request_json['object']['type'] == 'Flag': # Announce of reported content
|
||||
reported = find_reported_object(request_json['object']['object'])
|
||||
if reported:
|
||||
process_report(user, reported, request_json['object'])
|
||||
log_incoming_ap(id, APLOG_REPORT, APLOG_SUCCESS, request_json if store_ap_json else None)
|
||||
else:
|
||||
log_incoming_ap(id, APLOG_REPORT, APLOG_IGNORED, request_json if store_ap_json else None, 'Report ignored due to missing content')
|
||||
return
|
||||
#if request_json['object']['type'] == 'Flag': # Announce of reported content
|
||||
# reported = find_reported_object(request_json['object']['object'])
|
||||
# if reported:
|
||||
# process_report(user, reported, request_json['object'])
|
||||
# log_incoming_ap(id, APLOG_REPORT, APLOG_SUCCESS, request_json if store_ap_json else None)
|
||||
# else:
|
||||
# log_incoming_ap(id, APLOG_REPORT, APLOG_IGNORED, request_json if store_ap_json else None, 'Report ignored due to missing content')
|
||||
# return
|
||||
|
||||
if request_json['object']['type'] == 'Lock': # Announce of post lock
|
||||
mod = user
|
||||
post_id = request_json['object']['object']
|
||||
post = Post.query.filter_by(ap_id=post_id).first()
|
||||
reason = request_json['object']['summary'] if 'summary' in request_json['object'] else ''
|
||||
if post:
|
||||
if post.community.is_moderator(mod) or post.community.is_instance_admin(mod):
|
||||
post.comments_enabled = False
|
||||
db.session.commit()
|
||||
add_to_modlog_activitypub('lock_post', mod, community_id=post.community.id,
|
||||
link_text=shorten_string(post.title), link=f'post/{post.id}',
|
||||
reason=reason)
|
||||
log_incoming_ap(id, APLOG_LOCK, APLOG_SUCCESS, request_json if store_ap_json else None)
|
||||
else:
|
||||
log_incoming_ap(id, APLOG_LOCK, APLOG_FAILURE, request_json if store_ap_json else None, 'Lock: Does not have permission')
|
||||
else:
|
||||
log_incoming_ap(id, APLOG_LOCK, APLOG_FAILURE, request_json if store_ap_json else None, 'Lock: post not found')
|
||||
return
|
||||
#if request_json['object']['type'] == 'Lock': # Announce of post lock
|
||||
# mod = user
|
||||
# post_id = request_json['object']['object']
|
||||
# post = Post.query.filter_by(ap_id=post_id).first()
|
||||
# reason = request_json['object']['summary'] if 'summary' in request_json['object'] else ''
|
||||
# if post:
|
||||
# if post.community.is_moderator(mod) or post.community.is_instance_admin(mod):
|
||||
# post.comments_enabled = False
|
||||
# db.session.commit()
|
||||
# add_to_modlog_activitypub('lock_post', mod, community_id=post.community.id,
|
||||
# link_text=shorten_string(post.title), link=f'post/{post.id}',
|
||||
# reason=reason)
|
||||
# log_incoming_ap(id, APLOG_LOCK, APLOG_SUCCESS, request_json if store_ap_json else None)
|
||||
# else:
|
||||
# log_incoming_ap(id, APLOG_LOCK, APLOG_FAILURE, request_json if store_ap_json else None, 'Lock: Does not have permission')
|
||||
# else:
|
||||
# log_incoming_ap(id, APLOG_LOCK, APLOG_FAILURE, request_json if store_ap_json else None, 'Lock: post not found')
|
||||
# return
|
||||
|
||||
if request_json['object']['type'] == 'Add': # Announce of adding mods or stickying a post
|
||||
target = request_json['object']['target']
|
||||
featured_url = community.ap_featured_url
|
||||
moderators_url = community.ap_moderators_url
|
||||
if target == featured_url:
|
||||
post = Post.query.filter_by(ap_id=request_json['object']['object']).first()
|
||||
if post:
|
||||
post.sticky = True
|
||||
db.session.commit()
|
||||
log_incoming_ap(id, APLOG_ADD, APLOG_SUCCESS, request_json if store_ap_json else None)
|
||||
else:
|
||||
log_incoming_ap(id, APLOG_ADD, APLOG_FAILURE, request_json if store_ap_json else None, 'Cannot find: ' + request_json['object']['object'])
|
||||
return
|
||||
if target == moderators_url:
|
||||
user = find_actor_or_create(request_json['object']['object'])
|
||||
if user:
|
||||
existing_membership = CommunityMember.query.filter_by(community_id=community.id, user_id=user.id).first()
|
||||
if existing_membership:
|
||||
existing_membership.is_moderator = True
|
||||
else:
|
||||
new_membership = CommunityMember(community_id=community.id, user_id=user.id, is_moderator=True)
|
||||
db.session.add(new_membership)
|
||||
db.session.commit()
|
||||
log_incoming_ap(id, APLOG_ADD, APLOG_SUCCESS, request_json if store_ap_json else None)
|
||||
else:
|
||||
log_incoming_ap(id, APLOG_ADD, APLOG_FAILURE, request_json if store_ap_json else None, 'Cannot find: ' + request_json['object']['object'])
|
||||
return
|
||||
log_incoming_ap(id, APLOG_ADD, APLOG_FAILURE, request_json if store_ap_json else None, 'Unknown target for Add')
|
||||
return
|
||||
#if request_json['object']['type'] == 'Add': # Announce of adding mods or stickying a post
|
||||
# target = request_json['object']['target']
|
||||
# featured_url = community.ap_featured_url
|
||||
# moderators_url = community.ap_moderators_url
|
||||
# if target == featured_url:
|
||||
# post = Post.query.filter_by(ap_id=request_json['object']['object']).first()
|
||||
# if post:
|
||||
# post.sticky = True
|
||||
# db.session.commit()
|
||||
# log_incoming_ap(id, APLOG_ADD, APLOG_SUCCESS, request_json if store_ap_json else None)
|
||||
# else:
|
||||
# log_incoming_ap(id, APLOG_ADD, APLOG_FAILURE, request_json if store_ap_json else None, 'Cannot find: ' + request_json['object']['object'])
|
||||
# return
|
||||
# if target == moderators_url:
|
||||
# user = find_actor_or_create(request_json['object']['object'])
|
||||
# if user:
|
||||
# existing_membership = CommunityMember.query.filter_by(community_id=community.id, user_id=user.id).first()
|
||||
# if existing_membership:
|
||||
# existing_membership.is_moderator = True
|
||||
# else:
|
||||
# new_membership = CommunityMember(community_id=community.id, user_id=user.id, is_moderator=True)
|
||||
# db.session.add(new_membership)
|
||||
# db.session.commit()
|
||||
# log_incoming_ap(id, APLOG_ADD, APLOG_SUCCESS, request_json if store_ap_json else None)
|
||||
# else:
|
||||
# log_incoming_ap(id, APLOG_ADD, APLOG_FAILURE, request_json if store_ap_json else None, 'Cannot find: ' + request_json['object']['object'])
|
||||
# return
|
||||
# log_incoming_ap(id, APLOG_ADD, APLOG_FAILURE, request_json if store_ap_json else None, 'Unknown target for Add')
|
||||
# return
|
||||
|
||||
if request_json['object']['type'] == 'Remove': # Announce of removing mods or unstickying a post
|
||||
target = request_json['object']['target']
|
||||
featured_url = community.ap_featured_url
|
||||
moderators_url = community.ap_moderators_url
|
||||
if target == featured_url:
|
||||
post = Post.query.filter_by(ap_id=request_json['object']['object']).first()
|
||||
if post:
|
||||
post.sticky = False
|
||||
db.session.commit()
|
||||
log_incoming_ap(id, APLOG_REMOVE, APLOG_SUCCESS, request_json if store_ap_json else None)
|
||||
else:
|
||||
log_incoming_ap(id, APLOG_REMOVE, APLOG_FAILURE, request_json if store_ap_json else None, 'Cannot find: ' + target)
|
||||
return
|
||||
if target == moderators_url:
|
||||
user = find_actor_or_create(request_json['object']['object'], create_if_not_found=False)
|
||||
if user:
|
||||
existing_membership = CommunityMember.query.filter_by(community_id=community.id, user_id=user.id).first()
|
||||
if existing_membership:
|
||||
existing_membership.is_moderator = False
|
||||
db.session.commit()
|
||||
log_incoming_ap(id, APLOG_REMOVE, APLOG_SUCCESS, request_json if store_ap_json else None)
|
||||
else:
|
||||
log_incoming_ap(id, APLOG_REMOVE, APLOG_FAILURE, request_json if store_ap_json else None, 'Cannot find: ' + request_json['object']['object'])
|
||||
return
|
||||
log_incoming_ap(id, APLOG_REMOVE, APLOG_FAILURE, request_json if store_ap_json else None, 'Unknown target for Remove')
|
||||
return
|
||||
#if request_json['object']['type'] == 'Remove': # Announce of removing mods or unstickying a post
|
||||
# target = request_json['object']['target']
|
||||
# featured_url = community.ap_featured_url
|
||||
# moderators_url = community.ap_moderators_url
|
||||
# if target == featured_url:
|
||||
# post = Post.query.filter_by(ap_id=request_json['object']['object']).first()
|
||||
# if post:
|
||||
# post.sticky = False
|
||||
# db.session.commit()
|
||||
# log_incoming_ap(id, APLOG_REMOVE, APLOG_SUCCESS, request_json if store_ap_json else None)
|
||||
# else:
|
||||
# log_incoming_ap(id, APLOG_REMOVE, APLOG_FAILURE, request_json if store_ap_json else None, 'Cannot find: ' + target)
|
||||
# return
|
||||
# if target == moderators_url:
|
||||
# user = find_actor_or_create(request_json['object']['object'], create_if_not_found=False)
|
||||
# if user:
|
||||
# existing_membership = CommunityMember.query.filter_by(community_id=community.id, user_id=user.id).first()
|
||||
# if existing_membership:
|
||||
# existing_membership.is_moderator = False
|
||||
# db.session.commit()
|
||||
# log_incoming_ap(id, APLOG_REMOVE, APLOG_SUCCESS, request_json if store_ap_json else None)
|
||||
# else:
|
||||
# log_incoming_ap(id, APLOG_REMOVE, APLOG_FAILURE, request_json if store_ap_json else None, 'Cannot find: ' + request_json['object']['object'])
|
||||
# return
|
||||
# log_incoming_ap(id, APLOG_REMOVE, APLOG_FAILURE, request_json if store_ap_json else None, 'Unknown target for Remove')
|
||||
# return
|
||||
|
||||
if request_json['object']['type'] == 'Block': # Announce of user ban. Mod is banning a user from a community,
|
||||
blocker = user # or an admin is banning a user from all the site's communities as part of a site ban
|
||||
|
@ -1615,7 +1673,7 @@ def process_new_content(user, community, store_ap_json, request_json, announced=
|
|||
return
|
||||
|
||||
|
||||
def process_upvote(user, store_ap_json, request_json, announced=True):
|
||||
def process_upvote(user, store_ap_json, request_json, announced):
|
||||
id = request_json['id']
|
||||
ap_id = request_json['object'] if not announced else request_json['object']['object']
|
||||
if isinstance(ap_id, dict) and 'id' in ap_id:
|
||||
|
@ -1634,7 +1692,7 @@ def process_upvote(user, store_ap_json, request_json, announced=True):
|
|||
log_incoming_ap(id, APLOG_LIKE, APLOG_IGNORED, request_json if store_ap_json else None, 'Cannot upvote this')
|
||||
|
||||
|
||||
def process_downvote(user, store_ap_json, request_json, announced=True):
|
||||
def process_downvote(user, store_ap_json, request_json, announced):
|
||||
id = request_json['id']
|
||||
ap_id = request_json['object'] if not announced else request_json['object']['object']
|
||||
if isinstance(ap_id, dict) and 'id' in ap_id:
|
||||
|
|
|
@ -1782,8 +1782,38 @@ def update_post_from_activity(post: Post, request_json: dict):
|
|||
post.edited_at = utcnow()
|
||||
|
||||
if request_json['object']['type'] == 'Video':
|
||||
# fetching individual user details to attach to votes is probably too convoluted, so take the instance's word for it
|
||||
upvotes = 1 # from OP
|
||||
downvotes = 0
|
||||
endpoints = ['likes', 'dislikes']
|
||||
for endpoint in endpoints:
|
||||
if endpoint in request_json['object']:
|
||||
try:
|
||||
object_request = get_request(request_json['object'][endpoint], headers={'Accept': 'application/activity+json'})
|
||||
except httpx.HTTPError:
|
||||
time.sleep(3)
|
||||
try:
|
||||
object_request = get_request(request_json['object'][endpoint], headers={'Accept': 'application/activity+json'})
|
||||
except httpx.HTTPError:
|
||||
object_request = None
|
||||
if object_request and object_request.status_code == 200:
|
||||
try:
|
||||
object = object_request.json()
|
||||
except:
|
||||
object_request.close()
|
||||
object = None
|
||||
object_request.close()
|
||||
if object and 'totalItems' in object:
|
||||
if endpoint == 'likes':
|
||||
upvotes += object['totalItems']
|
||||
if endpoint == 'dislikes':
|
||||
downvotes += object['totalItems']
|
||||
post.up_votes = upvotes
|
||||
post.down_votes = downvotes
|
||||
post.score = upvotes - downvotes
|
||||
post.ranking = post.post_ranking(post.score, post.posted_at)
|
||||
# return now for PeerTube, otherwise rest of this function breaks the post
|
||||
# consider querying the Likes endpoint (that mostly seems to be what Updates are about)
|
||||
db.session.commit()
|
||||
return
|
||||
|
||||
# Links
|
||||
|
@ -2479,6 +2509,68 @@ def resolve_remote_post_from_search(uri: str) -> Union[Post, None]:
|
|||
return None
|
||||
|
||||
|
||||
# called from activitypub/routes if something is posted to us without any kind of signature (typically from PeerTube)
|
||||
def verify_object_from_source(request_json):
|
||||
uri = request_json['object']
|
||||
uri_domain = urlparse(uri).netloc
|
||||
if not uri_domain:
|
||||
return None
|
||||
|
||||
create_domain = urlparse(request_json['actor']).netloc
|
||||
if create_domain != uri_domain:
|
||||
return None
|
||||
|
||||
try:
|
||||
object_request = get_request(uri, headers={'Accept': 'application/activity+json'})
|
||||
except httpx.HTTPError:
|
||||
time.sleep(3)
|
||||
try:
|
||||
object_request = get_request(uri, headers={'Accept': 'application/activity+json'})
|
||||
except httpx.HTTPError:
|
||||
return None
|
||||
if object_request.status_code == 200:
|
||||
try:
|
||||
object = object_request.json()
|
||||
except:
|
||||
object_request.close()
|
||||
return None
|
||||
object_request.close()
|
||||
elif object_request.status_code == 401:
|
||||
try:
|
||||
site = Site.query.get(1)
|
||||
object_request = signed_get_request(uri, site.private_key, f"https://{current_app.config['SERVER_NAME']}/actor#main-key")
|
||||
except httpx.HTTPError:
|
||||
time.sleep(3)
|
||||
try:
|
||||
object_request = signed_get_request(uri, site.private_key, f"https://{current_app.config['SERVER_NAME']}/actor#main-key")
|
||||
except httpx.HTTPError:
|
||||
return None
|
||||
try:
|
||||
object = object_request.json()
|
||||
except:
|
||||
object_request.close()
|
||||
return None
|
||||
object_request.close()
|
||||
else:
|
||||
return None
|
||||
|
||||
if not 'id' in object or not 'type' in object or not 'attributedTo' in object:
|
||||
return None
|
||||
|
||||
if isinstance(object['attributedTo'], str):
|
||||
actor_domain = urlparse(object['attributedTo']).netloc
|
||||
elif isinstance(object['attributedTo'], dict) and 'id' in object['attributedTo']:
|
||||
actor_domain = urlparse(object['attributedTo']['id']).netloc
|
||||
else:
|
||||
return None
|
||||
|
||||
if uri_domain != actor_domain:
|
||||
return None
|
||||
|
||||
request_json['object'] = object
|
||||
return request_json
|
||||
|
||||
|
||||
# This is for followers on microblog apps
|
||||
# Used to let them know a Poll has been updated with a new vote
|
||||
# The plan is to also use it for activities on local user's posts that aren't understood by being Announced (anything beyond the initial Create)
|
||||
|
@ -2547,7 +2639,7 @@ def log_incoming_ap(id, aplog_type, aplog_result, request_json, message=None):
|
|||
db.session.commit()
|
||||
|
||||
|
||||
def find_community_ap_id(request_json):
|
||||
def find_community(request_json):
|
||||
locations = ['audience', 'cc', 'to']
|
||||
if 'object' in request_json and isinstance(request_json['object'], dict):
|
||||
rjs = [request_json, request_json['object']]
|
||||
|
@ -2561,30 +2653,32 @@ def find_community_ap_id(request_json):
|
|||
if not potential_id.startswith('https://www.w3.org') and not potential_id.endswith('/followers'):
|
||||
potential_community = Community.query.filter_by(ap_profile_id=potential_id.lower()).first()
|
||||
if potential_community:
|
||||
return potential_id
|
||||
return potential_community
|
||||
if isinstance(potential_id, list):
|
||||
for c in potential_id:
|
||||
if not c.startswith('https://www.w3.org') and not c.endswith('/followers'):
|
||||
potential_community = Community.query.filter_by(ap_profile_id=c.lower()).first()
|
||||
if potential_community:
|
||||
return c
|
||||
return potential_community
|
||||
|
||||
if not 'object' in request_json:
|
||||
return None
|
||||
|
||||
if 'inReplyTo' in request_json['object'] and request_json['object']['inReplyTo'] is not None:
|
||||
post_being_replied_to = Post.query.filter_by(ap_id=request_json['object']['inReplyTo']).first()
|
||||
post_being_replied_to = Post.query.filter_by(ap_id=request_json['object']['inReplyTo'].lower()).first()
|
||||
if post_being_replied_to:
|
||||
return post_being_replied_to.community.ap_profile_id
|
||||
return post_being_replied_to.community
|
||||
else:
|
||||
comment_being_replied_to = PostReply.query.filter_by(ap_id=request_json['object']['inReplyTo']).first()
|
||||
comment_being_replied_to = PostReply.query.filter_by(ap_id=request_json['object']['inReplyTo'].lower()).first()
|
||||
if comment_being_replied_to:
|
||||
return comment_being_replied_to.community.ap_profile_id
|
||||
return comment_being_replied_to.community
|
||||
|
||||
if request_json['object']['type'] == 'Video': # PeerTube
|
||||
if 'attributedTo' in request_json['object'] and isinstance(request_json['object']['attributedTo'], list):
|
||||
for a in request_json['object']['attributedTo']:
|
||||
if a['type'] == 'Group':
|
||||
return a['id']
|
||||
potential_community = Community.query.filter_by(ap_profile_id=a['id'].lower()).first()
|
||||
if potential_community:
|
||||
return potential_community
|
||||
|
||||
return None
|
||||
|
|
|
@ -406,6 +406,7 @@ def alpha_emoji():
|
|||
|
||||
# HTML routes
|
||||
from flask import abort, render_template
|
||||
from app.models import Community
|
||||
from app.utils import current_theme
|
||||
import os
|
||||
|
||||
|
@ -463,3 +464,19 @@ def get_alpha_communities():
|
|||
return render_template(f'themes/{theme}/{template_name}')
|
||||
else:
|
||||
return render_template(template_name)
|
||||
|
||||
|
||||
@bp.route('/api/alpha/c/<actor>', methods=['GET'])
|
||||
def community_profile(actor):
|
||||
if '@' in actor:
|
||||
community = Community.query.filter_by(ap_id=actor.lower(), banned=False).first()
|
||||
else:
|
||||
community = Community.query.filter_by(name=actor, ap_id=None).first()
|
||||
|
||||
template_name = "community.html"
|
||||
|
||||
theme = current_theme()
|
||||
if theme != '' and os.path.exists(f'app/templates/themes/{theme}/{template_name}'):
|
||||
return render_template(f'themes/{theme}/{template_name}', community_id=community.id)
|
||||
else:
|
||||
return render_template(template_name, community_id=community.id)
|
||||
|
|
|
@ -1,18 +1,65 @@
|
|||
from app import db
|
||||
from app import cache, db
|
||||
from app.api.alpha.views import user_view, community_view, instance_view
|
||||
from app.api.alpha.utils.validators import required, integer_expected, boolean_expected
|
||||
from app.utils import authorise_api_user
|
||||
from app.models import InstanceBlock, Language
|
||||
from app.models import CommunityMember, InstanceBlock, Language
|
||||
from app.shared.site import block_remote_instance, unblock_remote_instance
|
||||
|
||||
from flask import current_app, g
|
||||
|
||||
from sqlalchemy import text
|
||||
|
||||
@cache.memoize(timeout=86400)
|
||||
def users_total():
|
||||
return db.session.execute(text(
|
||||
'SELECT COUNT(id) as c FROM "user" WHERE ap_id is null AND verified is true AND banned is false AND deleted is false')).scalar()
|
||||
|
||||
|
||||
@cache.memoize(timeout=86400)
|
||||
def moderating_communities(user):
|
||||
cms = CommunityMember.query.filter_by(user_id=user.id, is_moderator=True)
|
||||
moderates = []
|
||||
for cm in cms:
|
||||
moderates.append({'community': community_view(cm.community_id, variant=1, stub=True), 'moderator': user_view(user, variant=1, stub=True)})
|
||||
return moderates
|
||||
|
||||
|
||||
@cache.memoize(timeout=86400)
|
||||
def joined_communities(user):
|
||||
cms = CommunityMember.query.filter_by(user_id=user.id, is_banned=False)
|
||||
follows = []
|
||||
for cm in cms:
|
||||
follows.append({'community': community_view(cm.community_id, variant=1, stub=True), 'follower': user_view(user, variant=1, stub=True)})
|
||||
return follows
|
||||
|
||||
|
||||
@cache.memoize(timeout=86400)
|
||||
def blocked_people(user):
|
||||
blocked = []
|
||||
blocked_ids = db.session.execute(text('SELECT blocked_id FROM "user_block" WHERE blocker_id = :blocker_id'), {"blocker_id": user.id}).scalars()
|
||||
for blocked_id in blocked_ids:
|
||||
blocked.append({'person': user_view(user, variant=1, stub=True), 'target': user_view(blocked_id, variant=1, stub=True)})
|
||||
return blocked
|
||||
|
||||
|
||||
@cache.memoize(timeout=86400)
|
||||
def blocked_communities(user):
|
||||
blocked = []
|
||||
blocked_ids = db.session.execute(text('SELECT community_id FROM "community_block" WHERE user_id = :user_id'), {"user_id": user.id}).scalars()
|
||||
for blocked_id in blocked_ids:
|
||||
blocked.append({'person': user_view(user, variant=1, stub=True), 'community': community_view(blocked_id, variant=1, stub=True)})
|
||||
return blocked
|
||||
|
||||
|
||||
@cache.memoize(timeout=86400)
|
||||
def blocked_instances(user):
|
||||
blocked = []
|
||||
blocked_ids = db.session.execute(text('SELECT instance_id FROM "instance_block" WHERE user_id = :user_id'), {"user_id": user.id}).scalars()
|
||||
for blocked_id in blocked_ids:
|
||||
blocked.append({'person': user_view(user, variant=1, stub=True), 'instance': instance_view(blocked_id, variant=1)})
|
||||
return blocked
|
||||
|
||||
|
||||
def get_site(auth):
|
||||
if auth:
|
||||
user = authorise_api_user(auth, return_type='model')
|
||||
|
@ -74,31 +121,13 @@ def get_site(auth):
|
|||
"comment_count": user.post_reply_count
|
||||
}
|
||||
},
|
||||
#"moderates": [],
|
||||
#"follows": [],
|
||||
"community_blocks": [],
|
||||
"instance_blocks": [],
|
||||
"person_blocks": [],
|
||||
"moderates": moderating_communities(user),
|
||||
"follows": joined_communities(user),
|
||||
"community_blocks": blocked_communities(user),
|
||||
"instance_blocks": blocked_instances(user),
|
||||
"person_blocks": blocked_people(user),
|
||||
"discussion_languages": [] # TODO
|
||||
}
|
||||
"""
|
||||
Note: Thunder doesn't use moderates[] and follows[] from here, but it would be more efficient if it did (rather than getting them from /user and /community)
|
||||
cms = CommunityMember.query.filter_by(user_id=user_id, is_moderator=True)
|
||||
for cm in cms:
|
||||
my_user['moderates'].append({'community': Community.api_json(variant=1, id=cm.community_id, stub=True), 'moderator': User.api_json(variant=1, id=user_id, stub=True)})
|
||||
cms = CommunityMember.query.filter_by(user_id=user_id, is_banned=False)
|
||||
for cm in cms:
|
||||
my_user['follows'].append({'community': Community.api_json(variant=1, id=cm.community_id, stub=True), 'follower': User.api_json(variant=1, id=user_id, stub=True)})
|
||||
"""
|
||||
blocked_ids = db.session.execute(text('SELECT blocked_id FROM "user_block" WHERE blocker_id = :blocker_id'), {"blocker_id": user.id}).scalars()
|
||||
for blocked_id in blocked_ids:
|
||||
my_user['person_blocks'].append({'person': user_view(user, variant=1, stub=True), 'target': user_view(blocked_id, variant=1, stub=True)})
|
||||
blocked_ids = db.session.execute(text('SELECT community_id FROM "community_block" WHERE user_id = :user_id'), {"user_id": user.id}).scalars()
|
||||
for blocked_id in blocked_ids:
|
||||
my_user['community_blocks'].append({'person': user_view(user, variant=1, stub=True), 'community': community_view(blocked_id, variant=1, stub=True)})
|
||||
blocked_ids = db.session.execute(text('SELECT instance_id FROM "instance_block" WHERE user_id = :user_id'), {"user_id": user.id}).scalars()
|
||||
for blocked_id in blocked_ids:
|
||||
my_user['instance_blocks'].append({'person': user_view(user, variant=1, stub=True), 'instance': instance_view(blocked_id, variant=1)})
|
||||
data = {
|
||||
"version": "1.0.0",
|
||||
"site": site
|
||||
|
|
|
@ -173,7 +173,8 @@ def cached_community_view_variant_1(community: Community, stub=False):
|
|||
'actor_id': community.public_url(),
|
||||
'local': community.is_local(),
|
||||
'hidden': not community.show_all,
|
||||
'instance_id': community.instance_id if community.instance_id else 1})
|
||||
'instance_id': community.instance_id if community.instance_id else 1,
|
||||
'ap_domain': community.ap_domain})
|
||||
if community.description and not stub:
|
||||
v1['description'] = community.description
|
||||
if community.icon_id:
|
||||
|
|
|
@ -722,8 +722,6 @@ def add_post(actor, type):
|
|||
post.ap_id = f"https://{current_app.config['SERVER_NAME']}/post/{post.id}"
|
||||
db.session.commit()
|
||||
|
||||
upvote_own_post(post)
|
||||
|
||||
if post.type == POST_TYPE_POLL:
|
||||
poll = Poll.query.filter_by(post_id=post.id).first()
|
||||
if not poll.local_only:
|
||||
|
@ -2007,11 +2005,3 @@ def check_url_already_posted():
|
|||
abort(404)
|
||||
|
||||
|
||||
def upvote_own_post(post):
|
||||
post.score = 1
|
||||
post.up_votes = 1
|
||||
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()
|
||||
cache.delete_memoized(recently_upvoted_posts, current_user.id)
|
||||
|
|
|
@ -290,10 +290,13 @@ def list_subscribed_communities():
|
|||
all_communities = Community.query.filter_by(banned=False)
|
||||
# get the user's joined communities
|
||||
user_joined_communities = joined_communities(current_user.id)
|
||||
user_moderating_communities = moderating_communities(current_user.id)
|
||||
# get the joined community ids list
|
||||
joined_ids = []
|
||||
for jc in user_joined_communities:
|
||||
joined_ids.append(jc.id)
|
||||
for mc in user_moderating_communities:
|
||||
joined_ids.append(mc.id)
|
||||
# filter down to just the joined communities
|
||||
communities = all_communities.filter(Community.id.in_(joined_ids))
|
||||
|
||||
|
|
|
@ -1171,7 +1171,7 @@ class Post(db.Model):
|
|||
find_licence_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, \
|
||||
is_video_hosting_site, communities_banned_from
|
||||
is_video_hosting_site, communities_banned_from, recently_upvoted_posts
|
||||
|
||||
microblog = False
|
||||
if 'name' not in request_json['object']: # Microblog posts
|
||||
|
@ -1399,11 +1399,16 @@ class Post(db.Model):
|
|||
if post.community_id not in communities_banned_from(user.id):
|
||||
notify_about_post(post)
|
||||
|
||||
# attach initial upvote to author
|
||||
vote = PostVote(user_id=user.id, post_id=post.id, author_id=user.id, effect=1)
|
||||
db.session.add(vote)
|
||||
if user.is_local():
|
||||
cache.delete_memoized(recently_upvoted_posts, user.id)
|
||||
if user.reputation > 100:
|
||||
post.up_votes += 1
|
||||
post.score += 1
|
||||
post.ranking = post.post_ranking(post.score, post.posted_at)
|
||||
db.session.commit()
|
||||
db.session.commit()
|
||||
|
||||
return post
|
||||
|
||||
|
|
|
@ -80,8 +80,8 @@
|
|||
<td><span title="{{ _('Banned') }}">{{ '<span class="red">Banned</span>'|safe if user.banned }}
|
||||
{{ '<span class="red">Banned posts</span>'|safe if user.ban_posts }}
|
||||
{{ '<span class="red">Banned comments</span>'|safe if user.ban_comments }}</span></td>
|
||||
<td><span title="{{ user.last_seen }}">{{ arrow.get(user.last_seen).humanize(locale=locale) }}</span></td>
|
||||
<td><span title="{{ _('Reports') }}">{{ user.reports if user.reports > 0 }}</span></td>
|
||||
<td><span title="{{ _('Attitude') }}">{% if user.attitude %}{{ (user.attitude * 100) | round | int }}%{% endif %}</span></td>
|
||||
<td><span title="{{ _('Reputation') }}">{% if user.reputation %}R {{ user.reputation | round | int }}{% endif %}</span></td>
|
||||
<td><span title="{{ _('Last Seen') }}: {{ user.last_seen }}">{{ arrow.get(user.last_seen).humanize(locale=locale) }}</span></td>
|
||||
<td><a href="{{ url_for('admin.admin_user_edit', user_id=user.id) }}">Edit</a>,
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{% extends 'themes/' + theme() + '/base.html' %}
|
||||
|
||||
{% block app_content %}
|
||||
<p class="mb-0">GET <code>/api/alpha/site</code></p>
|
||||
<p class="mb-0" id="site_request"></p>
|
||||
<details><summary>JSON</summary><pre id="site_json"></pre></details>
|
||||
|
||||
<p class="mb-0">POST <code>/api/alpha/user/login</code></p>
|
||||
|
|
|
@ -40,7 +40,7 @@
|
|||
<link id="icon_32" rel="icon" type="image/png" sizes="32x32" href="">
|
||||
<link id="icon_16" rel="icon" type="image/png" sizes="16x16" href="">
|
||||
<link id="icon_shortcut" rel="shortcut icon" type="image/png" href="">
|
||||
<link id="favicon" rel='icon' type="image/x-icon" href="">
|
||||
<link rel='icon' type="image/x-icon" href="/static/images/favicon.ico">
|
||||
|
||||
{{ bootstrap.load_css() }}
|
||||
<link href="{{ '/static/themes/' + theme() + '/css/navbars.css' }}" rel="stylesheet">
|
||||
|
|
12
app/templates/themes/x_api/community.html
Normal file
12
app/templates/themes/x_api/community.html
Normal file
|
@ -0,0 +1,12 @@
|
|||
{% extends 'themes/' + theme() + '/base.html' %}
|
||||
|
||||
{% block app_content %}
|
||||
<p class="mb-0" id="site_request"></p>
|
||||
<details><summary>JSON</summary><pre id="site_json"></pre></details>
|
||||
<p class="mb-0" id="community_request" data-value="{{ community_id }}">GET <code>/api/alpha/community?id={{ community_id }}</code></p>
|
||||
<details><summary>JSON</summary><pre id="community_json"></pre></details>
|
||||
<p class="mb-0" id="community_post_list_request">GET <code>/api/alpha/post/list?sort=Hot&page=1&community_id={{ community_id }}</code></p>
|
||||
<details><summary>JSON</summary><pre id="community_post_list_json"></pre></details>
|
||||
|
||||
<script src="{{ '/static/themes/' + theme() + '/js/community.js' }}" type="module" data-param1="{{ community_id }}"></script>
|
||||
{% endblock %}
|
|
@ -1,6 +1,12 @@
|
|||
{% extends 'themes/' + theme() + '/base.html' %}
|
||||
|
||||
{% block app_content %}
|
||||
<p class="mb-0">GET <code>/api/alpha/site</code></p>
|
||||
<p class="mb-0" id="site_request"></p>
|
||||
<details><summary>JSON</summary><pre id="site_json"></pre></details>
|
||||
|
||||
<hr />
|
||||
|
||||
<p class="mb-0" id="post_list_request"></p>
|
||||
<details><summary>JSON</summary><pre id="post_list_json"></pre></details>
|
||||
|
||||
{% endblock%}
|
||||
|
|
26
app/templates/themes/x_api/js/community.js
Normal file
26
app/templates/themes/x_api/js/community.js
Normal file
|
@ -0,0 +1,26 @@
|
|||
const element = document.getElementById('community_request');
|
||||
const community_id = element.getAttribute('data-value');
|
||||
|
||||
import { baseUrl } from './site.js';
|
||||
const community_api = baseUrl + '/api/alpha/community?id=' + community_id;
|
||||
const community_post_list_api = baseUrl + '/api/alpha/post/list?sort=Hot&page=1&community_id=' + community_id;
|
||||
|
||||
import { jwt } from './site.js';
|
||||
if (jwt != null) {
|
||||
var request = {method: "GET", headers: {Authorization: `Bearer ${jwt}`}};
|
||||
} else {
|
||||
var request = {method: "GET"};
|
||||
}
|
||||
|
||||
fetch(community_api, request)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
document.querySelector('#community_json').textContent = JSON.stringify(data, null, 2);
|
||||
})
|
||||
|
||||
|
||||
fetch(community_post_list_api, request)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
document.querySelector('#community_post_list_json').textContent = JSON.stringify(data, null, 2);
|
||||
})
|
|
@ -1,6 +1,6 @@
|
|||
const url = new URL(window.location.href);
|
||||
export const baseUrl = `${url.protocol}//${url.host}`;
|
||||
const api = baseUrl + '/api/alpha/site';
|
||||
const api_site = baseUrl + '/api/alpha/site';
|
||||
|
||||
let jwt = null;
|
||||
let session_jwt = sessionStorage.getItem('jwt');
|
||||
|
@ -14,39 +14,25 @@ if (session_jwt != null) {
|
|||
}
|
||||
export { jwt };
|
||||
|
||||
const ul = document.getElementById('navbar_items');
|
||||
const navbar = document.getElementById('navbar_items');
|
||||
if (jwt != null) {
|
||||
var request = {method: "GET", headers: {Authorization: `Bearer ${jwt}`}};
|
||||
ul.innerHTML = '<li class="nav-item dropdown">' +
|
||||
'<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">' +
|
||||
'Communities' +
|
||||
'</a>' +
|
||||
'<ul class="dropdown-menu">' +
|
||||
'<li><a class="dropdown-item" href="/api/alpha/communities">All communities</a></li>' +
|
||||
'</ul>' +
|
||||
'</li>' +
|
||||
|
||||
'<li class="nav-item"><a class="nav-link" href="/user/settings">User settings</a></li>' +
|
||||
|
||||
'<li class="nav-item"><a class="nav-link" href="/donate">Donate</a></li>' +
|
||||
|
||||
'<li class="nav-item"><a class="nav-link" href="/api/alpha/auth/logout">Logout (via API)</a></li>';
|
||||
} else {
|
||||
var request = {method: "GET"};
|
||||
ul.innerHTML = '<li class="nav-item"><a class="nav-link" href="/api/alpha/auth/login">Log in (via API)</a></li>' +
|
||||
'<li class="nav-item dropdown">' +
|
||||
'<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">' +
|
||||
'Communities' +
|
||||
'</a>' +
|
||||
'<ul class="dropdown-menu">' +
|
||||
'<li><a class="dropdown-item" href="/api/alpha/communities">All communities</a></li>' +
|
||||
'</ul>' +
|
||||
'</li>' +
|
||||
'<li class="nav-item"><a class="nav-link" href="/user/settings">User settings</a></li>' +
|
||||
'<li class="nav-item"><a class="nav-link" href="/donate">Donate</a></li>';
|
||||
navbar.innerHTML = '<li class="nav-item"><a class="nav-link" href="/api/alpha/auth/login">Log in (via API)</a></li>' +
|
||||
'<li class="nav-item dropdown">' +
|
||||
'<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">' +
|
||||
'Communities' +
|
||||
'</a>' +
|
||||
'<ul class="dropdown-menu">' +
|
||||
'<li><a class="dropdown-item" href="/api/alpha/communities">All communities</a></li>' +
|
||||
'</ul>' +
|
||||
'</li>' +
|
||||
'<li class="nav-item"><a class="nav-link" href="/user/settings">User settings</a></li>' +
|
||||
'<li class="nav-item"><a class="nav-link" href="/donate">Donate</a></li>';
|
||||
}
|
||||
|
||||
fetch(api, request)
|
||||
fetch(api_site, request)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
// head
|
||||
|
@ -55,12 +41,110 @@ fetch(api, request)
|
|||
document.querySelector('#icon_32').href = data.site.icon_32;
|
||||
document.querySelector('#icon_16').href = data.site.icon_16;
|
||||
document.querySelector('#icon_shortcut').href = data.site.icon_32;
|
||||
document.querySelector('#favicon').href = baseUrl + '/static/images/favicon.ico';
|
||||
|
||||
// navbar
|
||||
document.querySelector('#navbar_title').innerHTML = '<img src="' + data.site.icon + '" alt="Logo" width="36" height="36" />' + ' ' + data.site.name;
|
||||
|
||||
if (jwt != null) {
|
||||
const all_communities_item = document.createElement('li');
|
||||
all_communities_item.innerHTML = '<a class="dropdown-item" href="/api/alpha/communities">All communities</a>'
|
||||
|
||||
const communities_menu = document.createElement('ul');
|
||||
communities_menu.className = 'dropdown-menu'
|
||||
communities_menu.appendChild(all_communities_item)
|
||||
|
||||
if (data.my_user.moderates.length > 0) {
|
||||
const dropdown_divider = document.createElement('li');
|
||||
dropdown_divider.innerHTML = '<hr class="dropdown-divider">'
|
||||
communities_menu.appendChild(dropdown_divider)
|
||||
const dropdown_header = document.createElement('li');
|
||||
dropdown_header.innerHTML = '<h6 class="dropdown-header">Moderating</h6>'
|
||||
communities_menu.appendChild(dropdown_header)
|
||||
|
||||
for (let mods of data.my_user.moderates) {
|
||||
let moderated_community_item = document.createElement('li');
|
||||
if (mods.community.local) {
|
||||
moderated_community_item.innerHTML = '<a class="dropdown-item" href="' + baseUrl + '/api/alpha/c/' + mods.community.name + '">' +
|
||||
mods.community.title + '<span class="text-body-secondary">' + ' (' + mods.community.ap_domain + ')</span>' +
|
||||
'</a>'
|
||||
} else {
|
||||
moderated_community_item.innerHTML = '<a class="dropdown-item" href="' + baseUrl + '/api/alpha/c/' + mods.community.name + '@' + mods.community.ap_domain + '">' +
|
||||
mods.community.title + '<span class="text-body-secondary">' + ' (' + mods.community.ap_domain + ')</span>' +
|
||||
'</a>'
|
||||
}
|
||||
communities_menu.appendChild(moderated_community_item)
|
||||
}
|
||||
}
|
||||
|
||||
if (data.my_user.follows.length > 0) {
|
||||
const dropdown_divider = document.createElement('li');
|
||||
dropdown_divider.innerHTML = '<hr class="dropdown-divider">'
|
||||
communities_menu.appendChild(dropdown_divider)
|
||||
const dropdown_header = document.createElement('li');
|
||||
dropdown_header.innerHTML = '<h6 class="dropdown-header">Joined Communities</h6>'
|
||||
communities_menu.appendChild(dropdown_header)
|
||||
|
||||
for (let follows of data.my_user.follows) {
|
||||
let followed_community_item = document.createElement('li');
|
||||
if (follows.community.local) {
|
||||
followed_community_item.innerHTML = '<a class="dropdown-item" href="' + baseUrl + '/api/alpha/c/' + follows.community.name + '">' +
|
||||
follows.community.title + '<span class="text-body-secondary">' + ' (' + follows.community.ap_domain + ')</span>' +
|
||||
'</a>'
|
||||
} else {
|
||||
followed_community_item.innerHTML = '<a class="dropdown-item" href="' + baseUrl + '/api/alpha/c/' + follows.community.name + '@' + follows.community.ap_domain + '">' +
|
||||
follows.community.title + '<span class="text-body-secondary">' + ' (' + follows.community.ap_domain + ')</span>' +
|
||||
'</a>'
|
||||
}
|
||||
communities_menu.appendChild(followed_community_item)
|
||||
}
|
||||
}
|
||||
|
||||
const communities_item = document.createElement('li')
|
||||
communities_item.className = 'nav-item dropdown'
|
||||
communities_item.innerHTML = '<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">Communities</a>'
|
||||
communities_item.appendChild(communities_menu)
|
||||
navbar.appendChild(communities_item)
|
||||
|
||||
const user_settings_item = document.createElement('li')
|
||||
user_settings_item.className = 'nav-item'
|
||||
user_settings_item.innerHTML = '<a class="nav-link" href="/user/settings">User settings</a>';
|
||||
navbar.appendChild(user_settings_item)
|
||||
|
||||
const logout_item = document.createElement('li')
|
||||
logout_item.className = 'nav-item'
|
||||
logout_item.innerHTML = '<a class="nav-link" href="/api/alpha/auth/logout">Log out (via API)</a>';
|
||||
navbar.appendChild(logout_item)
|
||||
}
|
||||
|
||||
// site info
|
||||
let postlist = document.querySelector('#post_list_request')
|
||||
if (jwt != null) {
|
||||
document.querySelector('#site_request').innerHTML = 'GET <code>/api/alpha/site</code> [LOGGED IN]'
|
||||
if (postlist) {
|
||||
postlist.innerHTML = 'GET <code>/api/alpha/post/list?type_=Subscribed&sort=New&page=1</code></p>'
|
||||
}
|
||||
} else {
|
||||
document.querySelector('#site_request').innerHTML = 'GET <code>/api/alpha/site</code> [LOGGED OUT]'
|
||||
if (postlist) {
|
||||
postlist.innerHTML = 'GET <code>/api/alpha/post/list?type_=Popular&sort=Hot&page=1</code></p>'
|
||||
}
|
||||
}
|
||||
|
||||
document.querySelector('#site_json').textContent = JSON.stringify(data, null, 2);
|
||||
})
|
||||
|
||||
|
||||
let postlist = document.querySelector('#post_list_request');
|
||||
if (postlist) {
|
||||
if (jwt != null) {
|
||||
var api_postlist = baseUrl + '/api/alpha/post/list?type_=Subscribed&sort=New&page=1';
|
||||
} else {
|
||||
var api_postlist = baseUrl + '/api/alpha/post/list?type_=Popular&sort=Hot&page=1';
|
||||
}
|
||||
|
||||
fetch(api_postlist, request)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
document.querySelector('#post_list_json').textContent = JSON.stringify(data, null, 2);
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{% extends 'themes/' + theme() + '/base.html' %}
|
||||
|
||||
{% block app_content %}
|
||||
<p class="mb-0">GET <code>/api/alpha/site</code></p>
|
||||
<p class="mb-0" id="site_request"></p>
|
||||
<details><summary>JSON</summary><pre id="site_json"></pre></details>
|
||||
<p class="mb-0">GET <code>/api/alpha/community/list</code></p>
|
||||
<details><summary>JSON</summary><pre id="community_list_json"></pre></details>
|
||||
|
|
Loading…
Reference in a new issue