Documentation menu

Per-task content rules

Per-Task Telegram Content Rules and Filters

Blacklists, whitelists, link handling, regex gates, length limits and ordered replacements — evaluated cheapest-first, with per-source and per-target overrides.

Verified against the checkout built for v2.2.4 · updated 20 Oct 2026

On this page

FilteringNew in v2.0

Word-boundary, Unicode-folded matching that behaves the same in Persian, Arabic and English.

What a policy contains

ContentPolicy fields (core/domain/content_policy.py)
FieldMeaning
blacklist_wordsNever forward a message containing one of these words.
whitelist_wordsForward only when these words are present.
whitelist_modeany (default), all, or phrase — how the whitelist is satisfied.
block_linksRefuse any message that contains a link.
remove_linksForward the message with links stripped out.
strip_wordsForward the message with these words removed.
replacementsOrdered find → replace pairs, literal or re: regex.
regex_block / regex_requireRefuse on match / require a match; hostile patterns are rejected.
min_length / max_lengthLength gates applied before any expensive work.
allowed_media / blocked_mediaMedia allow and deny gates.
case_sensitive / word_boundary / normalizeMatching semantics, all overridable per task.

The matching semantics that matter

  • Unicode folding: Persian and Arabic spell the same letter several ways (ي vs ی, ك vs ک, أ vs ا) and use zero-width joiners. Both the message and the rule are folded before comparison, so one key catches every spelling.
  • Word boundaries: cat does not match concatenate. Keywords that begin or end with a non-word character (#promo, @channel, a URL) skip the boundary check, because a boundary there would be wrong.
  • Cheapest-first evaluation: lengths, media, links, literal words, then regexes. A burst of fifty posts never pays for regexes it does not need.
  • Single-pass replacements: replacements run over the original text in one pass, so a replacement value can never be re-scanned by a later rule. No cascades, no surprises.
  • Patterns are compiled once and cached, and nested-quantifier patterns like (a+)+ are refused outright rather than trusted with a worker.

Overrides are additive only

The task policy is the floor. A per-source or per-target override can add constraints — a stricter blacklist for a public target, link stripping only for the free tier — but every policy in the chain must allow the message for it to be delivered.

Transforms apply in chain order: task first, then source, then target. That ordering is deterministic and visible in the log entry, which records the policy provenance that decided the outcome.

Dry-run a policy before it goes live

Simulate against real text
# does this message survive my task's policy?
atf rules simulate <task-id> --text "Join our VIP group at t.me/vip — 50% off today"

# settings are validated before they are written
atf rules settings schema
atf rules settings set <task-id> remove_links=true block_links=false
atf rules settings get <task-id>

Frequently asked

Will a blacklist I typed in Persian catch text written on an Arabic keyboard?

Yes. Letter variants (Arabic yeh, alef forms, kaf) and zero-width joiners are folded before comparison, so both spellings normalise to the same string.

Can a per-target override loosen the task rules?

No. Overrides are additive by construction: each policy in the chain must allow the message. A target can be stricter than the task, never more permissive.

What happens to a message that a rule rejects after it was already delivered?

Edits are re-evaluated against the policy; if the edited message no longer passes, the destination copy is deleted. Set max_time_edit to bound how far back edits are honoured.

Are regex patterns safe to hand to this system?

Patterns are compiled once and cached, and nested-quantifier constructions are refused at validation time instead of being run against traffic.

Keep reading