Compare commits

..

3 commits

10 changed files with 72 additions and 21 deletions

View file

@ -12,8 +12,9 @@
- Moderators (and admins) have now access to mod tools
+ Allowed operations: change display name, description, restriction status, and exile (guild-local ban) members
+ Site administrators and guild owners can add moderators
- Administrators can claim ownership of abandoned guilds
- Guilds can have restricted posting/commenting now. Unmoderated guilds always have.
- Administrators can claim ownership of abandoned guilds
- Admins can now suspend users from admin panel
- Implemented guild subscriptions (not as in $$$, yes as in the follow button)
- Minimum karma requirement for creating a guild is now configurable via env variable `FREAK_CREATE_GUILD_THRESHOLD` (previously hardcoded at 15)
- Users can now set their display name, biography and color theme in `/settings`

View file

@ -54,7 +54,7 @@ post_report_reasons = [
REPORT_REASON_STRINGS = { **{x.num_code: x.description for x in post_report_reasons}, **{x.code: x.description for x in post_report_reasons} }
REPORT_REASONS = {x.code: x.num_code for x in post_report_reasons}
REPORT_REASONS: dict[str, int] = {x.code: x.num_code for x in post_report_reasons}
REPORT_TARGET_POST = 1
REPORT_TARGET_COMMENT = 2
@ -66,21 +66,22 @@ REPORT_UPDATE_ON_HOLD = 3
USERNAME_RE = r'[a-z2-9_-][a-z0-9_-]+'
ILLEGAL_USERNAMES = (
ILLEGAL_USERNAMES = tuple((
## masspings and administrative claims
'me', 'everyone', 'here', 'room', 'all', 'any', 'founder', 'owner',
'admin', 'administrator', 'mod', 'modteam', 'moderator', 'sysop', 'server', 'app'
'me everyone here room all any server app dev devel develop nil none '
'founder owner admin administrator mod modteam moderator sysop some '
## fictitious users and automations
'nobody', 'deleted', 'suspended', 'default', 'bot', 'developer', 'undefined', 'null',
'ai', 'automod', 'automoderator', 'assistant', 'privacy', 'anonymous', 'removed'
'nobody deleted suspended default bot developer undefined null '
'ai automod automoderator assistant privacy anonymous removed assistance '
## law enforcement corps and slurs because yes
'pedo', 'rape', 'rapist', 'nigger', 'retard', 'ncmec', 'police', 'cops', '911', 'childsafety',
'report', 'dmca', 'login', 'logout', 'security', 'order66', 'gestapo', 'ss', 'hitler',
'pedophile', 'lolicon', 'giphy', 'tenor', 'csam', 'cp', 'pedobear', 'lolita',
'loli', 'kkk', 'pnf', 'adl', 'cop', 'tranny', 'google', 'trustandsafety', 'safety', 'ice',
'pedo rape rapist nigger retard ncmec police cops 911 childsafety '
'report dmca login logout security order66 gestapo ss hitler heilhitler kgb '
'pedophile lolicon giphy tenor csam cp pedobear lolita lolice thanos '
'loli kkk pnf adl cop tranny google trustandsafety safety ice fbi nsa it '
## VVVVIP
'potus', 'realdonaldtrump', 'elonmusk', 'teddysphotos', 'mrbeast', 'jkrowling', 'pewdiepie'
)
'potus realdonaldtrump elonmusk teddysphotos mrbeast jkrowling pewdiepie '
'elizabethii king queen pontifex hogwarts lumos alohomora isis daesh '
).split())
def username_is_legal(username: str) -> bool:
if len(username) < 2 or len(username) > 100:
@ -175,7 +176,13 @@ class User(Base):
@property
def is_disabled(self):
return (self.banned_at is not None and (self.banned_until is None or self.banned_until <= datetime.datetime.now())) or self.is_disabled_by_user
now = datetime.datetime.now()
return (
# suspended
(self.banned_at is not None and (self.banned_until is None or self.banned_until >= now)) or
# self-disabled
self.is_disabled_by_user
)
@property
def is_active(self):

View file

@ -45,7 +45,7 @@
usernameInputMessage.className = 'username-input-message error';
return;
}
if (!resp.is_legal) {
if (resp.is_valid === false) {
usernameInputMessage.innerHTML = "You can't use this username.";
usernameInputMessage.className = 'username-input-message error';
return;

View file

@ -310,6 +310,12 @@ button, [type="submit"], [type="reset"], [type="button"]
&[disabled]
opacity: .5
cursor: not-allowed
border: var(--border)
color: var(--border)
&.primary[disabled]
color: var(--background)
background-color: var(--border)
&:first-child
margin-inline-start: 0

View file

@ -2,7 +2,7 @@
{% from "macros/title.html" import title_tag with context %}
{% block title %}
<title>{{ title_tag('X _ X') }}</title>
{{ title_tag('X _ X') }}
{% endblock %}
{% block body %}

View file

@ -2,7 +2,7 @@
{% from "macros/title.html" import title_tag with context %}
{% block title %}
<title>{{ title_tag('O _ O') }}</title>
{{ title_tag('O _ O') }}
{% endblock %}
{% block body %}

View file

@ -2,7 +2,7 @@
{% from "macros/title.html" import title_tag with context %}
{% block title %}
<title>{{ title_tag('O _ O') }}</title>
{{ title_tag('O _ O') }}
{% endblock %}
{% block body %}

View file

@ -24,7 +24,23 @@
{% endif %}
<!-- quick actions -->
<h3>Quick Actions</h3>
<form method="POST">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
<select name="reason">
<option selected value="0">(Select a reason)</option>
<option value="100">Multiple violations</option>
{% for k, v in report_reasons.items() %}
<option value="{{ k }}">{{ v }}</option>
{% endfor %}
</select>
<br />
{% if u.banned_at %}
<button type="submit" name="do" value="unsuspend">Remove suspension</button>
{% else %}
<button type="submit" name="do" value="suspend">Suspend</button>
<button type="submit" name="do" value="to_3d">Time-out (3 days)</button>
{% endif %}
</form>
<h3>Strikes</h3>

View file

@ -47,7 +47,7 @@
<!-- no user -->
{% elif current_user.is_authenticated %}
<li class="nomobile">
<a class="round border-accent" href="{{ url_for('create.create', on=current_guild.name) if current_guild else '/create/' }}" title="Create a post" aria-label="Create a post">
<a class="round border-accent" href="{{ url_for('create.create', on=current_guild.name) if current_guild and current_guild.allows_posting(current_user) else '/create/' }}" title="Create a post" aria-label="Create a post">
{{ icon('add') }}
<span>New post</span>
</a>

View file

@ -10,10 +10,12 @@ from markupsafe import Markup
from sqlalchemy import insert, select, update
from suou import additem, not_implemented
from ..models import REPORT_REASON_STRINGS, REPORT_TARGET_COMMENT, REPORT_TARGET_POST, REPORT_UPDATE_COMPLETE, REPORT_UPDATE_ON_HOLD, REPORT_UPDATE_REJECTED, Comment, Post, PostReport, User, UserStrike, db
from ..models import REPORT_REASON_STRINGS, REPORT_REASONS, REPORT_TARGET_COMMENT, REPORT_TARGET_POST, REPORT_UPDATE_COMPLETE, REPORT_UPDATE_ON_HOLD, REPORT_UPDATE_REJECTED, Comment, Post, PostReport, User, UserStrike, db
bp = Blueprint('admin', __name__)
current_user: User
## TODO make admin interface
def admin_required(func: Callable):
@ -191,7 +193,26 @@ def user_detail(id: int):
if u is None:
abort(404)
if request.method == 'POST':
abort(501)
action = request.form['do']
if action == 'suspend':
u.banned_at = datetime.datetime.now()
u.banned_by_id = current_user.id
u.banned_reason = REPORT_REASONS.get(request.form.get('reason'), 0)
db.session.commit()
elif action == 'unsuspend':
u.banned_at = None
u.banned_by_id = None
u.banned_until = None
u.banned_reason = None
db.session.commit()
elif action == 'to_3d':
u.banned_at = datetime.datetime.now()
u.banned_until = datetime.datetime.now() + datetime.timedelta(days=3)
u.banned_by_id = current_user.id
u.banned_reason = REPORT_REASONS.get(request.form.get('reason'), 0)
db.session.commit()
else:
abort(400)
strikes = db.session.execute(select(UserStrike).where(UserStrike.user_id == id).order_by(UserStrike.id.desc())).scalars()
return render_template('admin/admin_user_detail.html', u=u,
report_reasons=REPORT_REASON_STRINGS, account_status_string=colorized_account_status_string, strikes=strikes)