diff --git a/app/__init__.py b/app/__init__.py index c566e8f4..64ce80f8 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -18,6 +18,8 @@ from sqlalchemy_searchable import make_searchable from config import Config +import re + def get_locale(): try: @@ -97,12 +99,6 @@ def create_app(config_class=Config): from app.search import bp as search_bp app.register_blueprint(search_bp) - def get_resource_as_string(name, charset='utf-8'): - with app.open_resource(name) as f: - return f.read().decode(charset) - - app.jinja_env.globals['get_resource_as_string'] = get_resource_as_string - # send error reports via email if app.config['MAIL_SERVER'] and app.config['MAIL_ERRORS']: auth = None diff --git a/app/activitypub/routes.py b/app/activitypub/routes.py index 4d6aaa42..cae7a644 100644 --- a/app/activitypub/routes.py +++ b/app/activitypub/routes.py @@ -175,12 +175,12 @@ def user_profile(actor): actor = actor.strip() if current_user.is_authenticated and current_user.is_admin(): if '@' in actor: - user: User = User.query.filter_by(ap_id=actor.lower()).first() + user: User = User.query.filter_by(ap_id=actor).first() else: user: User = User.query.filter_by(user_name=actor, ap_id=None).first() else: if '@' in actor: - user: User = User.query.filter_by(ap_id=actor.lower(), deleted=False, banned=False).first() + user: User = User.query.filter_by(ap_id=actor, deleted=False, banned=False).first() else: user: User = User.query.filter_by(user_name=actor, deleted=False, ap_id=None).first() diff --git a/app/auth/routes.py b/app/auth/routes.py index 6a3a06ce..1ca6a631 100644 --- a/app/auth/routes.py +++ b/app/auth/routes.py @@ -98,6 +98,11 @@ def register(): if form.user_name.data in disallowed_usernames: flash(_('Sorry, you cannot use that user name'), 'error') else: + # Nazis use 88 and 14 in their user names very often. + if '88' in form.user_name.data or '14' in form.user_name.data: + resp = make_response(redirect(url_for('auth.please_wait'))) + resp.set_cookie('sesion', '17489047567495', expires=datetime(year=2099, month=12, day=30)) + return resp for referrer in blocked_referrers(): if referrer in session.get('Referer', ''): resp = make_response(redirect(url_for('auth.please_wait'))) diff --git a/app/community/routes.py b/app/community/routes.py index 6c32031b..87c44739 100644 --- a/app/community/routes.py +++ b/app/community/routes.py @@ -947,17 +947,30 @@ def community_moderate(actor): abort(404) -@bp.route('//moderate/banned', methods=['GET']) +@bp.route('//moderate/subscribers', methods=['GET']) @login_required -def community_moderate_banned(actor): +def community_moderate_subscribers(actor): community = actor_to_community(actor) if community is not None: if community.is_moderator() or current_user.is_admin(): + + page = request.args.get('page', 1, type=int) + low_bandwidth = request.cookies.get('low_bandwidth', '0') == '1' + + subscribers = User.query.join(CommunityMember, CommunityMember.user_id == User.id).filter(CommunityMember.community_id == community.id) + subscribers = subscribers.filter(CommunityMember.is_banned == False) + + # Pagination + subscribers = subscribers.paginate(page=page, per_page=100 if not low_bandwidth else 50, error_out=False) + next_url = url_for('community.community_moderate_subscribers', actor=actor, page=subscribers.next_num) if subscribers.has_next else None + prev_url = url_for('community.community_moderate_subscribers', actor=actor, page=subscribers.prev_num) if subscribers.has_prev and page != 1 else None + banned_people = User.query.join(CommunityBan, CommunityBan.user_id == User.id).filter(CommunityBan.community_id == community.id).all() - return render_template('community/community_moderate_banned.html', - title=_('People banned from of %(community)s', community=community.display_name()), - community=community, banned_people=banned_people, current='banned', + + return render_template('community/community_moderate_subscribers.html', title=_('Moderation of %(community)s', community=community.display_name()), + community=community, current='subscribers', subscribers=subscribers, banned_people=banned_people, + next_url=next_url, prev_url=prev_url, low_bandwidth=low_bandwidth, moderating_communities=moderating_communities(current_user.get_id()), joined_communities=joined_communities(current_user.get_id()), inoculation=inoculation[randint(0, len(inoculation) - 1)] @@ -1023,3 +1036,41 @@ def community_moderate_report_resolve(community_id, report_id): return redirect(url_for('community.community_moderate', actor=community.link())) else: return render_template('community/community_moderate_report_resolve.html', form=form) + + +@bp.route('/lookup//') +def lookup(community, domain): + if domain == current_app.config['SERVER_NAME']: + return redirect('/c/' + community) + + exists = Community.query.filter_by(name=community, ap_domain=domain).first() + if exists: + return redirect('/c/' + community + '@' + domain) + else: + address = '!' + community + '@' + domain + if current_user.is_authenticated: + new_community = None + + new_community = search_for_community(address) + if new_community is None: + if g.site.enable_nsfw: + flash(_('Community not found.'), 'warning') + else: + flash(_('Community not found. If you are searching for a nsfw community it is blocked by this instance.'), 'warning') + else: + if new_community.banned: + flash(_('That community is banned from %(site)s.', site=g.site.name), 'warning') + + return render_template('community/lookup_remote.html', + title=_('Search result for remote community'), new_community=new_community, + subscribed=community_membership(current_user, new_community) >= SUBSCRIPTION_MEMBER) + else: + # send them back where they came from + flash('Searching for remote communities requires login', 'error') + referrer = request.headers.get('Referer', None) + if referrer is not None: + return redirect(referrer) + else: + return redirect('/') + + diff --git a/app/static/structure.css b/app/static/structure.css index 47e25f42..3bfb3053 100644 --- a/app/static/structure.css +++ b/app/static/structure.css @@ -920,6 +920,8 @@ fieldset legend { position: relative; cursor: pointer; color: rgba(var(--bs-link-color-rgb), var(--bs-link-opacity, 1)); + min-width: 44px; + min-height: 44px; } .voting_buttons_new .upvote_button.digits_4, .voting_buttons_new .downvote_button.digits_4 { width: 68px; @@ -987,7 +989,7 @@ fieldset legend { .voting_buttons { float: right; display: block; - width: 55px; + min-width: 54px; padding: 0 0 5px 5px; line-height: 30px; font-size: 14px; @@ -1002,9 +1004,13 @@ fieldset legend { } .voting_buttons .upvote_button, .voting_buttons .downvote_button { position: relative; - padding-left: 3px; + padding: 0 3px; + min-height: 44px; + min-width: 44px; border-radius: 3px; cursor: pointer; + display: flex; + align-items: center; } .voting_buttons .upvote_button.digits_4, .voting_buttons .downvote_button.digits_4 { width: 68px; diff --git a/app/static/structure.scss b/app/static/structure.scss index 2de3d98e..06748afd 100644 --- a/app/static/structure.scss +++ b/app/static/structure.scss @@ -5,6 +5,8 @@ nav, etc which are used site-wide */ @import "scss/typography"; @import "scss/controls"; +$min-touch-target: 44px; + html { @include breakpoint(phablet) { scroll-padding-top: 80px; @@ -553,6 +555,8 @@ html { position: relative; cursor: pointer; color: rgba(var(--bs-link-color-rgb),var(--bs-link-opacity,1)); + min-width: $min-touch-target; + min-height: $min-touch-target; &.digits_4 { width: 68px; @@ -625,7 +629,7 @@ html { .voting_buttons { float: right; display: block; - width: 55px; + min-width: $min-touch-target + 10; padding: 0 0 5px 5px; line-height: 30px; font-size: 14px; @@ -640,9 +644,13 @@ html { .upvote_button, .downvote_button { position: relative; - padding-left: 3px; + padding: 0 3px; + min-height: $min-touch-target; + min-width: $min-touch-target; border-radius: 3px; cursor: pointer; + display: flex; + align-items: center; &.digits_4 { width: 68px; diff --git a/app/static/styles.css b/app/static/styles.css index d9d731ff..184ef11e 100644 --- a/app/static/styles.css +++ b/app/static/styles.css @@ -426,6 +426,10 @@ fieldset legend { background-color: #d8e5ee; } +html, body { + overscroll-behavior-x: none; +} + body { font-size: 0.95rem; } diff --git a/app/static/styles.scss b/app/static/styles.scss index 69d75c46..cb207a95 100644 --- a/app/static/styles.scss +++ b/app/static/styles.scss @@ -4,6 +4,10 @@ @import "scss/typography"; @import "scss/controls"; +html, body { + overscroll-behavior-x: none; +} + body { font-size: 0.95rem; } @@ -14,6 +18,7 @@ a { } #outer_container, footer { + //overscroll-behavior-x: none; a:not(.btn):hover { text-decoration: underline; } diff --git a/app/templates/community/_community_moderation_nav.html b/app/templates/community/_community_moderation_nav.html index 6dfd5de8..51825e1b 100644 --- a/app/templates/community/_community_moderation_nav.html +++ b/app/templates/community/_community_moderation_nav.html @@ -1,4 +1,4 @@ -
+
{% if community.is_owner() or current_user.is_admin() %} {{ _('Settings') }} @@ -10,8 +10,8 @@ {{ _('Reports') }} - - {{ _('Banned people') }} + + {{ _('Subscribers') }} {{ _('Appeals') }} diff --git a/app/templates/community/community_moderate_banned.html b/app/templates/community/community_moderate_banned.html deleted file mode 100644 index 74a5b200..00000000 --- a/app/templates/community/community_moderate_banned.html +++ /dev/null @@ -1,67 +0,0 @@ -{% if theme() and file_exists('app/templates/themes/' + theme() + '/base.html') %} - {% extends 'themes/' + theme() + '/base.html' %} -{% else %} - {% extends "base.html" %} -{% endif %} %} -{% from 'bootstrap/form.html' import render_field %} - -{% block app_content %} -
-
- - {% include "community/_community_moderation_nav.html" %} -
-
-

{{ _('Banned people') }}

-
-
- -
-
-

{{ _('See and manage who is banned from %(community)s', community=community.display_name()) }}

-

- {% if banned_people %} -
- - - - -
- - - - - - - - - {% for user in banned_people %} - - - - - - - - {% endfor %} -
NameLocal/RemoteReportsIPActions
- {{ user.display_name() }}{{ 'Local' if user.is_local() else 'Remote' }}{{ user.reports if user.reports > 0 }} {{ user.ip_address if user.ip_address }} {% if user.is_local() %} - View - {% else %} - View local | - View remote - {% endif %} - | {{ _('Un ban') }} -
- {% else %} -

{{ _('No banned people yet') }}

- {% endif %} -
-
-{% endblock %} \ No newline at end of file diff --git a/app/templates/community/community_moderate_subscribers.html b/app/templates/community/community_moderate_subscribers.html new file mode 100644 index 00000000..80588538 --- /dev/null +++ b/app/templates/community/community_moderate_subscribers.html @@ -0,0 +1,129 @@ +{% if theme() and file_exists('app/templates/themes/' + theme() + '/base.html') %} + {% extends 'themes/' + theme() + '/base.html' %} +{% else %} + {% extends "base.html" %} +{% endif %} %} +{% from 'bootstrap/form.html' import render_field %} + +{% block app_content %} +
+
+ + {% include "community/_community_moderation_nav.html" %} +
+
+

{{ _('Subscribers') }}

+
+
+ +
+
+

{{ _('See who is subscribed to %(community)s', community=community.display_name()) }}

+

+ {% if subscribers %} +
+ + + + + + + + + {% for user in subscribers.items %} + + + + + + + + {% endfor %} +
NameLocal/RemoteLast SeenIPActions
+ {{ render_username(user) }} + {% if user.is_local() %} Local {% else %} {{ user.ap_domain }}{% endif %}{{ moment(user.last_seen).fromNow() }} {{ user.ip_address if user.ip_address }} + +
+
+ + {% else %} +

{{ _('This community has no subscribers') }}

+ {% endif %} +

{{ _('Banned People') }}

+

{{ _('See and manage who is banned from %(community)s', community=community.display_name()) }}

+ {% if banned_people %} +
+ + + + +
+
+ + + + + + + + + {% for user in banned_people %} + + + + + + + + {% endfor %} +
NameLocal/RemoteReportsIPActions
{{ render_username(user) }}{% if user.is_local() %} Local {% else %} {{ user.ap_domain }}{% endif %}{{ user.reports if user.reports > 0 }} {{ user.ip_address if user.ip_address }} + +
+
+ {% else %} +

{{ _('No banned people yet') }}

+ {% endif %} +
+
+{% endblock %} \ No newline at end of file diff --git a/app/templates/community/lookup_remote.html b/app/templates/community/lookup_remote.html new file mode 100644 index 00000000..074204da --- /dev/null +++ b/app/templates/community/lookup_remote.html @@ -0,0 +1,32 @@ +{% if theme() and file_exists('app/templates/themes/' + theme() + '/base.html') %} + {% extends 'themes/' + theme() + '/base.html' %} +{% else %} + {% extends "base.html" %} +{% endif %} %} +{% from 'bootstrap/form.html' import render_form %} + +{% block app_content %} + {% if new_community and not new_community.banned %} +
+
+
+
+
{{ _('Found a community:') }}
+
+

+ + {{ new_community.title }}@{{ new_community.ap_domain }} +

+

{% if subscribed %} + {{ _('Leave') }} + {% else %} + {{ _('Join') }} + {% endif %} +

+
+
+
+
+
+ {% endif %} +{% endblock %} diff --git a/app/templates/post/_post_full.html b/app/templates/post/_post_full.html index 18ccccd8..9d384edd 100644 --- a/app/templates/post/_post_full.html +++ b/app/templates/post/_post_full.html @@ -42,7 +42,7 @@ {% endif %}
- {{ post.body_html|safe if post.body_html else '' }} + {{ post.body_html|community_links|safe if post.body_html else '' }}
{% else %} @@ -99,7 +99,7 @@ {% endif %} {% endif %}
- {{ post.body_html|safe if post.body_html else '' }} + {{ post.body_html|community_links|safe if post.body_html else '' }}
{% endif %} diff --git a/app/templates/post/post.html b/app/templates/post/post.html index 7d7f7c6a..b47b130c 100644 --- a/app/templates/post/post.html +++ b/app/templates/post/post.html @@ -109,7 +109,7 @@ {% endif %}
- {{ comment['comment'].body_html | safe }} + {{ comment['comment'].body_html | community_links | safe }}
{% if not comment['comment'].author.indexable %}{% endif %}
diff --git a/app/utils.py b/app/utils.py index a62a5d42..0a12ce53 100644 --- a/app/utils.py +++ b/app/utils.py @@ -252,6 +252,12 @@ def microblog_content_to_title(html: str) -> str: return result +def community_link_to_href(link: str) -> str: + pattern = r"!([a-zA-Z0-9_.-]*)@([a-zA-Z0-9_.-]*)\b" + server = r'/\g<2>>' + r'!\g<1>@\g<2>', link) + + def domain_from_url(url: str, create=True) -> Domain: parsed_url = urlparse(url.lower().replace('www.', '')) if parsed_url and parsed_url.hostname: diff --git a/pyfedi.py b/pyfedi.py index 040d22c2..410baaec 100644 --- a/pyfedi.py +++ b/pyfedi.py @@ -11,7 +11,7 @@ from flask import session, g, json, request, current_app from app.constants import POST_TYPE_LINK, POST_TYPE_IMAGE, POST_TYPE_ARTICLE from app.models import Site from app.utils import getmtime, gibberish, shorten_string, shorten_url, digits, user_access, community_membership, \ - can_create_post, can_upvote, can_downvote, shorten_number, ap_datetime, current_theme + can_create_post, can_upvote, can_downvote, shorten_number, ap_datetime, current_theme, community_link_to_href app = create_app() cli.register(app) @@ -44,6 +44,7 @@ with app.app_context(): app.jinja_env.globals['can_downvote'] = can_downvote app.jinja_env.globals['theme'] = current_theme app.jinja_env.globals['file_exists'] = os.path.exists + app.jinja_env.filters['community_links'] = community_link_to_href app.jinja_env.filters['shorten'] = shorten_string app.jinja_env.filters['shorten_url'] = shorten_url