diff --git a/docs/admin/webapp-config.rst b/docs/admin/webapp-config.rst index 4a673a09..f4952950 100644 --- a/docs/admin/webapp-config.rst +++ b/docs/admin/webapp-config.rst @@ -23,7 +23,7 @@ Full configuration "locale": "en", // DEPRECTATED, alias of defaultLocale "defaultLocale": "en", "locales": ["en", "de", "pt_BR"], // keep this empty to disable user-choosable locale. Order of this array is dropdown order - "dateLocale": "en-ie", + "date_locale": "en-ie", "timetravelTo": "2020-08-26T06:49:28.975Z", // forces local time to always be this (for schedule demo purposes ONLY) // if no token is found in URL hash redirect to given authentication URL. // used together with an external server which generates JW tokens based e.g. on user login and password diff --git a/server/tests/api/test_rooms.py b/server/tests/api/test_rooms.py index 7efc7b5c..3bff520f 100644 --- a/server/tests/api/test_rooms.py +++ b/server/tests/api/test_rooms.py @@ -12,7 +12,8 @@ @pytest.mark.django_db def test_room_list(client, world): r = client.get( - "/api/v1/worlds/sample/rooms/", HTTP_AUTHORIZATION=get_token_header(world) + "/api/v1/worlds/sample/rooms/", + HTTP_AUTHORIZATION=get_token_header(world), ) assert r.status_code == 200 assert r.data["count"] == 8 diff --git a/server/tests/conftest.py b/server/tests/conftest.py index 2d3c706e..b8087871 100644 --- a/server/tests/conftest.py +++ b/server/tests/conftest.py @@ -40,7 +40,9 @@ async def clear_redis(): @pytest.fixture(autouse=True) async def bbb_server(): await database_sync_to_async(BBBServer.objects.create)( - url="https://video1.pretix.eu/bigbluebutton/", secret="bogussecret", active=True + url="https://video1.pretix.eu/bigbluebutton/", + secret="bogussecret", + active=True, ) diff --git a/server/tests/consumers/test_connection_drop.py b/server/tests/consumers/test_connection_drop.py index 23bdb8b1..9abffd9c 100644 --- a/server/tests/consumers/test_connection_drop.py +++ b/server/tests/consumers/test_connection_drop.py @@ -34,7 +34,10 @@ async def test_remote_reload(): try: await sync_to_async(call_command)("connections", "force_reload", "*") - assert ["connection.reload", {}] == await communicator.receive_json_from() + assert [ + "connection.reload", + {}, + ] == await communicator.receive_json_from() finally: await communicator.disconnect() @@ -52,6 +55,9 @@ async def test_remote_reload_staggered(): "connections", "force_reload", "--interval", "20", "*" ) - assert ["connection.reload", {}] == await communicator.receive_json_from() + assert [ + "connection.reload", + {}, + ] == await communicator.receive_json_from() finally: await communicator.disconnect() diff --git a/server/tests/core/test_user_deletion.py b/server/tests/core/test_user_deletion.py index acfc2007..47de1491 100644 --- a/server/tests/core/test_user_deletion.py +++ b/server/tests/core/test_user_deletion.py @@ -29,7 +29,9 @@ def test_delete_user(world, chat_room): should_be_deleted_after_u1_is_deleted = [] bbb_server = BBBServer.objects.create( - url="https://video1.pretix.eu/bigbluebutton/", secret="bogussecret", active=True + url="https://video1.pretix.eu/bigbluebutton/", + secret="bogussecret", + active=True, ) bbbcall = BBBCall.objects.create(server=bbb_server, world=world) bbbcall.invited_members.add(u1) diff --git a/server/tests/live/test_auth.py b/server/tests/live/test_auth.py index f4f0d686..e0ea1846 100644 --- a/server/tests/live/test_auth.py +++ b/server/tests/live/test_auth.py @@ -240,7 +240,6 @@ async def test_update_user(): @pytest.mark.asyncio @pytest.mark.django_db async def test_wrong_user_command(): - async with world_communicator() as c: await c.send_json_to(["authenticate", {"client_id": 4}]) response = await c.receive_json_from() @@ -658,7 +657,10 @@ async def test_delete_user(world): response = await c_admin.receive_json_from() assert response[0] == "error" - assert ["connection.reload", {}] == await c_user.receive_json_from() + assert [ + "connection.reload", + {}, + ] == await c_user.receive_json_from() assert {"type": "websocket.close"} == await c_user.receive_output(timeout=3) # New connect will be an entirely new user @@ -690,7 +692,10 @@ async def test_ban_user(world): response = await c_admin.receive_json_from() assert response[0] == "error" - assert ["connection.reload", {}] == await c_user.receive_json_from() + assert [ + "connection.reload", + {}, + ] == await c_user.receive_json_from() assert {"type": "websocket.close"} == await c_user.receive_output(timeout=3) async with world_communicator() as c_user: @@ -1072,4 +1077,4 @@ async def test_anonymous_invite(client, world, stream_room, bbb_room): "room:question.ask", "room:poll.vote", "room:poll.read", - } \ No newline at end of file + } diff --git a/server/tests/live/test_bigbluebutton.py b/server/tests/live/test_bigbluebutton.py index 914dabec..70e9f1b4 100644 --- a/server/tests/live/test_bigbluebutton.py +++ b/server/tests/live/test_bigbluebutton.py @@ -137,7 +137,10 @@ async def test_bbb_down(bbb_room): async with world_communicator(named=True) as c: await c.send_json_to(["bbb.room_url", 123, {"room": str(bbb_room.id)}]) - m.get(re.compile(r"^https://video1.pretix.eu/bigbluebutton.*$"), status=500) + m.get( + re.compile(r"^https://video1.pretix.eu/bigbluebutton.*$"), + status=500, + ) response = await c.receive_json_from() assert response[0] == "error" diff --git a/server/tests/live/test_chat.py b/server/tests/live/test_chat.py index cbdc4d49..adfa1046 100644 --- a/server/tests/live/test_chat.py +++ b/server/tests/live/test_chat.py @@ -148,11 +148,7 @@ async def test_join_volatile_based_on_room_config(volatile_chat_room, chat_room, response = await c2.receive_json_from() assert response == [ "chat.channels", - { - "channels": [ - {"id": str(chat_room.channel.id), "unread_pointer": 0} - ] - }, + {"channels": [{"id": str(chat_room.channel.id), "unread_pointer": 0}]}, ] @@ -176,7 +172,10 @@ async def test_join_convert_volatile_to_persistent(volatile_chat_room, world): [ "chat.join", 123, - {"channel": str(volatile_chat_room.channel.id), "volatile": False}, + { + "channel": str(volatile_chat_room.channel.id), + "volatile": False, + }, ] ) response = await c.receive_json_from() @@ -208,7 +207,10 @@ async def test_join_convert_volatile_to_persistent_require_moderator( [ "chat.join", 123, - {"channel": str(volatile_chat_room.channel.id), "volatile": False}, + { + "channel": str(volatile_chat_room.channel.id), + "volatile": False, + }, ] ) response = await c.receive_json_from() @@ -225,7 +227,11 @@ async def test_join_without_name(chat_room): async with world_communicator(named=False) as c: await c.send_json_to(["chat.join", 123, {"channel": str(chat_room.channel.id)}]) response = await c.receive_json_from() - assert response == ["error", 123, {"code": "channel.join.missing_profile"}] + assert response == [ + "error", + 123, + {"code": "channel.join.missing_profile"}, + ] @pytest.mark.asyncio @@ -378,7 +384,11 @@ async def test_unsupported_event_type(chat_room): ] ) response = await c1.receive_json_from() - assert response == ["error", 123, {"code": "chat.unsupported_event_type"}] + assert response == [ + "error", + 123, + {"code": "chat.unsupported_event_type"}, + ] @pytest.mark.asyncio @@ -402,7 +412,11 @@ async def test_unsupported_content_type(chat_room): ] ) response = await c1.receive_json_from() - assert response == ["error", 123, {"code": "chat.unsupported_content_type"}] + assert response == [ + "error", + 123, + {"code": "chat.unsupported_content_type"}, + ] @pytest.mark.asyncio @@ -444,7 +458,11 @@ async def test_send_empty(chat_room): }, ] ) - assert await c1.receive_json_from() == ["error", 123, {"code": "chat.empty"}] + assert await c1.receive_json_from() == [ + "error", + 123, + {"code": "chat.empty"}, + ] @pytest.mark.asyncio @@ -972,7 +990,11 @@ async def test_last_disconnect_is_leave_in_volatile_channel(world, volatile_chat async with world_communicator(client_id=client_id) as c2: async with world_communicator(client_id=client_id, named=False) as c3: await c1.send_json_to( - ["chat.join", 123, {"channel": str(volatile_chat_room.channel.id)}] + [ + "chat.join", + 123, + {"channel": str(volatile_chat_room.channel.id)}, + ] ) response = await c1.receive_json_from() assert response == [ @@ -987,7 +1009,11 @@ async def test_last_disconnect_is_leave_in_volatile_channel(world, volatile_chat ] await c2.send_json_to( - ["chat.join", 124, {"channel": str(volatile_chat_room.channel.id)}] + [ + "chat.join", + 124, + {"channel": str(volatile_chat_room.channel.id)}, + ] ) response = await c2.receive_json_from() assert response[0] == "success" @@ -1000,7 +1026,11 @@ async def test_last_disconnect_is_leave_in_volatile_channel(world, volatile_chat assert response == ["chat.channels", {"channels": []}] await c3.send_json_to( - ["chat.join", 125, {"channel": str(volatile_chat_room.channel.id)}] + [ + "chat.join", + 125, + {"channel": str(volatile_chat_room.channel.id)}, + ] ) response = await c3.receive_json_from() assert response[0] == "success" @@ -1323,4 +1353,4 @@ async def test_force_join_after_login(world, chat_room): # Some asyncio test weirdness, I don't get why r = await c2.receive_json_from() assert r[0] == "chat.channels" - assert channel_id in [c["id"] for c in r[1]["channels"]] \ No newline at end of file + assert channel_id in [c["id"] for c in r[1]["channels"]] diff --git a/server/tests/live/test_chat_direct.py b/server/tests/live/test_chat_direct.py index 931d1dfb..b28ffabd 100644 --- a/server/tests/live/test_chat_direct.py +++ b/server/tests/live/test_chat_direct.py @@ -995,4 +995,3 @@ async def test_notification_sync_read_state_across_clients(world): response = await c2.receive_json_from() assert response[0] == "authenticated" assert response[1]["chat.notification_counts"] == {} - \ No newline at end of file diff --git a/server/tests/live/test_exhibition.py b/server/tests/live/test_exhibition.py index 1f669afe..7f45c32f 100644 --- a/server/tests/live/test_exhibition.py +++ b/server/tests/live/test_exhibition.py @@ -165,7 +165,10 @@ async def test_get(world, exhibition_room): "highlighted_room_id": None, "links": [], "social_media_links": [ - {"display_text": "linkedin", "url": "https://www.linkedin.com/"} + { + "display_text": "linkedin", + "url": "https://www.linkedin.com/", + } ], "staff": [], "room_id": str(exhibition_room.pk), diff --git a/server/tests/live/test_polls.py b/server/tests/live/test_polls.py index 7875308b..d0c294e7 100644 --- a/server/tests/live/test_polls.py +++ b/server/tests/live/test_polls.py @@ -205,8 +205,16 @@ async def test_poll_lifecycle(questions_room, world): "timestamp": poll["timestamp"], "is_pinned": False, "options": [ - {"content": "blue", "order": 1, "id": poll["options"][0]["id"]}, - {"content": "red", "order": 2, "id": poll["options"][1]["id"]}, + { + "content": "blue", + "order": 1, + "id": poll["options"][0]["id"], + }, + { + "content": "red", + "order": 2, + "id": poll["options"][1]["id"], + }, ], "results": { poll["options"][0]["id"]: 0, @@ -286,8 +294,16 @@ async def test_poll_lifecycle(questions_room, world): "timestamp": poll["timestamp"], "is_pinned": False, "options": [ - {"content": "blue", "order": 1, "id": poll["options"][0]["id"]}, - {"content": "red", "order": 2, "id": poll["options"][1]["id"]}, + { + "content": "blue", + "order": 1, + "id": poll["options"][0]["id"], + }, + { + "content": "red", + "order": 2, + "id": poll["options"][1]["id"], + }, ], "results": { poll["options"][0]["id"]: 1, @@ -316,8 +332,16 @@ async def test_poll_lifecycle(questions_room, world): "timestamp": poll["timestamp"], "is_pinned": False, "options": [ - {"content": "blue", "order": 1, "id": poll["options"][0]["id"]}, - {"content": "red", "order": 2, "id": poll["options"][1]["id"]}, + { + "content": "blue", + "order": 1, + "id": poll["options"][0]["id"], + }, + { + "content": "red", + "order": 2, + "id": poll["options"][1]["id"], + }, ], "results": { poll["options"][0]["id"]: 1, diff --git a/server/tests/live/test_questions.py b/server/tests/live/test_questions.py index 975d20d3..e265bbf2 100644 --- a/server/tests/live/test_questions.py +++ b/server/tests/live/test_questions.py @@ -64,7 +64,9 @@ async def test_ask_question_when_not_active(inactive_questions_room): async def test_ask_question(questions_room, world): async with world_communicator(room=questions_room) as c: async with world_communicator( - room=questions_room, token=get_token(world, ["moderator"]), first=False + room=questions_room, + token=get_token(world, ["moderator"]), + first=False, ) as c_mod: await c.send_json_to( [ diff --git a/server/tests/live/test_room.py b/server/tests/live/test_room.py index ff6684e8..b278db21 100644 --- a/server/tests/live/test_room.py +++ b/server/tests/live/test_room.py @@ -128,13 +128,21 @@ async def test_reactions_room_aggregate(world, stream_room): async with world_communicator() as c1, world_communicator() as c2: await c1.send_json_to(["room.enter", 123, {"room": str(stream_room.pk)}]) responses = [ - r[0] for r in (await c1.receive_json_from(), await c1.receive_json_from()) + r[0] + for r in ( + await c1.receive_json_from(), + await c1.receive_json_from(), + ) ] assert "world.user_count_change" in responses assert "success" in responses await c2.send_json_to(["room.enter", 123, {"room": str(stream_room.pk)}]) responses = [ - r[0] for r in (await c2.receive_json_from(), await c2.receive_json_from()) + r[0] + for r in ( + await c2.receive_json_from(), + await c2.receive_json_from(), + ) ] assert "world.user_count_change" in responses assert "success" in responses @@ -205,7 +213,11 @@ async def test_change_schedule_data_unauthorized(world, stream_room): await c2.send_json_to(["room.enter", 123, {"room": str(stream_room.pk)}]) responses = [ - r[0] for r in (await c2.receive_json_from(), await c2.receive_json_from()) + r[0] + for r in ( + await c2.receive_json_from(), + await c2.receive_json_from(), + ) ] assert "world.user_count_change" in responses assert "success" in responses @@ -259,7 +271,11 @@ async def test_config_get(world, stream_room): async def test_config_patch(world, stream_room): async with world_communicator(token=get_token(world, ["admin"])) as c1: await c1.send_json_to( - ["room.config.patch", 123, {"room": str(stream_room.pk), "name": "Foo"}] + [ + "room.config.patch", + 123, + {"room": str(stream_room.pk), "name": "Foo"}, + ] ) response = await c1.receive_json_from() assert response[0] == "success" @@ -272,14 +288,22 @@ async def test_config_patch(world, stream_room): async def test_config_reorder(world, chat_room, stream_room): async with world_communicator(token=get_token(world, ["admin"])) as c1: await c1.send_json_to( - ["room.config.reorder", 123, [str(chat_room.pk), str(stream_room.pk)]] + [ + "room.config.reorder", + 123, + [str(chat_room.pk), str(stream_room.pk)], + ] ) response = await c1.receive_json_from() assert response[0] == "success" assert response[2][0]["name"] == "Chat" assert response[2][1]["name"] == "Plenum" await c1.send_json_to( - ["room.config.reorder", 123, [str(stream_room.pk), str(chat_room.pk)]] + [ + "room.config.reorder", + 123, + [str(stream_room.pk), str(chat_room.pk)], + ] ) response = await c1.receive_json_from() assert response[0] == "success" @@ -312,7 +336,11 @@ async def test_change_schedule_data(world, stream_room): await c1.receive_json_from() # room.viewer.added responses = [ - r[0] for r in (await c2.receive_json_from(), await c2.receive_json_from()) + r[0] + for r in ( + await c2.receive_json_from(), + await c2.receive_json_from(), + ) ] assert "world.user_count_change" in responses assert "success" in responses diff --git a/server/tests/live/test_world.py b/server/tests/live/test_world.py index b161acd1..e30c5354 100644 --- a/server/tests/live/test_world.py +++ b/server/tests/live/test_world.py @@ -152,7 +152,7 @@ async def test_config_get(world): "iframe_blockers": {"default": {"enabled": False, "policy_url": None}}, "title": "Unsere tolle Online-Konferenz", "locale": "en", - "dateLocale": "en-ie", + "date_locale": "en-ie", "videoPlayer": None, "timezone": "Europe/Berlin", "connection_limit": 2, @@ -189,7 +189,11 @@ async def test_config_get(world): async def test_config_patch(world): async with world_communicator(token=get_token(world, ["admin"])) as c1: await c1.send_json_to( - ["world.config.patch", 123, {"title": "Foo", "social_logins": ["gravatar"]}] + [ + "world.config.patch", + 123, + {"title": "Foo", "social_logins": ["gravatar"]}, + ] ) response = await c1.receive_json_from() assert response[0] == "success" @@ -199,7 +203,11 @@ async def test_config_patch(world): await c1.receive_json_from() await c1.send_json_to( - ["world.config.patch", 123, {"title": "Foo", "social_logins": ["unknown"]}] + [ + "world.config.patch", + 123, + {"title": "Foo", "social_logins": ["unknown"]}, + ] ) response = await c1.receive_json_from() assert response[0] == "error" diff --git a/server/tests/storage/test_auth.py b/server/tests/storage/test_auth.py index d1966104..91943073 100644 --- a/server/tests/storage/test_auth.py +++ b/server/tests/storage/test_auth.py @@ -18,7 +18,9 @@ def test_invalid_token_header(client, world): ) assert r.status_code == 403 r = client.post( - "/storage/upload/", HTTP_AUTHORIZATION="Bearer foo bar", HTTP_HOST="localhost" + "/storage/upload/", + HTTP_AUTHORIZATION="Bearer foo bar", + HTTP_HOST="localhost", ) assert r.status_code == 403 @@ -38,7 +40,9 @@ def test_invalid_token(client, world): } token = jwt.encode(payload, config["secret"] + "aaaa", algorithm="HS256") r = client.post( - "/storage/upload/", HTTP_AUTHORIZATION="Bearer " + token, HTTP_HOST="localhost" + "/storage/upload/", + HTTP_AUTHORIZATION="Bearer " + token, + HTTP_HOST="localhost", ) assert r.status_code == 403 @@ -58,7 +62,9 @@ def test_expired_token(client, world): } token = jwt.encode(payload, config["secret"], algorithm="HS256") r = client.post( - "/storage/upload/", HTTP_AUTHORIZATION="Bearer " + token, HTTP_HOST="localhost" + "/storage/upload/", + HTTP_AUTHORIZATION="Bearer " + token, + HTTP_HOST="localhost", ) assert r.status_code == 403 @@ -82,7 +88,9 @@ def test_no_permission(client, world): world.rooms.all().delete() token = jwt.encode(payload, config["secret"], algorithm="HS256") r = client.post( - "/storage/upload/", HTTP_AUTHORIZATION="Bearer " + token, HTTP_HOST="localhost" + "/storage/upload/", + HTTP_AUTHORIZATION="Bearer " + token, + HTTP_HOST="localhost", ) assert r.status_code == 403 @@ -91,7 +99,9 @@ def test_no_permission(client, world): def test_client_id(client, world, chat_room): token = str(uuid.uuid4()) r = client.post( - "/storage/upload/", HTTP_AUTHORIZATION="Client " + token, HTTP_HOST="localhost" + "/storage/upload/", + HTTP_AUTHORIZATION="Client " + token, + HTTP_HOST="localhost", ) assert r.status_code == 400 @@ -111,6 +121,8 @@ def test_admin_token(client, world): } token = jwt.encode(payload, config["secret"], algorithm="HS256") r = client.post( - "/storage/upload/", HTTP_AUTHORIZATION="Bearer " + token, HTTP_HOST="localhost" + "/storage/upload/", + HTTP_AUTHORIZATION="Bearer " + token, + HTTP_HOST="localhost", ) assert r.status_code == 400 diff --git a/server/venueless/api/auth.py b/server/venueless/api/auth.py index 0a536547..c783b026 100644 --- a/server/venueless/api/auth.py +++ b/server/venueless/api/auth.py @@ -59,7 +59,8 @@ def has_permission(self, request, view): if isinstance(request.user, AnonymousUser): return False return request.world.has_permission_implicit( - traits=request.auth.get("traits"), permissions=[Permission.WORLD_API] + traits=request.auth.get("traits"), + permissions=[Permission.WORLD_API], ) diff --git a/server/venueless/api/urls.py b/server/venueless/api/urls.py index 01a5a3eb..d40dadf8 100644 --- a/server/venueless/api/urls.py +++ b/server/venueless/api/urls.py @@ -12,6 +12,9 @@ re_path("worlds/(?P[^/]+)/delete_user/?$", views.delete_user), path("worlds//", include(world_router.urls)), path("worlds//theme", views.WorldThemeView.as_view()), - path("worlds//favourite-talk/", views.UserFavouriteView.as_view()), + path( + "worlds//favourite-talk/", + views.UserFavouriteView.as_view(), + ), path("worlds//export-talk", views.ExportView.as_view()), ] diff --git a/server/venueless/api/views.py b/server/venueless/api/views.py index 28ecc785..c1e37e0f 100644 --- a/server/venueless/api/views.py +++ b/server/venueless/api/views.py @@ -2,16 +2,14 @@ import logging from contextlib import suppress from urllib.parse import urlparse -import jwt -import requests +import requests from asgiref.sync import async_to_sync from django.core import exceptions from django.db import transaction from django.http import JsonResponse from django.shortcuts import get_object_or_404 from django.utils.timezone import now -from django.views import View from rest_framework import viewsets from rest_framework.authentication import get_authorization_header from rest_framework.decorators import api_view, permission_classes @@ -93,7 +91,6 @@ def patch(self, request, **kwargs): class WorldThemeView(APIView): - permission_classes = [] def get(self, request, **kwargs): @@ -105,14 +102,20 @@ def get(self, request, **kwargs): """ try: world = get_object_or_404(World, id=kwargs["world_id"]) - return Response(WorldSerializer(world).data['config']['theme']) + return Response(WorldSerializer(world).data["config"]["theme"]) except KeyError: - logger.error("error happened when trying to get theme data of world: %s", kwargs["world_id"]) - return Response("error happened when trying to get theme data of world: " + kwargs["world_id"], status=503) + logger.error( + "error happened when trying to get theme data of world: %s", + kwargs["world_id"], + ) + return Response( + "error happened when trying to get theme data of world: " + + kwargs["world_id"], + status=503, + ) class UserFavouriteView(APIView): - permission_classes = [] @staticmethod @@ -123,7 +126,9 @@ def post(request, *args, **kwargs) -> JsonResponse: """ try: talk_list = json.loads(request.body.decode()) - user_code = UserFavouriteView.get_uid_from_token(request, kwargs["world_id"]) + user_code = UserFavouriteView.get_uid_from_token( + request, kwargs["world_id"] + ) user = User.objects.get(token_id=user_code) if not user_code or not user: # user not created yet, no error should be returned @@ -131,23 +136,22 @@ def post(request, *args, **kwargs) -> JsonResponse: return JsonResponse([], safe=False, status=200) if user.client_state is None: # If it's None, create a new dictionary with schedule.favs field - user.client_state = { - 'schedule': { - 'favs': talk_list - } - } + user.client_state = {"schedule": {"favs": talk_list}} else: # If client_state is not None, check if 'schedule' field exists - if 'schedule' not in user.client_state: + if "schedule" not in user.client_state: # If 'schedule' field doesn't exist, create it - user.client_state['schedule'] = {'favs': talk_list} + user.client_state["schedule"] = {"favs": talk_list} else: # If 'schedule' field exists, update the 'favs' field - user.client_state['schedule']['favs'] = talk_list + user.client_state["schedule"]["favs"] = talk_list user.save() return JsonResponse(talk_list, safe=False, status=200) except Exception as e: - logger.error("error happened when trying to add fav talks: %s", kwargs["world_id"]) + logger.error( + "error happened when trying to add fav talks: %s", + kwargs["world_id"], + ) logger.error(e) # Since this is called from background so no error should be returned return JsonResponse([], safe=False, status=200) @@ -156,39 +160,48 @@ def post(request, *args, **kwargs) -> JsonResponse: def get_uid_from_token(request, world_id): world = get_object_or_404(World, id=world_id) auth_header = get_authorization_header(request).split() - if auth_header and auth_header[0].lower() == b'bearer': + if auth_header and auth_header[0].lower() == b"bearer": if len(auth_header) == 1: - raise exceptions.AuthenticationFailed('Invalid token header. No credentials provided.') + raise exceptions.AuthenticationFailed( + "Invalid token header. No credentials provided." + ) elif len(auth_header) > 2: raise exceptions.AuthenticationFailed( - 'Invalid token header. Token string should not contain spaces.') + "Invalid token header. Token string should not contain spaces." + ) token_decode = world.decode_token(token=auth_header[1]) return token_decode.get("uid") class ExportView(APIView): - permission_classes = [] @staticmethod def get(request, *args, **kwargs): - export_type = request.GET.get('export_type', 'json') + export_type = request.GET.get("export_type", "json") world = get_object_or_404(World, id=kwargs["world_id"]) talk_config = world.config.get("pretalx") user = User.objects.filter(token_id=request.user) - talk_base_url = talk_config.get('domain') + "/" + talk_config.get('event') + "/schedule/export/" - export_endpoint = 'schedule.' + export_type + talk_base_url = ( + talk_config.get("domain") + + "/" + + talk_config.get("event") + + "/schedule/export/" + ) + export_endpoint = "schedule." + export_type talk_url = talk_base_url + export_endpoint - if 'my' in export_type and user: + if "my" in export_type and user: user_state = user.first().client_state - if user_state and user_state.get('schedule') and user_state.get('schedule').get('favs'): - talk_list = user_state.get('schedule').get('favs') - talk_list_str = ','.join(talk_list) - export_endpoint = 'schedule-my.' + export_type.replace('my','') + if ( + user_state + and user_state.get("schedule") + and user_state.get("schedule").get("favs") + ): + talk_list = user_state.get("schedule").get("favs") + talk_list_str = ",".join(talk_list) + export_endpoint = "schedule-my." + export_type.replace("my", "") talk_url = talk_base_url + export_endpoint + "?talks=" + talk_list_str - header = { - "Content-Type": "application/json" - } + header = {"Content-Type": "application/json"} response = requests.get(talk_url, headers=header) return Response(response.content.decode("utf-8")) diff --git a/server/venueless/asgi.py b/server/venueless/asgi.py index 8190816c..369f86df 100644 --- a/server/venueless/asgi.py +++ b/server/venueless/asgi.py @@ -2,6 +2,7 @@ ASGI entrypoint. Configures Django and then runs the application defined in the ASGI_APPLICATION setting. """ + import os import django diff --git a/server/venueless/control/migrations/0001_initial.py b/server/venueless/control/migrations/0001_initial.py index e725bce4..45c05c08 100644 --- a/server/venueless/control/migrations/0001_initial.py +++ b/server/venueless/control/migrations/0001_initial.py @@ -19,7 +19,10 @@ class Migration(migrations.Migration): fields=[ ("id", models.BigAutoField(primary_key=True, serialize=False)), ("object_id", models.JSONField()), - ("datetime", models.DateTimeField(auto_now_add=True, db_index=True)), + ( + "datetime", + models.DateTimeField(auto_now_add=True, db_index=True), + ), ("action_type", models.CharField(max_length=255)), ("data", models.JSONField()), ( diff --git a/server/venueless/control/urls.py b/server/venueless/control/urls.py index 24861177..b76fec15 100644 --- a/server/venueless/control/urls.py +++ b/server/venueless/control/urls.py @@ -18,26 +18,44 @@ path("users/", views.UserList.as_view(), name="user.list"), path("users//", views.UserUpdate.as_view(), name="user.update"), path("bbbs/", views.BBBServerList.as_view(), name="bbbserver.list"), - path("bbbs/moveroom/", views.BBBMoveRoom.as_view(), name="bbbserver.moveroom"), + path( + "bbbs/moveroom/", + views.BBBMoveRoom.as_view(), + name="bbbserver.moveroom", + ), path("bbbs/new/", views.BBBServerCreate.as_view(), name="bbbserver.create"), path( "bbbs//delete", views.BBBServerDelete.as_view(), name="bbbserver.delete", ), - path("bbbs//", views.BBBServerUpdate.as_view(), name="bbbserver.update"), + path( + "bbbs//", + views.BBBServerUpdate.as_view(), + name="bbbserver.update", + ), path("janus/", views.JanusServerList.as_view(), name="janusserver.list"), - path("janus/new/", views.JanusServerCreate.as_view(), name="janusserver.create"), + path( + "janus/new/", + views.JanusServerCreate.as_view(), + name="janusserver.create", + ), path( "janus//delete", views.JanusServerDelete.as_view(), name="janusserver.delete", ), path( - "janus//", views.JanusServerUpdate.as_view(), name="janusserver.update" + "janus//", + views.JanusServerUpdate.as_view(), + name="janusserver.update", ), path("turns/", views.TurnServerList.as_view(), name="turnserver.list"), - path("turns/new/", views.TurnServerCreate.as_view(), name="turnserver.create"), + path( + "turns/new/", + views.TurnServerCreate.as_view(), + name="turnserver.create", + ), path( "turns//delete", views.TurnServerDelete.as_view(), @@ -72,11 +90,23 @@ path("worlds/", views.WorldList.as_view(), name="world.list"), path("worlds/new/", views.WorldCreate.as_view(), name="world.create"), path("worlds/calendar", views.WorldCalendar.as_view(), name="world.calendar"), - path("worlds//admin", views.WorldAdminToken.as_view(), name="world.admin"), - path("worlds//clear", views.WorldClear.as_view(), name="world.clear"), + path( + "worlds//admin", + views.WorldAdminToken.as_view(), + name="world.admin", + ), + path( + "worlds//clear", + views.WorldClear.as_view(), + name="world.clear", + ), path("worlds//", views.WorldUpdate.as_view(), name="world.update"), path("feedback/", views.FeedbackList.as_view(), name="feedback.list"), - path("feedback//", views.FeedbackDetail.as_view(), name="feedback.detail"), + path( + "feedback//", + views.FeedbackDetail.as_view(), + name="feedback.detail", + ), path( "conftool/syncposters/", views.ConftoolSyncPosters.as_view(), diff --git a/server/venueless/core/management/commands/clear_unused_worlds.py b/server/venueless/core/management/commands/clear_unused_worlds.py index 8584ef22..a4357de8 100644 --- a/server/venueless/core/management/commands/clear_unused_worlds.py +++ b/server/venueless/core/management/commands/clear_unused_worlds.py @@ -37,9 +37,11 @@ def handle(self, *args, **options): ChatEvent.objects.filter(channel__world=world).aggregate( m=Max("timestamp") )["m"], - pytz.UTC.localize(datetime.combine(planned_end, time(0))) - if planned_end - else None, + ( + pytz.UTC.localize(datetime.combine(planned_end, time(0))) + if planned_end + else None + ), ] checks = [c for c in checks if c] if checks and max(checks) > cutoff: diff --git a/server/venueless/core/migrations/0001_initial.py b/server/venueless/core/migrations/0001_initial.py index 364d5cd3..9b8c1b2f 100644 --- a/server/venueless/core/migrations/0001_initial.py +++ b/server/venueless/core/migrations/0001_initial.py @@ -82,12 +82,16 @@ class Migration(migrations.Migration): ( "world", models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, to="core.World" + on_delete=django.db.models.deletion.CASCADE, + to="core.World", ), ), ], options={ - "unique_together": {("client_id", "world"), ("token_id", "world")}, + "unique_together": { + ("client_id", "world"), + ("token_id", "world"), + }, }, ), migrations.CreateModel( @@ -113,8 +117,14 @@ class Migration(migrations.Migration): ), ("name", models.CharField(max_length=300)), ("description", models.TextField(blank=True, null=True)), - ("picture", models.FileField(blank=True, null=True, upload_to="")), - ("import_id", models.CharField(blank=True, max_length=100, null=True)), + ( + "picture", + models.FileField(blank=True, null=True, upload_to=""), + ), + ( + "import_id", + models.CharField(blank=True, max_length=100, null=True), + ), ( "world", models.ForeignKey( @@ -128,7 +138,10 @@ class Migration(migrations.Migration): migrations.CreateModel( name="ChatEvent", fields=[ - ("id", models.BigIntegerField(primary_key=True, serialize=False)), + ( + "id", + models.BigIntegerField(primary_key=True, serialize=False), + ), ("timestamp", models.DateTimeField(auto_now_add=True)), ("event_type", models.CharField(max_length=200)), ("content", django.contrib.postgres.fields.jsonb.JSONField()), diff --git a/server/venueless/core/migrations/0004_auto_20200516_1225.py b/server/venueless/core/migrations/0004_auto_20200516_1225.py index 82741091..c958455f 100644 --- a/server/venueless/core/migrations/0004_auto_20200516_1225.py +++ b/server/venueless/core/migrations/0004_auto_20200516_1225.py @@ -31,7 +31,9 @@ class Migration(migrations.Migration): model_name="room", name="trait_grants", field=django.contrib.postgres.fields.jsonb.JSONField( - blank=True, default=venueless.core.models.room.default_grants, null=True + blank=True, + default=venueless.core.models.room.default_grants, + null=True, ), ), migrations.AddField( @@ -67,7 +69,8 @@ class Migration(migrations.Migration): model_name="room", name="module_config", field=django.contrib.postgres.fields.jsonb.JSONField( - default=venueless.core.models.room.empty_module_config, null=True + default=venueless.core.models.room.empty_module_config, + null=True, ), ), migrations.CreateModel( diff --git a/server/venueless/core/migrations/0010_user_moderation_state.py b/server/venueless/core/migrations/0010_user_moderation_state.py index 1175790d..14eed1d3 100644 --- a/server/venueless/core/migrations/0010_user_moderation_state.py +++ b/server/venueless/core/migrations/0010_user_moderation_state.py @@ -13,7 +13,11 @@ class Migration(migrations.Migration): model_name="user", name="moderation_state", field=models.CharField( - choices=[("", "None"), ("silenced", "Silenced"), ("banned", "Banned")], + choices=[ + ("", "None"), + ("silenced", "Silenced"), + ("banned", "Banned"), + ], default="", max_length=8, ), diff --git a/server/venueless/core/migrations/0016_bbbcall_bbbserver.py b/server/venueless/core/migrations/0016_bbbcall_bbbserver.py index 954ae8a9..e74a1f39 100644 --- a/server/venueless/core/migrations/0016_bbbcall_bbbserver.py +++ b/server/venueless/core/migrations/0016_bbbcall_bbbserver.py @@ -41,19 +41,22 @@ class Migration(migrations.Migration): ( "meeting_id", models.CharField( - default=venueless.core.models.bbb.random_key, max_length=300 + default=venueless.core.models.bbb.random_key, + max_length=300, ), ), ( "attendee_pw", models.CharField( - default=venueless.core.models.bbb.random_key, max_length=300 + default=venueless.core.models.bbb.random_key, + max_length=300, ), ), ( "moderator_pw", models.CharField( - default=venueless.core.models.bbb.random_key, max_length=300 + default=venueless.core.models.bbb.random_key, + max_length=300, ), ), ( @@ -68,7 +71,8 @@ class Migration(migrations.Migration): ( "server", models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, to="core.BBBServer" + on_delete=django.db.models.deletion.CASCADE, + to="core.BBBServer", ), ), ( diff --git a/server/venueless/core/migrations/0017_user_blocked_users.py b/server/venueless/core/migrations/0017_user_blocked_users.py index 6b97ec4e..216debcd 100644 --- a/server/venueless/core/migrations/0017_user_blocked_users.py +++ b/server/venueless/core/migrations/0017_user_blocked_users.py @@ -13,7 +13,9 @@ class Migration(migrations.Migration): model_name="user", name="blocked_users", field=models.ManyToManyField( - related_name="_user_blocked_users_+", to="core.User", symmetrical=False + related_name="_user_blocked_users_+", + to="core.User", + symmetrical=False, ), ), ] diff --git a/server/venueless/core/migrations/0028_world_feature_flags.py b/server/venueless/core/migrations/0028_world_feature_flags.py index 98e40559..be763aad 100644 --- a/server/venueless/core/migrations/0028_world_feature_flags.py +++ b/server/venueless/core/migrations/0028_world_feature_flags.py @@ -16,7 +16,8 @@ class Migration(migrations.Migration): model_name="world", name="feature_flags", field=django.contrib.postgres.fields.jsonb.JSONField( - blank=True, default=venueless.core.models.world.default_feature_flags + blank=True, + default=venueless.core.models.world.default_feature_flags, ), ), ] diff --git a/server/venueless/core/migrations/0042_roulettepairing_rouletterequest.py b/server/venueless/core/migrations/0042_roulettepairing_rouletterequest.py index 68f2bb03..02d5967e 100644 --- a/server/venueless/core/migrations/0042_roulettepairing_rouletterequest.py +++ b/server/venueless/core/migrations/0042_roulettepairing_rouletterequest.py @@ -25,7 +25,8 @@ class Migration(migrations.Migration): ( "room", models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, to="core.room" + on_delete=django.db.models.deletion.CASCADE, + to="core.room", ), ), ( @@ -60,13 +61,15 @@ class Migration(migrations.Migration): ( "room", models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, to="core.room" + on_delete=django.db.models.deletion.CASCADE, + to="core.room", ), ), ( "user", models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, to="core.user" + on_delete=django.db.models.deletion.CASCADE, + to="core.user", ), ), ], diff --git a/server/venueless/core/migrations/0047_poll_polloption_pollvote.py b/server/venueless/core/migrations/0047_poll_polloption_pollvote.py index 9431e24e..b113a7d9 100644 --- a/server/venueless/core/migrations/0047_poll_polloption_pollvote.py +++ b/server/venueless/core/migrations/0047_poll_polloption_pollvote.py @@ -38,7 +38,10 @@ class Migration(migrations.Migration): ( "poll_type", models.CharField( - choices=[("choice", "Choice"), ("multi", "Multi Choice")], + choices=[ + ("choice", "Choice"), + ("multi", "Multi Choice"), + ], default="choice", max_length=6, ), diff --git a/server/venueless/core/migrations/0049_streamingserver.py b/server/venueless/core/migrations/0049_streamingserver.py index d117ce40..72d447bd 100644 --- a/server/venueless/core/migrations/0049_streamingserver.py +++ b/server/venueless/core/migrations/0049_streamingserver.py @@ -26,13 +26,15 @@ class Migration(migrations.Migration): ( "url_input", models.CharField( - default="rtmp://server/app/{name}?token={token}", max_length=300 + default="rtmp://server/app/{name}?token={token}", + max_length=300, ), ), ( "url_output", models.CharField( - default="https://server/hls/{name}.m3u8", max_length=300 + default="https://server/hls/{name}.m3u8", + max_length=300, ), ), ], diff --git a/server/venueless/core/migrations/0051_poster_posterlink_posterpresenter_postervote.py b/server/venueless/core/migrations/0051_poster_posterlink_posterpresenter_postervote.py index a7edc0c2..53b3698c 100644 --- a/server/venueless/core/migrations/0051_poster_posterlink_posterpresenter_postervote.py +++ b/server/venueless/core/migrations/0051_poster_posterlink_posterpresenter_postervote.py @@ -36,7 +36,10 @@ class Migration(migrations.Migration): "tags", models.JSONField(default=venueless.core.models.poster.default_text), ), - ("category", models.CharField(blank=True, max_length=50, null=True)), + ( + "category", + models.CharField(blank=True, max_length=50, null=True), + ), ("poster_url", models.URLField(blank=True, null=True)), ("poster_preview", models.URLField(blank=True, null=True)), ( diff --git a/server/venueless/core/migrations/0065_chat_mentions.py b/server/venueless/core/migrations/0065_chat_mentions.py index cf634955..80eaf69a 100644 --- a/server/venueless/core/migrations/0065_chat_mentions.py +++ b/server/venueless/core/migrations/0065_chat_mentions.py @@ -1,7 +1,8 @@ # Generated by Django 4.2.13 on 2024-07-18 01:31 -from django.db import migrations, models import django.db.models.deletion +from django.db import migrations, models + import venueless.core.models.room import venueless.core.models.world import venueless.core.utils.json @@ -22,7 +23,8 @@ class Migration(migrations.Migration): model_name="room", name="module_config", field=models.JSONField( - default=venueless.core.models.room.empty_module_config, null=True + default=venueless.core.models.room.empty_module_config, + null=True, ), ), migrations.AlterField( @@ -34,7 +36,9 @@ class Migration(migrations.Migration): model_name="room", name="trait_grants", field=models.JSONField( - blank=True, default=venueless.core.models.room.default_grants, null=True + blank=True, + default=venueless.core.models.room.default_grants, + null=True, ), ), migrations.AlterField( @@ -56,7 +60,8 @@ class Migration(migrations.Migration): model_name="world", name="feature_flags", field=models.JSONField( - blank=True, default=venueless.core.models.world.default_feature_flags + blank=True, + default=venueless.core.models.world.default_feature_flags, ), ), migrations.AlterField( diff --git a/server/venueless/core/models/announcement.py b/server/venueless/core/models/announcement.py index 12c30917..00273e25 100644 --- a/server/venueless/core/models/announcement.py +++ b/server/venueless/core/models/announcement.py @@ -40,9 +40,11 @@ def serialize_public(self): return { "id": str(self.id), "text": self.text, - "show_until": self.show_until.isoformat() - if isinstance(self.show_until, dt.datetime) - else self.show_until, + "show_until": ( + self.show_until.isoformat() + if isinstance(self.show_until, dt.datetime) + else self.show_until + ), "state": self.state, "is_visible": self.is_visible, } diff --git a/server/venueless/core/models/auth.py b/server/venueless/core/models/auth.py index 878443ad..f4d533d6 100644 --- a/server/venueless/core/models/auth.py +++ b/server/venueless/core/models/auth.py @@ -28,7 +28,9 @@ class UserType(models.TextChoices): token_id = models.CharField(max_length=200, db_index=True, null=True, blank=True) world = models.ForeignKey(to="World", db_index=True, on_delete=models.CASCADE) moderation_state = models.CharField( - max_length=8, default=ModerationState.NONE, choices=ModerationState.choices + max_length=8, + default=ModerationState.NONE, + choices=ModerationState.choices, ) type = models.CharField( max_length=8, default=UserType.PERSON, choices=UserType.choices @@ -118,17 +120,19 @@ def serialize_public( "profile": self.profile, "pretalx_id": self.pretalx_id, "deleted": self.deleted, - "badges": sorted( - list( - { - badge - for trait, badge in trait_badges_map.items() - if trait in self.traits - } + "badges": ( + sorted( + list( + { + badge + for trait, badge in trait_badges_map.items() + if trait in self.traits + } + ) ) - ) - if trait_badges_map - else [], + if trait_badges_map + else [] + ), } d["inactive"] = self.last_login is None or self.last_login < now() - timedelta( hours=36 @@ -257,6 +261,9 @@ class ShortToken(models.Model): ) expires = models.DateTimeField() short_token = models.CharField( - db_index=True, unique=True, default=generate_short_token, max_length=150 + db_index=True, + unique=True, + default=generate_short_token, + max_length=150, ) long_token = models.TextField() diff --git a/server/venueless/core/models/chat.py b/server/venueless/core/models/chat.py index 91566535..b2ce44f6 100644 --- a/server/venueless/core/models/chat.py +++ b/server/venueless/core/models/chat.py @@ -44,7 +44,10 @@ class ChatEvent(models.Model): edited = models.DateTimeField(null=True) event_type = models.CharField(max_length=200) replaces = models.ForeignKey( - to="ChatEvent", related_name="replaced_by", null=True, on_delete=models.CASCADE + to="ChatEvent", + related_name="replaced_by", + null=True, + on_delete=models.CASCADE, ) sender = models.ForeignKey( "User", @@ -86,6 +89,7 @@ class ChatEventReaction(models.Model): related_name="reactions", ) + class ChatEventNotification(models.Model): recipient = models.ForeignKey( "User", @@ -105,6 +109,7 @@ class Meta: def __str__(self): return f"Notification for {self.recipient} in event {self.chat_event}" + class Membership(models.Model): channel = models.ForeignKey( to=Channel, @@ -134,4 +139,4 @@ def save(self, *args, **kwargs): def delete(self, *args, **kwargs): r = super().delete(*args, **kwargs) self.user.touch() - return r \ No newline at end of file + return r diff --git a/server/venueless/core/models/exhibitor.py b/server/venueless/core/models/exhibitor.py index 4d0045ce..e8dbfab5 100644 --- a/server/venueless/core/models/exhibitor.py +++ b/server/venueless/core/models/exhibitor.py @@ -79,9 +79,9 @@ def serialize(self): social_media_links=social_media_links, staff=staff, room_id=str(self.room_id), - highlighted_room_id=str(self.highlighted_room_id) - if self.highlighted_room_id - else None, + highlighted_room_id=( + str(self.highlighted_room_id) if self.highlighted_room_id else None + ), ) def serialize_short(self): @@ -196,9 +196,9 @@ def serialize(self): exhibitor=self.exhibitor.serialize_short(), user=self.user.serialize_public() if self.user else None, state=self.state, - answered_by=self.answered_by.serialize_public() - if self.answered_by - else None, + answered_by=( + self.answered_by.serialize_public() if self.answered_by else None + ), timestamp=self.timestamp.isoformat() if self.timestamp else None, ) diff --git a/server/venueless/core/models/room.py b/server/venueless/core/models/room.py index 26e05705..04e2a6e5 100644 --- a/server/venueless/core/models/room.py +++ b/server/venueless/core/models/room.py @@ -40,7 +40,10 @@ def with_permission( # Get all roles that grant view access roles = [ role - for role, permissions in [*world.roles.items(), *SYSTEM_ROLES.items()] + for role, permissions in [ + *world.roles.items(), + *SYSTEM_ROLES.items(), + ] if permission.value in permissions ] @@ -217,7 +220,10 @@ def generate_short_token(): class AnonymousInvite(models.Model): short_token = models.CharField( - db_index=True, unique=True, default=generate_short_token, max_length=150 + db_index=True, + unique=True, + default=generate_short_token, + max_length=150, ) world = models.ForeignKey( "World", related_name="anonymous_invites", on_delete=models.CASCADE diff --git a/server/venueless/core/models/streaming.py b/server/venueless/core/models/streaming.py index f9a82795..b6eba9a0 100644 --- a/server/venueless/core/models/streaming.py +++ b/server/venueless/core/models/streaming.py @@ -33,7 +33,11 @@ def generate_streamkey(self, name, days): ) token = jwt.encode( - {"name": n, "iat": iat, "exp": iat + datetime.timedelta(days=days)}, + { + "name": n, + "iat": iat, + "exp": iat + datetime.timedelta(days=days), + }, self.token_secret, algorithm="HS256", ) diff --git a/server/venueless/core/services/bbb.py b/server/venueless/core/services/bbb.py index 7ed2f970..78ffc732 100644 --- a/server/venueless/core/services/bbb.py +++ b/server/venueless/core/services/bbb.py @@ -239,9 +239,11 @@ async def get_join_url_for_room(self, room, user, moderator=False): record=config.get("record", False), voice_bridge=config.get("voice_bridge", None), prefer_server=config.get("prefer_server", None), - guest_policy="ASK_MODERATOR" - if config.get("waiting_room", False) - else "ALWAYS_ACCEPT", + guest_policy=( + "ASK_MODERATOR" + if config.get("waiting_room", False) + else "ALWAYS_ACCEPT" + ), ) create_url = get_url("create", create_params, server.url, server.secret) @@ -273,14 +275,18 @@ async def get_join_url_for_room(self, room, user, moderator=False): "meetingID": create_params["meetingID"], "fullName": escape_name(user.profile.get("display_name", "")), "userID": str(user.pk), - "password": create_params["moderatorPW"] - if moderator - else create_params["attendeePW"], + "password": ( + create_params["moderatorPW"] + if moderator + else create_params["attendeePW"] + ), "joinViaHtml5": "true", **avatar, - "guest": "true" - if not moderator and config.get("waiting_room", False) - else "false", + "guest": ( + "true" + if not moderator and config.get("waiting_room", False) + else "false" + ), "userdata-bbb_custom_style_url": scheme + self.world.domain + reverse("live:css.bbb"), @@ -387,7 +393,8 @@ async def get_recordings_for_room(self, room): # Work around an upstream bug if "///" in url_video: url_video = url_video.replace( - "///", f"//{urlparse(recordings_url).hostname}/" + "///", + f"//{urlparse(recordings_url).hostname}/", ) if ( not url_presentation diff --git a/server/venueless/core/services/chat.py b/server/venueless/core/services/chat.py index e2d6cf33..065f5aae 100644 --- a/server/venueless/core/services/chat.py +++ b/server/venueless/core/services/chat.py @@ -1,5 +1,4 @@ import re - from contextlib import suppress from channels.db import database_sync_to_async @@ -38,6 +37,7 @@ r"@([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})" ) + @database_sync_to_async def _get_channel(**kwargs): return ( @@ -66,6 +66,8 @@ def extract_mentioned_user_ids(message: str) -> set: set: A set of mentioned user IDs extracted from the message. """ return {match.group(1) for match in MENTION_RE.finditer(message)} + + class ChatService: def __init__(self, world): self.world = world @@ -156,27 +158,33 @@ def filter_mentions( ) -> set: """ Filters user IDs based on their membership or permission in a specified channel. - + Args: channel (Channel): The channel to filter the users for. uids (list): List of user IDs to be filtered. include_all_permitted (bool): If True, includes all users with permission `ROOM_CHAT_READ` in the channel's room. - + Returns: set: A set of user IDs that are either members of the channel or have the necessary permissions. """ if not uids: return set() - + if include_all_permitted: permitted_users = User.objects.filter(id__in=uids) - result = {str(u.id) for u in permitted_users if self.world.has_permission( - user=u, permission=Permission.ROOM_CHAT_READ, room=channel.room)} + result = { + str(u.id) + for u in permitted_users + if self.world.has_permission( + user=u, + permission=Permission.ROOM_CHAT_READ, + room=channel.room, + ) + } return result else: memberships = Membership.objects.filter(channel=channel, user_id__in=uids) return {str(m.user_id) for m in memberships} - @database_sync_to_async def membership_is_volatile(self, channel, uid): @@ -263,7 +271,8 @@ def get_events( user_ids = user_ids - set(users_known_to_client) users = { str(u.pk): u.serialize_public( - include_admin_info=include_admin_info, trait_badges_map=trait_badges_map + include_admin_info=include_admin_info, + trait_badges_map=trait_badges_map, ) for u in User.objects.filter(world=self.world, id__in=user_ids) } @@ -361,7 +370,7 @@ def add_reaction(self, event, reaction, user): chat_event=event, reaction=reaction, sender=user ) return self._get_event(pk=event.pk).serialize_public() - + def get_notification_counts(self, user_id: int) -> dict: """ Retrieves the count of notifications for a given user, grouped by channel ID. @@ -373,9 +382,12 @@ def get_notification_counts(self, user_id: int) -> dict: dict: A dictionary where the keys are channel IDs (as strings) and the values are the count of notifications. """ notifications = ChatEventNotification.objects.filter(recipient_id=user_id) - notification_counts = notifications.values("chat_event__channel_id").annotate(count=Count("id")) - return {str(n["chat_event__channel_id"]): n["count"] for n in notification_counts} - + notification_counts = notifications.values("chat_event__channel_id").annotate( + count=Count("id") + ) + return { + str(n["chat_event__channel_id"]): n["count"] for n in notification_counts + } @database_sync_to_async def store_notification(self, event_id: int, user_ids: list): @@ -395,7 +407,6 @@ def store_notification(self, event_id: int, user_ids: list): ] ChatEventNotification.objects.bulk_create(notifications) - @database_sync_to_async def remove_notifications(self, user_id: int, channel_id: int, max_id: int) -> bool: """ @@ -416,7 +427,6 @@ def remove_notifications(self, user_id: int, channel_id: int, max_id: int) -> bo ).delete() return deleted_count > 0 - @database_sync_to_async def get_or_create_direct_channel( self, user_ids, hide=True, hide_except: str = None @@ -576,4 +586,4 @@ async def enforce_forced_joins(self, user): await redis.sadd( f"chat:unread.notify:{channel.id}", str(user.id), - ) \ No newline at end of file + ) diff --git a/server/venueless/core/services/janus.py b/server/venueless/core/services/janus.py index 7744e446..6d3bef24 100644 --- a/server/venueless/core/services/janus.py +++ b/server/venueless/core/services/janus.py @@ -41,7 +41,12 @@ async def videoroom_add_token_if_exists(server, room_data, token, audiobridge=Fa server.url, subprotocols=["janus-protocol"] ) as websocket: await websocket.send( - json.dumps({"janus": "create", "transaction": get_random_string(length=12)}) + json.dumps( + { + "janus": "create", + "transaction": get_random_string(length=12), + } + ) ) resp = json.loads(await websocket.recv()) session_id = resp["data"]["id"] @@ -169,7 +174,12 @@ async def create_videoroom( server.url, subprotocols=["janus-protocol"] ) as websocket: await websocket.send( - json.dumps({"janus": "create", "transaction": get_random_string(length=12)}) + json.dumps( + { + "janus": "create", + "transaction": get_random_string(length=12), + } + ) ) resp = json.loads(await websocket.recv()) session_id = resp["data"]["id"] diff --git a/server/venueless/core/services/user.py b/server/venueless/core/services/user.py index 5edd1ee0..db2ed7dd 100644 --- a/server/venueless/core/services/user.py +++ b/server/venueless/core/services/user.py @@ -1,12 +1,12 @@ +import datetime as dt import json -import jwt import operator -import datetime as dt -import requests from collections import namedtuple from datetime import timedelta from functools import reduce +import jwt +import requests from channels.db import database_sync_to_async from channels.layers import get_channel_layer from django.core.paginator import InvalidPage, Paginator @@ -19,7 +19,7 @@ from ..models import AuditLog from ..models.auth import User from ..models.room import AnonymousInvite -from ..models.world import WorldView, World +from ..models.world import World, WorldView from ..permissions import Permission @@ -93,24 +93,29 @@ def get_public_users( inactive=( u["last_login"] is None or u["last_login"] < now() - timedelta(hours=36) ), - badges=sorted( - list( - { - badge - for trait, badge in trait_badges_map.items() - if trait in u["traits"] - } + badges=( + sorted( + list( + { + badge + for trait, badge in trait_badges_map.items() + if trait in u["traits"] + } + ) ) - ) - if trait_badges_map - else [], + if trait_badges_map + else [] + ), **( {"client_state": u["client_state"]} if include_admin_info and u["type"] == User.UserType.KIOSK else {} ), **( - {"moderation_state": u["moderation_state"], "token_id": u["token_id"]} + { + "moderation_state": u["moderation_state"], + "token_id": u["token_id"], + } if include_admin_info else {} ), @@ -227,7 +232,9 @@ def create_user( if anonymous_invite: user.world_grants.create(world_id=world_id, role="__anonymous_world") user.room_grants.create( - world_id=world_id, room_id=anonymous_invite.room_id, role="__anonymous_room" + world_id=world_id, + room_id=anonymous_invite.room_id, + role="__anonymous_room", ) return user @@ -249,7 +256,11 @@ def update_user( world_id=world_id, user=user, type="auth.user.traits.changed", - data={"object": str(user.pk), "old": user.traits, "new": traits}, + data={ + "object": str(user.pk), + "old": user.traits, + "new": traits, + }, ) user.traits = traits user.save(update_fields=["traits"]) @@ -317,20 +328,25 @@ def update_user( def update_fav_talks(user_token_id, talks, world_id): try: - talk_list = talks.get('schedule').get('favs') + talk_list = talks.get("schedule").get("favs") world = get_object_or_404(World, id=world_id) jwt_config = world.config.get("JWT_secrets") if not jwt_config: return - talk_token = get_user_video_token(user_token_id,jwt_config[0]) + talk_token = get_user_video_token(user_token_id, jwt_config[0]) talk_config = world.config.get("pretalx") if not talk_config: return - talk_url = talk_config.get('domain') + "/api/events/" + talk_config.get('event') + "/favourite-talk/" + talk_url = ( + talk_config.get("domain") + + "/api/events/" + + talk_config.get("event") + + "/favourite-talk/" + ) header = { "Content-Type": "application/json", - "Authorization": f"Bearer {talk_token}" + "Authorization": f"Bearer {talk_token}", } requests.post(talk_url, data=json.dumps(talk_list), headers=header) except World.DoesNotExist or Exception: @@ -341,15 +357,16 @@ def get_user_video_token(user_code, video_settings): iat = dt.datetime.utcnow() exp = iat + dt.timedelta(days=30) payload = { - "iss": video_settings.get('issuer'), - "aud": video_settings.get('audience'), + "iss": video_settings.get("issuer"), + "aud": video_settings.get("audience"), "exp": exp, "iat": iat, "uid": user_code, } - token = jwt.encode(payload, video_settings.get('secret'), algorithm="HS256") + token = jwt.encode(payload, video_settings.get("secret"), algorithm="HS256") return token + def start_view(user: User, delete=False): # The majority of WorldView that go "abandoned" (i.e. ``end`` is never set) are likely caused by server # crashes or restarts, in which case ``end`` can't be set. However, after a server crash, the client @@ -637,17 +654,19 @@ def list_users( pretalx_id=u["pretalx_id"], inactive=u["last_login"] is None or u["last_login"] < now() - timedelta(hours=36), - badges=sorted( - list( - { - badge - for trait, badge in trait_badges_map.items() - if trait in u["traits"] - } + badges=( + sorted( + list( + { + badge + for trait, badge in trait_badges_map.items() + if trait in u["traits"] + } + ) ) - ) - if trait_badges_map - else [], + if trait_badges_map + else [] + ), **( dict( moderation_state=u["moderation_state"], @@ -682,4 +701,4 @@ async def user_broadcast(event_type, data, user_id, socket_id): "data": data, "socket": socket_id, }, - ) \ No newline at end of file + ) diff --git a/server/venueless/core/services/world.py b/server/venueless/core/services/world.py index 51b063f9..a63872c5 100644 --- a/server/venueless/core/services/world.py +++ b/server/venueless/core/services/world.py @@ -15,10 +15,7 @@ from venueless.core.models import AuditLog, Channel, Room, World from venueless.core.models.auth import ShortToken -from venueless.core.models.room import ( - RoomConfigSerializer, - RoomView -) +from venueless.core.models.room import RoomConfigSerializer, RoomView from venueless.core.permissions import Permission @@ -30,8 +27,8 @@ class WorldConfigSerializer(serializers.Serializer): pretalx = serializers.DictField() title = serializers.CharField() locale = serializers.CharField() - dateLocale = serializers.CharField() - videoPlayer = serializers.DictField(allow_null=True) + date_locale = serializers.CharField() + video_player = serializers.DictField(allow_null=True) timezone = serializers.ChoiceField(choices=[(a, a) for a in common_timezones]) connection_limit = serializers.IntegerField(allow_null=True) available_permissions = serializers.SerializerMethodField("_available_permissions") @@ -190,7 +187,8 @@ def get_world_config_for_user(world, user): "profile_fields": world.config.get("profile_fields", []), "social_logins": world.config.get("social_logins", []), "iframe_blockers": world.config.get( - "iframe_blockers", {"default": {"enabled": False, "policy_url": None}} + "iframe_blockers", + {"default": {"enabled": False, "policy_url": None}}, ), "onsite_traits": world.config.get("onsite_traits", []), }, @@ -256,7 +254,8 @@ async def create_room(world, data, creator): user=creator, permission=Permission.WORLD_ROOMS_CREATE_CHAT ): raise ValidationError( - "This user is not allowed to create a room of this type.", code="denied" + "This user is not allowed to create a room of this type.", + code="denied", ) m = [m for m in data.get("modules", []) if m["type"] == "chat.native"][0] m["config"] = {"volatile": m.get("config", {}).get("volatile", False)} @@ -265,7 +264,8 @@ async def create_room(world, data, creator): user=creator, permission=Permission.WORLD_ROOMS_CREATE_BBB ): raise ValidationError( - "This user is not allowed to create a room of this type.", code="denied" + "This user is not allowed to create a room of this type.", + code="denied", ) m = [m for m in data.get("modules", []) if m["type"] == "call.bigbluebutton"][0] m["config"] = world.config.get("bbb_defaults", {}) @@ -275,7 +275,8 @@ async def create_room(world, data, creator): user=creator, permission=Permission.WORLD_ROOMS_CREATE_STAGE ): raise ValidationError( - "This user is not allowed to create a room of this type.", code="denied" + "This user is not allowed to create a room of this type.", + code="denied", ) m = [m for m in data.get("modules", []) if m["type"] == "livestream.native"][0] m["config"] = {"hls_url": m.get("config", {}).get("hls_url", "")} @@ -284,7 +285,8 @@ async def create_room(world, data, creator): user=creator, permission=Permission.ROOM_UPDATE ): raise ValidationError( - "This user is not allowed to create a room of this type.", code="denied" + "This user is not allowed to create a room of this type.", + code="denied", ) else: raise ValidationError( @@ -310,7 +312,10 @@ async def create_room(world, data, creator): f"world.{world.id}", {"type": "room.create", "room": str(room.id)} ) - return {"room": str(room.id), "channel": str(channel.id) if channel else None} + return { + "room": str(room.id), + "channel": str(channel.id) if channel else None, + } async def get_room_config_for_user(room: str, world_id: str, user): @@ -371,14 +376,14 @@ def _config_serializer(world, *args, **kwargs): "theme": world.config.get("theme", {}), "title": world.title, "locale": world.locale, - "dateLocale": world.config.get("dateLocale", "en-ie"), + "date_locale": world.config.get("date_locale", "en-ie"), "roles": world.roles, "bbb_defaults": bbb_defaults, "track_exhibitor_views": world.config.get("track_exhibitor_views", True), "track_room_views": world.config.get("track_room_views", True), "track_world_views": world.config.get("track_world_views", False), "pretalx": world.config.get("pretalx", {}), - "videoPlayer": world.config.get("videoPlayer"), + "video_player": world.config.get("video_player"), "timezone": world.timezone, "trait_grants": world.trait_grants, "connection_limit": world.config.get("connection_limit", 0), @@ -388,7 +393,8 @@ def _config_serializer(world, *args, **kwargs): "conftool_url": world.config.get("conftool_url", ""), "conftool_password": world.config.get("conftool_password", ""), "iframe_blockers": world.config.get( - "iframe_blockers", {"default": {"enabled": False, "policy_url": None}} + "iframe_blockers", + {"default": {"enabled": False, "policy_url": None}}, ), }, *args, diff --git a/server/venueless/core/utils/statsd.py b/server/venueless/core/utils/statsd.py index ffdcc933..3823e10a 100644 --- a/server/venueless/core/utils/statsd.py +++ b/server/venueless/core/utils/statsd.py @@ -43,7 +43,8 @@ def __init__(self, loop, host, port, prefix): async def start(self): logger.debug(f"StatsD connection to {self.host}:{self.port} established") _, self.protocol = await self.loop.create_datagram_endpoint( - lambda: StatsdProtocol(self.prefix), remote_addr=(self.host, self.port) + lambda: StatsdProtocol(self.prefix), + remote_addr=(self.host, self.port), ) def stop(self): diff --git a/server/venueless/graphs/report.py b/server/venueless/graphs/report.py index 6bb2ada7..845f2a6a 100644 --- a/server/venueless/graphs/report.py +++ b/server/venueless/graphs/report.py @@ -275,7 +275,10 @@ def story_for_room(self, room: Room): aggs = users_with_duration.aggregate(a=Avg("total_duration")) tdata = [ - [_("Total number of unique viewers"), str(unique_users.distinct().count())], + [ + _("Total number of unique viewers"), + str(unique_users.distinct().count()), + ], [_("Average time spent in room"), str(aggs["a"] or 0)], [ _("Median time spent in room"), @@ -419,7 +422,12 @@ def story_for_exhibitors(self): ] tdata = [ - [_("Exhibitor"), _("Views"), _("Unique viewers"), _("Contact requests")] + [ + _("Exhibitor"), + _("Views"), + _("Unique viewers"), + _("Contact requests"), + ] ] qs = self.world.exhibitors.annotate( @@ -611,5 +619,7 @@ def _graph(self, fig): imgdata.seek(0) w = self.pagesize[0] - 30 * mm return Image( - imgdata, width=w, height=fig.get_figheight() / fig.get_figwidth() * w + imgdata, + width=w, + height=fig.get_figheight() / fig.get_figwidth() * w, ) diff --git a/server/venueless/graphs/tasks.py b/server/venueless/graphs/tasks.py index 074791b8..1faa0ed1 100644 --- a/server/venueless/graphs/tasks.py +++ b/server/venueless/graphs/tasks.py @@ -228,7 +228,9 @@ def generate_room_views(world, input=None): adds = defaultdict(set) for v in views: bucket = v["start"].replace( - second=0, microsecond=0, minute=v["start"].minute // 5 * 5 + second=0, + microsecond=0, + minute=v["start"].minute // 5 * 5, ) while bucket < end and (not v["end"] or bucket < v["end"]): adds[bucket].add(v["user"]) @@ -309,9 +311,11 @@ def generate_session_views(world, input=None): ws.append( [ pretalx_uni18n(talk["title"]), - room_cache[talk["room"]].name - if talk["room"] in room_cache - else "?", + ( + room_cache[talk["room"]].name + if talk["room"] in room_cache + else "?" + ), talk_start.astimezone(tz).date(), talk_start.astimezone(tz).time(), talk_end.astimezone(tz).date(), @@ -607,7 +611,10 @@ def generate_attendee_session_list(world, input=None): sum_views = min( sum( ( - min(v.end or min(now(), v.start + timedelta(hours=3)), talk_end) + min( + v.end or min(now(), v.start + timedelta(hours=3)), + talk_end, + ) - max(v.start, talk_start) for v in u.room_views_for_talk ), diff --git a/server/venueless/graphs/urls.py b/server/venueless/graphs/urls.py index a2df70c6..3666bdb0 100644 --- a/server/venueless/graphs/urls.py +++ b/server/venueless/graphs/urls.py @@ -4,6 +4,7 @@ urlpatterns = [ re_path( - "attendance.(?P(svg|png|pdf))$", views.RoomAttendanceGraphView.as_view() + "attendance.(?P(svg|png|pdf))$", + views.RoomAttendanceGraphView.as_view(), ), ] diff --git a/server/venueless/graphs/utils.py b/server/venueless/graphs/utils.py index 71c0f63b..15632462 100644 --- a/server/venueless/graphs/utils.py +++ b/server/venueless/graphs/utils.py @@ -38,16 +38,16 @@ def __init__(self, filename_or_object, width=None, height=None, kind="direct"): self.drawWidth = self._w * factor self.drawHeight = self._h * factor - def wrap(self, aW, aH): + def wrap(self, aw, ah): return self.drawWidth, self.drawHeight - def drawOn(self, canv, x, y, _sW=0): - if _sW > 0 and hasattr(self, "hAlign"): + def drawOn(self, canv, x, y, _sw=0): + if _sw > 0 and hasattr(self, "hAlign"): a = self.hAlign if a in ("CENTER", "CENTRE", TA_CENTER): - x += 0.5 * _sW + x += 0.5 * _sw elif a in ("RIGHT", TA_RIGHT): - x += _sW + x += _sw elif a not in ("LEFT", TA_LEFT): raise ValueError("Bad hAlign value " + str(a)) diff --git a/server/venueless/graphs/views.py b/server/venueless/graphs/views.py index 4c850ab8..9330c9e8 100644 --- a/server/venueless/graphs/views.py +++ b/server/venueless/graphs/views.py @@ -1,6 +1,6 @@ import io -import re import logging +import re from collections import Counter, defaultdict from datetime import timedelta from os.path import dirname diff --git a/server/venueless/importers/conftool.py b/server/venueless/importers/conftool.py index fb928097..31ada938 100644 --- a/server/venueless/importers/conftool.py +++ b/server/venueless/importers/conftool.py @@ -264,7 +264,8 @@ def mirror_conftool_file(world, url, password, nonce, preview=False): images[0].save(o, format="PNG") o.seek(0) psf.file.save( - f"poster_{contenthash}_preview.png", ContentFile(o.getvalue()) + f"poster_{contenthash}_preview.png", + ContentFile(o.getvalue()), ) except Exception: psf.delete() @@ -303,7 +304,8 @@ def create_posters_from_conftool( for paper in root.xpath("paper"): try: poster = Poster.objects.get( - world=world, import_id=f"conftool/{paper.xpath('paperID')[0].text}" + world=world, + import_id=f"conftool/{paper.xpath('paperID')[0].text}", ) except Poster.DoesNotExist: poster = Poster( @@ -353,7 +355,11 @@ def create_posters_from_conftool( categories = m["config"].get("categories", []) if category_id not in [tt["id"] for tt in categories]: categories.append( - {"id": category_id, "label": category_name, "color": ""} + { + "id": category_id, + "label": category_name, + "color": "", + } ) m["config"]["categories"] = categories diff --git a/server/venueless/live/decorators.py b/server/venueless/live/decorators.py index c412f20e..df8b4847 100644 --- a/server/venueless/live/decorators.py +++ b/server/venueless/live/decorators.py @@ -99,14 +99,16 @@ async def wrapped(self, body, *args): self.module_config = module_config[0] else: raise ConsumerException( - "room.unknown", "Room does not contain a matching module." + "room.unknown", + "Room does not contain a matching module.", ) if permission_required is not None: if not getattr(self.consumer, "user", None): # pragma: no cover # Just a precaution, should never be called since MainConsumer.receive_json already checks this raise ConsumerException( - "protocol.unauthenticated", "No authentication provided." + "protocol.unauthenticated", + "No authentication provided.", ) if not await self.consumer.world.has_permission_async( user=self.consumer.user, diff --git a/server/venueless/live/modules/auth.py b/server/venueless/live/modules/auth.py index 94bc1124..783c9bbf 100644 --- a/server/venueless/live/modules/auth.py +++ b/server/venueless/live/modules/auth.py @@ -143,15 +143,18 @@ async def login(self, body): } if not await self.consumer.world.has_permission_async( - user=self.consumer.user, permission=Permission.WORLD_CONNECTIONS_UNLIMITED + user=self.consumer.user, + permission=Permission.WORLD_CONNECTIONS_UNLIMITED, ): await self._enforce_connection_limit() await self.consumer.channel_layer.group_add( - GROUP_USER.format(id=self.consumer.user.id), self.consumer.channel_name + GROUP_USER.format(id=self.consumer.user.id), + self.consumer.channel_name, ) await self.consumer.channel_layer.group_add( - GROUP_WORLD.format(id=self.consumer.world.id), self.consumer.channel_name + GROUP_WORLD.format(id=self.consumer.world.id), + self.consumer.channel_name, ) await ChatService(self.consumer.world).enforce_forced_joins(self.consumer.user) @@ -348,13 +351,15 @@ async def list(self, body): users = await get_public_users( self.consumer.world.pk, include_admin_info=await self.consumer.world.has_permission_async( - user=self.consumer.user, permission=Permission.WORLD_USERS_MANAGE + user=self.consumer.user, + permission=Permission.WORLD_USERS_MANAGE, ), type=body.get("type", User.UserType.PERSON), include_banned=not body or body.get("include_banned", True) and await self.consumer.world.has_permission_async( - user=self.consumer.user, permission=Permission.WORLD_USERS_MANAGE + user=self.consumer.user, + permission=Permission.WORLD_USERS_MANAGE, ), trait_badges_map=self.consumer.world.config.get("trait_badges_map"), ) @@ -386,11 +391,13 @@ async def user_list(self, body): badge=badge, search_fields=search_fields, include_admin_info=await self.consumer.world.has_permission_async( - user=self.consumer.user, permission=Permission.WORLD_USERS_MANAGE + user=self.consumer.user, + permission=Permission.WORLD_USERS_MANAGE, ), include_banned=body.get("include_banned", True) and await self.consumer.world.has_permission_async( - user=self.consumer.user, permission=Permission.WORLD_USERS_MANAGE + user=self.consumer.user, + permission=Permission.WORLD_USERS_MANAGE, ), trait_badges_map=self.consumer.world.config.get("trait_badges_map"), ) @@ -554,9 +561,9 @@ def create_user(): token_id=uid, world=self.consumer.world, show_publicly=False, - profile=body["profile"] - if isinstance(body.get("profile"), dict) - else {}, + profile=( + body["profile"] if isinstance(body.get("profile"), dict) else {} + ), traits=[], ) user.world_grants.create(world=self.consumer.world, role="__kiosk") @@ -603,4 +610,4 @@ def get_user(uid): if user: await self.consumer.send_success(user) else: - await self.consumer.send_error(code="user.not_found") \ No newline at end of file + await self.consumer.send_error(code="user.not_found") diff --git a/server/venueless/live/modules/chat.py b/server/venueless/live/modules/chat.py index 964ec861..b3c4fb93 100644 --- a/server/venueless/live/modules/chat.py +++ b/server/venueless/live/modules/chat.py @@ -5,11 +5,15 @@ import asgiref import emoji - -from sentry_sdk import configure_scope from channels.db import database_sync_to_async +from sentry_sdk import configure_scope + from venueless.core.permissions import Permission -from venueless.core.services.chat import ChatService, get_channel +from venueless.core.services.chat import ( + ChatService, + extract_mentioned_user_ids, + get_channel, +) from venueless.core.services.user import get_public_users from venueless.core.utils.redis import aredis from venueless.live.channels import GROUP_CHAT, GROUP_USER @@ -22,11 +26,6 @@ from venueless.live.exceptions import ConsumerException from venueless.live.modules.base import BaseModule from venueless.storage.tasks import retrieve_preview_information -from venueless.core.services.chat import ( - ChatService, - extract_mentioned_user_ids, - get_channel, -) logger = logging.getLogger(__name__) @@ -70,7 +69,8 @@ async def wrapped(self, body, *args): self.module_config = module_config[0] else: raise ConsumerException( - "room.unknown", "Room does not contain a matching module." + "room.unknown", + "Room does not contain a matching module.", ) if not getattr(self.consumer, "user", None): # pragma: no cover @@ -122,7 +122,8 @@ async def _subscribe(self, volatile=False): self.users_known_to_client.clear() # client implementation nukes cache on channel switch self.channels_subscribed.add(self.channel) await self.consumer.channel_layer.group_add( - GROUP_CHAT.format(channel=self.channel_id), self.consumer.channel_name + GROUP_CHAT.format(channel=self.channel_id), + self.consumer.channel_name, ) await self.service.track_subscription( self.channel_id, self.consumer.user.id, self.consumer.socket_id @@ -134,14 +135,17 @@ async def _subscribe(self, volatile=False): "unread_pointer": await self.service.get_highest_nonmember_id_in_channel( self.channel_id ), - "members": await self.service.get_channel_users( - self.channel, - include_admin_info=await self.consumer.world.has_permission_async( - user=self.consumer.user, permission=Permission.WORLD_USERS_MANAGE - ), - ) - if not volatile - else [], + "members": ( + await self.service.get_channel_users( + self.channel, + include_admin_info=await self.consumer.world.has_permission_async( + user=self.consumer.user, + permission=Permission.WORLD_USERS_MANAGE, + ), + ) + if not volatile + else [] + ), } async def _unsubscribe(self, clean_volatile_membership=True): @@ -157,7 +161,8 @@ async def _unsubscribe(self, clean_volatile_membership=True): ): await self._leave(volatile=True) await self.consumer.channel_layer.group_discard( - GROUP_CHAT.format(channel=self.channel_id), self.consumer.channel_name + GROUP_CHAT.format(channel=self.channel_id), + self.consumer.channel_name, ) @command("subscribe") @@ -174,7 +179,8 @@ async def subscribe(self, body): @command("join") @room_action( - permission_required=Permission.ROOM_CHAT_JOIN, module_required="chat.native" + permission_required=Permission.ROOM_CHAT_JOIN, + module_required="chat.native", ) async def join(self, body): if not self.consumer.user.profile.get("display_name"): @@ -261,7 +267,8 @@ async def leave(self, body): await self._leave(self.module_config.get("volatile", False)) async with aredis() as redis: await redis.srem( - f"chat:unread.notify:{self.channel_id}", str(self.consumer.user.id) + f"chat:unread.notify:{self.channel_id}", + str(self.consumer.user.id), ) else: await self.service.hide_channel_user(self.channel_id, self.consumer.user.id) @@ -298,7 +305,8 @@ async def fetch(self, body): skip_membership=volatile_config, users_known_to_client=self.users_known_to_client, include_admin_info=await self.consumer.world.has_permission_async( - user=self.consumer.user, permission=Permission.WORLD_USERS_MANAGE + user=self.consumer.user, + permission=Permission.WORLD_USERS_MANAGE, ), trait_badges_map=self.consumer.world.config.get("trait_badges_map"), ) @@ -313,9 +321,14 @@ async def mark_read(self, body): async with aredis() as redis: tr = redis.pipeline(transaction=False) tr.hset( - f"chat:read:{self.consumer.user.id}", self.channel_id, body.get("id") + f"chat:read:{self.consumer.user.id}", + self.channel_id, + body.get("id"), + ) + tr.sadd( + f"chat:unread.notify:{self.channel_id}", + str(self.consumer.user.id), ) - tr.sadd(f"chat:unread.notify:{self.channel_id}", str(self.consumer.user.id)) await tr.execute() await self.service.remove_notifications( self.consumer.user.id, self.channel_id, body.get("id") @@ -426,7 +439,9 @@ async def send(self, body): ) async with aredis() as redis: await redis.setex( - f"chat:direct:shownall:{self.channel_id}", 3600 * 24 * 7, "true" + f"chat:direct:shownall:{self.channel_id}", + 3600 * 24 * 7, + "true", ) event = await self.service.create_event( @@ -457,6 +472,7 @@ async def _publish_new_pointers(users): "data": {self.channel_id: event["event_id"]}, }, ) + async def _notify_users(users): users = [u for u in users if u != str(self.consumer.user.id)] await self.service.store_notification(event["event_id"], users) @@ -638,7 +654,7 @@ async def publish_event(self, body): user_profiles_required |= extract_mentioned_user_ids( data["content"].get("body", "") ) - + user_profiles_required -= self.users_known_to_client data["users"] = {} @@ -647,7 +663,8 @@ async def publish_event(self, body): self.consumer.world.id, ids=list(user_profiles_required), include_admin_info=await self.consumer.world.has_permission_async( - user=self.consumer.user, permission=Permission.WORLD_USERS_MANAGE + user=self.consumer.user, + permission=Permission.WORLD_USERS_MANAGE, ), trait_badges_map=self.consumer.world.config.get("trait_badges_map"), ) @@ -665,8 +682,14 @@ async def direct_create(self, body): hide = body.get("hide", True) - channel, created, users = await self.service.get_or_create_direct_channel( - user_ids=user_ids, hide=hide, hide_except=str(self.consumer.user.id) + ( + channel, + created, + users, + ) = await self.service.get_or_create_direct_channel( + user_ids=user_ids, + hide=hide, + hide_except=str(self.consumer.user.id), ) if not channel: raise ConsumerException("chat.denied") @@ -707,7 +730,9 @@ async def direct_create(self, body): if not hide: async with aredis() as redis: await redis.setex( - f"chat:direct:shownall:{self.channel_id}", 3600 * 24 * 7, "true" + f"chat:direct:shownall:{self.channel_id}", + 3600 * 24 * 7, + "true", ) reply["id"] = str(channel.id) @@ -723,4 +748,4 @@ async def dispatch_disconnect(self, close_code): for channel in frozenset(self.channels_subscribed): self.channel = channel self.channel_id = channel.pk - await self._unsubscribe() \ No newline at end of file + await self._unsubscribe() diff --git a/server/venueless/live/modules/exhibition.py b/server/venueless/live/modules/exhibition.py index 3a61e70c..6d4a99d8 100644 --- a/server/venueless/live/modules/exhibition.py +++ b/server/venueless/live/modules/exhibition.py @@ -33,7 +33,8 @@ async def dispatch_disconnect(self, close_code): @command("list.all") async def list_all(self, body): if not await self.consumer.world.has_permission_async( - user=self.consumer.user, permission=Permission.WORLD_ROOMS_CREATE_EXHIBITION + user=self.consumer.user, + permission=Permission.WORLD_ROOMS_CREATE_EXHIBITION, ): exhibitors = await self.service.get_all_exhibitors( staff_includes_user=self.consumer.user @@ -57,7 +58,10 @@ async def delete(self, body): )(user_id) await self.consumer.channel_layer.group_send( GROUP_USER.format(id=str(user_id)), - {"type": "exhibition.exhibition_data_update", "data": data}, + { + "type": "exhibition.exhibition_data_update", + "data": data, + }, ) await self.consumer.send_success({}) @@ -68,7 +72,8 @@ async def patch(self, body): staff += await self.service.get_staff(exhibitor_id=body["id"]) if await self.consumer.world.has_permission_async( - user=self.consumer.user, permission=Permission.WORLD_ROOMS_CREATE_EXHIBITION + user=self.consumer.user, + permission=Permission.WORLD_ROOMS_CREATE_EXHIBITION, ): exclude_fields = set() elif self.consumer.user.id in staff: @@ -119,7 +124,8 @@ async def list(self, body): @command("get") async def get(self, body): exhibitor = await self.service.get_exhibitor( - exhibitor_id=body["exhibitor"], track_view_for_user=self.consumer.user + exhibitor_id=body["exhibitor"], + track_view_for_user=self.consumer.user, ) if not exhibitor: await self.consumer.send_error("exhibition.unknown_exhibitor") @@ -175,7 +181,8 @@ async def contact_cancel(self, body): async def contact_accept(self, body): channel = body["channel"] request = await self.service.accept( - contact_request_id=body["contact_request"], staff=self.consumer.user + contact_request_id=body["contact_request"], + staff=self.consumer.user, ) if not request: await self.consumer.send_error("exhibition.unknown_contact_request") @@ -196,7 +203,10 @@ async def contact_accept(self, body): for user_id in staff: await self.consumer.channel_layer.group_send( GROUP_USER.format(id=str(user_id)), - {"type": "exhibition.contact_close", "contact_request": request}, + { + "type": "exhibition.contact_close", + "contact_request": request, + }, ) @event("contact_request") diff --git a/server/venueless/live/modules/poster.py b/server/venueless/live/modules/poster.py index 2f6c04cc..a7f8a242 100644 --- a/server/venueless/live/modules/poster.py +++ b/server/venueless/live/modules/poster.py @@ -18,7 +18,8 @@ def __init__(self, *args, **kwargs): @command("list.all") async def list_all(self, body): if not await self.consumer.world.has_permission_async( - user=self.consumer.user, permission=Permission.WORLD_ROOMS_CREATE_POSTER + user=self.consumer.user, + permission=Permission.WORLD_ROOMS_CREATE_POSTER, ): posters = await self.service.get_all_posters( presenter_includes_user=self.consumer.user, list_format=True @@ -43,7 +44,8 @@ async def patch(self, body): presenters = await self.service.get_presenters(poster_id=body["id"]) if await self.consumer.world.has_permission_async( - user=self.consumer.user, permission=Permission.WORLD_ROOMS_CREATE_POSTER + user=self.consumer.user, + permission=Permission.WORLD_ROOMS_CREATE_POSTER, ): exclude_fields = set() elif self.consumer.user.id in presenters: diff --git a/server/venueless/live/modules/question.py b/server/venueless/live/modules/question.py index 25ed4b2d..8c6c20de 100644 --- a/server/venueless/live/modules/question.py +++ b/server/venueless/live/modules/question.py @@ -27,7 +27,8 @@ class QuestionModule(BaseModule): @command("ask") @room_action( - permission_required=Permission.ROOM_QUESTION_ASK, module_required="question" + permission_required=Permission.ROOM_QUESTION_ASK, + module_required="question", ) async def ask_question(self, body): if not self.module_config.get("active", False): @@ -39,9 +40,11 @@ async def ask_question(self, body): content=body.get("content"), sender=self.consumer.user, room=self.room, - state=Question.States.MOD_QUEUE - if requires_moderation - else Question.States.VISIBLE, + state=( + Question.States.MOD_QUEUE + if requires_moderation + else Question.States.VISIBLE + ), ) await self.consumer.send_success({"question": question}) @@ -121,7 +124,8 @@ async def delete_question(self, body): @command("vote") @room_action( - permission_required=Permission.ROOM_QUESTION_VOTE, module_required="question" + permission_required=Permission.ROOM_QUESTION_VOTE, + module_required="question", ) async def vote(self, body): if not self.module_config.get("active", False): diff --git a/server/venueless/live/modules/room.py b/server/venueless/live/modules/room.py index 768ab87c..a6faab01 100644 --- a/server/venueless/live/modules/room.py +++ b/server/venueless/live/modules/room.py @@ -11,10 +11,7 @@ from django.utils.timezone import now from sentry_sdk import add_breadcrumb, configure_scope -from venueless.core.models.room import ( - AnonymousInvite, - RoomConfigSerializer, -) +from venueless.core.models.room import AnonymousInvite, RoomConfigSerializer from venueless.core.permissions import Permission from venueless.core.services.poll import get_polls, get_voted_polls from venueless.core.services.reactions import store_reaction @@ -407,7 +404,8 @@ async def change_schedule_data(self, body): key in ["title", "session", "computeSession"] for key in data.keys() ): raise ConsumerException( - code="room.unknown_schedule_data", message="Unknown schedule data" + code="room.unknown_schedule_data", + message="Unknown schedule data", ) await self.consumer.send_success({}) @@ -421,7 +419,11 @@ async def change_schedule_data(self, body): ) await self.consumer.channel_layer.group_send( GROUP_ROOM.format(id=self.room.pk), - {"type": "room.schedule", "schedule_data": data, "room": str(self.room.pk)}, + { + "type": "room.schedule", + "schedule_data": data, + "room": str(self.room.pk), + }, ) @event("schedule") @@ -435,7 +437,10 @@ async def push_schedule_data(self, body): await self.consumer.send_json( [ body["type"], - {"room": config["id"], "schedule_data": config.get("schedule_data")}, + { + "room": config["id"], + "schedule_data": config.get("schedule_data"), + }, ] ) diff --git a/server/venueless/live/modules/roulette.py b/server/venueless/live/modules/roulette.py index f6df8123..c4f283aa 100644 --- a/server/venueless/live/modules/roulette.py +++ b/server/venueless/live/modules/roulette.py @@ -29,7 +29,10 @@ async def start(self, body): self.used = True request, room_id, recent_pairs = await roulette_request( - self.consumer.user, self.room, self.consumer.socket_id, self.module_config + self.consumer.user, + self.room, + self.consumer.socket_id, + self.module_config, ) if room_id: await self.consumer.send_success( diff --git a/server/venueless/live/modules/world.py b/server/venueless/live/modules/world.py index 9c026a0a..41da3cca 100644 --- a/server/venueless/live/modules/world.py +++ b/server/venueless/live/modules/world.py @@ -50,7 +50,10 @@ async def push_world_update(self, body): async def push_schedule_update(self, body): await self.consumer.world.refresh_from_db_if_outdated(allowed_age=0) await self.consumer.send_json( - ["world.schedule.updated", self.consumer.world.config.get("pretalx", {})] + [ + "world.schedule.updated", + self.consumer.world.config.get("pretalx", {}), + ] ) @event("user_count_change") @@ -81,7 +84,7 @@ async def config_patch(self, body): if s.is_valid(): config_fields = ( "theme", - "dateLocale", + "date_locale", "connection_limit", "bbb_defaults", "pretalx", @@ -96,7 +99,13 @@ async def config_patch(self, body): "iframe_blockers", "social_logins", ) - model_fields = ("title", "locale", "timezone", "roles", "trait_grants") + model_fields = ( + "title", + "locale", + "timezone", + "roles", + "trait_grants", + ) update_fields = set() for f in model_fields: diff --git a/server/venueless/live/modules/zoom.py b/server/venueless/live/modules/zoom.py index 2e4e718b..f326c355 100644 --- a/server/venueless/live/modules/zoom.py +++ b/server/venueless/live/modules/zoom.py @@ -30,7 +30,11 @@ async def room_url(self, body): data = signing.dumps( { "mn": int( - re.sub("[^0-9]", "", self.module_config.get("meeting_number") or "") + re.sub( + "[^0-9]", + "", + self.module_config.get("meeting_number") or "", + ) ), "pw": self.module_config.get("password"), "un": self.consumer.user.profile.get("display_name"), diff --git a/server/venueless/live/views.py b/server/venueless/live/views.py index a7893ed8..d3725fff 100644 --- a/server/venueless/live/views.py +++ b/server/venueless/live/views.py @@ -155,9 +155,9 @@ def get(self, request, *args, **kwargs): "features": world.feature_flags, "externalAuthUrl": world.external_auth_url, "locale": world.locale, - "dateLocale": world.config.get("dateLocale", "en-ie"), + "date_locale": world.config.get("date_locale", "en-ie"), "theme": world.config.get("theme", {}), - "videoPlayer": world.config.get("videoPlayer", {}), + "video_player": world.config.get("video_player", {}), "mux": world.config.get("mux", {}), } ) diff --git a/server/venueless/platforms/storage/nanocdn.py b/server/venueless/platforms/storage/nanocdn.py index 932423a2..54040bf1 100644 --- a/server/venueless/platforms/storage/nanocdn.py +++ b/server/venueless/platforms/storage/nanocdn.py @@ -108,7 +108,8 @@ def _save(self, name, content): if "." in os.path.basename(name): bname, ext = os.path.basename(name).rsplit(".", 1) name = os.path.join( - os.path.dirname(name), bname + "." + sha1.hexdigest()[:14] + "." + ext + os.path.dirname(name), + bname + "." + sha1.hexdigest()[:14] + "." + ext, ) else: name = os.path.join( diff --git a/server/venueless/settings.py b/server/venueless/settings.py index 503d8b4d..304718ce 100644 --- a/server/venueless/settings.py +++ b/server/venueless/settings.py @@ -67,7 +67,8 @@ ) MAIL_FROM = SERVER_EMAIL = DEFAULT_FROM_EMAIL = os.environ.get( - "VENUELESS_MAIL_FROM", config.get("mail", "from", fallback="admin@localhost") + "VENUELESS_MAIL_FROM", + config.get("mail", "from", fallback="admin@localhost"), ) if DEBUG: EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend" @@ -95,13 +96,15 @@ config.get("database", "backend", fallback="postgresql"), ), "NAME": os.getenv( - "VENUELESS_DB_NAME", config.get("database", "name", fallback="venueless") + "VENUELESS_DB_NAME", + config.get("database", "name", fallback="venueless"), ), "USER": os.getenv( "VENUELESS_DB_USER", config.get("database", "user", fallback="") ), "PASSWORD": os.getenv( - "VENUELESS_DB_PASS", config.get("database", "password", fallback="") + "VENUELESS_DB_PASS", + config.get("database", "password", fallback=""), ), "HOST": os.getenv( "VENUELESS_DB_HOST", config.get("database", "host", fallback="") @@ -152,7 +155,8 @@ REDIS_USE_PUBSUB = os.getenv( - "VENUELESS_REDIS_USE_PUBSUB", config.get("redis", "use_pubsub", fallback="false") + "VENUELESS_REDIS_USE_PUBSUB", + config.get("redis", "use_pubsub", fallback="false"), ) in (True, "yes", "on", "true", "True", "1") CHANNEL_LAYERS = { @@ -175,12 +179,14 @@ } SITE_URL = os.getenv( - "VENUELESS_SITE_URL", config.get("venueless", "url", fallback="http://localhost") + "VENUELESS_SITE_URL", + config.get("venueless", "url", fallback="http://localhost"), ) ALLOWED_HOSTS = ["*"] SHORT_URL = os.getenv( - "VENUELESS_SHORT_URL", config.get("venueless", "short_url", fallback=SITE_URL) + "VENUELESS_SHORT_URL", + config.get("venueless", "short_url", fallback=SITE_URL), ) if os.getenv("VENUELESS_COOKIE_DOMAIN", ""): @@ -194,7 +200,8 @@ ) WEBSOCKET_PROTOCOL = os.getenv( - "VENUELESS_WEBSOCKET_PROTOCOL", config.get("websocket", "protocol", fallback="wss") + "VENUELESS_WEBSOCKET_PROTOCOL", + config.get("websocket", "protocol", fallback="wss"), ) STORAGES = { @@ -231,14 +238,16 @@ TWITTER_CLIENT_ID = os.getenv( - "VENUELESS_TWITTER_CLIENT_ID", config.get("twitter", "client_id", fallback="") + "VENUELESS_TWITTER_CLIENT_ID", + config.get("twitter", "client_id", fallback=""), ) TWITTER_CLIENT_SECRET = os.getenv( "VENUELESS_TWITTER_CLIENT_SECRET", config.get("twitter", "client_secret", fallback=""), ) LINKEDIN_CLIENT_ID = os.getenv( - "VENUELESS_LINKEDIN_CLIENT_ID", config.get("linkedin", "client_id", fallback="") + "VENUELESS_LINKEDIN_CLIENT_ID", + config.get("linkedin", "client_id", fallback=""), ) LINKEDIN_CLIENT_SECRET = os.getenv( "VENUELESS_LINKEDIN_CLIENT_SECRET", @@ -400,7 +409,11 @@ }, }, "loggers": { - "": {"handlers": ["file", "console"], "level": loglevel, "propagate": True}, + "": { + "handlers": ["file", "console"], + "level": loglevel, + "propagate": True, + }, "django.request": { "handlers": ["file", "console"], "level": loglevel, diff --git a/server/venueless/social/views/linkedin.py b/server/venueless/social/views/linkedin.py index 983ac2e0..4a0cb05b 100644 --- a/server/venueless/social/views/linkedin.py +++ b/server/venueless/social/views/linkedin.py @@ -18,7 +18,9 @@ def start_view(request): try: data = loads( - request.GET.get("token"), salt="venueless.social.start", max_age=600 + request.GET.get("token"), + salt="venueless.social.start", + max_age=600, ) except BadSignature: return HttpResponse("Invalid request token", status=403) @@ -120,9 +122,11 @@ def return_view(request): user, "linkedin", name=d["localizedFirstName"] + " " + d["localizedLastName"], - url=("https://www.linkedin.com/in/" + d["vanityName"]) - if "vanityName" in d - else None, + url=( + ("https://www.linkedin.com/in/" + d["vanityName"]) + if "vanityName" in d + else None + ), **kwargs, ) diff --git a/server/venueless/social/views/twitter.py b/server/venueless/social/views/twitter.py index a042fd85..2f92725d 100644 --- a/server/venueless/social/views/twitter.py +++ b/server/venueless/social/views/twitter.py @@ -18,7 +18,9 @@ def start_view(request): try: data = loads( - request.GET.get("token"), salt="venueless.social.start", max_age=600 + request.GET.get("token"), + salt="venueless.social.start", + max_age=600, ) except BadSignature: return HttpResponse("Invalid request token", status=403) diff --git a/server/venueless/storage/migrations/0001_initial.py b/server/venueless/storage/migrations/0001_initial.py index 88bddf39..fcd71d9e 100644 --- a/server/venueless/storage/migrations/0001_initial.py +++ b/server/venueless/storage/migrations/0001_initial.py @@ -50,7 +50,8 @@ class Migration(migrations.Migration): ( "world", models.ForeignKey( - on_delete=django.db.models.deletion.PROTECT, to="core.World" + on_delete=django.db.models.deletion.PROTECT, + to="core.World", ), ), ], diff --git a/server/venueless/storage/schedule_to_json.py b/server/venueless/storage/schedule_to_json.py index 2936e62d..061d26e1 100644 --- a/server/venueless/storage/schedule_to_json.py +++ b/server/venueless/storage/schedule_to_json.py @@ -182,7 +182,11 @@ def convert(io, timezone=None): "url": "URL", }, mandatory_fields=["Title", "Start", "End", "Room ID"], - methods={"start": to_event_iso, "end": to_event_iso, "speakers": to_list}, + methods={ + "start": to_event_iso, + "end": to_event_iso, + "speakers": to_list, + }, ) validate_data(result) return json.dumps(result, indent=4) diff --git a/server/venueless/storage/urls.py b/server/venueless/storage/urls.py index 2890ae7d..e7efd7d1 100644 --- a/server/venueless/storage/urls.py +++ b/server/venueless/storage/urls.py @@ -5,6 +5,8 @@ urlpatterns = [ path("upload/", views.UploadView.as_view(), name="upload"), path( - "schedule_import/", views.ScheduleImportView.as_view(), name="schedule_import" + "schedule_import/", + views.ScheduleImportView.as_view(), + name="schedule_import", ), ] diff --git a/server/venueless/storage/views.py b/server/venueless/storage/views.py index c8563a82..857f3ea7 100644 --- a/server/venueless/storage/views.py +++ b/server/venueless/storage/views.py @@ -1,6 +1,6 @@ import logging -from io import BytesIO import re +from io import BytesIO from asgiref.sync import async_to_sync from django.core.exceptions import PermissionDenied, ValidationError diff --git a/server/venueless/zoom/views.py b/server/venueless/zoom/views.py index 7aec10d4..afeb7aaa 100644 --- a/server/venueless/zoom/views.py +++ b/server/venueless/zoom/views.py @@ -1,6 +1,6 @@ import datetime -from calendar import timegm import re +from calendar import timegm import jwt from django.conf import settings @@ -101,9 +101,9 @@ def get_context_data(self, **kwargs): "support_chat": not inp["dc"], "debug": settings.DEBUG, "lang": get_closest_zoom_lang(self.world), - "langurl": "/zoom-de-DE.json" - if self.world.locale.startswith("de") - else "", + "langurl": ( + "/zoom-de-DE.json" if self.world.locale.startswith("de") else "" + ), } ) diff --git a/webapp/.eslintrc.js b/webapp/.eslintrc.js index 8b0bbd1c..d25500eb 100644 --- a/webapp/.eslintrc.js +++ b/webapp/.eslintrc.js @@ -45,6 +45,7 @@ module.exports = { camelcase: 0, 'babel/camelcase': ['error', {properties: 'never', ignoreDestructuring: true}], 'no-unused-expressions': 0, - 'babel/no-unused-expressions': 1 + 'babel/no-unused-expressions': 1, + 'space-before-function-paren': ['error', 'never'] } } diff --git a/webapp/config.js b/webapp/config.js index 4b8e6db4..616ed23e 100644 --- a/webapp/config.js +++ b/webapp/config.js @@ -3,8 +3,8 @@ import cloneDeep from 'lodash/cloneDeep' let config if (ENV_DEVELOPMENT || !window.venueless) { const hostname = window.location.hostname - const wsProtocol = window.location.protocol === 'https:' ? 'wss' : 'ws'; - const httpProtocol = window.location.protocol; + const wsProtocol = window.location.protocol === 'https:' ? 'wss' : 'ws' + const httpProtocol = window.location.protocol config = { api: { base: `${httpProtocol}//${hostname}:8443/api/v1/worlds/sample/`, @@ -17,8 +17,8 @@ if (ENV_DEVELOPMENT || !window.venueless) { locales: ['en', 'de', 'pt_BR', 'ar', 'fr', 'es', 'uk', 'ru'], theme: { logo: { - url: "/eventyay-video-logo.svg", - fitToWidth: false + url: '/eventyay-video-logo.svg', + fitToWidth: false }, colors: { primary: '#2185d0', diff --git a/webapp/src/App.vue b/webapp/src/App.vue index a51a1c56..6bdf70d5 100644 --- a/webapp/src/App.vue +++ b/webapp/src/App.vue @@ -54,7 +54,7 @@ const chatbarModules = ['chat.native', 'question', 'poll'] export default { components: { AppBar, RoomsSidebar, MediaSource, GreetingPrompt, Notifications }, - data () { + data() { return { themeVariables, backgroundRoom: null, @@ -66,34 +66,34 @@ export default { ...mapState(['fatalConnectionError', 'fatalError', 'connected', 'socketCloseCode', 'world', 'rooms', 'user', 'mediaSourcePlaceholderRect', 'userLocale', 'userTimezone']), ...mapState('notifications', ['askingPermission']), ...mapState('chat', ['call']), - room () { + room() { if (this.$route.name.startsWith('admin')) return if (this.$route.name === 'home') return this.rooms?.[0] return this.rooms?.find(room => room.id === this.$route.params.roomId) }, // TODO since this is used EVERYWHERE, use provide/inject? - modules () { + modules() { return this.room?.modules.reduce((acc, module) => { acc[module.type] = module return acc }, {}) }, - roomHasMedia () { + roomHasMedia() { return this.room?.modules.some(module => mediaModules.includes(module.type)) }, - stageStreamCollapsed () { + stageStreamCollapsed() { if (this.$mq.above.m) return false return this.$refs.primaryMediaSource?.$refs.livestream ? !this.$refs.primaryMediaSource.$refs.livestream.playing : false }, // force open sidebar on medium screens on home page (with no media) so certain people can find the menu - overrideSidebarCollapse () { + overrideSidebarCollapse() { return this.$mq.below.l && this.$mq.above.m && this.$route.name === 'home' && !this.roomHasMedia }, // safari cleverly includes the address bar cleverly in 100vh - mediaConstraintsStyle () { + mediaConstraintsStyle() { const hasStageTools = this.room?.modules.some(module => stageToolModules.includes(module.type)) const hasChatbar = ( (this.room?.modules.length > 1 && this.room?.modules.some(module => chatbarModules.includes(module.type))) || @@ -112,7 +112,7 @@ export default { } return style }, - browserhackStyle () { + browserhackStyle() { return { '--vh100': this.windowHeight + 'px', '--vh': this.windowHeight && (this.windowHeight / 100) + 'px' @@ -124,53 +124,53 @@ export default { rooms: 'roomListChange', room: 'roomChange', call: 'callChange', - $route () { + $route() { this.showSidebar = false }, stageStreamCollapsed: { - handler () { + handler() { this.$store.commit('updateStageStreamCollapsed', this.stageStreamCollapsed) }, immediate: true } }, - mounted () { + mounted() { this.windowHeight = window.innerHeight window.addEventListener('resize', this.onResize) window.addEventListener('focus', this.onFocus, true) }, - destroyed () { + destroyed() { window.removeEventListener('resize', this.onResize) window.removeEventListener('focus', this.onFocus) }, methods: { - onResize () { + onResize() { this.windowHeight = window.innerHeight }, - onFocus () { + onFocus() { this.$store.dispatch('notifications/clearDesktopNotifications') }, - toggleSidebar () { + toggleSidebar() { this.showSidebar = !this.showSidebar }, - clearTokenAndReload () { + clearTokenAndReload() { localStorage.removeItem('token') location.reload() }, - reload () { + reload() { location.reload() }, - worldChange () { + worldChange() { // initial connect document.title = this.world.title }, - callChange () { + callChange() { if (this.call) { // When a DM call starts, all other background media stops this.backgroundRoom = null } }, - roomChange (newRoom, oldRoom) { + roomChange(newRoom, oldRoom) { // HACK find out why this is triggered often if (newRoom === oldRoom) return // TODO non-room urls @@ -202,7 +202,7 @@ export default { this.backgroundRoom = null } }, - roomListChange () { + roomListChange() { if (this.room && !this.rooms.includes(this.room)) { this.$router.push('/').catch(() => {}) } diff --git a/webapp/src/components/AVDevicePrompt.vue b/webapp/src/components/AVDevicePrompt.vue index 16671cb3..498379c9 100644 --- a/webapp/src/components/AVDevicePrompt.vue +++ b/webapp/src/components/AVDevicePrompt.vue @@ -17,7 +17,7 @@ import Prompt from 'components/Prompt' export default { components: {Prompt}, props: {}, - data () { + data() { return { videoInput: localStorage.videoInput || '', audioInput: localStorage.audioInput || '', @@ -30,7 +30,7 @@ export default { } }, computed: {}, - mounted () { + mounted() { navigator.mediaDevices.enumerateDevices().then(this.gotDevices).catch((e) => { console.warn(e) alert('Could not access camera or microphone, is another program on your machine using it right now?') @@ -38,7 +38,7 @@ export default { }) }, methods: { - gotDevices (deviceInfos) { + gotDevices(deviceInfos) { this.videoInputs = [ { value: '', @@ -79,7 +79,7 @@ export default { } this.refreshVideo() }, - refreshVideo () { + refreshVideo() { console.log('refresh') if (this.stream) { this.stream.getTracks().forEach(track => { @@ -101,7 +101,7 @@ export default { // possibly "overconstrained" (camera doesn't exist) }) }, - save () { + save() { localStorage.videoInput = this.videoInput || '' localStorage.audioInput = this.audioInput || '' localStorage.audioOutput = this.audioOutput || '' diff --git a/webapp/src/components/AppBar.vue b/webapp/src/components/AppBar.vue index 8ea28854..b73a8e20 100644 --- a/webapp/src/components/AppBar.vue +++ b/webapp/src/components/AppBar.vue @@ -24,14 +24,14 @@ export default { default: false } }, - data () { + data() { return { theme } }, computed: { ...mapState(['user', 'world']), - isAnonymous () { + isAnonymous() { return Object.keys(this.user.profile).length === 0 }, } diff --git a/webapp/src/components/AppDropdown.vue b/webapp/src/components/AppDropdown.vue index 0c9c2f03..b6b6872d 100644 --- a/webapp/src/components/AppDropdown.vue +++ b/webapp/src/components/AppDropdown.vue @@ -1,82 +1,91 @@ - + +export default { + name: 'AppDropdown', + mixins: [clickaway], + props: { + className: { + type: String, + default: '', + }, + }, + provide() { + return { + sharedState: this.sharedState + } + }, + data() { + return { + sharedState: { + active: false, + }, + } + }, + computed: { + elClass() { + return this.className ? this.className + ' app-drop-down' : 'app-drop-down' + }, + }, + methods: { + toggle() { + this.sharedState.active = !this.sharedState.active + }, + away() { + this.sharedState.active = false + } + } +} + - \ No newline at end of file diff --git a/webapp/src/components/AppDropdownContent.vue b/webapp/src/components/AppDropdownContent.vue index 23d6a568..91fdebeb 100644 --- a/webapp/src/components/AppDropdownContent.vue +++ b/webapp/src/components/AppDropdownContent.vue @@ -1,6 +1,10 @@