Skip to content

Access control

Guards decide whether a handler runs. Put them under the route decorator.

Decorator Lets the update through when When blocked
@sponsor_required the user joined every sponsor channel sends a join prompt
@staff_required the database user has is_staff silent
@superuser_required the database user has is_superuser silent
from dj_telegram_bot.core.guards import sponsor_required, staff_required
from dj_telegram_bot.core.handlers import command_handler

@command_handler("stats")
@staff_required
def stats(ctx):
    ...

Tip

When a guard blocks the update, the handler does not run and the guard returns False.

@staff_required and @superuser_required

They check the flag on the database user, so they need a user model. They also block when there is no database user.

They are silent: the user gets no reply. To send a message, use @before_handler or check ctx.user_obj.is_staff inside the handler.

Force users to join one or more channels before the handler runs.

1. Add the channels

In the admin, open Channel sponsors and add:

Field Meaning
name button label
chat_id channel id; the -100 prefix is added automatically
link invite or public link shown on the button
is_active only active channels are checked

Warning

Add your bot as an administrator of each channel. Otherwise Telegram will not let it check membership.

2. Decorate the handler

@command_handler("start")
@sponsor_required
def start(ctx):
    ctx.bot.send_message(ctx.chat_id, "Welcome!")

If the user has not joined every active channel, they get a prompt with one button per channel and a confirmation button. The handler does not run.

3. Handle the confirmation button

The confirmation button sends callback_data="sponsor_required". Register a handler for it:

from dj_telegram_bot.core.guards import SponsorGuard
from dj_telegram_bot.core.handlers import callback_handler

@callback_handler("sponsor_required")
def sponsor_confirmed(ctx):
    if SponsorGuard(ctx).ensure_joined(use_cache=False):
        ctx.bot.send_message(ctx.chat_id, "Thanks, you can use the bot now!")

ensure_joined() returns True if the user joined every channel. Otherwise it sends the prompt again and returns False.

Where it applies

Update Checked?
private chat yes
group, supergroup no
channel post no
inline query yes
button on an inline message yes

Results are cached for 60 seconds per user and channel.

Texts

Texts come from Messages with these steps. English defaults are used if a message is missing.

related_step Used for Default
sponsor-prompt-text the prompt message To use the bot, please join the channels below.
sponsor-submit-text confirmation button label Confirm membership ✅
sponsor-join-error inline answers and callback alerts Please join the sponsor channels first, then try again.

Advanced

Customize inline answers

Inline queries and buttons on inline messages have no chat to send a prompt to, so the guard answers with an article (inline query) or an alert (button). To replace these answers, pass a function that receives the handler context and the channels the user has not joined:

def join_first_inline(ctx, channels):
    ...  # answer the inline query yourself

def join_first_callback(ctx, channels):
    ...  # answer a button press on an inline message

@inline_handler("*")
@sponsor_required(on_inline=join_first_inline)
def search(ctx):
    ...

@callback_handler("like:*")
@sponsor_required(on_callback=join_first_callback)
def like(ctx):
    ...

Pass only what you need: an argument you leave out keeps the default answer. @sponsor_required without arguments still works.

Note

Telegram limits an alert text to 200 characters.

Check every update

To check every update instead of single handlers, use the guard in a before handler.

Use the guard directly
guard = SponsorGuard(ctx)
missing = guard.get_not_joined(use_cache=False)   # list of ChannelSponsor
allowed = guard.ensure_joined()                  # True, or sends the prompt