Maya Workflow Kit
The whole Maya workflow in Blender, in one install. Free and open source, Blender 4.5 – 5.2.
Free Discord bot that automatically posts a daily hadith and the 99 names of Allah to your server. One-command setup, and every channel keeps its own place.
PluginsGitHub @SaiyedRushanOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
Repository information has not been collected yet.
No updates detected yet. GitHub releases will appear here.
A Discord bot that sends daily Islamic hadiths and the 99 beautiful names of Allah to configured Discord channels. The bot automatically sends messages at 6:00 PM Toronto time and provides slash commands for interactive hadith and name retrieval.
Taken from the repository README
Loading the full README.
Read the full README ↗A Discord bot that sends daily Islamic hadiths and the 99 beautiful names of Allah to configured Discord channels. The bot automatically sends messages at 6:00 PM Toronto time and provides slash commands for interactive hadith and name retrieval.
bot.py): Main bot logic with slash commands and scheduled tasksdb.py): Supabase integration for data storage and retrievalutils.py): Data models and message formatting functionsserver.py): runs the bot plus a small health check server on port 8080bot.py - Main Discord bot applicationserver.py - Production entry point: the bot plus a health check on port 8080db.py - Database operations and Supabase integrationutils.py - Data models and utility functionstest_setup.py - Setup verification scriptupdate_site.py - Regenerates the site's usage counts and community wall (make site)update_books_page.py - Regenerates docs/books.html, the book/chapter id reference (make books)OUTREACH.md - Copy-paste templates for introducing the bot to new communitiesoutreach/msa-contacts.md / .csv - Researched MSA contact list, tiered by how reachable they areAPP_DIRECTORY.md - Discord App Directory listing copy, assets and eligibility notesREDDIT.md - Ready-to-post Reddit drafts, screenshot to attach, and answers to the usual commentsassets/appdir/ - Icon and carousel images for the App Directory listingpyproject.toml / uv.lock - Dependencies and lockfile (managed with uv)Makefile - Common dev tasks (make dev / test / lint).env.example - Environment variables templateDockerfile - Container configurationdocker-compose.yml - Multi-container orchestrationProcfile - Heroku deployment configurationdata/99names.json - The 99 beautiful names of Allahdocs/data/communities.json - Communities listed on the website, opt-in onlygit clone https://github.com/SaiyedRushan/HadithBot.git
cd HadithBot
This project uses uv. With uv installed:
uv sync
This creates a .venv with all dependencies (runtime + dev) from uv.lock.
Run commands with uv run ..., or use the Makefile shortcuts:
make dev # run the bot with autoreload on save
make test # run tests
make lint # static checks (undefined names, bad imports)
Create a .env file in the project root:
# Discord Bot Configuration
DISCORD_TOKEN=your_discord_bot_token_here
# Supabase Configuration
SUPABASE_URL=your_supabase_project_url
SUPABASE_KEY=your_supabase_anon_key
.env filebot and applications.commands scopes. Slash
commands come from that second scope, not from a permission bit.discord_channel_state table:CREATE TABLE discord_channel_state (
channel_id TEXT PRIMARY KEY,
channel_name TEXT,
guild_id TEXT,
guild_name TEXT,
guild_member_count INTEGER,
last_hadith_no INTEGER NOT NULL DEFAULT 1,
last_name_no INTEGER NOT NULL DEFAULT 1,
last_book_id INTEGER NOT NULL DEFAULT 1,
last_chapter_id INTEGER NOT NULL DEFAULT 1,
hadiths_per_day INTEGER NOT NULL DEFAULT 3,
names_per_day INTEGER NOT NULL DEFAULT 3,
last_sent_at TIMESTAMP WITH TIME ZONE,
stopped_at TIMESTAMP WITH TIME ZONE,
stopped_reason TEXT,
warned_at TIMESTAMP WITH TIME ZONE,
embed_warned_at TIMESTAMP WITH TIME ZONE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
channel_name, guild_name, and guild_id are stored purely for readability
(so you can tell rows apart in the Supabase dashboard) — the bot keys everything
off channel_id. They're refreshed on every save, so renames stay in sync.
guild_member_count is how many people are in that server, as Discord reported
it on the last save. It's the only thing the site's "people in those servers"
figure is built from, and it's a count and nothing else: no member is named,
identified or stored. Adding the column to an existing database:
ALTER TABLE discord_channel_state ADD COLUMN guild_member_count INTEGER;
then run python backfill_channel_names.py once to fill it in for servers the
bot is already in.
stopped_at is when the channel stopped getting the daily message, and NULL
means it still gets it. It is the only thing the broadcast, the site counts and
the stale-channel check look at. stopped_reason says why, and is always set
or cleared with it:
stopped_reason | What happened | Starts again when |
|---|---|---|
bot_removed | The bot was kicked or banned, or the server was deleted | The bot is added back to the server |
channel_deleted | Discord says the channel no longer exists | Never (Discord doesn't reuse channel ids) |
stopped_by_admin | Someone ran /bismillah stop | Someone runs /bismillah setup in that channel |
Rows are never deleted. The reading position is worth keeping, so a channel that starts again carries on from the hadith it stopped at instead of restarting at the beginning. A channel that is already stopped keeps its first reason and date: a deleted channel in a server that later kicks and re-adds the bot stays deleted.
bot_removed is kept accurate in two ways, both run only by the production
bot (the dev bot shares the database). on_guild_remove and on_guild_join
handle it live, and a reconcile on every startup compares the stored servers
against the ones Discord hands the bot on connect, which covers a kick that
happened while the bot was down. channel_deleted is set when Discord reports
the deletion, or at the next broadcast if the bot was offline at the time.
warned_at is when the server's owner was last told that the hadith couldn't
be posted in this channel, and embed_warned_at when they were last told the
game card went out as plain text because the bot has no Embed Links there.
Either is sent at most once a week while the problem lasts, and cleared once
it's fixed. They're kept on the row so a restart doesn't send them again.
These columns came in with sql/2026-10-25-stops-and-warnings.sql, which also
filled in stopped_at and stopped_reason from the active and removed_at
columns they replace. Those two were dropped the next day by
sql/2026-10-26-drop-active-removed-at.sql.
hadiths table:CREATE TABLE hadiths (
id SERIAL PRIMARY KEY,
id_in_book INTEGER NOT NULL,
book_id INTEGER NOT NULL,
chapter_id INTEGER NOT NULL,
english_narrator TEXT,
english_text TEXT NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
chapters table:CREATE TABLE chapters (
id SERIAL PRIMARY KEY,
english TEXT NOT NULL,
arabic TEXT,
book_id INTEGER NOT NULL
);
books_metadata table:CREATE TABLE books_metadata (
id SERIAL PRIMARY KEY,
english_title TEXT NOT NULL,
arabic_title TEXT
);
hadith_flags table:CREATE TABLE hadith_flags (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
hadith_id BIGINT NOT NULL REFERENCES hadiths(id) ON DELETE CASCADE,
user_id TEXT NOT NULL,
guild_id TEXT,
guild_name TEXT,
channel_id TEXT,
reason TEXT,
resolved BOOLEAN NOT NULL DEFAULT FALSE,
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
-- one standing flag per person per hadith; re-flagging updates the reason
UNIQUE (hadith_id, user_id)
);
CREATE INDEX hadith_flags_open_idx
ON hadith_flags (created_at DESC) WHERE NOT resolved;
CREATE INDEX hadith_flags_hadith_idx ON hadith_flags (hadith_id);
Readers report a suspect hadith with the 🚩 button under each message. The
UNIQUE (hadith_id, user_id) constraint is what makes a flag count mean "this
many people" rather than "this many clicks" — a second report from the same
reader updates their note instead of adding a row.
.env fileRun the setup test script to verify everything is configured correctly:
python test_setup.py
This script will check:
python bot.py
python server.py
This starts the Discord bot and a health check server on port 8080.
Once the bot is running and added to your Discord server, you can test it using these slash commands:
/bismillah setup channel:#reminders hadiths_per_day:3 names_per_day:3 start_book_id:1 start_chapter_id:1 start_hadith_id:1 start_name:1
Sets up daily messages for a channel. Every option is optional — blank keeps the current value, so re-running it to change the counts won't reset the reading position. It refuses (and saves nothing) if the bot can't post in the channel.
/bismillah setup quran_verse:False
Every channel's daily message starts with a verse of the Quran, with buttons for
Ibn Kathir's tafsir, the verse on Quran.com and a ChatGPT question about it. This
turns it off (and quran_verse:True turns it back on). Every channel gets the same verse each day: quran.py picks it
from the date and fetches it once from alquran.cloud (Arabic plus Saheeh
International), so there is no Quran table in the database.
/bismillah diagnose
/bismillah diagnose send_test:True
Checks every configured channel and reports what's blocking it — the first thing
to run when messages aren't arriving. send_test attempts a real post and shows
Discord's own error.
You shouldn't usually need to run it unprompted. The bot speaks up on its own as soon as it loses access to a channel it posts in, whether that's Send Messages being denied or the channel being made private, and again if the daily send fails for any other reason. It DMs the server owner with the channel name, the permissions to grant and how to turn the messages off instead. If the owner's DMs are closed it posts the same notice in the system channel, or any other channel it can still write to. It warns once per channel and stays quiet until delivery works again.
/bismillah random
/bismillah specific book_no:1 chapter_no:1 hadith_no:1
/bismillah random_name
/bismillah specific_name number:1
/bismillah specific_names number:1
/bismillah stop channel:#reminders
/bismillah status
stop pauses daily messages (the reading position is kept, so resuming picks up
where it left off). status lists the configured channels in the server.
/bismillah send-now
Sends the channel's daily message immediately, running the same
run_daily_broadcast() the 6:00 PM scheduler calls — so what you see is what the
scheduled send would produce. It's a real delivery, not a preview: the reading
position advances.
make test # pytest
make lint # ruff
The suite covers message formatting, hadith progression, and the stale-channel
logic. Tests under tests/ are pure unit tests with no network or database; the
root-level test_setup.py and test_hadith_progression.py are interactive smoke
scripts that hit the live database, and are excluded from collection by
pytest.ini.
make status # container state + recent logs
make health # gateway state and guild count
make logs # tail the live logs
A scheduled workflow runs
check_stale_channels.py each morning and reports any active channel that
didn't receive its daily message — the failure mode that is otherwise silent
until someone in the server mentions it. Set DISCORD_ALERT_WEBHOOK as a
repository secret to receive the report as a Discord message.
Ensure these environment variables are set in your production environment:
DISCORD_TOKEN=your_production_discord_bot_token
SUPABASE_URL=your_production_supabase_url
SUPABASE_KEY=your_production_supabase_key
uv sync --frozen --no-dev
uv run python server.py
# /etc/systemd/system/hadithbot.service
[Unit]
Description=HadithBot Discord Bot
After=network.target
[Service]
Type=simple
User=your_user
WorkingDirectory=/path/to/HadithBot
Environment=PATH=/path/to/HadithBot/.venv/bin
ExecStart=/path/to/HadithBot/.venv/bin/python server.py
Restart=always
[Install]
WantedBy=multi-user.target
Using Docker Compose (Recommended):
# Copy environment file
cp .env.example .env
# Edit .env with your actual values
# Build and run
docker compose up -d
# View logs
docker compose logs -f
# Stop
docker compose down
Using Docker directly:
# Build the image
docker build -t hadithbot .
# Run the container
docker run -d \
--name hadithbot \
-p 8080:8080 \
--env-file .env \
-v $(pwd)/data:/app/data:ro \
hadithbot
# View logs
docker logs -f hadithbot
The project includes a Dockerfile and docker-compose.yml for easy containerized deployment.
Heroku:
The project includes a Procfile for Heroku deployment.
heroku create your-hadithbot-app
heroku config:set DISCORD_TOKEN=your_token
heroku config:set SUPABASE_URL=your_url
heroku config:set SUPABASE_KEY=your_key
git push heroku main
Railway/Render: Similar process with their respective deployment methods.
server.py answers on port 8080:
GET http://your-domain:8080/
Response: "Hello. I am alive!"
GET http://your-domain:8080/health
Response: {"status":"healthy","bot":"online","latency":"34.94ms","guilds":16}
/health returns 503 until the bot has logged in to Discord.
The bot includes comprehensive logging. Monitor logs for:
Monitor your Supabase dashboard for:
The bot automatically handles daily message scheduling using Discord.py's task loops. No external cron jobs are required.
Daily Schedule: 6:00 PM Toronto Time (America/Toronto timezone)
Bot not responding to commands:
await self.tree.sync() in setup_hook)Database connection errors:
Daily messages not sending:
Import errors:
data/ directoryEnable debug logging by modifying the logging level in bot.py:
logging.basicConfig(level=logging.DEBUG)
This project is open source. Please ensure compliance with Discord's Terms of Service and API guidelines when using this bot.