community setting: accept downvotes from community members only

This commit is contained in:
rimu 2025-01-11 12:07:58 +13:00
parent 57da9f7182
commit cb7d4fc40f
7 changed files with 86 additions and 5 deletions

View file

@ -8,6 +8,8 @@ from wtforms.validators import ValidationError, DataRequired, Email, EqualTo, Le
from flask_babel import _, lazy_gettext as _l
from app import db
from app.constants import DOWNVOTE_ACCEPT_ALL, DOWNVOTE_ACCEPT_MEMBERS, DOWNVOTE_ACCEPT_INSTANCE, \
DOWNVOTE_ACCEPT_TRUSTED
from app.models import Community, utcnow
from app.utils import domain_from_url, MultiCheckboxField
from PIL import Image, ImageOps
@ -54,6 +56,13 @@ class EditCommunityForm(FlaskForm):
local_only = BooleanField(_l('Only accept posts from current instance'))
restricted_to_mods = BooleanField(_l('Only moderators can post'))
new_mods_wanted = BooleanField(_l('New moderators wanted'))
downvote_accept_modes = [(DOWNVOTE_ACCEPT_ALL, _l('Everyone')),
(DOWNVOTE_ACCEPT_MEMBERS, _l('Community members')),
(DOWNVOTE_ACCEPT_INSTANCE, _l('This instance')),
(DOWNVOTE_ACCEPT_TRUSTED, _l('Trusted instances')),
]
downvote_accept_mode = SelectField(_l('Accept downvotes from'), coerce=int, choices=downvote_accept_modes, validators=[Optional()], render_kw={'class': 'form-select'})
topic = SelectField(_l('Topic'), coerce=int, validators=[Optional()], render_kw={'class': 'form-select'})
languages = SelectMultipleField(_l('Languages'), coerce=int, validators=[Optional()], render_kw={'class': 'form-select'})
layouts = [('', _l('List')),

View file

@ -1019,6 +1019,7 @@ def community_edit(community_id: int):
community.new_mods_wanted = form.new_mods_wanted.data
community.topic_id = form.topic.data if form.topic.data != 0 else None
community.default_layout = form.default_layout.data
community.downvote_accept_mode = form.downvote_accept_mode.data
icon_file = request.files['icon_file']
if icon_file and icon_file.filename != '':
@ -1067,6 +1068,7 @@ def community_edit(community_id: int):
form.topic.data = community.topic_id if community.topic_id else None
form.languages.data = community.language_ids()
form.default_layout.data = community.default_layout
form.downvote_accept_mode.data = community.downvote_accept_mode
return render_template('community/community_edit.html', title=_('Edit community'), form=form,
current_app=current_app, current="edit_settings",
community=community, moderating_communities=moderating_communities(current_user.get_id()),

View file

@ -35,6 +35,11 @@ NOTIF_REPLY = 4
ROLE_STAFF = 3
ROLE_ADMIN = 4
DOWNVOTE_ACCEPT_ALL = 0
DOWNVOTE_ACCEPT_MEMBERS = 2
DOWNVOTE_ACCEPT_INSTANCE = 4
DOWNVOTE_ACCEPT_TRUSTED = 6
MICROBLOG_APPS = ["mastodon", "misskey", "akkoma", "iceshrimp", "pleroma"]
APLOG_IN = True

View file

@ -75,7 +75,7 @@ class Instance(db.Model):
start_trying_again = db.Column(db.DateTime) # When to start trying again. Should grow exponentially with each failure.
gone_forever = db.Column(db.Boolean, default=False) # True once this instance is considered offline forever - never start trying again
ip_address = db.Column(db.String(50))
trusted = db.Column(db.Boolean, default=False)
trusted = db.Column(db.Boolean, default=False, index=True)
posting_warning = db.Column(db.String(512))
nodeinfo_href = db.Column(db.String(100))
@ -438,6 +438,7 @@ class Community(db.Model):
topic_id = db.Column(db.Integer, db.ForeignKey('topic.id'), index=True)
default_layout = db.Column(db.String(15))
posting_warning = db.Column(db.String(512))
downvote_accept_mode = db.Column(db.Integer, default=0) # 0 = All, 2 = Community members, 4 = This instance, 6 = Trusted instances
ap_id = db.Column(db.String(255), index=True)
ap_profile_id = db.Column(db.String(255), index=True, unique=True)

View file

@ -59,20 +59,22 @@
{{ render_field(form.restricted_to_mods) }}
{{ render_field(form.local_only) }}
{{ render_field(form.new_mods_wanted) }}
{{ render_field(form.downvote_accept_mode) }}
{{ render_field(form.topic) }}
{{ render_field(form.languages) }}
{{ render_field(form.default_layout) }}
<div class="row">
<div class="row mb-4">
<div class="col-auto">
{{ render_field(form.submit) }}
</div>
<div class="col-auto">
</div>
<div class="row">
<div class="col-auto pt-4">
{% if community.is_local() and (community.is_owner() or current_user.is_admin()) %}
<p><a class="btn btn-primary btn-warning" href="{{ url_for('community.community_delete', community_id=community.id) }}" rel="nofollow">Delete community</a></p>
{% endif %}
</div>
</div>
</form>
</div>
</div>

View file

@ -19,6 +19,8 @@ from bs4 import BeautifulSoup, MarkupResemblesLocatorWarning
import warnings
import jwt
from app.constants import DOWNVOTE_ACCEPT_ALL, DOWNVOTE_ACCEPT_TRUSTED, DOWNVOTE_ACCEPT_INSTANCE, \
DOWNVOTE_ACCEPT_MEMBERS
warnings.filterwarnings("ignore", category=MarkupResemblesLocatorWarning)
import os
@ -715,6 +717,20 @@ def can_downvote(user, community: Community, site=None) -> bool:
if (user.attitude and user.attitude < -0.40) or user.reputation < -10: # this should exclude about 3.7% of users.
return False
if community.downvote_accept_mode != DOWNVOTE_ACCEPT_ALL:
if community.downvote_accept_mode == DOWNVOTE_ACCEPT_MEMBERS:
if not community.is_member(user):
return False
elif community.downvote_accept_mode == DOWNVOTE_ACCEPT_INSTANCE:
if user.instance_id != community.instance_id:
return False
elif community.downvote_accept_mode == DOWNVOTE_ACCEPT_TRUSTED:
if community.instance_id == user.instance_id:
pass
else:
if user.instance_id not in trusted_instance_ids():
return False
if community.id in communities_banned_from(user.id):
return False
@ -812,6 +828,11 @@ def reply_is_stupid(body) -> bool:
return False
@cache.memoize(timeout=10)
def trusted_instance_ids() -> List[int]:
return [instance.id for instance in Instance.query.filter(Instance.trusted == True)]
def inbox_domain(inbox: str) -> str:
inbox = inbox.lower()
if 'https://' in inbox or 'http://' in inbox:

View file

@ -0,0 +1,41 @@
"""downvote accept mode
Revision ID: 03258111eef1
Revises: 9df13396fd54
Create Date: 2025-01-11 11:32:54.226698
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '03258111eef1'
down_revision = '9df13396fd54'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('community', schema=None) as batch_op:
batch_op.add_column(sa.Column('downvote_accept_mode', sa.Integer(), nullable=True))
with op.batch_alter_table('community', schema=None) as batch_op:
batch_op.execute('UPDATE "community" SET downvote_accept_mode = 0')
with op.batch_alter_table('instance', schema=None) as batch_op:
batch_op.create_index(batch_op.f('ix_instance_trusted'), ['trusted'], unique=False)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('instance', schema=None) as batch_op:
batch_op.drop_index(batch_op.f('ix_instance_trusted'))
with op.batch_alter_table('community', schema=None) as batch_op:
batch_op.drop_column('downvote_accept_mode')
# ### end Alembic commands ###