mirror of
https://codeberg.org/rimu/pyfedi
synced 2025-01-23 11:26:56 -08:00
alt user names
This commit is contained in:
parent
e4663ea3a3
commit
2f15fd5aea
10 changed files with 98 additions and 34 deletions
|
@ -1,10 +1,11 @@
|
|||
from urllib.parse import urlparse, parse_qs
|
||||
|
||||
from flask import request, current_app, abort, jsonify, json, g, url_for, redirect, make_response
|
||||
from flask_login import current_user
|
||||
from sqlalchemy import desc, or_
|
||||
import werkzeug.exceptions
|
||||
|
||||
from app import db, constants, cache, celery
|
||||
from app.activitypub import bp
|
||||
from flask import request, current_app, abort, jsonify, json, g, url_for, redirect, make_response
|
||||
|
||||
from app.activitypub.signature import HttpSignature, post_request, VerificationError, default_context
|
||||
from app.community.routes import show_community
|
||||
|
@ -27,8 +28,6 @@ from app.utils import gibberish, get_setting, is_image_url, allowlist_html, rend
|
|||
domain_from_url, markdown_to_html, community_membership, ap_datetime, ip_address, can_downvote, \
|
||||
can_upvote, can_create_post, awaken_dormant_instance, shorten_string, can_create_post_reply, sha256_digest, \
|
||||
community_moderators, lemmy_markdown_to_html
|
||||
from sqlalchemy import desc
|
||||
import werkzeug.exceptions
|
||||
|
||||
|
||||
@bp.route('/testredis')
|
||||
|
@ -77,7 +76,7 @@ def webfinger():
|
|||
|
||||
seperator = 'u'
|
||||
type = 'Person'
|
||||
user = User.query.filter_by(user_name=actor.strip(), deleted=False, banned=False, ap_id=None).first()
|
||||
user = User.query.filter(or_(User.user_name == actor.strip(), User.alt_user_name == actor.strip())).filter_by(deleted=False, banned=False, ap_id=None).first()
|
||||
if user is None:
|
||||
community = Community.query.filter_by(name=actor.strip(), ap_id=None).first()
|
||||
if community is None:
|
||||
|
@ -233,18 +232,21 @@ def user_profile(actor):
|
|||
if '@' in actor:
|
||||
user: User = User.query.filter_by(ap_id=actor.lower()).first()
|
||||
else:
|
||||
user: User = User.query.filter_by(user_name=actor, ap_id=None).first()
|
||||
user: User = User.query.filter(or_(User.user_name == actor, User.alt_user_name == actor)).filter_by(ap_id=None).first()
|
||||
if user is None:
|
||||
user = User.query.filter_by(ap_profile_id=f'https://{current_app.config["SERVER_NAME"]}/u/{actor.lower()}', deleted=False, ap_id=None).first()
|
||||
else:
|
||||
if '@' in actor:
|
||||
user: User = User.query.filter_by(ap_id=actor.lower(), deleted=False, banned=False).first()
|
||||
else:
|
||||
user: User = User.query.filter_by(user_name=actor, deleted=False, ap_id=None).first()
|
||||
user: User = User.query.filter(or_(User.user_name == actor, User.alt_user_name == actor)).filter_by(deleted=False, ap_id=None).first()
|
||||
if user is None:
|
||||
user = User.query.filter_by(ap_profile_id=f'https://{current_app.config["SERVER_NAME"]}/u/{actor.lower()}', deleted=False, ap_id=None).first()
|
||||
|
||||
if user is not None:
|
||||
main_user_name = True
|
||||
if user.alt_user_name == actor:
|
||||
main_user_name = False
|
||||
if request.method == 'HEAD':
|
||||
if is_activitypub_request():
|
||||
resp = jsonify('')
|
||||
|
@ -256,43 +258,48 @@ def user_profile(actor):
|
|||
server = current_app.config['SERVER_NAME']
|
||||
actor_data = { "@context": default_context(),
|
||||
"type": "Person" if not user.bot else "Service",
|
||||
"id": user.public_url(),
|
||||
"id": user.public_url(main_user_name),
|
||||
"preferredUsername": actor,
|
||||
"name": user.title if user.title else user.user_name,
|
||||
"inbox": f"{user.public_url()}/inbox",
|
||||
"outbox": f"{user.public_url()}/outbox",
|
||||
"inbox": f"{user.public_url(main_user_name)}/inbox",
|
||||
"outbox": f"{user.public_url(main_user_name)}/outbox",
|
||||
"discoverable": user.searchable,
|
||||
"indexable": user.indexable,
|
||||
"manuallyApprovesFollowers": False if not user.ap_manually_approves_followers else user.ap_manually_approves_followers,
|
||||
"publicKey": {
|
||||
"id": f"{user.public_url()}#main-key",
|
||||
"owner": user.public_url(),
|
||||
"publicKeyPem": user.public_key # .replace("\n", "\\n") #LOOKSWRONG
|
||||
"id": f"{user.public_url(main_user_name)}#main-key",
|
||||
"owner": user.public_url(main_user_name),
|
||||
"publicKeyPem": user.public_key
|
||||
},
|
||||
"endpoints": {
|
||||
"sharedInbox": f"https://{server}/inbox"
|
||||
},
|
||||
"published": ap_datetime(user.created),
|
||||
}
|
||||
if user.avatar_id is not None:
|
||||
if not main_user_name:
|
||||
actor_data['name'] = 'Anonymous'
|
||||
if user.avatar_id is not None and main_user_name:
|
||||
actor_data["icon"] = {
|
||||
"type": "Image",
|
||||
"url": f"https://{current_app.config['SERVER_NAME']}{user.avatar_image()}"
|
||||
}
|
||||
if user.cover_id is not None:
|
||||
if user.cover_id is not None and main_user_name:
|
||||
actor_data["image"] = {
|
||||
"type": "Image",
|
||||
"url": f"https://{current_app.config['SERVER_NAME']}{user.cover_image()}"
|
||||
}
|
||||
if user.about_html:
|
||||
if user.about_html and main_user_name:
|
||||
actor_data['summary'] = user.about_html
|
||||
if user.matrix_user_id:
|
||||
if user.matrix_user_id and main_user_name:
|
||||
actor_data['matrixUserId'] = user.matrix_user_id
|
||||
resp = jsonify(actor_data)
|
||||
resp.content_type = 'application/activity+json'
|
||||
return resp
|
||||
else:
|
||||
return show_profile(user)
|
||||
if main_user_name:
|
||||
return show_profile(user)
|
||||
else:
|
||||
return render_template('errors/alt_profile.html')
|
||||
else:
|
||||
abort(404)
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from datetime import date, datetime, timedelta
|
||||
from datetime import date, datetime
|
||||
from random import randint
|
||||
from flask import redirect, url_for, flash, request, make_response, session, Markup, current_app, g
|
||||
from werkzeug.urls import url_parse
|
||||
from flask_login import login_user, logout_user, current_user
|
||||
|
@ -12,7 +13,7 @@ from app.auth.util import random_token, normalize_utf, ip2location
|
|||
from app.email import send_verification_email, send_password_reset_email
|
||||
from app.models import User, utcnow, IpBan, UserRegistration, Notification, Site
|
||||
from app.utils import render_template, ip_address, user_ip_banned, user_cookie_banned, banned_ip_addresses, \
|
||||
finalize_user_setup, blocked_referrers
|
||||
finalize_user_setup, blocked_referrers, gibberish
|
||||
|
||||
|
||||
@bp.route('/login', methods=['GET', 'POST'])
|
||||
|
@ -123,7 +124,7 @@ def register():
|
|||
user = User(user_name=form.user_name.data, title=form.user_name.data, email=form.real_email.data,
|
||||
verification_token=verification_token, instance_id=1, ip_address=ip_address(),
|
||||
banned=user_ip_banned() or user_cookie_banned(), email_unread_sent=False,
|
||||
referrer=session.get('Referer', ''))
|
||||
referrer=session.get('Referer', ''), alt_user_name=gibberish(randint(8, 20)))
|
||||
user.set_password(form.password.data)
|
||||
ip_address_info = ip2location(user.ip_address)
|
||||
user.ip_address_country = ip_address_info['country'] if ip_address_info else ''
|
||||
|
|
|
@ -81,6 +81,9 @@ class Instance(db.Model):
|
|||
role = InstanceRole.query.filter_by(instance_id=self.id, user_id=user_id).first()
|
||||
return role and role.role == 'admin'
|
||||
|
||||
def votes_are_public(self):
|
||||
return self.software.lower() == 'lemmy' or self.software.lower() == 'mbin' or self.software.lower() == 'kbin'
|
||||
|
||||
def __repr__(self):
|
||||
return '<Instance {}>'.format(self.domain)
|
||||
|
||||
|
@ -585,6 +588,7 @@ class User(UserMixin, db.Model):
|
|||
query_class = FullTextSearchQuery
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
user_name = db.Column(db.String(255), index=True)
|
||||
alt_user_name = db.Column(db.String(255), index=True)
|
||||
title = db.Column(db.String(256))
|
||||
email = db.Column(db.String(255), index=True)
|
||||
password_hash = db.Column(db.String(128))
|
||||
|
@ -734,6 +738,9 @@ class User(UserMixin, db.Model):
|
|||
size += self.cover.filesize()
|
||||
return size
|
||||
|
||||
def vote_privately(self):
|
||||
return self.alt_user_name is not None and self.alt_user_name != ''
|
||||
|
||||
def num_content(self):
|
||||
content = 0
|
||||
content += db.session.execute(text('SELECT COUNT(id) as c FROM "post" WHERE user_id = :user_id'), {'user_id': self.id}).scalar()
|
||||
|
@ -873,8 +880,11 @@ class User(UserMixin, db.Model):
|
|||
result = self.ap_profile_id if self.ap_profile_id else f"https://{current_app.config['SERVER_NAME']}/u/{self.user_name.lower()}"
|
||||
return result
|
||||
|
||||
def public_url(self):
|
||||
result = self.ap_public_url if self.ap_public_url else f"https://{current_app.config['SERVER_NAME']}/u/{self.user_name}"
|
||||
def public_url(self, main_user_name=True):
|
||||
if main_user_name:
|
||||
result = self.ap_public_url if self.ap_public_url else f"https://{current_app.config['SERVER_NAME']}/u/{self.user_name}"
|
||||
else:
|
||||
result = f"https://{current_app.config['SERVER_NAME']}/u/{self.alt_user_name}"
|
||||
return result
|
||||
|
||||
def created_recently(self):
|
||||
|
|
|
@ -397,12 +397,12 @@ def post_vote(post_id: int, vote_direction):
|
|||
if not post.community.local_only:
|
||||
if undo:
|
||||
action_json = {
|
||||
'actor': current_user.public_url(),
|
||||
'actor': current_user.public_url(not(post.community.instance.votes_are_public() and current_user.vote_privately())),
|
||||
'type': 'Undo',
|
||||
'id': f"https://{current_app.config['SERVER_NAME']}/activities/undo/{gibberish(15)}",
|
||||
'audience': post.community.public_url(),
|
||||
'object': {
|
||||
'actor': current_user.public_url(),
|
||||
'actor': current_user.public_url(not(post.community.instance.votes_are_public() and current_user.vote_privately())),
|
||||
'object': post.public_url(),
|
||||
'type': undo,
|
||||
'id': f"https://{current_app.config['SERVER_NAME']}/activities/{undo.lower()}/{gibberish(15)}",
|
||||
|
@ -412,7 +412,7 @@ def post_vote(post_id: int, vote_direction):
|
|||
else:
|
||||
action_type = 'Like' if vote_direction == 'upvote' else 'Dislike'
|
||||
action_json = {
|
||||
'actor': current_user.public_url(),
|
||||
'actor': current_user.public_url(not(post.community.instance.votes_are_public() and current_user.vote_privately())),
|
||||
'object': post.profile_id(),
|
||||
'type': action_type,
|
||||
'id': f"https://{current_app.config['SERVER_NAME']}/activities/{action_type.lower()}/{gibberish(15)}",
|
||||
|
@ -437,7 +437,7 @@ def post_vote(post_id: int, vote_direction):
|
|||
send_to_remote_instance(instance.id, post.community.id, announce)
|
||||
else:
|
||||
success = post_request_in_background(post.community.ap_inbox_url, action_json, current_user.private_key,
|
||||
current_user.public_url() + '#main-key')
|
||||
current_user.public_url(not(post.community.instance.votes_are_public() and current_user.vote_privately())) + '#main-key')
|
||||
if not success:
|
||||
flash('Failed to send vote', 'warning')
|
||||
|
||||
|
@ -514,7 +514,7 @@ def comment_vote(comment_id, vote_direction):
|
|||
'id': f"https://{current_app.config['SERVER_NAME']}/activities/undo/{gibberish(15)}",
|
||||
'audience': comment.community.public_url(),
|
||||
'object': {
|
||||
'actor': current_user.public_url(),
|
||||
'actor': current_user.public_url(not(comment.community.instance.votes_are_public() and current_user.vote_privately())),
|
||||
'object': comment.public_url(),
|
||||
'type': undo,
|
||||
'id': f"https://{current_app.config['SERVER_NAME']}/activities/{undo.lower()}/{gibberish(15)}",
|
||||
|
@ -524,7 +524,7 @@ def comment_vote(comment_id, vote_direction):
|
|||
else:
|
||||
action_type = 'Like' if vote_direction == 'upvote' else 'Dislike'
|
||||
action_json = {
|
||||
'actor': current_user.public_url(),
|
||||
'actor': current_user.public_url(not(comment.community.instance.votes_are_public() and current_user.vote_privately())),
|
||||
'object': comment.public_url(),
|
||||
'type': action_type,
|
||||
'id': f"https://{current_app.config['SERVER_NAME']}/activities/{action_type.lower()}/{gibberish(15)}",
|
||||
|
@ -548,10 +548,8 @@ def comment_vote(comment_id, vote_direction):
|
|||
if instance.inbox and not current_user.has_blocked_instance(instance.id) and not instance_banned(instance.domain):
|
||||
send_to_remote_instance(instance.id, comment.community.id, announce)
|
||||
else:
|
||||
success = post_request_in_background(comment.community.ap_inbox_url, action_json, current_user.private_key,
|
||||
current_user.public_url() + '#main-key')
|
||||
if not success:
|
||||
flash('Failed to send vote', 'warning')
|
||||
post_request_in_background(comment.community.ap_inbox_url, action_json, current_user.private_key,
|
||||
current_user.public_url(not(comment.community.instance.votes_are_public() and current_user.vote_privately())) + '#main-key')
|
||||
|
||||
current_user.last_seen = utcnow()
|
||||
current_user.ip_address = ip_address()
|
||||
|
|
|
@ -114,7 +114,6 @@
|
|||
|
||||
setTheme(getPreferredTheme());
|
||||
</script>
|
||||
<link rel="search" type="application/pagefind" href="/search" title="PieFed.social">
|
||||
</head>
|
||||
<body class="d-flex flex-column{{ ' low_bandwidth' if low_bandwidth }}">
|
||||
<a href="#outer_container" class="skip-link" role="navigation" aria-label="Skip main navigation" tabindex="">Skip to main content</a>
|
||||
|
|
6
app/templates/errors/alt_profile.html
Normal file
6
app/templates/errors/alt_profile.html
Normal file
|
@ -0,0 +1,6 @@
|
|||
<html>
|
||||
<body>
|
||||
<p>{{ _('In PieFed, accounts have a main profile and an alternative profile (used for private voting). You are viewing the alternative profile of an account.') }}</p>
|
||||
<p><a href="https://join.piefed.social/private-voting">{{ _('More about this') }}</a></p>
|
||||
</body>
|
||||
</html>
|
|
@ -33,6 +33,7 @@
|
|||
{{ render_field(form.default_sort) }}
|
||||
{{ render_field(form.default_filter) }}
|
||||
{{ render_field(form.theme) }}
|
||||
{{ render_field(form.vote_privately) }}
|
||||
<h5>Import</h5>
|
||||
{{ render_field(form.import_file) }}
|
||||
{{ render_field(form.submit) }}
|
||||
|
|
|
@ -44,6 +44,7 @@ class SettingsForm(FlaskForm):
|
|||
searchable = BooleanField(_l('Show profile in user list'))
|
||||
indexable = BooleanField(_l('My posts appear in search results'))
|
||||
manually_approves_followers = BooleanField(_l('Manually approve followers'))
|
||||
vote_privately = BooleanField(_l('Vote privately'))
|
||||
import_file = FileField(_l('Import community subscriptions and user blocks from Lemmy'))
|
||||
sorts = [('hot', _l('Hot')),
|
||||
('top', _l('Top')),
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
from datetime import datetime, timedelta
|
||||
from time import sleep
|
||||
from random import randint
|
||||
|
||||
from flask import redirect, url_for, flash, request, make_response, session, Markup, current_app, abort, json, g
|
||||
from flask_login import login_user, logout_user, current_user, login_required
|
||||
|
@ -238,6 +239,11 @@ def change_settings():
|
|||
current_user.markdown_editor = form.markdown_editor.data
|
||||
current_user.interface_language = form.interface_language.data
|
||||
session['ui_language'] = form.interface_language.data
|
||||
if form.vote_privately.data:
|
||||
if current_user.alt_user_name is None or current_user.alt_user_name == '':
|
||||
current_user.alt_user_name = gibberish(randint(8, 20))
|
||||
else:
|
||||
current_user.alt_user_name = ''
|
||||
import_file = request.files['import_file']
|
||||
if propagate_indexable:
|
||||
db.session.execute(text('UPDATE "post" set indexable = :indexable WHERE user_id = :user_id'),
|
||||
|
@ -274,6 +280,7 @@ def change_settings():
|
|||
form.theme.data = current_user.theme
|
||||
form.markdown_editor.data = current_user.markdown_editor
|
||||
form.interface_language.data = current_user.interface_language
|
||||
form.vote_privately.data = current_user.vote_privately()
|
||||
|
||||
return render_template('user/edit_settings.html', title=_('Edit profile'), form=form, user=current_user,
|
||||
moderating_communities=moderating_communities(current_user.get_id()),
|
||||
|
|
34
migrations/versions/2cae414cbc7a_alt_user_names.py
Normal file
34
migrations/versions/2cae414cbc7a_alt_user_names.py
Normal file
|
@ -0,0 +1,34 @@
|
|||
"""alt user names
|
||||
|
||||
Revision ID: 2cae414cbc7a
|
||||
Revises: f1f38dabd541
|
||||
Create Date: 2024-08-19 19:38:36.616993
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '2cae414cbc7a'
|
||||
down_revision = 'f1f38dabd541'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
with op.batch_alter_table('user', schema=None) as batch_op:
|
||||
batch_op.add_column(sa.Column('alt_user_name', sa.String(length=255), nullable=True))
|
||||
batch_op.create_index(batch_op.f('ix_user_alt_user_name'), ['alt_user_name'], unique=False)
|
||||
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
with op.batch_alter_table('user', schema=None) as batch_op:
|
||||
batch_op.drop_index(batch_op.f('ix_user_alt_user_name'))
|
||||
batch_op.drop_column('alt_user_name')
|
||||
|
||||
# ### end Alembic commands ###
|
Loading…
Reference in a new issue