FoukoApi
A Rust framework for building cross-platform bots. Write a command once, run it on Telegram, Discord and anything you plug in next.
api.fouko.xyz · fouko.xyz · Discord · Telegram
[!NOTE] FoukoApi is in its alpha series (currently
0.1.2-alpha.1). The public surface below is stable in spirit; feature-gated internals may still shift.
What is it
FoukoApi is a small, opinionated framework that lets you describe a bot once - its commands, handlers, state, linked-account UX - and run the same bot on multiple chat platforms without forking logic for each one.
No custom DSL, no macros you have to learn. Just Rust functions and a Bot builder.
Contents
Features
- One codebase, many platforms. Telegram and Discord shipped, more adapters possible behind the same
Platformtrait. Your handler doesn't care which one it's running on. - Build a
Keyboardonce - Telegram gets inline keyboards, Discord gets message components. A keyboard that fits Telegram but overflows Discord's 5x5 cap is repacked there instead of rejected. Embedwith title / description / fields / footer / color renders as a real Discord embed and as HTML on Telegram, from the same code.- Pluggable storage. Any async key-value store works behind the
Storagetrait. Bundled: in-memory (good for tests) and SQLite (good for a self-hosted bot). - Account linking.
Accounts::start_link/redeem_linkties Telegram and Discord identities with a 6-char code. A ready-made/linkcommand ships with it. - Economy. XP, a level curve, coins, atomic transfers, cooldowns, achievements, a leaderboard. Balances follow the account link, so a user's XP is the same on both platforms.
I18n- a plain key/language catalogue with English fallback and{}placeholders. No macros, no build step, no.pofiles.- Push messages.
Notifiersends into any chat without an incoming event - reminders, scheduled jobs.send_dmreaches a user's DMs by id,user_nameresolves a display name outside any update. - Per-user rate limiting is on by default (tune with
Bot::rate_limit), andBot::on_floodfires a handler when the total update flow spikes - handy against spam waves. - Colorful startup banner: gradient ASCII art plus an aligned status table. Honors
NO_COLORand non-TTY output. - Secrets at rest.
Secret(featurecrypto) encrypts strings with AES-256-GCM, keyed off an operator passphrase. - GenAI client.
GenClient(featuregenai) speaks to any OpenAI-compatible host: chat with SSE streaming, tool calling, vision input, model discovery with capability tags, image/video/speech generation with selectable voices. - Telegram Mini Apps.
Button::web_appopens a Mini App from a button (Discord falls back to a link), andvalidate_init_data(featurewebapp) verifies the app'sinitDatasignature server-side. - Quick https tunnels.
tunnel::cloudflared_quick(featuretunnel) gets a free public https URL for a local port via thecloudflaredbinary - a Mini App can run from a laptop behind NAT. - Handler quality-of-life:
Ctx::typing()shows a typing indicator,reply_temporaryauto-deletes after N seconds,avatar_url/banner_url/chat_infolook up user and chat details cross-platform. - Built-in
/helpand/lang, one builder call each, localised per user. Group commands into sections withBot::category. - Registered commands are published as Discord slash commands and Telegram's command menu automatically, so they show up in each client's autocomplete.
- Async from the ground up, built on
tokio. No unsafe, no build-time code generation, no custom DSL. - Every platform is a trait impl. Rolling an adapter for a niche chat service is a weekend project.
Quick start
# Cargo.toml
[]
= "0.1.2-alpha.1"
= { = "1", = ["full"] }
use ;
async
That single /help command responds on every platform you added, and the Keyboard is rendered natively on each one (inline keyboard in Telegram, message components in Discord).
Mini-wiki
Cargo features
| Feature | Default | What it does |
|---|---|---|
telegram |
yes | Adds the Telegram adapter (teloxide). |
discord |
yes | Adds the Discord adapter (serenity). |
sqlite |
yes | Enables the bundled SQLite storage backend. |
crypto |
no | Secret - AES-256-GCM at-rest encryption. |
genai |
no | GenClient - OpenAI-compatible generation client. |
webapp |
no | Telegram Mini App initData validation. |
tunnel |
no | Cloudflare quick tunnels via the cloudflared binary. |
full |
no | Turns on everything above. |
= { = "0.1.2-alpha.1", = false, = ["telegram", "sqlite"] }
Environment variables
bootstrap_env() creates a commented .env template on the first run. Defaults:
| Variable | What it is |
|---|---|
TG_TOKEN |
Telegram bot token from @BotFather. |
DISCORD_TOKEN |
Discord bot token (Developer Portal -> Bot -> Reset Token). |
FOUKO_DB |
Storage URL. sqlite:./foukobot.sqlite, memory:, or a custom scheme your own Storage understands. If unset, a SQLite file is placed next to the discovered .env (never inside target/). |
RUST_LOG |
Standard tracing filter, e.g. info,foukoapi=debug. |
Core types
Bot - builder. Add platforms, commands, on_message hooks, defaults.
Platform - "here's a Telegram/Discord token". Plugged into Bot::add_platform.
Ctx - everything a handler needs: platform, user_id, text, args,
reply / reply_with / edit_reply, callback_data, is_dm, ...
Reply - what you send back. Reply::text(..), Reply::embed(..),
with optional .keyboard(kb).
Embed - rich reply card. .title / .description / .field(_inline) /
.footer / .image / .thumbnail / .url / .color.
Keyboard - rows of Buttons. Button::callback("label", "cb_data") or
Button::url("label", "https://...").
Storage - async KV store trait (get/set/del, set_nx write-if-absent,
list_prefix). AnyStorage is a boxed version.
Accounts - cross-platform user linking. Sits on top of Storage.
Economy - XP, levels, coins, transfers, cooldowns, achievements,
leaderboard. Sits on top of Accounts so balances follow
the account link.
I18n - key/language string catalogue with English fallback and tf(..)
placeholder filling.
Notifier - outbound handle: push into a chat or a user's DMs without an
incoming event, resolve display names by id.
RateLimiter - per-user rate limiting; on by default with a relaxed policy.
Built-in commands
One builder call each, all localised and DM-aware where it makes sense:
new
.with_accounts // needed by /help (i18n), /lang, /link
.with_default_help // lists every described command
.with_default_lang_command // picker with inline buttons
.with_default_link_command // 6-char code flow + unlink button
Minimal bot with state
use ;
async
Account linking flow
┌─ Telegram ─┐ ┌─ Discord ─┐
│ /link │ ── 6-char code ─▶ │
│ │ │ /link CODE │
│ │ ◀── linked ─────│ │
└────────────┘ └────────────┘
│ │
└──── pick primary (once) ───┘
│
primary keeps XP/coins/settings
the other side stays clean
┌─ later, anywhere ─┐
│ /link -> Unlink │
│ │
│ primary keeps its │
│ profile, other │
│ side -> fresh │
└───────────────────┘
Accounts::start_linkissues a 6-char code valid for 5 minutes.Accounts::redeem_linkon the other side ties them together.- The built-in
/linkcommand (Bot::with_default_link_command) adds an inline-button primary picker that locks in the choice exactly once. After that, the only way to pick a different primary is to/link-> Unlink and start over. - After
Accounts::unlink, the primary-ident keeps its data (XP/coins/settings/lang); the ex-partner platform falls back to its own ident and therefore starts from a clean slate.
Economy
Economy wraps the same Accounts you already have, so a user who linked two platforms shares one wallet:
use ;
async
Coins are minted at one per XP_PER_COIN (10) XP; add_xp returns an XpGain so you can announce freshly minted coins. Balance updates are serialised, so parallel grants don't lose XP and concurrent transfers can't overdraw a wallet. Cooldowns (cooldown_remaining / touch_cooldown), achievements (grant_achievement / achievements) and cosmetic slots (title, color) round it out for /daily, /gamble and a profile card.
Translations (I18n)
use I18n;
let i18n = new
.add
.add;
assert_eq!;
assert_eq!; // English fallback
assert_eq!;
Missing translations fall back to English; a missing key falls back to the key itself, so nothing ever renders blank.
Utility helpers (foukoapi::util)
Small, platform-agnostic helpers that you'll reach for in pretty much every bot:
use ;
assert_eq!;
assert_eq!;
assert_eq!;
capitalize(&str) -> String- uppercases the first char, UTF-8 safe.progress_bar(done, total, width) -> String-▰/▱bar of the given width.urlencode(&str) -> String- minimal percent-encoder for query-string args.split_chunks(&str, limit) -> Vec<String>- split long text on line/word boundaries; the adapters use it to dodge platform length limits.
Status
| Platform | Status |
|---|---|
| Telegram | ✅ Working |
| Discord | ✅ Working |
| Feature | Status |
|---|---|
| Command router | ✅ Working |
| Keyboards / inline buttons | ✅ Working |
| Embeds (Discord + HTML TG) | ✅ Working |
| Storage (memory + sqlite) | ✅ Working |
| Account linking + primary | ✅ Working |
| Economy (XP, coins, achievements, leaderboard) | ✅ Working |
Translations (I18n) |
✅ Working |
Built-in /help, /lang, /link |
✅ Working |
Push messages (Notifier) |
✅ Working |
| Rate limiting + flood watch | ✅ Working |
| Startup banner | ✅ Working |
Secrets at rest (crypto) |
✅ Working |
GenAI client (genai) |
✅ Working |
Mini App validation (webapp) |
✅ Working |
Quick tunnels (tunnel) |
✅ Working |
| Util helpers | ✅ Working |
| Argument parsing helpers | ⏳ Planned |
| Middleware chain | ⏳ Planned |
Examples
The examples/ directory is where ready-to-run sample bots live. A reference bot that uses every feature is also published as FoukoBot - its source is a good place to watch the API take shape.
# run the quickstart example (needs TG_TOKEN / DISCORD_TOKEN)
# streaming GenAI showcase (needs TG_TOKEN and an OpenAI-compatible host)
MSRV
Rust 1.75 or newer (pinned via rust-version in Cargo.toml).
Contributing
Issues and PRs are welcome. The repo follows the usual "fork, branch, PR" flow. Be kind in the tracker, keep diffs small, and run cargo fmt + cargo clippy --all-targets before opening a PR.
License
Licensed under either of MIT or Apache-2.0, at your option. Unless you explicitly state otherwise, any contribution you submit for inclusion is dual-licensed the same way, without additional terms.
Part of the Fouko family.