Add a webchat

This commit is contained in:
Rodolphe Breard 2019-07-23 15:16:03 +02:00
parent b3154265c1
commit 5e7b176e39
23 changed files with 175 additions and 100 deletions

View file

@ -73,6 +73,10 @@ You can set the following variables in the `.env` file:
* `KHAGANAT_NSFW_TAGS`: Coma-separated list of words that triggers the content warning in logs, default is `\#nsfw`. * `KHAGANAT_NSFW_TAGS`: Coma-separated list of words that triggers the content warning in logs, default is `\#nsfw`.
* `KHAGANAT_NSFW_NAME`: Name of the cookie holding the NSFW allowance, default is `nsfw_allowed`. * `KHAGANAT_NSFW_NAME`: Name of the cookie holding the NSFW allowance, default is `nsfw_allowed`.
* `KHAGANAT_INTERNAL_IPS`: List of IP considered as internal, coma separated, default is `127.0.0.1,::1`. * `KHAGANAT_INTERNAL_IPS`: List of IP considered as internal, coma separated, default is `127.0.0.1,::1`.
* `KHAGANAT_XMPP_BOSH_URL`: URL of the BOSH server used to contact the XMPP server. Default is empty.
* `KHAGANAT_XMPP_JID`: The JID to use for the XMPP session. Default is empty.
* `KHAGANAT_XMPP_ROOMS`: List of rooms to automatically join when connecting. Default is empty.
* `KHAGANAT_XMPP_WEBSOCKET_URL`: URL of the websocket server used to contact the XMPP server. Default is empty.
## Quick update ## Quick update

7
chat/admin.py Normal file
View file

@ -0,0 +1,7 @@
from django.contrib import admin
from .models import LogSource
class LogSourceAdmin(admin.ModelAdmin):
list_display = ('name', 'slug', 'hidden')
admin.site.register(LogSource, LogSourceAdmin)

View file

@ -2,4 +2,4 @@ from django.apps import AppConfig
class LogsConfig(AppConfig): class LogsConfig(AppConfig):
name = 'logs' name = 'chat'

View file

@ -2,7 +2,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: 1.0\n" "Project-Id-Version: 1.0\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-02-04 19:45+0100\n" "POT-Creation-Date: 2019-07-23 15:09+0200\n"
"PO-Revision-Date: 2018-01-27 18:35+0100\n" "PO-Revision-Date: 2018-01-27 18:35+0100\n"
"Last-Translator: Khaganat <assoc@khaganat.net>\n" "Last-Translator: Khaganat <assoc@khaganat.net>\n"
"Language-Team: Khaganat <assoc@khaganat.net>\n" "Language-Team: Khaganat <assoc@khaganat.net>\n"
@ -16,36 +16,36 @@ msgstr ""
msgid "date" msgid "date"
msgstr "" msgstr ""
#: templates/logs/entries.html:10 templates/logs/entries.html:21 #: templates/chat/chat_conversejs.html:15
msgid "logs_back" msgid "chat_title"
msgstr "Back" msgstr "Khaganat's chat room"
#: templates/logs/index.html:4 templates/logs/index.html:8 #: templates/chat/entries.html:4 templates/chat/entries.html:11
msgid "logs_title" msgid "logs_title"
msgstr "Khaganat's logs" msgstr "Khaganat's logs"
#: templates/logs/index.html:32 #: templates/chat/entries.html:21
msgid "logs_no_logs_available"
msgstr "No logs available."
#: templates/chat/entries.html:33
msgid "go" msgid "go"
msgstr "" msgstr ""
#: templates/logs/index.html:36 #: templates/chat/entries.html:40
#, python-format #, python-format
msgid "%(name)s is hidden." msgid "%(name)s is hidden."
msgstr "" msgstr ""
#: templates/logs/index.html:37 #: templates/chat/entries.html:41
msgid "show" msgid "show"
msgstr "" msgstr ""
#: templates/logs/index.html:39 #: templates/chat/entries.html:43
#, python-format #, python-format
msgid "%(name)s is published." msgid "%(name)s is published."
msgstr "" msgstr ""
#: templates/logs/index.html:40 #: templates/chat/entries.html:44
msgid "hide" msgid "hide"
msgstr "" msgstr ""
#: templates/logs/index.html:51
msgid "logs_no_logs_available"
msgstr "No logs available."

View file

@ -2,7 +2,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: 1.0\n" "Project-Id-Version: 1.0\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-02-04 19:45+0100\n" "POT-Creation-Date: 2019-07-23 15:09+0200\n"
"PO-Revision-Date: 2018-01-27 18:35+0100\n" "PO-Revision-Date: 2018-01-27 18:35+0100\n"
"Last-Translator: Khaganat <assoc@khaganat.net>\n" "Last-Translator: Khaganat <assoc@khaganat.net>\n"
"Language-Team: Khaganat <assoc@khaganat.net>\n" "Language-Team: Khaganat <assoc@khaganat.net>\n"
@ -16,36 +16,39 @@ msgstr ""
msgid "date" msgid "date"
msgstr "" msgstr ""
#: templates/logs/entries.html:10 templates/logs/entries.html:21 #: templates/chat/chat_conversejs.html:15
msgid "logs_back" msgid "chat_title"
msgstr "Retour" msgstr "Salon de discussion de Khaganat"
#: templates/logs/index.html:4 templates/logs/index.html:8 #: templates/chat/entries.html:4 templates/chat/entries.html:11
msgid "logs_title" msgid "logs_title"
msgstr "Journaux de conversation de Khaganat" msgstr "Journaux de conversation de Khaganat"
#: templates/logs/index.html:32 #: templates/chat/entries.html:21
msgid "logs_no_logs_available"
msgstr "Aucun journal de conversation disponible."
#: templates/chat/entries.html:33
msgid "go" msgid "go"
msgstr "" msgstr ""
#: templates/logs/index.html:36 #: templates/chat/entries.html:40
#, python-format #, python-format
msgid "%(name)s is hidden." msgid "%(name)s is hidden."
msgstr "%(name)s est masqué." msgstr "%(name)s est masqué."
#: templates/logs/index.html:37 #: templates/chat/entries.html:41
msgid "show" msgid "show"
msgstr "montrer" msgstr "montrer"
#: templates/logs/index.html:39 #: templates/chat/entries.html:43
#, python-format #, python-format
msgid "%(name)s is published." msgid "%(name)s is published."
msgstr "%(name)s est publié." msgstr "%(name)s est publié."
#: templates/logs/index.html:40 #: templates/chat/entries.html:44
msgid "hide" msgid "hide"
msgstr "masquer" msgstr "masquer"
#: templates/logs/index.html:51 #~ msgid "logs_back"
msgid "logs_no_logs_available" #~ msgstr "Retour"
msgstr "Aucun journal de conversation disponible."

View file

@ -1,5 +1,5 @@
from django.core.management.base import BaseCommand, CommandError from django.core.management.base import BaseCommand, CommandError
from logs.models import Entry from chat.models import Entry
class Command(BaseCommand): class Command(BaseCommand):

View file

@ -1,4 +1,4 @@
# Generated by Django 2.0.1 on 2018-01-27 20:08 # Generated by Django 2.2.3 on 2019-07-23 09:50
from django.db import migrations, models from django.db import migrations, models
import django.db.models.deletion import django.db.models.deletion
@ -13,17 +13,7 @@ class Migration(migrations.Migration):
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name='Entry', name='LogSource',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('hidden', models.BooleanField(default=False)),
('created', models.DateTimeField()),
('nick', models.CharField(max_length=128)),
('content', models.CharField(max_length=2048)),
],
),
migrations.CreateModel(
name='Source',
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=128)), ('name', models.CharField(max_length=128)),
@ -31,9 +21,15 @@ class Migration(migrations.Migration):
('hidden', models.BooleanField(default=False)), ('hidden', models.BooleanField(default=False)),
], ],
), ),
migrations.AddField( migrations.CreateModel(
model_name='entry', name='LogEntry',
name='source', fields=[
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='logs.Source'), ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('hidden', models.BooleanField(default=False)),
('created', models.DateTimeField()),
('nick', models.CharField(max_length=128)),
('content', models.CharField(max_length=2048)),
('source', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='chat.LogSource')),
],
), ),
] ]

View file

@ -1,7 +1,7 @@
from django.db import models from django.db import models
class Source(models.Model): class LogSource(models.Model):
name = models.CharField(max_length=128) name = models.CharField(max_length=128)
slug = models.CharField(max_length=128, unique=True) slug = models.CharField(max_length=128, unique=True)
hidden = models.BooleanField(default=False) hidden = models.BooleanField(default=False)
@ -10,8 +10,8 @@ class Source(models.Model):
return self.name return self.name
class Entry(models.Model): class LogEntry(models.Model):
source = models.ForeignKey(Source, on_delete=models.CASCADE) source = models.ForeignKey(LogSource, on_delete=models.CASCADE)
hidden = models.BooleanField(default=False) hidden = models.BooleanField(default=False)
created = models.DateTimeField() created = models.DateTimeField()
nick = models.CharField(max_length=128) nick = models.CharField(max_length=128)

View file

@ -0,0 +1,50 @@
{% extends "khaganat/base.html" %}
{% load i18n %}
{% block headers %}
<!-- TODO: build it instead of using a CDN -->
<link rel="stylesheet" type="text/css" media="screen" href="https://cdn.conversejs.org/4.2.0/css/converse.min.css">
<script src="https://cdn.conversejs.org/4.2.0/dist/converse.min.js" charset="utf-8"></script>
<style>
#converse-container {
height: 800px; // TODO: change this!
}
</style>
{% endblock %}
{% block title %}{% trans "chat_title" %}{% endblock %}
{% block content %}
<div class="content-bloc">
<div id="converse-container">
<div id="conversejs"></div>
</div>
</div>
{% endblock %}
{% block endscript %}
<script>
converse.initialize({
allow_contact_removal: false,
allow_contact_requests: false,
allow_logout: false,
allow_muc: true,
allow_muc_invitations: false,
allow_registration: false,
anonymous: true,
authentication: 'anonymous',
auto_join_rooms: [
{% for name in rooms %}'{{name}}',{% endfor %}
],
auto_login: true,
{% if bosh_url %}bosh_service_url: '{{ bosh_url }}',{% endif %}
debug: {{ debug|lower }},
hide_open_bookmarks: false,
{% if jid %}jid: '{{ jid }}',{% endif %}
show_controlbox_by_default: true,
sticky_controlbox: true,
view_mode: 'embedded',
{% if websocket_url %}websocket_url: '{{ websocket_url }}',{% endif %}
});
</script>
{% endblock %}

24
chat/urls.py Normal file
View file

@ -0,0 +1,24 @@
from django.urls import path
from . import views
urlpatterns = [
path('', views.chat_view, name='chat'),
path('logs/', views.LogEntriesView.as_view(), name='log_index'),
path(
'logs/<slug:source>/<int:year>/<int:month>/<int:day>/',
views.LogEntriesView.as_view(),
name='log_day'
),
path(
'logs/switch/source/<int:pk>/',
views.switch_log_source,
name='log_switch_source'
),
path(
'logs/switch/entry/<int:pk>/',
views.switch_log_entry,
name='log_switch_entry'
),
path('logs/search/', views.log_search_view, name='log_search'),
]

View file

@ -1,5 +1,5 @@
from django.contrib.admin.views.decorators import staff_member_required from django.contrib.admin.views.decorators import staff_member_required
from django.shortcuts import redirect, get_object_or_404 from django.shortcuts import redirect, render, get_object_or_404
from django.db.models.functions import TruncDate from django.db.models.functions import TruncDate
from django.db.models import Count from django.db.models import Count
from django.views import generic from django.views import generic
@ -7,12 +7,22 @@ from django.conf import settings
from django.urls import reverse from django.urls import reverse
from django.http import Http404 from django.http import Http404
from nsfw import views as nsfw from nsfw import views as nsfw
from .models import Source, Entry from .models import LogSource, LogEntry
from .forms import SearchForm from .forms import SearchForm
from utils import is_link_legit from utils import is_link_legit
import datetime import datetime
def chat_view(request):
ctx = {
'debug': settings.DEBUG,
'bosh_url': settings.KHAGANAT_XMPP_BOSH_URL,
'jid': settings.KHAGANAT_XMPP_JID,
'rooms': settings.KHAGANAT_XMPP_ROOMS,
'websocket_url': settings.KHAGANAT_XMPP_WEBSOCKET_URL,
}
return render(request, 'chat/chat_conversejs.html', ctx)
def _get_dates(): def _get_dates():
now = datetime.date.today() now = datetime.date.today()
start_date = now - datetime.timedelta( start_date = now - datetime.timedelta(
@ -36,16 +46,16 @@ def _switch_hidden(request, obj, pk):
@staff_member_required @staff_member_required
def switch_source(request, pk): def switch_log_source(request, pk):
return _switch_hidden(request, Source, pk) return _switch_hidden(request, LogSource, pk)
@staff_member_required @staff_member_required
def switch_entry(request, pk): def switch_log_entry(request, pk):
return _switch_hidden(request, Entry, pk) return _switch_hidden(request, LogEntry, pk)
def search_view(request): def log_search_view(request):
if request.method == 'POST': if request.method == 'POST':
form = SearchForm(request.POST) form = SearchForm(request.POST)
if form.is_valid(): if form.is_valid():
@ -59,8 +69,8 @@ def search_view(request):
raise Http404('No search parameters.') raise Http404('No search parameters.')
class EntriesView(generic.ListView): class LogEntriesView(generic.ListView):
template_name = 'logs/entries.html' template_name = 'chat/entries.html'
context_object_name = 'entries' context_object_name = 'entries'
filter_nsfw = False filter_nsfw = False
@ -104,7 +114,7 @@ class EntriesView(generic.ListView):
nb_max = settings.KHAGANAT_LOGS_MAX_DAYS nb_max = settings.KHAGANAT_LOGS_MAX_DAYS
nb_max -= settings.KHAGANAT_LOGS_MIN_DAYS nb_max -= settings.KHAGANAT_LOGS_MIN_DAYS
lst = Entry.objects.filter( lst = LogEntry.objects.filter(
source=source, source=source,
) )
if not self.request.user.is_staff: if not self.request.user.is_staff:
@ -123,12 +133,12 @@ class EntriesView(generic.ListView):
"""Return the current source.""" """Return the current source."""
if self.kwargs.get('source') is None: if self.kwargs.get('source') is None:
return None return None
return Source.objects.get(slug=self.kwargs['source']) return LogSource.objects.get(slug=self.kwargs['source'])
def get_sources(self): def get_sources(self):
"""Return available sources.""" """Return available sources."""
now, start_date, end_date = _get_dates() now, start_date, end_date = _get_dates()
qs = Entry.objects.all() qs = LogEntry.objects.all()
if not self.request.user.is_staff: if not self.request.user.is_staff:
qs = qs.filter( qs = qs.filter(
@ -175,13 +185,13 @@ class EntriesView(generic.ListView):
(now - dt).days < settings.KHAGANAT_LOGS_MIN_DAYS, (now - dt).days < settings.KHAGANAT_LOGS_MIN_DAYS,
)) ))
if out_of_bounds and not is_staff: if out_of_bounds and not is_staff:
return Entry.objects.none() return LogEntry.objects.none()
src = self.get_source() src = self.get_source()
if src is None: if src is None:
return Entry.objects.none() return LogEntry.objects.none()
if src.hidden and not is_staff: if src.hidden and not is_staff:
return Entry.objects.none() return LogEntry.objects.none()
qs = Entry.objects.filter( qs = LogEntry.objects.filter(
source=src, source=src,
created__year=dt.year created__year=dt.year
).filter( ).filter(

View file

@ -41,15 +41,15 @@ INSTALLED_APPS = [
'django.contrib.contenttypes', 'django.contrib.contenttypes',
'django.contrib.sessions', 'django.contrib.sessions',
'django.contrib.messages', 'django.contrib.messages',
'khaganat.apps.KhaganatConfig',
'neluser.apps.NeluserConfig',
'pages.apps.PagesConfig',
'navbar.apps.NavbarConfig',
'static_extra.apps.KhaganatStaticFilesConfig',
'logs.apps.LogsConfig',
'nsfw.apps.NsfwConfig',
'npb.apps.NpbConfig',
'bulma', 'bulma',
'chat.apps.LogsConfig',
'khaganat.apps.KhaganatConfig',
'navbar.apps.NavbarConfig',
'neluser.apps.NeluserConfig',
'npb.apps.NpbConfig',
'nsfw.apps.NsfwConfig',
'pages.apps.PagesConfig',
'static_extra.apps.KhaganatStaticFilesConfig',
] ]
MIDDLEWARE = [ MIDDLEWARE = [
@ -196,3 +196,10 @@ if config('KHAGANAT_FORCE_HTTPS', default=False, cast=bool):
KHAGANAT_NSFW_TAGS = config('KHAGANAT_NSFW_TAGS', default='\\#nsfw', cast=Csv()) KHAGANAT_NSFW_TAGS = config('KHAGANAT_NSFW_TAGS', default='\\#nsfw', cast=Csv())
KHAGANAT_NSFW_NAME = config('KHAGANAT_NSFW_NAME', default='nsfw_allowed') KHAGANAT_NSFW_NAME = config('KHAGANAT_NSFW_NAME', default='nsfw_allowed')
KHAGANAT_NSFW_OK = ('y', 'yes', 't', 'true', '1') KHAGANAT_NSFW_OK = ('y', 'yes', 't', 'true', '1')
# Converse.js
KHAGANAT_XMPP_BOSH_URL = config('KHAGANAT_XMPP_BOSH_URL', default='') or None
KHAGANAT_XMPP_JID = config('KHAGANAT_XMPP_JID', default='') or None
KHAGANAT_XMPP_ROOMS = config('KHAGANAT_XMPP_ROOMS', default='', cast=Csv()) or []
KHAGANAT_XMPP_WEBSOCKET_URL = config('KHAGANAT_XMPP_WEBSOCKET_URL', default='') or None

View file

@ -58,5 +58,6 @@
</ul> </ul>
</div> </div>
</footer> </footer>
{% block endscript %}{% endblock %}
</body> </body>
</html> </html>

View file

@ -29,6 +29,6 @@ urlpatterns += i18n_patterns(
path('account/', include('neluser.urls')), path('account/', include('neluser.urls')),
path('page/', include('pages.urls')), path('page/', include('pages.urls')),
path('paste/', include('npb.urls', namespace='npb')), path('paste/', include('npb.urls', namespace='npb')),
path('logs/', include('logs.urls')), path('chat/', include('chat.urls')),
path('nsfw/', include('nsfw.urls')), path('nsfw/', include('nsfw.urls')),
) )

View file

@ -1,4 +0,0 @@
from django.contrib import admin
from .models import Source
admin.site.register(Source)

View file

@ -1,23 +0,0 @@
from django.urls import path
from . import views
urlpatterns = [
path('', views.EntriesView.as_view(), name='log_index'),
path(
'<slug:source>/<int:year>/<int:month>/<int:day>/',
views.EntriesView.as_view(),
name='log_day'
),
path(
'switch/source/<int:pk>/',
views.switch_source,
name='log_switch_source'
),
path(
'switch/entry/<int:pk>/',
views.switch_entry,
name='log_switch_entry'
),
path('search/', views.search_view, name='log_search'),
]