Tflows

A FlowBot-based scripting system that lets you define Discord bot behavior entirely through a concise, expressive DSL — no callback hell, no boilerplate.

What is Tflows?

Tflows is a lightweight Python library built on top of discord.py that replaces the traditional event-callback model with a compact domain-specific language (DSL). Instead of writing Python handler functions for every command, you pass a plain-text script to bot.command() and Tflows handles parsing, variable resolution, and Discord output at runtime.

It is designed for developers who want fast iteration — define a new command in seconds, iterate on its output without restarting the bot, and keep all logic in one readable place.

🐢 Traditional discord.py

  • Per-command function callbacks
  • Manual ctx.send() everywhere
  • Boilerplate embed construction
  • Verbose variable lookup code
  • Restart required for every change

⚡ Tflows DSL

  • Scripts passed as plain text strings
  • Auto-routed text or embed output
  • Inline embed directives
  • $variable syntax, resolved at runtime
  • Hot-editable scripts, minimal surface

Key Features

📝

Command Scripting

Define bot commands with a simple string-based DSL instead of callback functions.

⚙️

Runtime Parsing

Scripts are parsed and executed at invocation time — no pre-compilation required.

💎

Variable System

Rich built-in variables: $ping, $id, $image, $server, and more.

🖼️

Embed DSL

Build rich Discord embeds with just a few directives — no Embed objects needed.

🎛️

Mode Flags

Context-aware variables with mode flags for flexible output formatting.

🚀

Zero Config

Works out of the box. Just install, import FlowBot, and start scripting.

Who is it for?

Tflows is for Discord bot developers who want to ship command logic quickly — whether you're prototyping a bot, building a community tool, or just tired of writing the same boilerplate over and over. It's especially useful when you want non-technical collaborators to edit bot behavior without touching Python.

Tflows currently wraps discord.py. Python 3.8+ and a valid Discord bot token are required.

Installation #

Install Tflows from PyPI using pip. No additional dependencies need to be manually installed — discord.py is pulled in automatically.

shell
$ pip install tflows

Verify the installation

python
import tflows
print(tflows.__version__)

Links

Quick Start #

Below is a minimal working bot. It registers a single !test command that replies with a message showing current latency.

python · main.py
from tflows import FlowBot

bot = FlowBot(prefix="!")

bot.command(
  name="test",
  code="""
send Hello World $ping
"""
)

bot.run("YOUR_BOT_TOKEN")

Trigger it in Discord with !test. The bot replies: Hello World 42ms (latency will vary).

Never hard-code your bot token in source files. Use environment variables or a .env file with python-dotenv.

A richer example with an embed

tflows DSL
embed
$title[Server Status]
$desc[
Latency: $pingms
Server: $server
Members: $membercount
Time: $time(24h)
]

Architecture #

Tflows is a thin orchestration layer over discord.py. It replaces the event-callback model with a parse-and-execute pipeline that runs inside a command's context.

FlowBot
Main class
Registry
name + code
Parser
line-by-line
Resolver
$variables
Builder
text / embed
Discord
ctx.send()

Components

class

FlowBot

Extends discord.py's bot client. Adds .command() for DSL registration and holds the command registry.

module

Command Registry

A dictionary mapping command names to their DSL code strings. Looked up on every invocation.

module

Script Parser

Reads the code string line-by-line, identifies directives (embed, send), and delegates each token.

module

Variable Resolver

Scans each line for $variable patterns and replaces them with live values from the Discord context.

module

Output Builder

Constructs either a plain string or a discord.Embed object depending on whether embed mode was activated.

integration

discord.py

The underlying library handles gateway events, authentication, message delivery, and rate limits.

FlowBot #

FlowBot is the main entry point. It subclasses discord.py's commands.Bot and adds DSL-aware command registration.

python
from tflows import FlowBot

bot = FlowBot(prefix="!")

bot.command(
    name="greet",
    code="""
send Hi $id(user)!
"""
)

bot.run("TOKEN")

Constructor

ParameterTypeDescription
prefix str The command prefix character(s), e.g. "!", "?", "..".

bot.command() Parameters

ParameterTypeDescription
name str The command trigger name. Users invoke it as {prefix}{name}.
code str A multi-line DSL script string. Parsed and executed at invocation time.

Execution lifecycle

1

Command triggered

User types !{name} in a Discord channel with the correct prefix.

2

Registry lookup

FlowBot finds the matching command entry and retrieves the code string.

3

DSL parsing

The code is split into lines and each line is tokenised by the Script Parser.

4

Variable resolution

All $variable tokens are replaced with live values from the Discord context object.

5

Output dispatched

The Output Builder calls ctx.send() with a plain string or a constructed discord.Embed.

DSL Commands #

The DSL supports a small set of keywords that control output mode and content. Every script is evaluated line-by-line from top to bottom.

send

Sends a plain text message to the channel. Everything after the keyword is the message content.

tflows DSL
send Hello World $ping

embed

Switches the output mode to embed. After this keyword, subsequent $title and $desc directives build up a discord.Embed object. No arguments required.

The embed is sent automatically when the script block finishes execution. You do not need a closing statement.

Embed directives

DirectiveDescription
$title[TEXT] Sets the embed title. Supports $ping and other variable interpolation.
$desc[TEXT] Sets the embed description. Supports multiline content and all variable types.

Available variables in directives

$ping $time() $id $image $server $membercount

Embed DSL #

Embeds are Discord's rich message format — they support titles, descriptions, colors, and more. Tflows' embed mode lets you build them declaratively with zero Python embed object management.

tflows DSL
embed
$title[Hello from Tflows]
$desc[
Ping: $pingms
Server: $server
Members: $membercount
]

How it works

1

Mode switch

The parser reads embed on its own line and activates embed mode for all subsequent lines.

2

Directive parsing

$title[…] and $desc[…] capture everything between the brackets as their value.

3

Variable interpolation

Values inside brackets are scanned for $variable tokens and resolved before the embed is built.

4

Auto-send

At end-of-script, the completed discord.Embed is sent via ctx.send(embed=...).

Supported fields

DirectiveTypeNotes
$title[TEXT] string Embed title. Variables supported inside brackets.
$desc[TEXT] string Embed description. Multiline content supported. Variables supported inside brackets.

Variables #

Variables are special tokens embedded in your DSL scripts, prefixed with $. They are resolved at runtime against the live Discord command context and replaced with their current values before output is generated.

Variables come in two flavors: static (no arguments, always produce the same type of value) and function (accept an optional mode flag in parentheses).

$ping

static

Returns the bot's current WebSocket latency in milliseconds as a rounded integer string.

UsageReturns
$pingLatency in ms, e.g. 42
tflows DSL
send Pong! $pingms

$uptime

dynamic

Returns how long the bot has been running since startup. Supports multiple formatting modes including full, short, clock, seconds, and custom token-based output.

Usage Returns
$uptime Default format, e.g. 1h 23m 10s
$uptime(full) Full format including days, e.g. 0d 1h 23m 10s
$uptime(short) Compact format, e.g. 1h 23m
$uptime(clock) Clock-style format, e.g. 01:23:10
$uptime(seconds) Raw uptime in seconds, e.g. 4990
$uptime(d, h, m, s) Custom formatted output using tokens
tflows DSL
embed
$title[Uptime Variants]

$desc[
Default: $uptime
Full: $uptime(full)
Short: $uptime(short)
Clock: $uptime(clock)
Seconds: $uptime(seconds)
Custom: $uptime(d, h, m, s)
]

$log

function

Sends a message to a specific Discord channel for tracking command usage, user actions, and bot events. Unlike console logging, this outputs directly inside a server channel.

Parameter Description
CHANNEL_ID Target Discord channel ID where the log message will be sent
MESSAGE Content of the log message
tflows DSL
$log(CHANNEL_ID){User executed a command}

Behavior

  • Sends a message directly to the specified Discord channel
  • Executes when the command runs successfully (unless wrapped in error handling logic)
  • Supports variables like $id, $time, and other engine variables
  • Does not print to console, only sends to Discord channels

Examples

tflows DSL
$log(123456789012345678){User executed !ping command}
tflows DSL
$log(123456789012345678){$user used a command at $time}
tflows DSL
$log(987654321098765432){$user was muted by <@$id> for spam}

Notes

  • Bot must have permission to send messages in the target channel
  • If the channel ID is invalid, logging may silently fail or throw an error depending on configuration
  • You can use <@$id> to mention the command user since $id returns raw ID

$image

function

Returns a Discord CDN avatar image URL. Accepts an optional mode flag to control whose avatar is returned.

ModeDescription
$image defaultCommand author's avatar URL
$image(user)Same as default — author's avatar
$image(mention)First mentioned user's avatar. Falls back to author if no mention.
$image(act)Mentioned user's avatar if a mention is present, otherwise author's.
tflows DSL
$image
$image(user)
$image(mention)
$image(act)

$server

function

Returns information about the current Discord guild (server). Defaults to returning the server name.

ModeDescription
$server defaultGuild name
$server(name)Explicitly returns the guild name
$server(boost)Current number of Nitro boosts
$server(boostlvl)Current boost tier level (0, 1, 2, or 3)
tflows DSL
$server
$server(name)
$server(boost)
$server(boostlvl)

$membercount

function

Returns the guild member count. Can be filtered to include all members, only humans, or only bots.

ModeDescription
$membercount defaultTotal member count (humans + bots)
$membercount(all)Same as default
$membercount(user)Count of non-bot members only
$membercount(bots)Count of bot accounts only
tflows DSL
$membercount
$membercount(all)
$membercount(user)
$membercount(bots)

$time

function

Outputs the current date and/or time. Accepts flags to control format; multiple flags can be combined with a semicolon delimiter.

ModeDescription
$time() defaultFull date + time in the default format
$time(12h)12-hour clock format (AM/PM)
$time(24h)24-hour clock format
$time(nodate)Time only — date portion omitted
$time(notime)Date only — time portion omitted
$time(nodate;24h)Combine flags with ; for composed output
tflows DSL
$time()
$time(12h)
$time(24h)
$time(nodate)
$time(notime)
$time(nodate;24h)

$id

function

Returns a Discord user ID as a string. Accepts an optional mode to select which user's ID to return.

ModeDescription
$id defaultCommand author's Discord user ID
$id(user)Same as default
$id(mention)First mentioned user's ID. Falls back to author if no mention.
$id(act)Mentioned user's ID if present, otherwise author's ID.
tflows DSL
$id
$id(user)
$id(mention)
$id(act)

Execution Flow #

Understanding exactly what happens when a user invokes a Tflows command helps you write more predictable scripts and debug issues faster.

1

Discord gateway event

A user sends a message in a channel your bot can read. The discord.py gateway fires an on_message event containing the full message object and context.

2

Prefix check

FlowBot checks whether the message starts with the configured prefix. If not, it's ignored entirely.

3

Registry lookup

The command name (the word after the prefix) is looked up in the registry dictionary. If not found, the invocation silently fails.

4

Script handed to parser

The stored code string is split by newlines. Empty lines are skipped. Each non-empty line is processed in order.

5

Mode detection

If a line is the literal token embed, the parser switches into embed mode. All subsequent directive lines feed into the embed builder.

6

Variable resolution

Each line is scanned with a regex for $identifier patterns. Matched tokens are replaced by calling the corresponding resolver with the live Discord context.

7

Output dispatch

In text mode: the resolved line content is sent via ctx.send(content). In embed mode: a discord.Embed is assembled from the resolved directives and sent via ctx.send(embed=...).

Scripts are re-parsed on every invocation. This means you can update the code string at runtime and the change takes effect on the very next command call — without restarting the bot.

Best Practices #

Following these patterns will make your Tflows scripts more readable, maintainable, and robust.

🎯

One command, one purpose

Keep each command focused. A !status command should show server stats; don't mix in unrelated data. Lean scripts are easier to debug and iterate on.

🔐

Never expose your bot token

Store tokens in environment variables or a .env file loaded with python-dotenv. Use os.environ.get("BOT_TOKEN") instead of string literals.

📦

Use embeds for information-dense output

If you're displaying more than two or three pieces of data, switch to embed mode. Embeds are visually structured and easier for users to scan.

🔄

Prefer act mode for flexible mention handling

Using $id(act) and $image(act) means your command works gracefully whether or not the user mentions someone — sensible fallback built-in.

🧪

Test variables individually

Build a !debug command that dumps all variables you need. Verify their output before composing them into production commands.

📝

Keep script strings in separate files for large bots

For bots with many commands, store DSL scripts as .txt or .tflow files and load them with open().read(). This keeps your main Python file clean.

Common Mistakes #

These are the most frequent errors new Tflows users encounter, and how to fix them.

Forgetting the embed keyword

Using $title or $desc without first writing embed on its own line will not produce an embed — the directives will either error or be ignored.

incorrect
# ❌ embed mode never activated
$title[My Title]
$desc[My description]
correct
# ✅ embed mode activated first
embed
$title[My Title]
$desc[My description]

Wrong bracket syntax for directives

Directive values must use square brackets […]. Parentheses and curly braces will not be parsed correctly.

incorrect
$title(Wrong brackets)   # ❌ parentheses
$desc{Also wrong}       # ❌ curly braces
correct
$title[Correct]          # ✅ square brackets

Incorrect mode flag names

Mode flags are case-sensitive and must match the documented identifiers exactly. Check the Variables reference for the exact spelling.

incorrect
$membercount(users)   # ❌ should be "user"
$image(Mention)       # ❌ should be lowercase "mention"

All variable mode flags are lowercase. $time(24H) will not work; use $time(24h).

Using mention without an actual mention

$id(mention) and $image(mention) fall back to the author if no mention is present — but the fallback exists to prevent crashes, not to produce the expected output. If you need flexible mention handling, use act mode instead and document the expected usage.

Examples #

Server info command

python
bot.command(
    name="server",
    code="""
embed
$title[$server Info]
$desc[
Members: $membercount
Humans: $membercount(user)
Bots: $membercount(bots)
Boost Level: $server(boostlvl)
Boosts: $server(boost)
]
"""
)

Ping + time status command

python
bot.command(
    name="status",
    code="""
embed
$title[Bot Status]
$desc[
Latency: $pingms
Time: $time(nodate;24h)
Date: $time(notime)
]
"""
)

Avatar + ID lookup

python
bot.command(
    name="whois",
    code="""
embed
$title[User Info]
$desc[
ID: $id(act)
Avatar: $image(act)
]
"""
)

Simple plain-text output

tflows DSL
send Pong! $pingms — $time(nodate;24h)

Complete time system demo

python
bot.command(
    name="time",
    code="""
embed
$title[Tflows Time Demo]
$desc[
Default: $time()
No Date: $time(nodate)
No Time: $time(notime)
24h: $time(24h)
12h: $time(12h)
24h No Date: $time(nodate;24h)
]
"""
)
On this page