This commit is contained in:
copyrighttxt
2026-07-06 13:28:00 -04:00
commit 05211f73ef
441 changed files with 94128 additions and 0 deletions
+54
View File
@@ -0,0 +1,54 @@
{% extends '__layout__.html' %}
{% block title %}Messages{% endblock %}
{% block head %}
<link href="/static/css/message.css" rel="stylesheet"/>
<style>
.text-secondary {
color: rgb(199, 199, 199) !important;
}
.message {
color: rgb(60,60,60) !important;
}
</style>
{% endblock %}
{% block content %}
<div id="main">
<div class="container" style="max-width: 800px;">
<h1 class="m-0">{%if isOutgoing:%}Outgoing {%endif%}Messages</h1>
{% if not isOutgoing: %}
<a class="m-0 mb-2 text-decoration-none" href="/messages/outgoing" style="font-size: 11px;">Click here to see your outgoing messages</a>
{%else%}
<a class="m-0 mb-2 text-decoration-none" href="/messages" style="font-size: 11px;">Click here to see your incoming messages</a>
{%endif%}
<p class="m-0" style="font-size: 12px;">Do not send any personal or sensitive information about yourself! A staff member will never ask for your password or email.</p>
<div class="linebreak"></div>
<div>
{% for message in UserMessages %}
<a href="/messages/view/{{message.id}}" class="text-decoration-none">
<div class="w-100 border p-2 message mb-2 rounded {% if message.read == False %}border-primary{%endif%}">
<div class="d-flex">
<img class="overflow-hidden me-2 " width="48" height="48" src="/Thumbs/Avatar.ashx?x=48&y=48&userId={%if not isOutgoing %}{{message.sender.id}}{%else%}{{message.recipient.id}}{%endif%}" alt="{%if not isOutgoing %}{{message.sender.username}}{%else%}{{message.recipient.username}}{%endif%}"/>
<div class="overflow-hidden w-100">
<div class="d-flex align-items-center w-100">
<b class="text-white">{%if not isOutgoing %}{{message.sender.username}}{%else%}{{message.recipient.username}}{%endif%}</b>
<p class="m-0 text-secondary ms-auto" style="font-size:small;">{{message.created.strftime("%b %d, %Y %I:%M %p")}} UTC</p>
</div>
<p class="text-white m-0 text-truncate">{{message.subject}} - <span class="text-secondary fw-normal">{{message.content}}</span></p>
</div>
</div>
</div>
</a>
{%endfor%}
{% if len(UserMessages.items) == 0: %}
<h5 class="mt-2 mb-2 text-center">No results found</h5>
{%endif%}
</div>
<div class="linebreak"></div>
<div class="d-flex">
<a class="ms-auto m-0 text-decoration-none {% if not UserMessages.has_prev %}text-secondary{%endif%}" {% if UserMessages.has_prev %}href="/messages/{%if isOutgoing:%}outgoing{%endif%}?page={{UserMessages.prev_num}}"{%endif%}>Previous</a>
<p class="ms-2 me-2 text-white m-0">Page {{UserMessages.page}} of {{UserMessages.pages}}</p>
<a class="me-auto m-0 text-decoration-none {% if not UserMessages.has_next %}text-secondary{%endif%}" {% if UserMessages.has_next %}href="/messages/{%if isOutgoing:%}outgoing{%endif%}?page={{UserMessages.next_num}}"{%endif%}>Next</a>
</div>
</div>
</div>
{% endblock %}
+148
View File
@@ -0,0 +1,148 @@
from flask import Blueprint, render_template, request, redirect, url_for, flash, make_response, jsonify, abort
from app.models.messages import Message
from app.util import auth, turnstile, websiteFeatures
from app.extensions import db, csrf, limiter, redis_controller
from app.models.user import User
from app.util.textfilter import FilterText
from sqlalchemy import or_
MessageRoute = Blueprint('message', __name__, template_folder="pages")
def CreateSystemMessage( subject : str, message : str, userid : int ):
"""
Creates a system message from the system user to the specified user
"""
TargetUser = User.query.filter_by(id=userid).first()
if TargetUser is None:
return False
NewMessage : Message = Message(
subject=subject,
content=message,
sender_id=1,
recipient_id=userid
)
db.session.add(NewMessage)
db.session.commit()
return True
@MessageRoute.route("/my/messages", methods=["GET"])
@auth.authenticated_required
def my_messages():
return redirect("/messages")
@MessageRoute.route("/new/<userid>", methods=["GET"])
@auth.authenticated_required
def new_message(userid):
TargetUser = User.query.filter_by(id=userid).first()
if TargetUser is None:
return "User not found",404
if TargetUser.accountstatus == 4 or TargetUser.accountstatus == 3:
abort(404)
AuthenticatedUser = auth.GetCurrentUser()
if AuthenticatedUser.id == TargetUser.id:
return "You cannot message yourself",400
MessageReplyContext = request.args.get("reply", default=None, type=int)
if MessageReplyContext is not None:
ReplyMessage = Message.query.filter_by( id = MessageReplyContext ).filter( or_( Message.sender_id == AuthenticatedUser.id, Message.recipient_id == AuthenticatedUser.id ) ).first()
else:
ReplyMessage = None
return render_template("messages/new.html", targetuser=TargetUser, replymessage=ReplyMessage)
@MessageRoute.route("/new/<userid>", methods=["POST"])
@auth.authenticated_required
@limiter.limit("10/minute")
def send_message(userid):
if 'cf-turnstile-response' not in request.form or request.form.get('cf-turnstile-response') == '':
flash("Please complete the captcha", "error")
return redirect(f"/messages/new/{userid}")
if 'message' not in request.form or request.form.get('message') == '':
flash("Please enter a message", "error")
return redirect(f"/messages/new/{userid}")
if 'subject' not in request.form or request.form.get('subject') == '':
flash("Please enter a subject", "error")
return redirect(f"/messages/new/{userid}")
if not websiteFeatures.GetWebsiteFeature("MessageSending"):
flash("Message sending is temporarily disabled", "error")
return redirect(f"/messages/new/{userid}")
TargetUser : User | None = User.query.filter_by(id=userid).first()
if TargetUser is None:
abort(404)
if TargetUser.accountstatus == 4 or TargetUser.accountstatus == 3:
abort(404)
AuthenticatedUser = auth.GetCurrentUser()
if AuthenticatedUser.id == TargetUser.id:
abort(400)
if len(request.form.get("message")) > 1024:
flash("Your message is too long", "error")
return redirect(f"/messages/new/{TargetUser.id}")
if len(request.form.get('subject')) > 64:
flash("Your subject is too long", "error")
return redirect(f"/messages/new/{TargetUser.id}")
if not turnstile.VerifyToken(request.form.get('cf-turnstile-response')):
flash("Invalid captcha", "error")
return redirect(f"/messages/new/{TargetUser.id}")
if redis_controller.get(f"message:{TargetUser.id}:{AuthenticatedUser.id}") is not None:
flash("You are sending messages too quickly", "error")
return redirect(f"/messages/new/{TargetUser.id}")
NewLineCount = request.form.get('message').count("\n")
if NewLineCount > 10:
flash("Your message has too many newlines", "error")
return redirect(f"/messages/new/{TargetUser.id}")
redis_controller.set(f"message:{TargetUser.id}:{AuthenticatedUser.id}", "1", ex=60)
FilteredSubject = FilterText(request.form.get('subject'))
FilteredMessage = FilterText(request.form.get('message'))
NewMessage = Message(
sender_id=AuthenticatedUser.id,
recipient_id=TargetUser.id,
subject=FilteredSubject,
content=FilteredMessage
)
db.session.add(NewMessage)
db.session.commit()
return redirect(f"/messages/view/{NewMessage.id}")
@MessageRoute.route("/view/<messageid>", methods=["GET"])
@auth.authenticated_required
def view_message(messageid):
MessageObj : Message = Message.query.filter_by(id=messageid).first()
if MessageObj is None:
abort(404)
AuthenicatatedUser = auth.GetCurrentUser()
if AuthenicatatedUser.id != MessageObj.sender_id and AuthenicatatedUser.id != MessageObj.recipient_id:
abort(404)
if AuthenicatatedUser.id == MessageObj.recipient_id:
MessageObj.read = True
db.session.commit()
SenderObj = User.query.filter_by(id=MessageObj.sender_id).first()
RecieverObj = User.query.filter_by(id=MessageObj.recipient_id).first()
MessageCreated = MessageObj.created.strftime("%b %d, %Y %I:%M %p")
messagelines = MessageObj.content.split("\n")
return render_template("messages/view.html", message=MessageObj, created=MessageCreated,sender=SenderObj, reciever=RecieverObj, messagelines=messagelines)
@MessageRoute.route("/", methods=['GET'])
@auth.authenticated_required
def messages():
AuthenticatedUser : User = auth.GetCurrentUser()
PageNumber = max( request.args.get('page', default=1, type=int), 1 )
UserMessagesList = Message.query.filter_by(recipient_id = AuthenticatedUser.id).order_by(Message.created.desc()).paginate(page = PageNumber, per_page = 10, error_out = False)
return render_template("messages/index.html", UserMessages=UserMessagesList, isOutgoing=False)
@MessageRoute.route("/outgoing", methods=['GET'])
@auth.authenticated_required
def outgoing_messages():
AuthenticatedUser : User = auth.GetCurrentUser()
PageNumber = max( request.args.get('page', default=1, type=int), 1 )
UserMessagesList = Message.query.filter_by(sender_id = AuthenticatedUser.id).order_by(Message.created.desc()).paginate(page = PageNumber, per_page = 10, error_out = False)
return render_template("messages/index.html", UserMessages=UserMessagesList, isOutgoing=True)
+43
View File
@@ -0,0 +1,43 @@
{% extends '__layout__.html' %}
{% block title %}New Message{% endblock %}
{% block head %}
<link href="/static/css/message.css" rel="stylesheet"/>
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
{% endblock %}
{% block content %}
<div id="main">
<div class="container" style="max-width: 800px;">
{% with messages = get_flashed_messages() %}
{% if messages %}
<div>
{% for message in messages %}
<div class="alert border border-danger p-2 text-center messagealerts">
{{ message }}
</div>
{% endfor %}
</div>
{% endif %}
{% endwith %}
<h4>New Message</h4>
<div class="input-group mb-2">
<span class="input-group-text">Recipient</span>
<input class="form-control" type="text" value="{{targetuser.username}}" readonly>
</div>
<form method="post" class="w-100">
<div class="input-group mb-2">
<span class="input-group-text">Subject</span>
<input class="form-control" type="text" name="subject" {% if not replymessage %}required{%endif%}{% if replymessage %} disabled value='RE: {{replymessage.subject}}'{%endif%}>
</div>
<textarea class="form-control rounded" placeholder="Message ( 1024 Chars limit )" name="message" style="min-height:200px" required></textarea>
<p class="m-0" style="font-size: 12px;">Do not send any personal or sensitive information about yourself! A staff member will never ask for your password or email.</p>
<div class="d-flex justify-content-end align-items-top mt-2">
<div class="cf-turnstile" data-theme="dark" data-sitekey="{{turnstilekey}}"></div>
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
<div class="ms-3">
<button type="submit" class="btn btn-primary">Send</button>
</div>
</div>
</form>
</div>
</div>
{% endblock %}
+42
View File
@@ -0,0 +1,42 @@
{% extends '__layout__.html' %}
{% block title %}{{message.subject}}{% endblock %}
{% block head %}
<link href="/static/css/message.css" rel="stylesheet"/>
<style>
.text-secondary {
color: rgb(199, 199, 199) !important;
}
</style>
{% endblock %}
{% block content %}
<div id="main">
<div class="container" style="max-width: 800px;">
<h3>{{message.subject}}</h3>
<p class="text-secondary m-0" style="font-size:12px;">To: <a href="/users/{{reciever.id}}/profile" class="text-white text-decoration-none">{{reciever.username}}</a></p>
<div class="linebreak"></div>
<a href="/users/{{sender.id}}/profile" class="text-decoration-none text-white">
<div class="d-flex">
<img class="overflow-hidden me-2 " width="60" height="60" src="/Thumbs/Avatar.ashx?x=60&y=60&userId={{sender.id}}" alt="{{sender.username}}"/>
<div>
<b>{{sender.username}}</b>
<p class="m-0 text-secondary">{{created}} UTC</p>
</div>
</div>
</a>
{% for line in messagelines: %}
{% if line.strip() != '' %}
<p class="text-secondary m-0">{{line}}</p>
{%else%}
<br>
{%endif%}
{% endfor %}
<div class="linebreak"></div>
<div class="d-flex">
<a class="btn btn-border border-primary p-1 ps-2 pe-2 btn-sm mb-2 me-2" href="/messages">Back to messages</a>
{% if sender.id != currentuser.id %}
<a class="btn btn-border border-success p-1 ps-2 pe-2 btn-sm mb-2" href="/messages/new/{{sender.id}}?reply={{message.id}}">Reply</a>
{% endif %}
</div>
</div>
</div>
{% endblock %}