mirror of
https://codeberg.org/rimu/pyfedi
synced 2025-01-23 19:36:56 -08:00
flag low effort commenters
This commit is contained in:
parent
fa953ba976
commit
709980c6f3
13 changed files with 90 additions and 6 deletions
|
@ -1426,6 +1426,8 @@ def create_post_reply(activity_log: ActivityPubLog, community: Community, in_rep
|
||||||
post_reply.ranking = confidence(post_reply.up_votes, post_reply.down_votes)
|
post_reply.ranking = confidence(post_reply.up_votes, post_reply.down_votes)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
|
user.recalculate_avg_comment_length()
|
||||||
|
|
||||||
# send notification to the post/comment being replied to
|
# send notification to the post/comment being replied to
|
||||||
if parent_comment_id:
|
if parent_comment_id:
|
||||||
notify_about_post_reply(parent_comment, post_reply)
|
notify_about_post_reply(parent_comment, post_reply)
|
||||||
|
|
|
@ -3,9 +3,10 @@ from time import time
|
||||||
from typing import List, Union
|
from typing import List, Union
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
|
from bs4 import BeautifulSoup
|
||||||
from flask import current_app, escape, url_for, render_template_string
|
from flask import current_app, escape, url_for, render_template_string
|
||||||
from flask_login import UserMixin, current_user
|
from flask_login import UserMixin, current_user
|
||||||
from sqlalchemy import or_, text
|
from sqlalchemy import or_, text, desc
|
||||||
from werkzeug.security import generate_password_hash, check_password_hash
|
from werkzeug.security import generate_password_hash, check_password_hash
|
||||||
from flask_babel import _, lazy_gettext as _l
|
from flask_babel import _, lazy_gettext as _l
|
||||||
from sqlalchemy.orm import backref
|
from sqlalchemy.orm import backref
|
||||||
|
@ -615,6 +616,8 @@ class User(UserMixin, db.Model):
|
||||||
markdown_editor = db.Column(db.Boolean, default=False)
|
markdown_editor = db.Column(db.Boolean, default=False)
|
||||||
interface_language = db.Column(db.String(10)) # a locale that the translation system understands e.g. 'en' or 'en-us'. If empty, use browser default
|
interface_language = db.Column(db.String(10)) # a locale that the translation system understands e.g. 'en' or 'en-us'. If empty, use browser default
|
||||||
language_id = db.Column(db.Integer, db.ForeignKey('language.id')) # the default choice in the language dropdown when composing posts & comments
|
language_id = db.Column(db.Integer, db.ForeignKey('language.id')) # the default choice in the language dropdown when composing posts & comments
|
||||||
|
average_comment_length = db.Column(db.Integer)
|
||||||
|
comment_length_warning = db.Column(db.Integer, default=15)
|
||||||
|
|
||||||
avatar = db.relationship('File', lazy='joined', foreign_keys=[avatar_id], single_parent=True, cascade="all, delete-orphan")
|
avatar = db.relationship('File', lazy='joined', foreign_keys=[avatar_id], single_parent=True, cascade="all, delete-orphan")
|
||||||
cover = db.relationship('File', lazy='joined', foreign_keys=[cover_id], single_parent=True, cascade="all, delete-orphan")
|
cover = db.relationship('File', lazy='joined', foreign_keys=[cover_id], single_parent=True, cascade="all, delete-orphan")
|
||||||
|
@ -808,6 +811,19 @@ class User(UserMixin, db.Model):
|
||||||
else:
|
else:
|
||||||
self.attitude = (total_upvotes - total_downvotes) / (total_upvotes + total_downvotes)
|
self.attitude = (total_upvotes - total_downvotes) / (total_upvotes + total_downvotes)
|
||||||
|
|
||||||
|
def recalculate_avg_comment_length(self):
|
||||||
|
replies = db.session.execute(text('SELECT body, body_html FROM "post_reply" WHERE user_id = :user_id ORDER BY created_at DESC LIMIT 30'),
|
||||||
|
{'user_id': self.id}).all()
|
||||||
|
lengths = []
|
||||||
|
for reply in replies:
|
||||||
|
if reply.body.strip() != '':
|
||||||
|
lengths.append(len(reply.body.strip()))
|
||||||
|
else:
|
||||||
|
soup = BeautifulSoup(reply.body_html, 'html.parser')
|
||||||
|
lengths.append(len(soup.get_text()))
|
||||||
|
if len(lengths) > 5:
|
||||||
|
self.average_comment_length = math.ceil(sum(lengths) / len(lengths))
|
||||||
|
|
||||||
def subscribed(self, community_id: int) -> int:
|
def subscribed(self, community_id: int) -> int:
|
||||||
if community_id is None:
|
if community_id is None:
|
||||||
return False
|
return False
|
||||||
|
|
|
@ -111,6 +111,8 @@ def show_post(post_id: int):
|
||||||
db.session.add(reply)
|
db.session.add(reply)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
|
current_user.recalculate_avg_comment_length()
|
||||||
|
|
||||||
notify_about_post_reply(None, reply)
|
notify_about_post_reply(None, reply)
|
||||||
|
|
||||||
# Subscribe to own comment
|
# Subscribe to own comment
|
||||||
|
@ -671,6 +673,8 @@ def add_reply(post_id: int, comment_id: int):
|
||||||
db.session.add(reply)
|
db.session.add(reply)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
|
current_user.recalculate_avg_comment_length()
|
||||||
|
|
||||||
# Notify subscribers
|
# Notify subscribers
|
||||||
notify_about_post_reply(in_reply_to, reply)
|
notify_about_post_reply(in_reply_to, reply)
|
||||||
|
|
||||||
|
|
|
@ -14,4 +14,8 @@ $super-dark-grey: #424549;
|
||||||
|
|
||||||
.orangered {
|
.orangered {
|
||||||
color: orangered;
|
color: orangered;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.purple {
|
||||||
|
color: purple;
|
||||||
|
}
|
||||||
|
|
|
@ -8,6 +8,10 @@ nav, etc which are used site-wide */
|
||||||
color: orangered;
|
color: orangered;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.purple {
|
||||||
|
color: purple;
|
||||||
|
}
|
||||||
|
|
||||||
.pl-0 {
|
.pl-0 {
|
||||||
padding-left: 0 !important;
|
padding-left: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,10 @@
|
||||||
color: orangered;
|
color: orangered;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.purple {
|
||||||
|
color: purple;
|
||||||
|
}
|
||||||
|
|
||||||
.pl-0 {
|
.pl-0 {
|
||||||
padding-left: 0 !important;
|
padding-left: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
|
@ -138,7 +138,9 @@
|
||||||
</div>
|
</div>
|
||||||
{% if post.type == POST_TYPE_POLL %}
|
{% if post.type == POST_TYPE_POLL %}
|
||||||
<div class="post_poll">
|
<div class="post_poll">
|
||||||
{% if poll_results %}
|
{% if poll_results and poll_total_votes == 0 %}
|
||||||
|
<p>{{ _('The poll has finished, yet no votes were cast.') }}</p>
|
||||||
|
{% elif poll_results and poll_total_votes %}
|
||||||
<ul>
|
<ul>
|
||||||
{% for choice in poll_choices %}
|
{% for choice in poll_choices %}
|
||||||
<li>
|
<li>
|
||||||
|
|
|
@ -100,6 +100,9 @@
|
||||||
{% elif comment['comment'].author.reputation < 0 %}
|
{% elif comment['comment'].author.reputation < 0 %}
|
||||||
<span class="fe fe-warning orangered" title="Low reputation."> </span>
|
<span class="fe fe-warning orangered" title="Low reputation."> </span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if current_user.is_authenticated and current_user.comment_length_warning and comment['comment'].author.average_comment_length and comment['comment'].author.average_comment_length <= current_user.comment_length_warning %}
|
||||||
|
<span class="fe fe-warning purple" title="{{ _('Usually makes short and low effort comments.') }}"> </span>
|
||||||
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if comment['comment'].author.id == post.author.id %}<span title="Submitter of original post" aria-label="{{ _('Post creator') }}" class="small">[OP]</span>{% endif %}
|
{% if comment['comment'].author.id == post.author.id %}<span title="Submitter of original post" aria-label="{{ _('Post creator') }}" class="small">[OP]</span>{% endif %}
|
||||||
<span class="text-muted small" aria_label="{{ _('When: ') }}">{{ moment(comment['comment'].posted_at).fromNow(refresh=True) }}{% if comment['comment'].edited_at %}, edited {{ moment(comment['comment'].edited_at).fromNow(refresh=True) }} {% endif %}</span>
|
<span class="text-muted small" aria_label="{{ _('When: ') }}">{{ moment(comment['comment'].posted_at).fromNow(refresh=True) }}{% if comment['comment'].edited_at %}, edited {{ moment(comment['comment'].edited_at).fromNow(refresh=True) }} {% endif %}</span>
|
||||||
|
|
|
@ -35,6 +35,8 @@
|
||||||
{{ render_field(form.markdown_editor) }}
|
{{ render_field(form.markdown_editor) }}
|
||||||
{{ render_field(form.default_sort) }}
|
{{ render_field(form.default_sort) }}
|
||||||
{{ render_field(form.theme) }}
|
{{ render_field(form.theme) }}
|
||||||
|
{{ render_field(form.comment_length_warning) }}
|
||||||
|
<small class="field_hint">{{ _('Accounts which usually post comments shorter than this will have a warning. Set to 0 to disable warning.') }}</small>
|
||||||
<h5>Import</h5>
|
<h5>Import</h5>
|
||||||
{{ render_field(form.import_file) }}
|
{{ render_field(form.import_file) }}
|
||||||
{{ render_field(form.submit) }}
|
{{ render_field(form.submit) }}
|
||||||
|
|
|
@ -2,7 +2,7 @@ from flask import session
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
from flask_wtf import FlaskForm
|
from flask_wtf import FlaskForm
|
||||||
from wtforms import StringField, SubmitField, PasswordField, BooleanField, EmailField, TextAreaField, FileField, \
|
from wtforms import StringField, SubmitField, PasswordField, BooleanField, EmailField, TextAreaField, FileField, \
|
||||||
RadioField, DateField, SelectField
|
RadioField, DateField, SelectField, IntegerField
|
||||||
from wtforms.fields.choices import SelectMultipleField
|
from wtforms.fields.choices import SelectMultipleField
|
||||||
from wtforms.validators import ValidationError, DataRequired, Email, EqualTo, Length, Optional
|
from wtforms.validators import ValidationError, DataRequired, Email, EqualTo, Length, Optional
|
||||||
from flask_babel import _, lazy_gettext as _l
|
from flask_babel import _, lazy_gettext as _l
|
||||||
|
@ -50,6 +50,7 @@ class SettingsForm(FlaskForm):
|
||||||
]
|
]
|
||||||
default_sort = SelectField(_l('By default, sort posts by'), choices=sorts, validators=[DataRequired()], coerce=str, render_kw={'class': 'form-select'})
|
default_sort = SelectField(_l('By default, sort posts by'), choices=sorts, validators=[DataRequired()], coerce=str, render_kw={'class': 'form-select'})
|
||||||
theme = SelectField(_l('Theme'), coerce=str, render_kw={'class': 'form-select'})
|
theme = SelectField(_l('Theme'), coerce=str, render_kw={'class': 'form-select'})
|
||||||
|
comment_length_warning = IntegerField(_('Useless comment cutoff length'))
|
||||||
submit = SubmitField(_l('Save settings'))
|
submit = SubmitField(_l('Save settings'))
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -188,6 +188,7 @@ def change_settings():
|
||||||
current_user.email_unread = form.email_unread.data
|
current_user.email_unread = form.email_unread.data
|
||||||
current_user.markdown_editor = form.markdown_editor.data
|
current_user.markdown_editor = form.markdown_editor.data
|
||||||
current_user.interface_language = form.interface_language.data
|
current_user.interface_language = form.interface_language.data
|
||||||
|
current_user.comment_length_warning = form.comment_length_warning.data
|
||||||
session['ui_language'] = form.interface_language.data
|
session['ui_language'] = form.interface_language.data
|
||||||
import_file = request.files['import_file']
|
import_file = request.files['import_file']
|
||||||
if propagate_indexable:
|
if propagate_indexable:
|
||||||
|
@ -227,6 +228,7 @@ def change_settings():
|
||||||
form.theme.data = current_user.theme
|
form.theme.data = current_user.theme
|
||||||
form.markdown_editor.data = current_user.markdown_editor
|
form.markdown_editor.data = current_user.markdown_editor
|
||||||
form.interface_language.data = current_user.interface_language
|
form.interface_language.data = current_user.interface_language
|
||||||
|
form.comment_length_warning.data = current_user.comment_length_warning
|
||||||
|
|
||||||
return render_template('user/edit_settings.html', title=_('Edit profile'), form=form, user=current_user,
|
return render_template('user/edit_settings.html', title=_('Edit profile'), form=form, user=current_user,
|
||||||
moderating_communities=moderating_communities(current_user.get_id()),
|
moderating_communities=moderating_communities(current_user.get_id()),
|
||||||
|
|
10
app/utils.py
10
app/utils.py
|
@ -258,6 +258,13 @@ def markdown_to_text(markdown_text) -> str:
|
||||||
return markdown_text.replace("# ", '')
|
return markdown_text.replace("# ", '')
|
||||||
|
|
||||||
|
|
||||||
|
def html_to_text(html) -> str:
|
||||||
|
if html is None or html == '':
|
||||||
|
return ''
|
||||||
|
soup = BeautifulSoup(html, 'html.parser')
|
||||||
|
return soup.get_text()
|
||||||
|
|
||||||
|
|
||||||
def microblog_content_to_title(html: str) -> str:
|
def microblog_content_to_title(html: str) -> str:
|
||||||
title = ''
|
title = ''
|
||||||
if '<p>' in html:
|
if '<p>' in html:
|
||||||
|
@ -267,8 +274,7 @@ def microblog_content_to_title(html: str) -> str:
|
||||||
if title and title.strip() != '' and len(title.strip()) >= 5:
|
if title and title.strip() != '' and len(title.strip()) >= 5:
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
soup = BeautifulSoup(html, 'html.parser')
|
title = html_to_text(html)
|
||||||
title = soup.get_text()
|
|
||||||
|
|
||||||
period_index = title.find('.')
|
period_index = title.find('.')
|
||||||
question_index = title.find('?')
|
question_index = title.find('?')
|
||||||
|
|
34
migrations/versions/e76f2067e689_comment_length.py
Normal file
34
migrations/versions/e76f2067e689_comment_length.py
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
"""comment length
|
||||||
|
|
||||||
|
Revision ID: e76f2067e689
|
||||||
|
Revises: 05e3a7023a1e
|
||||||
|
Create Date: 2024-05-21 21:58:22.133194
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = 'e76f2067e689'
|
||||||
|
down_revision = '05e3a7023a1e'
|
||||||
|
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('average_comment_length', sa.Integer(), nullable=True))
|
||||||
|
batch_op.add_column(sa.Column('comment_length_warning', sa.Integer(), nullable=True))
|
||||||
|
|
||||||
|
# ### 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_column('comment_length_warning')
|
||||||
|
batch_op.drop_column('average_comment_length')
|
||||||
|
|
||||||
|
# ### end Alembic commands ###
|
Loading…
Reference in a new issue