1
1
Fork
You've already forked pubsub-chat
0
Chat with any Redis-compatible PubSub-enabled database.
  • Python 92.1%
  • Makefile 7.9%
2026年03月20日 22:09:51 +01:00
.meta doc: added gif for demo 2026年03月20日 22:09:51 +01:00
.flake8 PubSub Chat: use a Redis-compatible, PubSub-enabled database to chat 2026年03月16日 23:25:09 +01:00
.gitignore PubSub Chat: use a Redis-compatible, PubSub-enabled database to chat 2026年03月16日 23:25:09 +01:00
AGENTS.md PubSub Chat: use a Redis-compatible, PubSub-enabled database to chat 2026年03月16日 23:25:09 +01:00
CLAUDE.md PubSub Chat: use a Redis-compatible, PubSub-enabled database to chat 2026年03月16日 23:25:09 +01:00
Makefile PubSub Chat: use a Redis-compatible, PubSub-enabled database to chat 2026年03月16日 23:25:09 +01:00
pubsub-chat.py PubSub Chat: use a Redis-compatible, PubSub-enabled database to chat 2026年03月16日 23:25:09 +01:00
pubsub-chat.py.lock PubSub Chat: use a Redis-compatible, PubSub-enabled database to chat 2026年03月16日 23:25:09 +01:00
README.md doc: added gif for demo 2026年03月20日 22:09:51 +01:00

PubSub Chat

Chat using any Redis-compatible PubSub-enabled database.

Why?

Back in 2021, I've played around with ncat / netcat and produced a very basic chat application that didn't require a fully-fledged server with bells and whistle. A duo of shell scripts – 564 bytes for the server, 1470 for the client. That was so cool to create a minimalist chat app, a bit rough at edges, but it worked.
In 2024, I've played around with the concept of PubSub. Let's admit it: Redis is a fantastic tool, and I'm convinced that nobody uses more than 10% of its features.

The pubsub functions in Redis also look a lot like Instant Messaging to me, channels, subscribe, publish... So it led me to play around with this protocol and I started to create a "proof of concept" chat application.

What was also fun was to create a text-based app using Textual for the TUI and Rich for the chat tab rendering. I also discovered that Redis was not the only database compatible with the Redis Serialization Protocol and its functions, and there were many clones that were 100% compatible with my chat application.

And then, this project fell asleep for a few years. And was never published.

And there came the year 2026 and its continuous enshittification of social networks and chat applications (yes, I'm looking at you, Discord). I was still idle-ing on IRC, and I think I may still do it until I die, but it reminded me of my little side-project and thus it came back to life.

The Python ecosystem provided by uv helped a lot in this revival. Now my pubsub-chat is a cheesy standalone Python script.

This piece of software is published under the terms of the MIT License. See the top-level docstring at the beginning of the script for more details.

HUGE WARNING!!!

This piece of software is NOT a production-ready tool. This is a TOY. It is far from being as advanced as any other chat protocol, such as IRC, Matrix, XMPP, or else. And by design IT WILL NEVER BE.

We do NOT guarantee that your communications will be safely protected and kept private. Bear in mind that the "highest" protection resides in the requirepass mode for the database. Yet any message you receive or transmit is NOT encrypted in any way. Somebody with the credentials for this database will be able to spy on your conversations without you knowing about it. So please, don't shoot yourself in the foot.

SSL/TLS support is planned but I can't make any promise about it.

Demo (GIF)

GIF demo of the application

Requirements

  • Have access to a local or distant Redis-compatible database instance, knowing least its host and port. For a local server, you can use make serve or run your own instance somewhere, locally or not, using Docker or Podman.
  • pubsub-chat is (at least) compatible with Redis, KeyDB, Garnet, Kvrocks, Redict, Dragonfly and Valkey.
  • Have uv installed on your client machine (you can do without it - see below, but that's the easiest way).
  • pubsub-chat requires Python 3.10+ (up to 3.15, as I've tested it).

Usage

Launch the client thanks to uv:

./pubsub-chat.py YOURNICKNAME

This will connect you to the localhost server, on the default channel.

Alternatively, you can chose to connect to (an)other channel(s) like this:

./pubsub-chat.py YOURNICKNAME other-channel

The number of channels is not limited:

./pubsub-chat.py YOURNICKNAME my-channel another-channel

Note: channels with their name starting with an underscore ("_") won't be displayed in the channel list (via the /list command), so they can be considered as "private" (but in fact, they're not: anyone with the database credentials will be able to join (subscribe) them, if they can find the channel name).

For connections to remote servers, use the --host and --port arguments:

./pubsub-chat.py YOURNICKNAME --host redis.host.example --port 4321

Basic Authentication

If the server has been launched with the requirepass argument, each user will have to authenticate using the --password option.

Example:

Server:

redis-server --requirepass myobviouspassword

Client:

./pubsub-chat.py --password=myobviouspassword YOURNICKNAME

Please note that this password may be passed to the client using the PUBSUBCHAT_PASSWORD environment variable instead.

Examples:

PUBSUBCHAT_PASSWORD=whatawonderfulpassword ./pubsub-chat.py YOURNICKNAME
# or more permanently
export PUBSUBCHAT_PASSWORD=coffee-is-life
./pubsub-chat.py YOURNICKNAME

"slash" commands

  • /help: show this help
  • /names: display who is on the current channel
  • /list: display the list of available channels
  • /join [channel [ channel... ]: join one or more new channel(s)
  • /leave: leave the current channel
  • /go <channel>: switch focus to the designated channel
  • /topic [ topic text ]: display or set the topic of the current channel
  • /ttl: display my "time to live"
  • /quit [ reason ]: quit this application

Caveats and problems

Disconnections

If the application abruptly quits, or is killed, of if your connection is cut by entering a tunnel, you may experience an annoying moment: if you try to reconnect using the same "nickname", the application will be rejected with te following message:

Can't use this nick (meuh) on this server

As explained above, the "presence" of the nicks on the server is handled via the TTL of the "member key". So this means that, either someone has "stolen" your nickname – so there's nothing you can do about this, or your previous session hasn't timed out ; so you'll have to wait for the records to get automatically dropped.

Or you may try to log in using a different nickname.

Alternatively, if you are really impatient, you can try to use the make admin-clean-redis or simply your favorite Redis client to clean up the variables yourself.

In the future, we may provide more tools to manage this uneasy situation.

Life without uv

Don't despair.

If you have a Python 3.10+ you can still use this tool in a classic virtualenv:

python -m venv venv
venv/bin/pip install textual redis rich
venv/bin/python pubsub-chat.py MYNICKNAME # add credentials if needed

But seriously, uv is becoming a new standard in terms of package and virtualenv management, so, do yourself a favor, make the switch.

Design

The main mantra of this tool is: Only rely on the capacities of the Redis-compatible database. I didn't want to create a *server application. I've focused on making sure that the chat features would be handled by the client app, leveraging two major tools:

  • The PubSub functions (obviously)
  • The TTL property you can gove to any Redis value.

Once it has established the connection to the Redis-compatible database, the client app:

  1. Checks for the existence of the user's "member" key in the DB (key: "pubsubchat:<nick>"). If it's already there, it means that someone with this nickname is connected, so you can't use it. It quits the app.
  2. If not, it creates the key with a TTL of 300s (by default).
  3. It also "connects" to the channel(s) passed as an argument, or, if none, to the default channel. In terms of database, it means that we add the nickname to the "member list" of the channel (key: "pubsubchat:channel-member:<channel>:<nick>"). This variable also has a TTL.
  4. Then the main loop starts.

As it's possible with Textual, we start two background processes:

  1. a "pull" process that reads the result of the SUBSCRIBE <channel> contents (and display it in the appropriate tab),
  2. a "ping" process that regularly hits the "member" key and the "channel member" keys to refresh their TTL.

As you may have noticed, I've reversed the "IRC way".
IRC usually does the "PING/PONG" dance the other way around: it's the server that sends a PING to the clients and disconnects them if they don't respond by a PONG. In pubsub-chat, it's the client that informs the server that it's still alive.

This may not be obvious at first, but this simple reverse move allowed me to completely remove any need for a piece of software on the server end. The client is doing the job. The server is only here to register channels (and their metadata, like the "topic"), handle Publish / Subscribe commands, and that's it. If a client connection dies silently, the TTL will slowly make the member-related variables fade away.

Hack me

A stance on "AI" agents, vibe-coding and AIGen contributions

Don't bother, I will reject any kind of contributions that are AIGen. Thank you.

For more details, see the AGENTS.md and CLAUDE.md files.

For real humans

You may submit a PR, but if so, please use the available linters/formatters before pushing your code:

make lint

I probably won't integrate it, but don't hesitate to publish your own script, this project is not exclusively mine, it can be yours.