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.
consumers.py
Extending 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): ...
utils.py
Explaining 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.
utils.py
Implementation in 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
consumers.py
Integration with 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.
Issue #589)
Adding Scroll-Based Chat Pagination** (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)