aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--pydis_site/apps/api/models/bot/deleted_message.py5
-rw-r--r--pydis_site/apps/api/models/bot/message.py12
-rw-r--r--pydis_site/apps/staff/apps.py2
-rw-r--r--pydis_site/apps/staff/templatetags/__init__.py3
-rw-r--r--pydis_site/apps/staff/templatetags/deletedmessage_filters.py17
-rw-r--r--pydis_site/apps/staff/urls.py4
-rw-r--r--pydis_site/apps/staff/viewsets/__init__.py2
-rw-r--r--pydis_site/apps/staff/viewsets/logs.py20
-rw-r--r--pydis_site/static/css/staff/logs.css215
-rw-r--r--pydis_site/templates/staff/logs.html76
10 files changed, 346 insertions, 10 deletions
diff --git a/pydis_site/apps/api/models/bot/deleted_message.py b/pydis_site/apps/api/models/bot/deleted_message.py
index eb7f4c89..1eb4516e 100644
--- a/pydis_site/apps/api/models/bot/deleted_message.py
+++ b/pydis_site/apps/api/models/bot/deleted_message.py
@@ -12,3 +12,8 @@ class DeletedMessage(Message):
help_text="The deletion context this message is part of.",
on_delete=models.CASCADE
)
+
+ class Meta:
+ """Sets the default ordering for list views to oldest first."""
+
+ ordering = ["id"]
diff --git a/pydis_site/apps/api/models/bot/message.py b/pydis_site/apps/api/models/bot/message.py
index 6b566620..0713b9d2 100644
--- a/pydis_site/apps/api/models/bot/message.py
+++ b/pydis_site/apps/api/models/bot/message.py
@@ -1,6 +1,11 @@
+from datetime import datetime
+
+import pytz
from django.contrib.postgres import fields as pgfields
from django.core.validators import MinValueValidator
from django.db import models
+from django.utils import timezone
+
from pydis_site.apps.api.models.bot.tag import validate_tag_embed
from pydis_site.apps.api.models.bot.user import User
@@ -49,6 +54,13 @@ class Message(ModelReprMixin, models.Model):
help_text="Embeds attached to this message."
)
+ @property
+ def timestamp(self) -> datetime:
+ """Attribute that represents the message timestamp as derived from the snowflake id."""
+ tz_naive_datetime = datetime.utcfromtimestamp(((self.id >> 22) + 1420070400000) / 1000)
+ tz_aware_datetime = timezone.make_aware(tz_naive_datetime, timezone=pytz.timezone("UTC"))
+ return tz_aware_datetime
+
class Meta:
"""Metadata provided for Django's ORM."""
diff --git a/pydis_site/apps/staff/apps.py b/pydis_site/apps/staff/apps.py
index fb8bda03..70a15f40 100644
--- a/pydis_site/apps/staff/apps.py
+++ b/pydis_site/apps/staff/apps.py
@@ -4,4 +4,4 @@ from django.apps import AppConfig
class StaffConfig(AppConfig):
"""Django AppConfig for the staff app."""
- name = 'staff' \ No newline at end of file
+ name = 'staff'
diff --git a/pydis_site/apps/staff/templatetags/__init__.py b/pydis_site/apps/staff/templatetags/__init__.py
new file mode 100644
index 00000000..e8b6983a
--- /dev/null
+++ b/pydis_site/apps/staff/templatetags/__init__.py
@@ -0,0 +1,3 @@
+from .deletedmessage_filters import footer_datetime, hex_colour
+
+__all__ = ["hex_colour", "footer_datetime"]
diff --git a/pydis_site/apps/staff/templatetags/deletedmessage_filters.py b/pydis_site/apps/staff/templatetags/deletedmessage_filters.py
new file mode 100644
index 00000000..f950870f
--- /dev/null
+++ b/pydis_site/apps/staff/templatetags/deletedmessage_filters.py
@@ -0,0 +1,17 @@
+from datetime import datetime
+
+from django import template
+
+register = template.Library()
+
+
+def hex_colour(color: int) -> str:
+ """Converts an integer representation of a colour to the RGB hex value."""
+ return f"#{color:0>6X}"
+
+
+def footer_datetime(timestamp: str) -> datetime:
+ """Takes an embed timestamp and returns a timezone-aware datetime object."""
+ return datetime.fromisoformat(timestamp)
diff --git a/pydis_site/apps/staff/urls.py b/pydis_site/apps/staff/urls.py
index 95b3caf3..a564d516 100644
--- a/pydis_site/apps/staff/urls.py
+++ b/pydis_site/apps/staff/urls.py
@@ -1,12 +1,10 @@
from django.conf import settings
from django.conf.urls.static import static
-from django.contrib import admin
-from django.urls import include, path
+from django.urls import path
from .viewsets import LogView
app_name = 'staff'
urlpatterns = [
path('bot/logs/<int:pk>/', LogView.as_view(), name="logs"),
- path('admin/', admin.site.urls),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
diff --git a/pydis_site/apps/staff/viewsets/__init__.py b/pydis_site/apps/staff/viewsets/__init__.py
index ccb57d43..6b10eb83 100644
--- a/pydis_site/apps/staff/viewsets/__init__.py
+++ b/pydis_site/apps/staff/viewsets/__init__.py
@@ -1,3 +1,3 @@
from .logs import LogView
-__all__ = ["LogView"] \ No newline at end of file
+__all__ = ["LogView"]
diff --git a/pydis_site/apps/staff/viewsets/logs.py b/pydis_site/apps/staff/viewsets/logs.py
index d59847a3..0898d606 100644
--- a/pydis_site/apps/staff/viewsets/logs.py
+++ b/pydis_site/apps/staff/viewsets/logs.py
@@ -1,3 +1,5 @@
+import logging
+
from django.core.handlers.wsgi import WSGIRequest
from django.http import HttpResponse
from django.shortcuts import get_object_or_404, render
@@ -5,11 +7,27 @@ from django.views import View
from pydis_site.apps.api.models.bot.message_deletion_context import MessageDeletionContext
+log = logging.getLogger(__name__)
+
class LogView(View):
+ """The default view for the Deleted Messages logs."""
+
template_name = "staff/logs.html"
def get(self, request: WSGIRequest, pk: int) -> HttpResponse:
+ """Get method that answers a request with an html response by rendering a template."""
message_context = get_object_or_404(MessageDeletionContext, pk=pk)
+
+ actor = message_context.actor
+ creation = message_context.creation
messages = message_context.deletedmessage_set.all()
- return render(request, self.template_name, {"message_context": message_context, "messages": messages})
+
+ template_fields = {
+ 'actor': actor,
+ 'actor_colour': message_context.actor.top_role.colour,
+ 'creation': creation,
+ 'messages': messages
+ }
+
+ return render(request, self.template_name, template_fields)
diff --git a/pydis_site/static/css/staff/logs.css b/pydis_site/static/css/staff/logs.css
index ef271e1e..a09d33ac 100644
--- a/pydis_site/static/css/staff/logs.css
+++ b/pydis_site/static/css/staff/logs.css
@@ -1,16 +1,31 @@
main.site-content {
background-color: hsl(220, 8%, 23%);
color: #dcddde;
- font-family: sans-serif;
font-size: 0.9375rem;
font-weight: 400;
line-height: 1.3;
letter-spacing: 0;
text-rendering: optimizeLegibility;
+ padding: 1rem;
+ font-family: Whitney,Helvetica Neue,Helvetica,Arial,Lucida Grande,sans-serif;
+}
+
+.has-small-margin {
+ margin: 1rem 0;
+}
+
+.deleted-header {
+ font-weight: 700;
+ margin-top: 1rem;
}
.discord-message {
- margin: 1rem;
+ margin-bottom: 15px;
+}
+
+.discord-message:first-child {
+ border-top: 1px;
+
}
.discord-message-header {
@@ -20,7 +35,7 @@ main.site-content {
.discord-username {
font-size: 1rem;
- font-weight: 500;
+ font-weight: 600;
}
.discord-message-metadata {
@@ -40,4 +55,198 @@ main.site-content {
color: #dcddde;
font-weight: 300;
margin-left: 0.3rem;
+}
+
+.discord-embed {
+
+ position: relative;
+ margin-top: 5px;
+ max-width: 520px;
+ display: flex;
+}
+
+.discord-embed a {
+ text-decoration: none;
+ color: hsl(197, 100%, 41%);
+}
+
+.discord-embed a:hover {
+ text-decoration: underline;
+ color: hsl(197, 100%, 41%);
+}
+
+.discord-embed-color {
+ width: 4px;
+ border-radius: 3px 0 0 3px;
+ flex-shrink: 0;
+}
+
+.discord-embed-inner {
+ background-color: #34363b;
+ padding: 8px 10px;
+ border-radius: 0 3px 3px 0;
+ box-sizing: border-box;
+ border: 1px solid hsla(225, 8%, 20%, 0.6);
+ display: flex;
+ flex-direction: column;
+}
+
+.discord-embed-content {
+ width: 100%;
+ display: flex;
+}
+
+.discord-embed-main {
+ flex: 1;
+}
+
+.discord-embed-thumbnail > img {
+ max-width: 80px;
+ max-height: 80px;
+ border-radius: 3px;
+ width: auto;
+ object-fit: contain;
+ margin-left: 20px;
+ flex-shrink: 0;
+ border-style: none;
+}
+
+.discord-embed-author {
+ display: flex;
+ align-items: center;
+ margin-bottom: 5px;
+ font-weight: 600;
+ font-size: 14px;
+ line-height: 1.15;
+}
+
+.discord-embed-author-icon {
+ margin-right: 9px;
+ width: 20px;
+ height: 20px;
+ object-fit: contain;
+ border-radius: 50%;
+}
+
+.discord-embed-author a {
+ color: white;
+}
+
+.discord-embed-author a:hover {
+ color: white;
+}
+
+.discord-embed-title {
+ margin-bottom: 5px;
+ font-size: 14px;
+ display: inline-block;
+ font-weight: 600;
+}
+
+.discord-embed-description {
+ margin-bottom: 10px;
+}
+
+.discord-embed-fields {
+ display: flex;
+ flex-direction: row;
+ flex-wrap: wrap;
+ margin-top: -10px;
+ margin-bottom: 10px;
+}
+
+.discord-embed-field {
+ flex: 0;
+ padding-top: 5px;
+ min-width: 100%;
+ max-width: 506px;
+}
+
+.discord-embed-field-name {
+ margin-bottom: 4px;
+ font-weight: 600;
+}
+
+.discord-embed-field-value {
+ font-weight: 500;
+}
+
+.discord-embed-field-inline {
+ flex: 1;
+ min-width: 150px;
+ flex-basis: auto;
+}
+
+.discord-embed-main > :last-child {
+ margin-bottom: 0 !important;
+}
+
+.discord-embed-image {
+ position: relative;
+ display: inline-block;
+ margin-bottom: 10px;
+}
+
+.discord-embed-image > img {
+ margin: 0;
+ vertical-align: bottom;
+ max-width: 300px;
+ display: flex;
+ overflow: hidden;
+ border-radius: 2px;
+}
+
+.discord-embed-footer-text {
+ font-size: .70rem !important;
+ letter-spacing: 0;
+ display: inline-block;
+}
+
+.discord-embed-footer-icon {
+ margin-right: 10px;
+ height: 18px;
+ width: 18px;
+ object-fit: contain;
+ float: left;
+ border-radius: 50%;
+}
+
+.discord-embed-content {
+ margin-bottom: 10px;
+}
+
+.discord-embed-inner > :last-child {
+ margin-bottom: 0 !important;
+}
+
+/* Discord Font definitions */
+@font-face {
+ font-family: Whitney;
+ font-style: light;
+ font-weight:300;
+ src:url(https://discordapp.com/assets/6c6374bad0b0b6d204d8d6dc4a18d820.woff) format("woff")
+}
+@font-face {
+ font-family: Whitney;
+ font-style: normal;
+ font-weight:500;
+ src:url(https://discordapp.com/assets/e8acd7d9bf6207f99350ca9f9e23b168.woff) format("woff")
+}
+@font-face {
+ font-family:Whitney;
+ font-style: medium;
+ font-weight:600;
+ src:url(https://discordapp.com/assets/3bdef1251a424500c1b3a78dea9b7e57.woff) format("woff")
+}
+@font-face {
+ font-family: WhitneyMedium;
+ font-style: medium;
+ font-weight: 600;
+ src:url(https://discordapp.com/assets/be0060dafb7a0e31d2a1ca17c0708636.woff) format("woff")
+}
+@font-face {
+ font-family: Whitney;
+ font-style: bold;
+ font-weight: 700;
+ src:url(https://discordapp.com/assets/8e12fb4f14d9c4592eb8ec9f22337b04.woff) format("woff")
} \ No newline at end of file
diff --git a/pydis_site/templates/staff/logs.html b/pydis_site/templates/staff/logs.html
index 66b42f6a..49d9c368 100644
--- a/pydis_site/templates/staff/logs.html
+++ b/pydis_site/templates/staff/logs.html
@@ -1,5 +1,6 @@
{% extends 'base/base.html' %}
{% load static %}
+{% load deletedmessage_filters %}
{% block title %}Logs for Deleted Message Context {{ message_context.id }}{% endblock %}
@@ -8,15 +9,88 @@
{% endblock %}
{% block content %}
+ <ul class="is-size-7">
+ <li>Deleted by: <span style="color: {{ actor_colour | hex_colour }}">{{ actor }}</span></li>
+ <li>Date: {{ creation }}</li>
+ </ul>
+ <div class="is-divider has-small-margin"></div>
{% for message in messages %}
<div class="discord-message">
<div class="discord-message-header">
- <span class="discord-username" style="color: #{{ message.author.top_role.hex_colour }}">{{ message.author.name }}#{{ message.author.discriminator }}</span><span class="discord-message-metadata">Today at 6:15 PM | #helpers | User ID: 190549806198816768</span>
+ <span class="discord-username" style="color: {{ message.author.top_role.colour | hex_colour }}">{{ message.author }}</span><span class="discord-message-metadata">{{ message.timestamp }} | User ID: {{ message.author.id }}</span>
</div>
<div class="discord-message-content">
{{ message.content|linebreaks }}
</div>
+
+ {% for embed in message.embeds %}
+ <div class="discord-embed is-size-7">
+ <div class="discord-embed-color" style="background-color: {% if embed.color %}{{ embed.color | hex_colour }}{% else %}#cacbce{% endif %}">
+ </div>
+ <div class="discord-embed-inner">
+ <div class="discord-embed-content">
+ <div class="discord-embed-main">
+
+ {% if embed.author %}
+ <div class="discord-embed-author">
+ {% if embed.author.icon_url %}<img alt="Author Icon" class="discord-embed-author-icon" src="{{ embed.author.icon_url }}">{% endif %}
+ {% if embed.author.url %}<a class="discord-embed-author-url" href="{{ embed.author.url }}">{% endif %}
+ <span class="discord-embed-author-name">{{ embed.author.name }}</span>
+ {% if embed.author.url %}</a>{% endif %}
+ </div>
+ {% endif %}
+
+ {% if embed.title %}
+ <div class="discord-embed-title">
+ {% if embed.url %}<a href="{{ embed.url }}">{% endif %}
+ {{ embed.title }}
+ {% if embed.url %}</a>{% endif %}
+ </div>
+ {% endif %}
+
+ {% if embed.description %}
+ <div class="discord-embed-description">
+ {{ embed.description | linebreaksbr }}
+ </div>
+ {% endif %}
+
+ {% if embed.fields %}
+ <div class="discord-embed-fields">
+ {% for field in embed.fields %}
+ <div class="discord-embed-field{% if field.inline %} discord-embed-field-inline{% endif %}">
+ <div class="discord-embed-field-name">{{ field.name }}</div>
+ <div class="discord-embed-field-value">{{ field.value }}</div>
+ </div>
+ {% endfor %}
+ </div>
+ {% endif %}
+ {% if embed.image %}
+ <div class="discord-embed-image">
+ <img alt="Discord Embed Image" src="{{ embed.image.url }}">
+ </div>
+ {% endif %}
+ </div>
+ {% if embed.thumbnail %}
+ <div class="discord-embed-thumbnail">
+ <img alt="Embed thumbnail" src="{{ embed.thumbnail.url }}">
+ </div>
+ {% endif %}
+ </div>
+ {% if embed.footer or embed.timestamp %}
+ <div class="discord-embed-footer">
+ {% if embed.footer.icon_url %}
+ <img class="discord-embed-footer-icon" alt="Footer Icon" src="{{ embed.footer.icon_url }}">
+ {% endif %}
+
+ {% if embed.footer.text or embed.timestamp %}<span class="discord-embed-footer-text">{% endif %}
+ {% if embed.footer.text %}{{ embed.footer.text }}{% endif %}{% if embed.footer.text and embed.timestamp %} • {% endif %}{% if embed.timestamp %}{{ embed.timestamp | footer_datetime }}{% endif %}
+ {% if embed.footer.text or embed.timestamp %}</span>{% endif %}
+ </div>
+ {% endif %}
+ </div>
+ </div>
+ {% endfor %}
</div>
{% endfor %}
{% endblock %} \ No newline at end of file