Final Report 2025 ‐ Live Chat - uchicago-cs/chigame GitHub Wiki

CMSC 22000 Final report

Quarter: Spring 2025

Feature: Live Chat

Design and Implementation

We built the live chat feature using Django channels, opting for WebSockets instead of server polling (utilized in the existing chat features present in the Chigame app) to make messaging truly live. To make this happen, we added the channels and daphne libraries.

This feature stands out from the rest of the Chigame app, so we've put together dedicated external documentation for the finer details. The info present in this final report gives more of a high-level overview, we recommend checking out the existing documentation if any further clarification is needed.

Features

Since LiveChat was a new feature, we kept things relatively simple and didn’t manage to polish every detail, but here’s what we did add:

  • Core live chat functionality—joining a channel, seeing messages, sending messages, etc.
  • Dedicated pages for individual live chats and an overview of all chats.
  • Message search to quickly find past conversations.
  • Replying & quoting similar to how other platforms handle message referencing.
  • Message deletion so users can remove their own messages.
  • Togglable profanity filtering for cleaner conversations.
  • Context menu (right-click on a message) for some of the options described above.
  • Floating and centered full-page chat view

Each of these ties into models, consumers.py, their own files, or dedicated views, all of which are covered in more depth throughout the rest of this report.

Models

The basic live chat feature and functionality relies on the following models:

LiveChat

  • id (Autoincrementing Primary Key)
  • name (String)
  • users (ManyToManyField to User)

One key detail to note is that we use the id as the unique channel ID for the Django channels library. This is explained in more depth later in the report.

LiveChatMessage

  • id (Autoincrementing Primary Key)
  • live_chat (Foreign Key to LiveChat)
  • user (Foreign Key to User)
  • sent_at (DateTimeField)
  • content (String)
  • reply_to (Foreign Key to Self)

LiveChatUser

  • id (Autoincrementing Primary Key)
  • user (Foreign Key to User)
  • live_chat (Foreign Key to LiveChat)

Each LiveChat handles multiple messages, and users can belong to multiple chats. We designed this to make adding and removing users seamless, ensuring that LiveChats remain dynamic, naturally supporting group chats from the start.

One feature we haven't implemented yet is group chat creation, though our existing models are built to support it.

Aside from that, everything else follows standard functionality. You might notice the reply_to field, making the model 'self-referential', allowing for single-message replies. This is part of our message replying feature, similar to "quoting" a message on other platforms (like how GitHub comments let you quote someone by adding > before their message before yours).

While this approach works, it's not necessarily the best or worst. In the future, we could improve it by introducing threads, likely requiring a model update—perhaps adding a LiveChatThread as a separate model.

The rest of the (not crucial) models we added were as follows:

LiveChatMessageReaction

  • id (Autoincrementing Primary Key)
  • user (Foreign Key to User)
  • message (Foreign Key to LiveChatMessage)
  • content (validated Char, max=10)

LiveChatPollOption

  • id (Autoincrementing Primary Key)
  • content (String)

LiveChatPoll

  • id (Autoincrementing Primary Key)
  • live_chat (Foreign Key to LiveChat)
  • question (String)
  • options (ManyToManyField to LiveChatPollOption)
  • created_at (DateTimeField)
  • updated_at (DateTimeField)

LiveChatPollVote

  • id (Autoincrementing Primary Key)
  • poll (Foreign Key to LiveChatPoll)
  • option (Foreign Key to LiveChatPollOption)
  • user (Foreign Key to User)

The models for these features are pretty straightforward, but just to clarify—we implemented models for message reactions and polls. While we got message reactions up and running, the polls feature didn’t quite make it to completion.

That said, all the models are in place, so with some API routes and frontend work, this could be resolved fairly quickly. It’s definitely an interesting feature to explore! However, it’s worth noting that polls aren’t strictly a chat feature—they could eventually be moved to the user app of Chigame.

For the latest details on models, check out Tables. If you have any questions about model relationships, refer to the ERD. Our hope is that future teams working on Chigame will keep these pages updated!

Dependencies

The two dependencies required for implementing the live chat feature are channels and daphne. Both have already been added to requirements/base.txt, so unless Django updates these libraries in the future, no changes should be needed for live chat to function properly.

channels==4.0.0  # https://github.com/django/channels
daphne==4.0.0 # https://github.com/django/daphne

So, what is Django channels?

Django Channels extends the built-in Django abilities to handle not only HTTP but also protocols that require long-running connections, such as WebSockets, chatbots, IoT, and more. This makes it suitable for building real-time features like chat, notifications, or collaborative tools. (Django Channels Quickstart)

Unlike traditional Django (which uses WSGI and synchronous HTTP requests), Django Channels enables long-lived, bidirectional connections between client and server using ASGI (Asynchronous Server Gateway Interface). (Django Channels Quickstart)

This allows live chat to send and receive messages in real time without polling! A key part of this infrastructure is consumers.py, where we handle async database actions to ensure that changes made in the channel are also reflected in the database.

The only thing we use WebSockets for is connecting to channels and sending messages—all other functionality is managed through additional frontend and backend layers. Something that has been mentioned a few times but not clarified are web sockets

Message Deletion

Currently, when a user deletes a message, it’s only removed from their frontend view. A request is then sent to the server to delete it from the database. However, this should be improved in the future to ensure messages are instantly removed for all users. A better approach would be to delete messages via the channel first, then update the database—ensuring that deleted messages are securely hidden.

What are WebSockets?

WebSockets are a communication protocol that provides full-duplex (two-way) communication over a single TCP connection. (Django Channels Quickstart)

In contrast to HTTP polling (which repeatedly asks the server for updates), WebSockets keep a connection open, so the server can push updates to the client as they happen. This is good for chat apps, where users expect messages to appear without delay. (Django Channels Quickstart)

Consumers.py

consumers.py implements real-time WebSocket communication using Django Channels (channels library). The class, ChatConsumer enables bidirectional, persistent connections between users in a chat room. (Consumers.py Explained)

This file is essential for WebSocket functionality. You can find more details in this guide: Consumers.py Explained, which covers its purpose and features.

Additionally, it's important to clarify that the id of the LiveChat model serves as the *unique WebSocket channel identifier (this is the ID we connect to when establishing a WebSocket connection).

This means that each live chat instance has its own dedicated WebSocket channel, allowing users within that chat to send and receive messages in real time. By using the id as the channel identifier, we ensure that messages are properly routed to the correct chat session without interference from other chats.

Quick Overview

consumers.py handles key operations like:

  • Connecting to a channel
  • Disconnecting from a channel
  • Receiving messages
  • Sending messages

It also uses decorators for async database functions, allowing us to perform actions when sending and receiving messages efficiently.

Extending consumers.py

As functionality for the live chat feature is expanded (for example, with the introduction of channel side message introduction), consumers.py will inevitably need to be extended with additional functionality.

For a deeper dive, please check out Consumers.py Explained. We highly recommend that you or your future team read this before making any changes. Since consumers.py is a single file, we can into quite a few merge conflicts during development. In the future, it might also be helpful to better organize functions, perhaps by adding comments to separate sections (as many methods serve specific purposes such as receiving, sending, etc.).

Async Database Operations

We also strongly recommend using @database_async_to_async when creating functions that add an extra database layer on top of channel functionality. This ensures that operations remain asynchronous, preventing lag or delays when sending and receiving messages.

With this approach, users can instantly send and receive messages in channels, while the database updates happen lazily in the background. Maintaining this principle throughout the live chat feature development will ensure that we keep the chatting experience smooth and responsive (part of the reason why we migrated away from polling anyway).

Templates

Below is the file structure of the templates/chat directory:

chat
| components
  | _context_menu.html
  | _header.html
  | _input.html
  | _messages.html
  | _script.html
| index.html
| live-chat-list.html

This outlines the pages we added, with components serving as a folder to organize the code for the chat page.

We introduced:

  • A chat page for real-time conversations.
  • A live chat list that includes a search feature for easy navigation.

Views

We didnt actually have a lot of views to add for the app, we ceratinly expect this to grow as more functionaltiy is to be added to the function (like adding the ability to post or create chats, add users, mute and block, etc. (things we will go over in the "Next Steps" section).

def chat(request, chat_id): ...

First one is chat, relatively self explanatory, this is the chat page - we also add a check to see if the user was authenticated

def live_chat_list(request): ...

List of chats is also pretty straight forward

def delete_message(request, message_id): ...

@csrf_exempt
@require_POST
def react_to_message(request, message_id): ...

Explaining utils.py

This is a section that is not that well documented in the project, and it could certainly benefit from being better documented. Utils, at the moment, only includes profanity filtering. We have created this fiele so that any sort of - miscellaneous functionality can be added in here, so this section of the report will be dedicated simply to cofvering profanity.

If you wish to extend this file, i'd perhaps recommend separating utils into a seperate directory iwthin the chat app, and calling each python file within its own util, such as taking what is utils.py now and moving it to utils/profanity.py.

Profanity Filtering

To enhance both safety and user experience, we introduced profanity filtering. Users can toggle this feature in their settings, allowing them to enable or disable profanity filtering at a high level. We also worked on per-chat profanity filtering, though it’s not as polished as it could be and may not function perfectly.

How Profanity Filtering Works

We handle profanity filtering using regex. Initially, the list of profane words was hardcoded, but we later moved them to a text file: PROFANITY.txt within the chat app directory. This file contains comma-separated values, making it easy to update. In hindsight, a .csv file might have been a better choice for clarity, but since all current functionality is built around the TXT file, switching formats would be a future improvement.

Implementation in utils.py

The utils.py file includes the profanity filter class:

class ProfanityFilter:
    """
    A simple class that allows for filtering of profanity and censoring of messages.
    """

Beyond initialization, this class contains two key methods:

  • censor_message – Filters profanity from messages.
  • contains_profanity – Checks if a message contains profanity

Integration with consumers.py

These methods are integrated into consumers.py, ensuring that messages are filtered before they are received. This means that the clean version of a message is stored in the database, while the displayed version is censored if the user has profanity filtering enabled.

A Key Flaw & Future Improvement

One major issue is that censored messages may still be loaded uncensored from the database, since filtering currently happens at the channel level rather than the database level.

A possible solution would be to apply censorship before storing messages, ensuring profanity is consistently filtered across all layers. However, this approach would remove the ability to toggle profanity filtering, which is an important feature for user customization.

A better fix might be to apply filtering dynamically on the frontend—so when messages are loaded, they are checked against the profanity filter and updated accordingly. This way, users who have profanity filtering enabled will see censored messages, while those who don’t will see the original text.

Next Steps

Throughout this report, we've highlighted several smaller fixes, but here are the most important ones—along with why they matter. Where relevant, we've linked backlog issues for reference.

Adding Scroll-Based Chat Pagination** (Issue #589)

One of the biggest performance boosters for a chat app is pagination. A scroll-based approach would be ideal, as it allows users to load messages dynamically rather than all at once.

Since we're using Django templating (rather than a JS state management library such as Vue or React), implementing client-side pagination may be trickier—but it's definitely possible. This would significantly improve performance, especially for chats with 500+ messages (which can happen faster than expected, particularly in group chats).

Migrating to Vue JS

During development, we noticed that the Frontend team had started integrating Vue JS into the project. Vue offers client-side JavaScript with extended functionality, making it a much better fit for features like:

  • Loading messages efficiently
  • Infinite scroll
  • Handling animations & context menus

Migrating to Vue would also allow us to leverage more libraries, making future development smoother.

Fixing Message Deletion

As mentioned earlier, deleting messages doesn’t live broadcast, it’s currently client-side only. If a user deletes a message, others won’t see it removed until they refresh the page.

This is not how message deletion should work. A proper fix would ensure that deletions are instantly reflected across all clients.

Popup Chat on Main Pages

Originally, we envisioned LiveChat as an overlay, similar to Roblox’s chat system, allowing users to chat from non-game pages.

We did some frontend work to prototype a retractable/expandable chat, but we haven’t yet:

  • Overlaid it onto other pages
  • Added personal friend/message lists

A potential solution would be to query a list of chats the user is in and display them in the popup. This would be a great addition to the platform.

Better Safety Features

Right now, features like muting/blocking users are not fully implemented, and reporting users hasn’t been added at all.

As the game scales, user safety will become increasingly important. A centralized system for managing these issues would be a huge improvement.

Bug Fixes

A few bugs and user experience gaps should be addressed to ensure a fully smooth and stable live chat experience:

  • Improve reply scrolling reliability - ensure it works for freshly sent messages
  • Fix inconsistent behavior with context menu where it doesn't appear for some messages
  • Fix issue where usernames occasionally fail to display correctly when consecutive messages are sent
  • Add unauthenticated access handling - implement proper redirection or feedback when unauthenticated users try to access chat (currently results in a crash)