mirror of
https://codeberg.org/rimu/pyfedi
synced 2025-01-23 19:36:56 -08:00
send private messages
This commit is contained in:
parent
e4e02dcc6c
commit
a77de0c883
15 changed files with 431 additions and 43 deletions
|
@ -77,6 +77,9 @@ def create_app(config_class=Config):
|
||||||
from app.topic import bp as topic_bp
|
from app.topic import bp as topic_bp
|
||||||
app.register_blueprint(topic_bp)
|
app.register_blueprint(topic_bp)
|
||||||
|
|
||||||
|
from app.chat import bp as chat_bp
|
||||||
|
app.register_blueprint(chat_bp)
|
||||||
|
|
||||||
def get_resource_as_string(name, charset='utf-8'):
|
def get_resource_as_string(name, charset='utf-8'):
|
||||||
with app.open_resource(name) as f:
|
with app.open_resource(name) as f:
|
||||||
return f.read().decode(charset)
|
return f.read().decode(charset)
|
||||||
|
|
|
@ -11,7 +11,8 @@ from app.post.routes import continue_discussion, show_post
|
||||||
from app.user.routes import show_profile
|
from app.user.routes import show_profile
|
||||||
from app.constants import POST_TYPE_LINK, POST_TYPE_IMAGE, SUBSCRIPTION_MEMBER
|
from app.constants import POST_TYPE_LINK, POST_TYPE_IMAGE, SUBSCRIPTION_MEMBER
|
||||||
from app.models import User, Community, CommunityJoinRequest, CommunityMember, CommunityBan, ActivityPubLog, Post, \
|
from app.models import User, Community, CommunityJoinRequest, CommunityMember, CommunityBan, ActivityPubLog, Post, \
|
||||||
PostReply, Instance, PostVote, PostReplyVote, File, AllowedInstances, BannedInstances, utcnow, Site, Notification
|
PostReply, Instance, PostVote, PostReplyVote, File, AllowedInstances, BannedInstances, utcnow, Site, Notification, \
|
||||||
|
ChatMessage
|
||||||
from app.activitypub.util import public_key, users_total, active_half_year, active_month, local_posts, local_comments, \
|
from app.activitypub.util import public_key, users_total, active_half_year, active_month, local_posts, local_comments, \
|
||||||
post_to_activity, find_actor_or_create, default_context, instance_blocked, find_reply_parent, find_liked_object, \
|
post_to_activity, find_actor_or_create, default_context, instance_blocked, find_reply_parent, find_liked_object, \
|
||||||
lemmy_site_data, instance_weight, is_activitypub_request, downvote_post_reply, downvote_post, upvote_post_reply, \
|
lemmy_site_data, instance_weight, is_activitypub_request, downvote_post_reply, downvote_post, upvote_post_reply, \
|
||||||
|
@ -20,7 +21,7 @@ from app.activitypub.util import public_key, users_total, active_half_year, acti
|
||||||
update_post_from_activity, undo_vote, undo_downvote
|
update_post_from_activity, undo_vote, undo_downvote
|
||||||
from app.utils import gibberish, get_setting, is_image_url, allowlist_html, html_to_markdown, render_template, \
|
from app.utils import gibberish, get_setting, is_image_url, allowlist_html, html_to_markdown, render_template, \
|
||||||
domain_from_url, markdown_to_html, community_membership, ap_datetime, markdown_to_text, ip_address, can_downvote, \
|
domain_from_url, markdown_to_html, community_membership, ap_datetime, markdown_to_text, ip_address, can_downvote, \
|
||||||
can_upvote, can_create, awaken_dormant_instance
|
can_upvote, can_create, awaken_dormant_instance, shorten_string
|
||||||
import werkzeug.exceptions
|
import werkzeug.exceptions
|
||||||
|
|
||||||
|
|
||||||
|
@ -373,6 +374,33 @@ def process_inbox_request(request_json, activitypublog_id, ip_address):
|
||||||
if request_json['type'] == 'Create':
|
if request_json['type'] == 'Create':
|
||||||
activity_log.activity_type = 'Create'
|
activity_log.activity_type = 'Create'
|
||||||
user_ap_id = request_json['object']['attributedTo']
|
user_ap_id = request_json['object']['attributedTo']
|
||||||
|
if request_json['object']['type'] == 'ChatMessage':
|
||||||
|
activity_log.activity_type = 'Create ChatMessage'
|
||||||
|
sender = find_actor_or_create(user_ap_id)
|
||||||
|
recipient_ap_id = request_json['object']['to'][0]
|
||||||
|
recipient = find_actor_or_create(recipient_ap_id)
|
||||||
|
if sender and recipient and recipient.is_local():
|
||||||
|
if recipient.has_blocked_user(sender.id) or recipient.has_blocked_instance(sender.instance_id):
|
||||||
|
activity_log.exception_message = "Sender blocked by recipient"
|
||||||
|
else:
|
||||||
|
# Save ChatMessage to DB
|
||||||
|
encrypted = request_json['object']['encrypted'] if 'encrypted' in request_json['object'] else None
|
||||||
|
new_message = ChatMessage(sender_id=sender.id, recipient_id=recipient.id,
|
||||||
|
body=request_json['object']['source']['content'],
|
||||||
|
body_html=allowlist_html(markdown_to_html(request_json['object']['source']['content'])),
|
||||||
|
encrypted=encrypted)
|
||||||
|
db.session.add(new_message)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
# Notify recipient
|
||||||
|
notify = Notification(title=shorten_string('New message from ' + sender.display_name()),
|
||||||
|
url=f'/chat/{new_message.id}', user_id=recipient.id,
|
||||||
|
author_id=sender.id)
|
||||||
|
db.session.add(notify)
|
||||||
|
recipient.unread_notifications += 1
|
||||||
|
db.session.commit()
|
||||||
|
activity_log.result = 'success'
|
||||||
|
else:
|
||||||
try:
|
try:
|
||||||
community_ap_id = request_json['to'][0]
|
community_ap_id = request_json['to'][0]
|
||||||
if community_ap_id == 'https://www.w3.org/ns/activitystreams#Public': # kbin does this when posting a reply
|
if community_ap_id == 'https://www.w3.org/ns/activitystreams#Public': # kbin does this when posting a reply
|
||||||
|
|
5
app/chat/__init__.py
Normal file
5
app/chat/__init__.py
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
from flask import Blueprint
|
||||||
|
|
||||||
|
bp = Blueprint('chat', __name__)
|
||||||
|
|
||||||
|
from app.chat import routes
|
14
app/chat/forms.py
Normal file
14
app/chat/forms.py
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
from flask import request, g
|
||||||
|
from flask_login import current_user
|
||||||
|
from flask_wtf import FlaskForm
|
||||||
|
from wtforms import StringField, SubmitField, TextAreaField, BooleanField, HiddenField, SelectField, FileField
|
||||||
|
from wtforms.validators import ValidationError, DataRequired, Email, EqualTo, Length, Optional
|
||||||
|
from flask_babel import _, lazy_gettext as _l
|
||||||
|
|
||||||
|
from app import db
|
||||||
|
|
||||||
|
|
||||||
|
class AddReply(FlaskForm):
|
||||||
|
message = TextAreaField(_l('Message'), validators=[DataRequired(), Length(min=1, max=5000)], render_kw={'placeholder': 'Type a reply here...'})
|
||||||
|
recipient_id = HiddenField()
|
||||||
|
submit = SubmitField(_l('Reply'))
|
103
app/chat/routes.py
Normal file
103
app/chat/routes.py
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
|
from flask import request, flash, json, url_for, current_app, redirect, g
|
||||||
|
from flask_login import login_required, current_user
|
||||||
|
from flask_babel import _
|
||||||
|
from sqlalchemy import desc, or_, and_, text
|
||||||
|
|
||||||
|
from app import db, celery
|
||||||
|
from app.activitypub.signature import post_request
|
||||||
|
from app.chat.forms import AddReply
|
||||||
|
from app.models import AllowedInstances, BannedInstances, ActivityPubLog, utcnow, Site, Community, CommunityMember, \
|
||||||
|
User, Instance, File, Report, Topic, UserRegistration, ChatMessage, Notification
|
||||||
|
from app.utils import render_template, permission_required, set_setting, get_setting, gibberish, markdown_to_html, \
|
||||||
|
moderating_communities, joined_communities, finalize_user_setup, theme_list, allowlist_html, shorten_string
|
||||||
|
from app.chat import bp
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('/chat', methods=['GET', 'POST'])
|
||||||
|
@bp.route('/chat/<int:sender_id>', methods=['GET', 'POST'])
|
||||||
|
@login_required
|
||||||
|
def chat_home(sender_id=None):
|
||||||
|
form = AddReply()
|
||||||
|
if form.validate_on_submit():
|
||||||
|
recipient = User.query.get(form.recipient_id.data)
|
||||||
|
reply = ChatMessage(sender_id=current_user.id, recipient_id=recipient.id,
|
||||||
|
body=form.message.data, body_html=allowlist_html(markdown_to_html(form.message.data)))
|
||||||
|
if recipient.is_local():
|
||||||
|
# Notify local recipient
|
||||||
|
notify = Notification(title=shorten_string('New message from ' + current_user.display_name()), url='/chat/' + str(current_user.id),
|
||||||
|
user_id=recipient.id,
|
||||||
|
author_id=current_user.id)
|
||||||
|
db.session.add(notify)
|
||||||
|
recipient.unread_notifications += 1
|
||||||
|
db.session.add(reply)
|
||||||
|
db.session.commit()
|
||||||
|
else:
|
||||||
|
db.session.add(reply)
|
||||||
|
db.session.commit()
|
||||||
|
# Federate reply
|
||||||
|
reply_json = {
|
||||||
|
"actor": current_user.profile_id(),
|
||||||
|
"id": f"https://{current_app.config['SERVER_NAME']}/activities/create/{gibberish(15)}",
|
||||||
|
"object": {
|
||||||
|
"attributedTo": current_user.profile_id(),
|
||||||
|
"content": reply.body_html,
|
||||||
|
"id": f"https://{current_app.config['SERVER_NAME']}/private_message/{reply.id}",
|
||||||
|
"mediaType": "text/html",
|
||||||
|
"published": utcnow().isoformat() + 'Z', # Lemmy is inconsistent with the date format they use
|
||||||
|
"source": {
|
||||||
|
"content": reply.body,
|
||||||
|
"mediaType": "text/markdown"
|
||||||
|
},
|
||||||
|
"to": [
|
||||||
|
recipient.profile_id()
|
||||||
|
],
|
||||||
|
"type": "ChatMessage"
|
||||||
|
},
|
||||||
|
"to": [
|
||||||
|
recipient.profile_id()
|
||||||
|
],
|
||||||
|
"type": "Create"
|
||||||
|
}
|
||||||
|
success = post_request(recipient.ap_inbox_url, reply_json, current_user.private_key,
|
||||||
|
current_user.profile_id() + '#main-key')
|
||||||
|
if not success:
|
||||||
|
flash(_('Message failed to send to remote server. Try again later.'), 'error')
|
||||||
|
|
||||||
|
return redirect(url_for('chat.chat_home', sender_id=recipient.id, _anchor=f'message_{reply.id}'))
|
||||||
|
else:
|
||||||
|
senders = User.query.filter(User.banned == False).join(ChatMessage, ChatMessage.sender_id == User.id)
|
||||||
|
senders = senders.filter(ChatMessage.recipient_id == current_user.id).order_by(desc(ChatMessage.created_at)).limit(500).all()
|
||||||
|
|
||||||
|
if senders:
|
||||||
|
messages_with = senders[0].id if sender_id is None else sender_id
|
||||||
|
sender_id = messages_with
|
||||||
|
messages = ChatMessage.query.filter(or_(
|
||||||
|
and_(ChatMessage.recipient_id == current_user.id, ChatMessage.sender_id == messages_with),
|
||||||
|
and_(ChatMessage.recipient_id == messages_with, ChatMessage.sender_id == current_user.id))
|
||||||
|
)
|
||||||
|
messages = messages.order_by(ChatMessage.created_at).all()
|
||||||
|
if messages:
|
||||||
|
if messages[0].sender_id == current_user.id:
|
||||||
|
other_party = User.query.get(messages[0].recipient_id)
|
||||||
|
else:
|
||||||
|
other_party = User.query.get(messages[0].sender_id)
|
||||||
|
else:
|
||||||
|
other_party = None
|
||||||
|
form.recipient_id.data = messages_with
|
||||||
|
else:
|
||||||
|
messages = []
|
||||||
|
other_party = None
|
||||||
|
if sender_id and int(sender_id):
|
||||||
|
sql = f"UPDATE notification SET read = true WHERE url = '/chat/{sender_id}' AND user_id = {current_user.id}"
|
||||||
|
db.session.execute(text(sql))
|
||||||
|
db.session.commit()
|
||||||
|
current_user.unread_notifications = Notification.query.filter_by(user_id=current_user.id, read=False).count()
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
return render_template('chat/home.html', title=_('Chat'), senders=senders, messages=messages, other_party=other_party,
|
||||||
|
form=form,
|
||||||
|
moderating_communities=moderating_communities(current_user.get_id()),
|
||||||
|
joined_communities=joined_communities(current_user.get_id()),
|
||||||
|
site=g.site)
|
|
@ -959,6 +959,20 @@ class PostReplyVote(db.Model):
|
||||||
created_at = db.Column(db.DateTime, default=utcnow)
|
created_at = db.Column(db.DateTime, default=utcnow)
|
||||||
|
|
||||||
|
|
||||||
|
class ChatMessage(db.Model):
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
sender_id = db.Column(db.Integer, db.ForeignKey('user.id'), index=True)
|
||||||
|
recipient_id = db.Column(db.Integer, db.ForeignKey('user.id'), index=True)
|
||||||
|
body = db.Column(db.Text)
|
||||||
|
body_html = db.Column(db.Text)
|
||||||
|
reported = db.Column(db.Boolean, default=False)
|
||||||
|
read = db.Column(db.Boolean, default=False)
|
||||||
|
encrypted = db.Column(db.String(15))
|
||||||
|
created_at = db.Column(db.DateTime, default=utcnow)
|
||||||
|
|
||||||
|
sender = db.relationship('User', foreign_keys=[sender_id])
|
||||||
|
|
||||||
|
|
||||||
# save every activity to a log, to aid debugging
|
# save every activity to a log, to aid debugging
|
||||||
class ActivityPubLog(db.Model):
|
class ActivityPubLog(db.Model):
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
@ -1064,6 +1078,7 @@ class Site(db.Model):
|
||||||
allow_or_block_list = db.Column(db.Integer, default=2) # 1 = allow list, 2 = block list
|
allow_or_block_list = db.Column(db.Integer, default=2) # 1 = allow list, 2 = block list
|
||||||
allowlist = db.Column(db.Text, default='')
|
allowlist = db.Column(db.Text, default='')
|
||||||
blocklist = db.Column(db.Text, default='')
|
blocklist = db.Column(db.Text, default='')
|
||||||
|
auto_decline_referrers = db.Column(db.Text, default='rdrama.net')
|
||||||
created_at = db.Column(db.DateTime, default=utcnow)
|
created_at = db.Column(db.DateTime, default=utcnow)
|
||||||
updated = db.Column(db.DateTime, default=utcnow)
|
updated = db.Column(db.DateTime, default=utcnow)
|
||||||
last_active = db.Column(db.DateTime, default=utcnow)
|
last_active = db.Column(db.DateTime, default=utcnow)
|
||||||
|
|
|
@ -759,7 +759,7 @@ def post_report(post_id: int):
|
||||||
# todo: only notify admins for certain types of report
|
# todo: only notify admins for certain types of report
|
||||||
for admin in Site.admins():
|
for admin in Site.admins():
|
||||||
if admin.id not in already_notified:
|
if admin.id not in already_notified:
|
||||||
notify = Notification(title='Suspicious content', url=post.ap_id, user_id=admin.id, author_id=current_user.id)
|
notify = Notification(title='Suspicious content', url='/admin/reports', user_id=admin.id, author_id=current_user.id)
|
||||||
db.session.add(notify)
|
db.session.add(notify)
|
||||||
admin.unread_notifications += 1
|
admin.unread_notifications += 1
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
@ -861,7 +861,7 @@ def post_reply_report(post_id: int, comment_id: int):
|
||||||
# todo: only notify admins for certain types of report
|
# todo: only notify admins for certain types of report
|
||||||
for admin in Site.admins():
|
for admin in Site.admins():
|
||||||
if admin.id not in already_notified:
|
if admin.id not in already_notified:
|
||||||
notify = Notification(title='Suspicious content', url=post.ap_id, user_id=admin.id, author_id=current_user.id)
|
notify = Notification(title='Suspicious content', url='/admin/reports', user_id=admin.id, author_id=current_user.id)
|
||||||
db.session.add(notify)
|
db.session.add(notify)
|
||||||
admin.unread_notifications += 1
|
admin.unread_notifications += 1
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
|
@ -10,6 +10,7 @@ document.addEventListener("DOMContentLoaded", function () {
|
||||||
setupLightDark();
|
setupLightDark();
|
||||||
setupKeyboardShortcuts();
|
setupKeyboardShortcuts();
|
||||||
setupTopicChooser();
|
setupTopicChooser();
|
||||||
|
setupConversationChooser();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
@ -508,6 +509,16 @@ function setupTopicChooser() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setupConversationChooser() {
|
||||||
|
const changeSender = document.getElementById('changeSender');
|
||||||
|
if(changeSender) {
|
||||||
|
changeSender.addEventListener('change', function() {
|
||||||
|
const user_id = changeSender.options[changeSender.selectedIndex].value;
|
||||||
|
location.href = '/chat/' + user_id;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function formatTime(seconds) {
|
function formatTime(seconds) {
|
||||||
const hours = Math.floor(seconds / 3600);
|
const hours = Math.floor(seconds / 3600);
|
||||||
const minutes = Math.floor((seconds % 3600) / 60);
|
const minutes = Math.floor((seconds % 3600) / 60);
|
||||||
|
|
|
@ -1205,4 +1205,28 @@ fieldset legend {
|
||||||
line-height: 32px;
|
line-height: 32px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.conversation .message {
|
||||||
|
width: 90%;
|
||||||
|
max-width: 100%;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
clear: both;
|
||||||
|
}
|
||||||
|
@media (min-width: 992px) {
|
||||||
|
.conversation .message {
|
||||||
|
width: 70%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.conversation .message.from_other_party {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
.conversation .message.from_me {
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
.conversation .message_created_at {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
.conversation form .form-control-label {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
/*# sourceMappingURL=structure.css.map */
|
/*# sourceMappingURL=structure.css.map */
|
||||||
|
|
|
@ -895,3 +895,27 @@ fieldset {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.conversation {
|
||||||
|
.message {
|
||||||
|
width: 90%;
|
||||||
|
@include breakpoint(tablet) {
|
||||||
|
width: 70%;
|
||||||
|
}
|
||||||
|
max-width: 100%;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
clear: both;
|
||||||
|
|
||||||
|
&.from_other_party {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
&.from_me {
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.message_created_at {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
form .form-control-label {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
|
@ -693,6 +693,24 @@ div.navbar {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sender_list {
|
||||||
|
border-right: solid 1px #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message {
|
||||||
|
border: solid 1px #ddd;
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 8px 15px 0 15px;
|
||||||
|
}
|
||||||
|
.message.from_other_party {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
.message.from_me {
|
||||||
|
color: var(--bs-card-cap-color);
|
||||||
|
background-color: var(--bs-card-cap-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* high contrast */
|
||||||
@media (prefers-contrast: more) {
|
@media (prefers-contrast: more) {
|
||||||
:root {
|
:root {
|
||||||
--bs-link-color: black;
|
--bs-link-color: black;
|
||||||
|
|
|
@ -319,6 +319,26 @@ div.navbar {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sender_list {
|
||||||
|
border-right: solid 1px #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message {
|
||||||
|
border: solid 1px #ddd;
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 8px 15px 0 15px;
|
||||||
|
|
||||||
|
&.from_other_party {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
&.from_me {
|
||||||
|
color: var(--bs-card-cap-color);
|
||||||
|
background-color: var(--bs-card-cap-bg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* high contrast */
|
||||||
@media (prefers-contrast: more) {
|
@media (prefers-contrast: more) {
|
||||||
:root {
|
:root {
|
||||||
--bs-link-color: black;
|
--bs-link-color: black;
|
||||||
|
|
68
app/templates/chat/home.html
Normal file
68
app/templates/chat/home.html
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
{% 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 %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col main_pane">
|
||||||
|
<nav aria-label="breadcrumb" id="breadcrumb_nav" title="Navigation">
|
||||||
|
<ol class="breadcrumb">
|
||||||
|
<li class="breadcrumb-item"><a href="/">{{ _('Home') }}</a></li>
|
||||||
|
<li class="breadcrumb-item active">{{ _('Chat') }}</li>
|
||||||
|
</ol>
|
||||||
|
</nav>
|
||||||
|
<h1 class="mt-2">{{ _('Chat') }}</h1>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col col-md-2 d-none d-md-block sender_list">
|
||||||
|
<h3>{{ _('People') }}</h3>
|
||||||
|
<ul class="list-group list-group-flush">
|
||||||
|
{% for sender in senders %}
|
||||||
|
<li class="list-group-item">
|
||||||
|
{% if other_party %}
|
||||||
|
{% if other_party.id != sender.id %}
|
||||||
|
<a href="{{ url_for('chat.chat_home', sender_id=sender.id) }}">{% if sender.avatar_image() %}<img src="{{ sender.avatar_image() }}" class="community_icon rounded-circle" loading="lazy" alt="" />{% endif %}
|
||||||
|
{{ sender.link() }}
|
||||||
|
</a>
|
||||||
|
{% else %}
|
||||||
|
{% if sender.avatar_image() %}<img src="{{ sender.avatar_image() }}" class="community_icon rounded-circle" loading="lazy" alt="" />{% endif %}
|
||||||
|
{{ sender.link() }}
|
||||||
|
{% endif %}
|
||||||
|
{% else %}
|
||||||
|
<a href="{{ url_for('chat.chat_home', sender_id=sender.id) }}">{% if sender.avatar_image() %}<img src="{{ sender.avatar_image() }}" class="community_icon rounded-circle" loading="lazy" alt="" />{% endif %}
|
||||||
|
{{ sender.link() }}
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="col col-md-10">
|
||||||
|
{% if other_party %}
|
||||||
|
<h3 class="d-none d-md-inline">{{ _('Messages with %(name)s', name=other_party.display_name()) }}</h3>
|
||||||
|
<h3 class="d-md-none">{{ _('Messages with: ') }}
|
||||||
|
<select id="changeSender">{% for sender in senders %}
|
||||||
|
<option value="{{ sender.id }}" {{ 'selected' if sender.id == other_party.id }}>{{ sender.link() }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select></h3>
|
||||||
|
<div class="conversation">
|
||||||
|
{% for message in messages %}
|
||||||
|
<div id="message_{{ message.id }}" class="card message {{ 'from_other_party' if message.sender_id != current_user.id else 'from_me' }}">
|
||||||
|
<div class="message_body">
|
||||||
|
<span class="message_created_at text-muted small">{{ moment(message.created_at).fromNow(refresh=True) }}</span>
|
||||||
|
<span class="message_sender">{{ message.sender.display_name() }}</span>: {{ message.body_html|safe }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
{{ render_form(form) }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
|
@ -325,7 +325,7 @@ def report_profile(actor):
|
||||||
already_notified = set()
|
already_notified = set()
|
||||||
for admin in Site.admins():
|
for admin in Site.admins():
|
||||||
if admin.id not in already_notified:
|
if admin.id not in already_notified:
|
||||||
notify = Notification(title='Reported user', url=user.ap_id, user_id=admin.id, author_id=current_user.id)
|
notify = Notification(title='Reported user', url='/admin/reports', user_id=admin.id, author_id=current_user.id)
|
||||||
db.session.add(notify)
|
db.session.add(notify)
|
||||||
admin.unread_notifications += 1
|
admin.unread_notifications += 1
|
||||||
user.reports += 1
|
user.reports += 1
|
||||||
|
|
55
migrations/versions/fe1e3fbf5b9d_chat.py
Normal file
55
migrations/versions/fe1e3fbf5b9d_chat.py
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
"""chat
|
||||||
|
|
||||||
|
Revision ID: fe1e3fbf5b9d
|
||||||
|
Revises: 75f5b458c2f9
|
||||||
|
Create Date: 2024-02-17 09:53:47.915062
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = 'fe1e3fbf5b9d'
|
||||||
|
down_revision = '75f5b458c2f9'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.create_table('chat_message',
|
||||||
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('sender_id', sa.Integer(), nullable=True),
|
||||||
|
sa.Column('recipient_id', sa.Integer(), nullable=True),
|
||||||
|
sa.Column('body', sa.Text(), nullable=True),
|
||||||
|
sa.Column('body_html', sa.Text(), nullable=True),
|
||||||
|
sa.Column('reported', sa.Boolean(), nullable=True),
|
||||||
|
sa.Column('read', sa.Boolean(), nullable=True),
|
||||||
|
sa.Column('encrypted', sa.String(length=15), nullable=True),
|
||||||
|
sa.Column('created_at', sa.DateTime(), nullable=True),
|
||||||
|
sa.ForeignKeyConstraint(['recipient_id'], ['user.id'], ),
|
||||||
|
sa.ForeignKeyConstraint(['sender_id'], ['user.id'], ),
|
||||||
|
sa.PrimaryKeyConstraint('id')
|
||||||
|
)
|
||||||
|
with op.batch_alter_table('chat_message', schema=None) as batch_op:
|
||||||
|
batch_op.create_index(batch_op.f('ix_chat_message_recipient_id'), ['recipient_id'], unique=False)
|
||||||
|
batch_op.create_index(batch_op.f('ix_chat_message_sender_id'), ['sender_id'], unique=False)
|
||||||
|
|
||||||
|
with op.batch_alter_table('site', schema=None) as batch_op:
|
||||||
|
batch_op.add_column(sa.Column('auto_decline_referrers', sa.Text(), nullable=True))
|
||||||
|
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
with op.batch_alter_table('site', schema=None) as batch_op:
|
||||||
|
batch_op.drop_column('auto_decline_referrers')
|
||||||
|
|
||||||
|
with op.batch_alter_table('chat_message', schema=None) as batch_op:
|
||||||
|
batch_op.drop_index(batch_op.f('ix_chat_message_sender_id'))
|
||||||
|
batch_op.drop_index(batch_op.f('ix_chat_message_recipient_id'))
|
||||||
|
|
||||||
|
op.drop_table('chat_message')
|
||||||
|
# ### end Alembic commands ###
|
Loading…
Add table
Reference in a new issue