mirror of
https://codeberg.org/rimu/pyfedi
synced 2025-01-23 19:36:56 -08:00
API: initial support for new posts (polls and uploaded images not supported yet)
This commit is contained in:
parent
22b8ed6782
commit
0cab646195
6 changed files with 482 additions and 56 deletions
|
@ -1,7 +1,7 @@
|
||||||
from app.api.alpha import bp
|
from app.api.alpha import bp
|
||||||
from app.api.alpha.utils import get_site, post_site_block, \
|
from app.api.alpha.utils import get_site, post_site_block, \
|
||||||
get_search, \
|
get_search, \
|
||||||
get_post_list, get_post, post_post_like, put_post_save, put_post_subscribe, \
|
get_post_list, get_post, post_post_like, put_post_save, put_post_subscribe, post_post, \
|
||||||
get_reply_list, post_reply_like, put_reply_save, put_reply_subscribe, post_reply, put_reply, post_reply_delete, post_reply_report, \
|
get_reply_list, post_reply_like, put_reply_save, put_reply_subscribe, post_reply, put_reply, post_reply_delete, post_reply_report, \
|
||||||
get_community_list, get_community, post_community_follow, post_community_block, \
|
get_community_list, get_community, post_community_follow, post_community_block, \
|
||||||
get_user, post_user_block
|
get_user, post_user_block
|
||||||
|
@ -9,12 +9,14 @@ from app.shared.auth import log_user_in
|
||||||
|
|
||||||
from flask import current_app, jsonify, request
|
from flask import current_app, jsonify, request
|
||||||
|
|
||||||
|
def enable_api():
|
||||||
|
return True if current_app.debug else False
|
||||||
|
|
||||||
# Site
|
# Site
|
||||||
@bp.route('/api/alpha/site', methods=['GET'])
|
@bp.route('/api/alpha/site', methods=['GET'])
|
||||||
def get_alpha_site():
|
def get_alpha_site():
|
||||||
if not current_app.debug:
|
if not enable_api():
|
||||||
return jsonify({'error': 'alpha api routes only available in debug mode'})
|
return jsonify({'error': 'alpha api is not enabled'})
|
||||||
try:
|
try:
|
||||||
auth = request.headers.get('Authorization')
|
auth = request.headers.get('Authorization')
|
||||||
return jsonify(get_site(auth))
|
return jsonify(get_site(auth))
|
||||||
|
@ -24,8 +26,8 @@ def get_alpha_site():
|
||||||
|
|
||||||
@bp.route('/api/alpha/site/block', methods=['POST'])
|
@bp.route('/api/alpha/site/block', methods=['POST'])
|
||||||
def get_alpha_site_block():
|
def get_alpha_site_block():
|
||||||
if not current_app.debug:
|
if not enable_api():
|
||||||
return jsonify({'error': 'alpha api routes only available in debug mode'})
|
return jsonify({'error': 'alpha api is not enabled'})
|
||||||
try:
|
try:
|
||||||
auth = request.headers.get('Authorization')
|
auth = request.headers.get('Authorization')
|
||||||
data = request.get_json(force=True) or {}
|
data = request.get_json(force=True) or {}
|
||||||
|
@ -37,8 +39,8 @@ def get_alpha_site_block():
|
||||||
# Misc
|
# Misc
|
||||||
@bp.route('/api/alpha/search', methods=['GET'])
|
@bp.route('/api/alpha/search', methods=['GET'])
|
||||||
def get_alpha_search():
|
def get_alpha_search():
|
||||||
if not current_app.debug:
|
if not enable_api():
|
||||||
return jsonify({'error': 'alpha api routes only available in debug mode'})
|
return jsonify({'error': 'alpha api is not enabled'})
|
||||||
try:
|
try:
|
||||||
auth = request.headers.get('Authorization')
|
auth = request.headers.get('Authorization')
|
||||||
data = request.args.to_dict() or None
|
data = request.args.to_dict() or None
|
||||||
|
@ -50,8 +52,8 @@ def get_alpha_search():
|
||||||
# Community
|
# Community
|
||||||
@bp.route('/api/alpha/community', methods=['GET'])
|
@bp.route('/api/alpha/community', methods=['GET'])
|
||||||
def get_alpha_community():
|
def get_alpha_community():
|
||||||
if not current_app.debug:
|
if not enable_api():
|
||||||
return jsonify({'error': 'alpha api routes only available in debug mode'})
|
return jsonify({'error': 'alpha api is not enabled'})
|
||||||
try:
|
try:
|
||||||
auth = request.headers.get('Authorization')
|
auth = request.headers.get('Authorization')
|
||||||
data = request.args.to_dict() or None
|
data = request.args.to_dict() or None
|
||||||
|
@ -62,8 +64,8 @@ def get_alpha_community():
|
||||||
|
|
||||||
@bp.route('/api/alpha/community/list', methods=['GET'])
|
@bp.route('/api/alpha/community/list', methods=['GET'])
|
||||||
def get_alpha_community_list():
|
def get_alpha_community_list():
|
||||||
if not current_app.debug:
|
if not enable_api():
|
||||||
return jsonify({'error': 'alpha api routes only available in debug mode'})
|
return jsonify({'error': 'alpha api is not enabled'})
|
||||||
try:
|
try:
|
||||||
auth = request.headers.get('Authorization')
|
auth = request.headers.get('Authorization')
|
||||||
data = request.args.to_dict() or None
|
data = request.args.to_dict() or None
|
||||||
|
@ -74,8 +76,8 @@ def get_alpha_community_list():
|
||||||
|
|
||||||
@bp.route('/api/alpha/community/follow', methods=['POST'])
|
@bp.route('/api/alpha/community/follow', methods=['POST'])
|
||||||
def post_alpha_community_follow():
|
def post_alpha_community_follow():
|
||||||
if not current_app.debug:
|
if not enable_api():
|
||||||
return jsonify({'error': 'alpha api routes only available in debug mode'})
|
return jsonify({'error': 'alpha api is not enabled'})
|
||||||
try:
|
try:
|
||||||
auth = request.headers.get('Authorization')
|
auth = request.headers.get('Authorization')
|
||||||
data = request.get_json(force=True) or {}
|
data = request.get_json(force=True) or {}
|
||||||
|
@ -86,8 +88,8 @@ def post_alpha_community_follow():
|
||||||
|
|
||||||
@bp.route('/api/alpha/community/block', methods=['POST'])
|
@bp.route('/api/alpha/community/block', methods=['POST'])
|
||||||
def post_alpha_community_block():
|
def post_alpha_community_block():
|
||||||
if not current_app.debug:
|
if not enable_api():
|
||||||
return jsonify({'error': 'alpha api routes only available in debug mode'})
|
return jsonify({'error': 'alpha api is not enabled'})
|
||||||
try:
|
try:
|
||||||
auth = request.headers.get('Authorization')
|
auth = request.headers.get('Authorization')
|
||||||
data = request.get_json(force=True) or {}
|
data = request.get_json(force=True) or {}
|
||||||
|
@ -99,8 +101,8 @@ def post_alpha_community_block():
|
||||||
# Post
|
# Post
|
||||||
@bp.route('/api/alpha/post/list', methods=['GET'])
|
@bp.route('/api/alpha/post/list', methods=['GET'])
|
||||||
def get_alpha_post_list():
|
def get_alpha_post_list():
|
||||||
if not current_app.debug:
|
if not enable_api():
|
||||||
return jsonify({'error': 'alpha api routes only available in debug mode'})
|
return jsonify({'error': 'alpha api is not enabled'})
|
||||||
try:
|
try:
|
||||||
auth = request.headers.get('Authorization')
|
auth = request.headers.get('Authorization')
|
||||||
data = request.args.to_dict() or None
|
data = request.args.to_dict() or None
|
||||||
|
@ -111,8 +113,8 @@ def get_alpha_post_list():
|
||||||
|
|
||||||
@bp.route('/api/alpha/post', methods=['GET'])
|
@bp.route('/api/alpha/post', methods=['GET'])
|
||||||
def get_alpha_post():
|
def get_alpha_post():
|
||||||
if not current_app.debug:
|
if not enable_api():
|
||||||
return jsonify({'error': 'alpha api routes only available in debug mode'})
|
return jsonify({'error': 'alpha api is not enabled'})
|
||||||
try:
|
try:
|
||||||
auth = request.headers.get('Authorization')
|
auth = request.headers.get('Authorization')
|
||||||
data = request.args.to_dict() or None
|
data = request.args.to_dict() or None
|
||||||
|
@ -123,8 +125,8 @@ def get_alpha_post():
|
||||||
|
|
||||||
@bp.route('/api/alpha/post/like', methods=['POST'])
|
@bp.route('/api/alpha/post/like', methods=['POST'])
|
||||||
def post_alpha_post_like():
|
def post_alpha_post_like():
|
||||||
if not current_app.debug:
|
if not enable_api():
|
||||||
return jsonify({'error': 'alpha api routes only available in debug mode'})
|
return jsonify({'error': 'alpha api is not enabled'})
|
||||||
try:
|
try:
|
||||||
auth = request.headers.get('Authorization')
|
auth = request.headers.get('Authorization')
|
||||||
data = request.get_json(force=True) or {}
|
data = request.get_json(force=True) or {}
|
||||||
|
@ -135,8 +137,8 @@ def post_alpha_post_like():
|
||||||
|
|
||||||
@bp.route('/api/alpha/post/save', methods=['PUT'])
|
@bp.route('/api/alpha/post/save', methods=['PUT'])
|
||||||
def put_alpha_post_save():
|
def put_alpha_post_save():
|
||||||
if not current_app.debug:
|
if not enable_api():
|
||||||
return jsonify({'error': 'alpha api routes only available in debug mode'})
|
return jsonify({'error': 'alpha api is not enabled'})
|
||||||
try:
|
try:
|
||||||
auth = request.headers.get('Authorization')
|
auth = request.headers.get('Authorization')
|
||||||
data = request.get_json(force=True) or {}
|
data = request.get_json(force=True) or {}
|
||||||
|
@ -147,8 +149,8 @@ def put_alpha_post_save():
|
||||||
|
|
||||||
@bp.route('/api/alpha/post/subscribe', methods=['PUT'])
|
@bp.route('/api/alpha/post/subscribe', methods=['PUT'])
|
||||||
def put_alpha_post_subscribe():
|
def put_alpha_post_subscribe():
|
||||||
if not current_app.debug:
|
if not enable_api():
|
||||||
return jsonify({'error': 'alpha api routes only available in debug mode'})
|
return jsonify({'error': 'alpha api is not enabled'})
|
||||||
try:
|
try:
|
||||||
auth = request.headers.get('Authorization')
|
auth = request.headers.get('Authorization')
|
||||||
data = request.get_json(force=True) or {}
|
data = request.get_json(force=True) or {}
|
||||||
|
@ -157,11 +159,23 @@ def put_alpha_post_subscribe():
|
||||||
return jsonify({"error": str(ex)}), 400
|
return jsonify({"error": str(ex)}), 400
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('/api/alpha/post', methods=['POST'])
|
||||||
|
def post_alpha_post():
|
||||||
|
if not enable_api():
|
||||||
|
return jsonify({'error': 'alpha api is not enabled'})
|
||||||
|
try:
|
||||||
|
auth = request.headers.get('Authorization')
|
||||||
|
data = request.get_json(force=True) or {}
|
||||||
|
return jsonify(post_post(auth, data))
|
||||||
|
except Exception as ex:
|
||||||
|
return jsonify({"error": str(ex)}), 400
|
||||||
|
|
||||||
|
|
||||||
# Reply
|
# Reply
|
||||||
@bp.route('/api/alpha/comment/list', methods=['GET'])
|
@bp.route('/api/alpha/comment/list', methods=['GET'])
|
||||||
def get_alpha_comment_list():
|
def get_alpha_comment_list():
|
||||||
if not current_app.debug:
|
if not enable_api():
|
||||||
return jsonify({'error': 'alpha api routes only available in debug mode'})
|
return jsonify({'error': 'alpha api is not enabled'})
|
||||||
try:
|
try:
|
||||||
auth = request.headers.get('Authorization')
|
auth = request.headers.get('Authorization')
|
||||||
data = request.args.to_dict() or None
|
data = request.args.to_dict() or None
|
||||||
|
@ -172,8 +186,8 @@ def get_alpha_comment_list():
|
||||||
|
|
||||||
@bp.route('/api/alpha/comment/like', methods=['POST'])
|
@bp.route('/api/alpha/comment/like', methods=['POST'])
|
||||||
def post_alpha_comment_like():
|
def post_alpha_comment_like():
|
||||||
if not current_app.debug:
|
if not enable_api():
|
||||||
return jsonify({'error': 'alpha api routes only available in debug mode'})
|
return jsonify({'error': 'alpha api is not enabled'})
|
||||||
try:
|
try:
|
||||||
auth = request.headers.get('Authorization')
|
auth = request.headers.get('Authorization')
|
||||||
data = request.get_json(force=True) or {}
|
data = request.get_json(force=True) or {}
|
||||||
|
@ -184,8 +198,8 @@ def post_alpha_comment_like():
|
||||||
|
|
||||||
@bp.route('/api/alpha/comment/save', methods=['PUT'])
|
@bp.route('/api/alpha/comment/save', methods=['PUT'])
|
||||||
def put_alpha_comment_save():
|
def put_alpha_comment_save():
|
||||||
if not current_app.debug:
|
if not enable_api():
|
||||||
return jsonify({'error': 'alpha api routes only available in debug mode'})
|
return jsonify({'error': 'alpha api is not enabled'})
|
||||||
try:
|
try:
|
||||||
auth = request.headers.get('Authorization')
|
auth = request.headers.get('Authorization')
|
||||||
data = request.get_json(force=True) or {}
|
data = request.get_json(force=True) or {}
|
||||||
|
@ -196,8 +210,8 @@ def put_alpha_comment_save():
|
||||||
|
|
||||||
@bp.route('/api/alpha/comment/subscribe', methods=['PUT'])
|
@bp.route('/api/alpha/comment/subscribe', methods=['PUT'])
|
||||||
def put_alpha_comment_subscribe():
|
def put_alpha_comment_subscribe():
|
||||||
if not current_app.debug:
|
if not enable_api():
|
||||||
return jsonify({'error': 'alpha api routes only available in debug mode'})
|
return jsonify({'error': 'alpha api is not enabled'})
|
||||||
try:
|
try:
|
||||||
auth = request.headers.get('Authorization')
|
auth = request.headers.get('Authorization')
|
||||||
data = request.get_json(force=True) or {}
|
data = request.get_json(force=True) or {}
|
||||||
|
@ -208,8 +222,8 @@ def put_alpha_comment_subscribe():
|
||||||
|
|
||||||
@bp.route('/api/alpha/comment', methods=['POST'])
|
@bp.route('/api/alpha/comment', methods=['POST'])
|
||||||
def post_alpha_comment():
|
def post_alpha_comment():
|
||||||
if not current_app.debug:
|
if not enable_api():
|
||||||
return jsonify({'error': 'alpha api routes only available in debug mode'})
|
return jsonify({'error': 'alpha api is not enabled'})
|
||||||
try:
|
try:
|
||||||
auth = request.headers.get('Authorization')
|
auth = request.headers.get('Authorization')
|
||||||
data = request.get_json(force=True) or {}
|
data = request.get_json(force=True) or {}
|
||||||
|
@ -220,8 +234,8 @@ def post_alpha_comment():
|
||||||
|
|
||||||
@bp.route('/api/alpha/comment', methods=['PUT'])
|
@bp.route('/api/alpha/comment', methods=['PUT'])
|
||||||
def put_alpha_comment():
|
def put_alpha_comment():
|
||||||
if not current_app.debug:
|
if not enable_api():
|
||||||
return jsonify({'error': 'alpha api routes only available in debug mode'})
|
return jsonify({'error': 'alpha api is not enabled'})
|
||||||
try:
|
try:
|
||||||
auth = request.headers.get('Authorization')
|
auth = request.headers.get('Authorization')
|
||||||
data = request.get_json(force=True) or {}
|
data = request.get_json(force=True) or {}
|
||||||
|
@ -232,8 +246,8 @@ def put_alpha_comment():
|
||||||
|
|
||||||
@bp.route('/api/alpha/comment/delete', methods=['POST'])
|
@bp.route('/api/alpha/comment/delete', methods=['POST'])
|
||||||
def post_alpha_comment_delete():
|
def post_alpha_comment_delete():
|
||||||
if not current_app.debug:
|
if not enable_api():
|
||||||
return jsonify({'error': 'alpha api routes only available in debug mode'})
|
return jsonify({'error': 'alpha api is not enabled'})
|
||||||
try:
|
try:
|
||||||
auth = request.headers.get('Authorization')
|
auth = request.headers.get('Authorization')
|
||||||
data = request.get_json(force=True) or {}
|
data = request.get_json(force=True) or {}
|
||||||
|
@ -244,8 +258,8 @@ def post_alpha_comment_delete():
|
||||||
|
|
||||||
@bp.route('/api/alpha/comment/report', methods=['POST'])
|
@bp.route('/api/alpha/comment/report', methods=['POST'])
|
||||||
def post_alpha_comment_report():
|
def post_alpha_comment_report():
|
||||||
if not current_app.debug:
|
if not enable_api():
|
||||||
return jsonify({'error': 'alpha api routes only available in debug mode'})
|
return jsonify({'error': 'alpha api is not enabled'})
|
||||||
try:
|
try:
|
||||||
auth = request.headers.get('Authorization')
|
auth = request.headers.get('Authorization')
|
||||||
data = request.get_json(force=True) or {}
|
data = request.get_json(force=True) or {}
|
||||||
|
@ -257,8 +271,8 @@ def post_alpha_comment_report():
|
||||||
# User
|
# User
|
||||||
@bp.route('/api/alpha/user', methods=['GET'])
|
@bp.route('/api/alpha/user', methods=['GET'])
|
||||||
def get_alpha_user():
|
def get_alpha_user():
|
||||||
if not current_app.debug:
|
if not enable_api():
|
||||||
return jsonify({'error': 'alpha api routes only available in debug mode'})
|
return jsonify({'error': 'alpha api is not enabled'})
|
||||||
try:
|
try:
|
||||||
auth = request.headers.get('Authorization')
|
auth = request.headers.get('Authorization')
|
||||||
data = request.args.to_dict() or None
|
data = request.args.to_dict() or None
|
||||||
|
@ -269,8 +283,8 @@ def get_alpha_user():
|
||||||
|
|
||||||
@bp.route('/api/alpha/user/login', methods=['POST'])
|
@bp.route('/api/alpha/user/login', methods=['POST'])
|
||||||
def post_alpha_user_login():
|
def post_alpha_user_login():
|
||||||
if not current_app.debug:
|
if not enable_api():
|
||||||
return jsonify({'error': 'alpha api routes only available in debug mode'})
|
return jsonify({'error': 'alpha api is not enabled'})
|
||||||
try:
|
try:
|
||||||
SRC_API = 3 # would be in app.constants
|
SRC_API = 3 # would be in app.constants
|
||||||
data = request.get_json(force=True) or {}
|
data = request.get_json(force=True) or {}
|
||||||
|
@ -281,8 +295,8 @@ def post_alpha_user_login():
|
||||||
|
|
||||||
@bp.route('/api/alpha/user/block', methods=['POST'])
|
@bp.route('/api/alpha/user/block', methods=['POST'])
|
||||||
def post_alpha_user_block():
|
def post_alpha_user_block():
|
||||||
if not current_app.debug:
|
if not enable_api():
|
||||||
return jsonify({'error': 'alpha api routes only available in debug mode'})
|
return jsonify({'error': 'alpha api is not enabled'})
|
||||||
try:
|
try:
|
||||||
auth = request.headers.get('Authorization')
|
auth = request.headers.get('Authorization')
|
||||||
data = request.get_json(force=True) or {}
|
data = request.get_json(force=True) or {}
|
||||||
|
@ -320,7 +334,6 @@ def alpha_community():
|
||||||
|
|
||||||
# Post - not yet implemented
|
# Post - not yet implemented
|
||||||
@bp.route('/api/alpha/post', methods=['PUT'])
|
@bp.route('/api/alpha/post', methods=['PUT'])
|
||||||
@bp.route('/api/alpha/post', methods=['POST'])
|
|
||||||
@bp.route('/api/alpha/post/delete', methods=['POST'])
|
@bp.route('/api/alpha/post/delete', methods=['POST'])
|
||||||
@bp.route('/api/alpha/post/remove', methods=['POST'])
|
@bp.route('/api/alpha/post/remove', methods=['POST'])
|
||||||
@bp.route('/api/alpha/post/lock', methods=['POST'])
|
@bp.route('/api/alpha/post/lock', methods=['POST'])
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
from app.api.alpha.utils.site import get_site, post_site_block
|
from app.api.alpha.utils.site import get_site, post_site_block
|
||||||
from app.api.alpha.utils.misc import get_search
|
from app.api.alpha.utils.misc import get_search
|
||||||
from app.api.alpha.utils.post import get_post_list, get_post, post_post_like, put_post_save, put_post_subscribe
|
from app.api.alpha.utils.post import get_post_list, get_post, post_post_like, put_post_save, put_post_subscribe, post_post
|
||||||
from app.api.alpha.utils.reply import get_reply_list, post_reply_like, put_reply_save, put_reply_subscribe, post_reply, put_reply, post_reply_delete, post_reply_report
|
from app.api.alpha.utils.reply import get_reply_list, post_reply_like, put_reply_save, put_reply_subscribe, post_reply, put_reply, post_reply_delete, post_reply_report
|
||||||
from app.api.alpha.utils.community import get_community, get_community_list, post_community_follow, post_community_block
|
from app.api.alpha.utils.community import get_community, get_community_list, post_community_follow, post_community_block
|
||||||
from app.api.alpha.utils.user import get_user, post_user_block
|
from app.api.alpha.utils.user import get_user, post_user_block
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
from app import cache
|
from app import cache
|
||||||
from app.api.alpha.views import post_view
|
from app.api.alpha.views import post_view
|
||||||
from app.api.alpha.utils.validators import required, integer_expected, boolean_expected
|
from app.api.alpha.utils.validators import required, integer_expected, boolean_expected, string_expected
|
||||||
from app.models import Post, Community, CommunityMember, utcnow
|
from app.models import Post, Community, CommunityMember, utcnow
|
||||||
from app.shared.post import vote_for_post, bookmark_the_post, remove_the_bookmark_from_post, toggle_post_notification
|
from app.shared.post import vote_for_post, bookmark_the_post, remove_the_bookmark_from_post, toggle_post_notification, make_post
|
||||||
from app.utils import authorise_api_user, blocked_users, blocked_communities, blocked_instances, community_ids_from_instances
|
from app.utils import authorise_api_user, blocked_users, blocked_communities, blocked_instances, community_ids_from_instances, is_image_url
|
||||||
|
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from sqlalchemy import desc
|
from sqlalchemy import desc
|
||||||
|
@ -149,3 +149,34 @@ def put_post_subscribe(auth, data):
|
||||||
post_json = post_view(post=post_id, variant=4, user_id=user_id)
|
post_json = post_view(post=post_id, variant=4, user_id=user_id)
|
||||||
return post_json
|
return post_json
|
||||||
|
|
||||||
|
|
||||||
|
def post_post(auth, data):
|
||||||
|
required(['title', 'community_id'], data)
|
||||||
|
integer_expected(['language_id'], data)
|
||||||
|
boolean_expected(['nsfw'], data)
|
||||||
|
string_expected(['string', 'body'], data)
|
||||||
|
|
||||||
|
title = data['title']
|
||||||
|
community_id = data['community_id']
|
||||||
|
body = data['body'] if 'body' in data else ''
|
||||||
|
url = data['url'] if 'url' in data else None
|
||||||
|
nsfw = data['nsfw'] if 'nsfw' in data else False
|
||||||
|
language_id = data['language_id'] if 'language_id' in data else 2 # FIXME: use site language
|
||||||
|
if language_id < 2:
|
||||||
|
language_id = 2
|
||||||
|
|
||||||
|
if not url:
|
||||||
|
type = 'discussion'
|
||||||
|
elif is_image_url(url):
|
||||||
|
type = 'image'
|
||||||
|
else:
|
||||||
|
type = 'link'
|
||||||
|
|
||||||
|
input = {'title': title, 'body': body, 'url': url, 'nsfw': nsfw, 'language_id': language_id, 'notify_author': True}
|
||||||
|
community = Community.query.filter_by(id=community_id).one()
|
||||||
|
user_id, post = make_post(input, community, type, SRC_API, auth)
|
||||||
|
|
||||||
|
post_json = post_view(post=post, variant=4, user_id=user_id)
|
||||||
|
return post_json
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,18 @@
|
||||||
from app import db
|
from app import db
|
||||||
from app.constants import *
|
from app.constants import *
|
||||||
from app.models import NotificationSubscription, Post, PostBookmark
|
from app.community.util import tags_from_string
|
||||||
|
from app.models import Language, NotificationSubscription, Post, PostBookmark
|
||||||
from app.shared.tasks import task_selector
|
from app.shared.tasks import task_selector
|
||||||
from app.utils import render_template, authorise_api_user, shorten_string
|
from app.utils import render_template, authorise_api_user, shorten_string, gibberish, ensure_directory_exists
|
||||||
|
|
||||||
from flask import abort, flash, redirect, request, url_for
|
from flask import abort, flash, redirect, request, url_for, current_app
|
||||||
from flask_babel import _
|
from flask_babel import _
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
|
|
||||||
|
from pillow_heif import register_heif_opener
|
||||||
|
from PIL import Image, ImageOps
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
# would be in app/constants.py
|
# would be in app/constants.py
|
||||||
SRC_WEB = 1
|
SRC_WEB = 1
|
||||||
|
@ -127,3 +132,111 @@ def toggle_post_notification(post_id: int, src, auth=None):
|
||||||
return user_id
|
return user_id
|
||||||
else:
|
else:
|
||||||
return render_template('post/_post_notification_toggle.html', post=post)
|
return render_template('post/_post_notification_toggle.html', post=post)
|
||||||
|
|
||||||
|
|
||||||
|
def make_post(input, community, type, src, auth=None, uploaded_file=None):
|
||||||
|
if src == SRC_API:
|
||||||
|
user = authorise_api_user(auth, return_type='model')
|
||||||
|
#if not basic_rate_limit_check(user):
|
||||||
|
# raise Exception('rate_limited')
|
||||||
|
title = input['title']
|
||||||
|
body = input['body']
|
||||||
|
url = input['url']
|
||||||
|
nsfw = input['nsfw']
|
||||||
|
notify_author = input['notify_author']
|
||||||
|
language_id = input['language_id']
|
||||||
|
tags = []
|
||||||
|
else:
|
||||||
|
user = current_user
|
||||||
|
title = input.title.data
|
||||||
|
body = input.body.data
|
||||||
|
url = input.link_url.data
|
||||||
|
nsfw = input.nsfw.data
|
||||||
|
notify_author = input.notify_author.data
|
||||||
|
language_id = input.language_id.data
|
||||||
|
tags = tags_from_string(input.tags.data)
|
||||||
|
|
||||||
|
language = Language.query.filter_by(id=language_id).one()
|
||||||
|
|
||||||
|
request_json = {
|
||||||
|
'id': None,
|
||||||
|
'object': {
|
||||||
|
'name': title,
|
||||||
|
'type': 'Page',
|
||||||
|
'stickied': False if src == SRC_API else input.sticky.data,
|
||||||
|
'sensitive': nsfw,
|
||||||
|
'nsfl': False if src == SRC_API else input.nsfl.data,
|
||||||
|
'id': gibberish(), # this will be updated once we have the post.id
|
||||||
|
'mediaType': 'text/markdown',
|
||||||
|
'content': body,
|
||||||
|
'tag': tags,
|
||||||
|
'language': {'identifier': language.code, 'name': language.name}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if type == 'link' or (type == 'image' and src == SRC_API):
|
||||||
|
request_json['object']['attachment'] = [{'type': 'Link', 'href': url}]
|
||||||
|
elif type == 'image' and src == SRC_WEB and uploaded_file and uploaded_file.filename != '':
|
||||||
|
# check if this is an allowed type of file
|
||||||
|
file_ext = os.path.splitext(uploaded_file.filename)[1]
|
||||||
|
if file_ext.lower() not in allowed_extensions:
|
||||||
|
abort(400, description="Invalid image type.")
|
||||||
|
|
||||||
|
new_filename = gibberish(15)
|
||||||
|
# set up the storage directory
|
||||||
|
directory = 'app/static/media/posts/' + new_filename[0:2] + '/' + new_filename[2:4]
|
||||||
|
ensure_directory_exists(directory)
|
||||||
|
|
||||||
|
final_place = os.path.join(directory, new_filename + file_ext)
|
||||||
|
uploaded_file.seek(0)
|
||||||
|
uploaded_file.save(final_place)
|
||||||
|
|
||||||
|
if file_ext.lower() == '.heic':
|
||||||
|
register_heif_opener()
|
||||||
|
if file_ext.lower() == '.avif':
|
||||||
|
import pillow_avif
|
||||||
|
|
||||||
|
Image.MAX_IMAGE_PIXELS = 89478485
|
||||||
|
|
||||||
|
# resize if necessary
|
||||||
|
if not final_place.endswith('.svg'):
|
||||||
|
img = Image.open(final_place)
|
||||||
|
if '.' + img.format.lower() in allowed_extensions:
|
||||||
|
img = ImageOps.exif_transpose(img)
|
||||||
|
|
||||||
|
# limit full sized version to 2000px
|
||||||
|
img.thumbnail((2000, 2000))
|
||||||
|
img.save(final_place)
|
||||||
|
|
||||||
|
request_json['object']['attachment'] = [{
|
||||||
|
'type': 'Image',
|
||||||
|
'url': f'https://{current_app.config["SERVER_NAME"]}/{final_place.replace("app/", "")}',
|
||||||
|
'name': input.image_alt_text.data,
|
||||||
|
'file_path': final_place
|
||||||
|
}]
|
||||||
|
elif type == 'video':
|
||||||
|
request_json['object']['attachment'] = [{'type': 'Document', 'url': url}]
|
||||||
|
elif type == 'poll':
|
||||||
|
request_json['object']['type'] = 'Question'
|
||||||
|
choices = [input.choice_1, input.choice_2, input.choice_3, input.choice_4, input.choice_5,
|
||||||
|
input.choice_6, input.choice_7, input.choice_8, input.choice_9, input.choice_10]
|
||||||
|
key = 'oneOf' if input.mode.data == 'single' else 'anyOf'
|
||||||
|
request_json['object'][key] = []
|
||||||
|
for choice in choices:
|
||||||
|
choice_data = choice.data.strip()
|
||||||
|
if choice_data:
|
||||||
|
request_json['object'][key].append({'name': choice_data})
|
||||||
|
request_json['object']['endTime'] = end_poll_date(input.finish_in.data)
|
||||||
|
|
||||||
|
post = Post.new(user, community, request_json)
|
||||||
|
post.ap_id = f"https://{current_app.config['SERVER_NAME']}/post/{post.id}"
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
task_selector('make_post', user_id=user.id, post_id=post.id)
|
||||||
|
|
||||||
|
if src == SRC_API:
|
||||||
|
return user.id, post
|
||||||
|
else:
|
||||||
|
return post
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ from app.shared.tasks.likes import vote_for_post, vote_for_reply
|
||||||
from app.shared.tasks.notes import make_reply, edit_reply
|
from app.shared.tasks.notes import make_reply, edit_reply
|
||||||
from app.shared.tasks.deletes import delete_reply, restore_reply
|
from app.shared.tasks.deletes import delete_reply, restore_reply
|
||||||
from app.shared.tasks.flags import report_reply
|
from app.shared.tasks.flags import report_reply
|
||||||
|
from app.shared.tasks.pages import make_post, edit_post
|
||||||
|
|
||||||
from flask import current_app
|
from flask import current_app
|
||||||
|
|
||||||
|
@ -17,7 +18,9 @@ def task_selector(task_key, send_async=True, **kwargs):
|
||||||
'edit_reply': edit_reply,
|
'edit_reply': edit_reply,
|
||||||
'delete_reply': delete_reply,
|
'delete_reply': delete_reply,
|
||||||
'restore_reply': restore_reply,
|
'restore_reply': restore_reply,
|
||||||
'report_reply': report_reply
|
'report_reply': report_reply,
|
||||||
|
'make_post': make_post,
|
||||||
|
'edit_post': edit_post
|
||||||
}
|
}
|
||||||
|
|
||||||
if current_app.debug:
|
if current_app.debug:
|
||||||
|
|
266
app/shared/tasks/pages.py
Normal file
266
app/shared/tasks/pages.py
Normal file
|
@ -0,0 +1,266 @@
|
||||||
|
from app import celery, db
|
||||||
|
from app.activitypub.signature import default_context, post_request
|
||||||
|
from app.constants import POST_TYPE_LINK, POST_TYPE_ARTICLE, POST_TYPE_IMAGE, POST_TYPE_VIDEO, POST_TYPE_POLL, MICROBLOG_APPS
|
||||||
|
from app.models import CommunityBan, Instance, Notification, Post, User, UserFollower, utcnow
|
||||||
|
from app.user.utils import search_for_user
|
||||||
|
from app.utils import gibberish, instance_banned, ap_datetime
|
||||||
|
|
||||||
|
from flask import current_app
|
||||||
|
from flask_babel import _
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
|
||||||
|
""" Post JSON format
|
||||||
|
{
|
||||||
|
'id':
|
||||||
|
'type':
|
||||||
|
'attributedTo':
|
||||||
|
'to': []
|
||||||
|
'cc': []
|
||||||
|
'tag': []
|
||||||
|
'audience':
|
||||||
|
'content':
|
||||||
|
'mediaType':
|
||||||
|
'source': {}
|
||||||
|
'published':
|
||||||
|
'updated': (inner oject of Update only)
|
||||||
|
'language': {}
|
||||||
|
'name': (not included in Polls, which are sent out as microblogs)
|
||||||
|
'attachment': []
|
||||||
|
'commentsEnabled':
|
||||||
|
'sensitive':
|
||||||
|
'nsfl':
|
||||||
|
'stickied':
|
||||||
|
'image': (for posts with thumbnails only)
|
||||||
|
'endTime': (last 3 are for polls)
|
||||||
|
'votersCount':
|
||||||
|
'oneOf' / 'anyOf':
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
""" Create / Update / Announce JSON format
|
||||||
|
{
|
||||||
|
'id':
|
||||||
|
'type':
|
||||||
|
'actor':
|
||||||
|
'object':
|
||||||
|
'to': []
|
||||||
|
'cc': []
|
||||||
|
'@context': (outer object only)
|
||||||
|
'audience': (not in Announce)
|
||||||
|
'tag': [] (not in Announce)
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@celery.task
|
||||||
|
def make_post(send_async, user_id, post_id):
|
||||||
|
send_post(user_id, post_id)
|
||||||
|
|
||||||
|
|
||||||
|
@celery.task
|
||||||
|
def edit_post(send_async, user_id, post_id):
|
||||||
|
send_post(user_id, post_id, edit=True)
|
||||||
|
|
||||||
|
|
||||||
|
def send_post(user_id, post_id, edit=False):
|
||||||
|
user = User.query.filter_by(id=user_id).one()
|
||||||
|
post = Post.query.filter_by(id=post_id).one()
|
||||||
|
community = post.community
|
||||||
|
|
||||||
|
# Find any users Mentioned in post body with @user@instance syntax
|
||||||
|
recipients = []
|
||||||
|
pattern = r"@([a-zA-Z0-9_.-]*)@([a-zA-Z0-9_.-]*)\b"
|
||||||
|
matches = re.finditer(pattern, post.body)
|
||||||
|
for match in matches:
|
||||||
|
recipient = None
|
||||||
|
if match.group(2) == current_app.config['SERVER_NAME']:
|
||||||
|
user_name = match.group(1)
|
||||||
|
if user_name != user.user_name:
|
||||||
|
try:
|
||||||
|
recipient = search_for_user(user_name)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
ap_id = f"{match.group(1)}@{match.group(2)}"
|
||||||
|
try:
|
||||||
|
recipient = search_for_user(ap_id)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
if recipient:
|
||||||
|
add_recipient = True
|
||||||
|
for existing_recipient in recipients:
|
||||||
|
if ((not recipient.ap_id and recipient.user_name == existing_recipient.user_name) or
|
||||||
|
(recipient.ap_id and recipient.ap_id == existing_recipient.ap_id)):
|
||||||
|
add_recipient = False
|
||||||
|
break
|
||||||
|
if add_recipient:
|
||||||
|
recipients.append(recipient)
|
||||||
|
|
||||||
|
# Notify any local users that have been Mentioned
|
||||||
|
for recipient in recipients:
|
||||||
|
if recipient.is_local():
|
||||||
|
if edit:
|
||||||
|
existing_notification = Notification.query.filter(Notification.user_id == recipient.id, Notification.url == f"https://{current_app.config['SERVER_NAME']}/post/{post.id}").first()
|
||||||
|
else:
|
||||||
|
existing_notification = None
|
||||||
|
if not existing_notification:
|
||||||
|
notification = Notification(user_id=recipient.id, title=_(f"You have been mentioned in post {post.id}"),
|
||||||
|
url=f"https://{current_app.config['SERVER_NAME']}/post/{post.id}",
|
||||||
|
author_id=user.id)
|
||||||
|
recipient.unread_notifications += 1
|
||||||
|
db.session.add(notification)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
if community.local_only or not community.instance.online():
|
||||||
|
return
|
||||||
|
|
||||||
|
banned = CommunityBan.query.filter_by(user_id=user_id, community_id=community.id).first()
|
||||||
|
if banned:
|
||||||
|
return
|
||||||
|
if not community.is_local():
|
||||||
|
if user.has_blocked_instance(community.instance.id) or instance_banned(community.instance.domain):
|
||||||
|
return
|
||||||
|
|
||||||
|
type = 'Question' if post.type == POST_TYPE_POLL else 'Page'
|
||||||
|
to = [community.public_url(), "https://www.w3.org/ns/activitystreams#Public"]
|
||||||
|
cc = []
|
||||||
|
tag = post.tags_for_activitypub()
|
||||||
|
for recipient in recipients:
|
||||||
|
tag.append({'href': recipient.public_url(), 'name': recipient.mention_tag(), 'type': 'Mention'})
|
||||||
|
cc.append(recipient.public_url())
|
||||||
|
language = {'identifier': post.language_code(), 'name': post.language_name()}
|
||||||
|
source = {'content': post.body, 'mediaType': 'text/markdown'}
|
||||||
|
attachment = []
|
||||||
|
if post.type == POST_TYPE_LINK or post.type == POST_TYPE_VIDEO:
|
||||||
|
attachment.append({'href': post.url, 'type': 'Link'})
|
||||||
|
elif post.type == POST_TYPE_IMAGE:
|
||||||
|
attachment.append({'type': 'Image', 'url': post.image.source_url, 'name': post.image.alt_text})
|
||||||
|
|
||||||
|
page = {
|
||||||
|
'id': post.public_url(),
|
||||||
|
'type': type,
|
||||||
|
'attributedTo': user.public_url(),
|
||||||
|
'to': to,
|
||||||
|
'cc': cc,
|
||||||
|
'tag': tag,
|
||||||
|
'audience': community.public_url(),
|
||||||
|
'content': post.body_html if post.type != POST_TYPE_POLL else '<p>' + post.title + '</p>',
|
||||||
|
'mediaType': 'text/html',
|
||||||
|
'source': source,
|
||||||
|
'published': ap_datetime(post.posted_at),
|
||||||
|
'language': language,
|
||||||
|
'name': post.title,
|
||||||
|
'attachment': attachment,
|
||||||
|
'commentsEnabled': post.comments_enabled,
|
||||||
|
'sensitive': post.nsfw or post.nsfl,
|
||||||
|
'nsfl': post.nsfl,
|
||||||
|
'stickied': post.sticky
|
||||||
|
}
|
||||||
|
if post.type != POST_TYPE_POLL:
|
||||||
|
page['name'] = post.title
|
||||||
|
if edit:
|
||||||
|
page['updated']: ap_datetime(utcnow())
|
||||||
|
if post.image_id:
|
||||||
|
image_url = ''
|
||||||
|
if post.image.source_url:
|
||||||
|
image_url = post.image.source_url
|
||||||
|
elif post.image.file_path:
|
||||||
|
image_url = post.image.file_path.replace('app/static/', f"https://{current_app.config['SERVER_NAME']}/static/")
|
||||||
|
elif post.image.thumbnail_path:
|
||||||
|
image_url = post.image.thumbnail_path.replace('app/static/', f"https://{current_app.config['SERVER_NAME']}/static/")
|
||||||
|
page['image'] = {'type': 'Image', 'url': image_url}
|
||||||
|
if post.type == POST_TYPE_POLL:
|
||||||
|
poll = Poll.query.filter_by(post_id=post.id).first()
|
||||||
|
page['endTime'] = ap_datetime(poll.end_poll)
|
||||||
|
page['votersCount'] = 0
|
||||||
|
choices = []
|
||||||
|
for choice in PollChoice.query.filter_by(post_id=post.id).all():
|
||||||
|
choices.append({'type': 'Note', 'name': choice.choice_text, 'replies': {'type': 'Collection', 'totalItems': 0}})
|
||||||
|
page['oneOf' if poll.mode == 'single' else 'anyOf'] = choices
|
||||||
|
|
||||||
|
activity = 'create' if not edit else 'update'
|
||||||
|
create_id = f"https://{current_app.config['SERVER_NAME']}/activities/{activity}/{gibberish(15)}"
|
||||||
|
type = 'Create' if not edit else 'Update'
|
||||||
|
create = {
|
||||||
|
'id': create_id,
|
||||||
|
'type': type,
|
||||||
|
'actor': user.public_url(),
|
||||||
|
'object': page,
|
||||||
|
'to': to,
|
||||||
|
'cc': cc,
|
||||||
|
'@context': default_context(),
|
||||||
|
'tag': tag
|
||||||
|
}
|
||||||
|
|
||||||
|
domains_sent_to = [current_app.config['SERVER_NAME']]
|
||||||
|
|
||||||
|
# send the activity as an Announce if the community is local, or as a Create if not
|
||||||
|
if community.is_local():
|
||||||
|
del create['@context']
|
||||||
|
|
||||||
|
announce_id = f"https://{current_app.config['SERVER_NAME']}/activities/announce/{gibberish(15)}"
|
||||||
|
actor = community.public_url()
|
||||||
|
cc = [community.ap_followers_url]
|
||||||
|
group_announce = {
|
||||||
|
'id': announce_id,
|
||||||
|
'type': 'Announce',
|
||||||
|
'actor': community.public_url(),
|
||||||
|
'object': create,
|
||||||
|
'to': to,
|
||||||
|
'cc': cc,
|
||||||
|
'@context': default_context()
|
||||||
|
}
|
||||||
|
microblog_announce = {
|
||||||
|
'id': announce_id,
|
||||||
|
'type': 'Announce',
|
||||||
|
'actor': community.public_url(),
|
||||||
|
'object': post.ap_id,
|
||||||
|
'to': to,
|
||||||
|
'cc': cc,
|
||||||
|
'@context': default_context()
|
||||||
|
}
|
||||||
|
for instance in community.following_instances():
|
||||||
|
if instance.inbox and instance.online() and not user.has_blocked_instance(instance.id) and not instance_banned(instance.domain):
|
||||||
|
if instance.software in MICROBLOG_APPS:
|
||||||
|
post_request(instance.inbox, microblog_announce, community.private_key, community.public_url() + '#main-key')
|
||||||
|
else:
|
||||||
|
post_request(instance.inbox, group_announce, community.private_key, community.public_url() + '#main-key')
|
||||||
|
domains_sent_to.append(instance.domain)
|
||||||
|
else:
|
||||||
|
post_request(community.ap_inbox_url, create, user.private_key, user.public_url() + '#main-key')
|
||||||
|
domains_sent_to.append(community.instance.domain)
|
||||||
|
|
||||||
|
# send copy of the Create to anyone else Mentioned in post, but not on an instance that's already sent to.
|
||||||
|
if '@context' not in create:
|
||||||
|
create['@context'] = default_context()
|
||||||
|
for recipient in recipients:
|
||||||
|
if recipient.instance.domain not in domains_sent_to:
|
||||||
|
post_request(recipient.instance.inbox, create, user.private_key, user.public_url() + '#main-key')
|
||||||
|
domains_sent_to.append(recipient.instance.domain)
|
||||||
|
|
||||||
|
# send amended copy of the Create to anyone who is following the User, but hasn't already received something
|
||||||
|
followers = UserFollower.query.filter_by(local_user_id=post.user_id)
|
||||||
|
if not followers:
|
||||||
|
return
|
||||||
|
|
||||||
|
if 'name' in page:
|
||||||
|
del page['name']
|
||||||
|
note = page
|
||||||
|
note['type'] = 'Note'
|
||||||
|
if post.type == POST_TYPE_LINK or post.type == POST_TYPE_VIDEO:
|
||||||
|
note['content'] = '<p><a href=' + post.url + '>' + post.title + '</a></p>'
|
||||||
|
else:
|
||||||
|
note['content'] = '<p>' + post.title + '</p>'
|
||||||
|
if post.body_html:
|
||||||
|
note['content'] = note['content'] + post.body_html
|
||||||
|
note['inReplyTo'] = None
|
||||||
|
create['object'] = note
|
||||||
|
|
||||||
|
instances = Instance.query.join(User, User.instance_id == Instance.id).join(UserFollower, UserFollower.remote_user_id == User.id)
|
||||||
|
instances = instances.filter(UserFollower.local_user_id == post.user_id).filter(Instance.gone_forever == False)
|
||||||
|
for instance in instances:
|
||||||
|
if instance.domain not in domains_sent_to:
|
||||||
|
post_request(instance.inbox, create, user.private_key, user.public_url() + '#main-key')
|
||||||
|
|
Loading…
Add table
Reference in a new issue