Skip to main content

xincord

xincord is a modular Discord bot built on discord.py: a small core loads plugins through entry points and gives them YAML config, SQLite storage, i18n, message templates and component IDs that survive restarts.

Status: 1.x, running the owner's bots since 2026-10-12. The plugin API (docs/plugin-api.md) follows semver.

The bundled plugins: AutoRole, BotInfo, Calculator, ChatLogger, CustomCommands, DynamicVoiceChannel, Economy, Example, Feedback, Giveaway, MusicPlayer (with a Lavalink node), NtustAnnouncements, NtustCourseWatch, Ticket, TicketAwayNotice, UptimeHeartbeat, VoiceLogger and WelcomeGoodbye.

Packages

Distribution Import package Contents
xincord xincord.core the plugin host, storage, i18n and message templates
xincord-plugins xincord.plugins the official plugins

Running

uv tool install xincord --with xincord-plugins   # or: pip install xincord-plugins
xincord --help
xincord

In a clone, uv run xincord runs the workspace's code.

xincord works in the current directory: it writes config.yaml on the first start, keeps each plugin's files in plugins/<Name>/, its database in data/xincord.db, and the data files that a plugin keeps itself in data/<Name>/ (ChatLogger's message log). Set the bot token in the environment variable XINCORD_TOKEN (or token in config.yaml) and list the plugins to load under plugins. --no-online loads the config and the plugins without connecting and exits with status 1 if one did not load. SIGINT or SIGTERM stops the bot; SIGHUP, or /xincord reload [plugin] run by the application owner, reloads the config and the plugins without disconnecting. In a terminal, the console > prompt accepts reload [plugin] and stop (or exit, shutdown, ^D).

Docker

No clone needed: an empty directory with compose.yaml and .env runs the published image, and the bot keeps config.yaml and plugins/ next to them.

curl -fsSLO https://raw.githubusercontent.com/xinshoutw/xincord/main/compose.yaml
curl -fsSL -o .env https://raw.githubusercontent.com/xinshoutw/xincord/main/.env.example
# in .env: XINCORD_TOKEN, LAVALINK_PASSWORD, and XINCORD_VERSION, TZ, COMPOSE_PROFILES=music (MusicPlayer's
# node), Spotify or YouTube's settings if wanted
docker compose up -d             # start; the bot writes config.yaml and plugins/ on the first start
docker compose kill -s HUP bot   # reload the config and the plugins
docker compose logs -f bot       # logs, rotated at 10 MB
docker compose exec -T bot python -m xincord.core.backup   # back up data/ into backups/
docker compose pull              # update to XINCORD_VERSION, then docker compose up -d
docker compose stop              # stop

The image runs xincord as a non-root user; the database stays in the <directory>_data volume (the empty data/ beside compose.yaml is only its mount point). The backup command copies it while the bot runs into backups/<UTC time>/ beside compose.yaml and keeps the newest 7 (--keep): run it daily from cron or launchd, and copy backups/ off the host. The lavalink service plays MusicPlayer's audio with the config inline in compose.yaml; it runs when .env sets COMPOSE_PROFILES=music. Its password is LAVALINK_PASSWORD in .env, and lavalink.password in plugins/MusicPlayer/config.yaml is the same. Videos that YouTube plays only to a signed-in client need its OAuth on a spare Google account; YouTube's signatures are solved by a remote yt-cipher server, the public one by default (.env.example). In a clone, docker compose -f compose.yaml -f compose.build.yaml up -d --build builds the image instead of pulling it.

Development

Requires uv and Node.js 20.19 or newer (for OpenSpec).

uv sync
uv run prek install
scripts/gate.sh

Specifications live in openspec/; project rules for contributors and coding agents are in AGENTS.md. docs/ holds the architecture, the decisions, the plugin conventions, the plugin API and the pitfalls. Renovate opens version updates and Dependabot security updates (.github/renovate.json, .github/dependabot.yml).

Releasing

A GPG-signed v* tag releases xincord:

# Set the same version in pyproject.toml and packages/xincord-plugins/pyproject.toml, and in
# the latter's requirement xincord==<version>.
uv lock
git commit -S -am "build: release 0.2.0"
git push origin main
git tag -s v0.2.0 -m "xincord 0.2.0"
git push origin v0.2.0

The release workflow checks the tag's signature and the versions, builds both distributions and the linux/amd64 and linux/arm64 image ghcr.io/xinshoutw/xincord, and creates the GitHub Release (a prerelease while the version is 0.x). PyPI publishing starts at 1.0.0: xincord, then xincord-plugins, each in its own environment (pypi, pypi-plugins) that waits for your approval. Running the workflow by hand is a dry run that pushes and publishes nothing.

License

xincord is licensed under the GNU Affero General Public License, version 3 or later (AGPL-3.0-or-later).

Metadata

Release files for xincord 1.0.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for xincord 1.0.0
File Size Uploaded
xincord-1.0.0.tar.gz 61.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for xincord 1.0.0
File Interpreter ABI Platform
xincord-1.0.0-py3-none-any.whl Python 3 none any Details

Total release size: 132.0 kB

Release files / xincord-1.0.0.tar.gz

Download URL xincord-1.0.0.tar.gz
Size 61.9 kB
Tags Source
SHA-256 checksum
How to use checksums
6339a720bcdbe005130b136b0843dab77ee2387d7584aef38a6ac5b264332469
BLAKE2b-256 checksum
How to use checksums
ab61cc20af8ceaed2d85b899bfec59b5e1c2e32853fe83088bd5a5cd5ea1ffd0
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.0 {"installer":{"name":"uv","version":"0.13.0","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"26.04","id":"resolute","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / xincord-1.0.0-py3-none-any.whl

Download URL xincord-1.0.0-py3-none-any.whl
Size 70.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
3cf5b5968316830115637236276408564886f62078af6afa9b312086a4ffabff
BLAKE2b-256 checksum
How to use checksums
f31296500f431556f827252c6898374929acd8fe6acf11a31a82d5b39086652a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.0 {"installer":{"name":"uv","version":"0.13.0","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"26.04","id":"resolute","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release history Release notifications | RSS feed

This release

1.0.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page