PoliNetwork · implementation proposal

Stop the campaign before it spreads.

Replace broad language filtering with campaign fingerprints, join approval, and a short first-post quarantine. The signal is repeated behavior, not nationality.

Production evidence Sanitized examples Prepared 4 September 2026

Finding: one coordinated spam campaign, not a language problem

Most targets reuse short Han-script recruitment or payment lures, a Telegram mention, rotating digits, and a narrow set of display-name patterns. They arrive through many fresh accounts and repeat the same normalized templates across groups.

Entry-time metadata alone is not accurate enough for a blanket ban. The reliable design is a strict join gate for known fingerprints, followed by limited permissions until the first message is classified.

34,356campaign messages inferred in retained data
1,547campaign accounts across the network
62%of retained traffic matched the campaign
0.6%of campaign accounts caught by the current script gate
164mmedian delay before the first manual BanAll
99.4%observed precision for Han script plus mention

What the accounts have in common

These are stable campaign features seen across accounts. No claim is made about a person’s origin, citizenship, or language preference.

1

Repeated message shape

Short Han-script lure, often 14–21 characters, ending in a Telegram handle. Digits and handles rotate.

2

Fresh, fast accounts

1,542 of 1,547 campaign accounts first appeared since 31 August. 99.7% posted within ten minutes of first sighting.

3

Cross-account repetition

Twenty-two normalized templates explain the known campaign. Different accounts distribute the same text across groups.

Why the current checks miss it

Current non-Latin rule0.6%
Exact duplicate, 5 min28.7%
Name fingerprint at join33.4%
Han script + mention100%

Do not use broad profile rules

A fresh numeric ID, no username, no language code, or a Han-script name is not enough. Those signals also match legitimate students.

Use them only as small supporting weights. Require a confirmed campaign signature, known infrastructure, or repeated cross-account behavior for automatic BanAll.

Proposed control path

Four small controls cover the full lifecycle. The first two reduce entry. The last two stop any account that gets through.

Join request

Decline exact denylisted IDs and confirmed campaign fingerprints. Send uncertain accounts to a challenge or review queue.

Restricted admission

Approve with inline-bot and rich-media permission disabled until the member passes the first-message check.

First-post classifier

Normalize text, inspect mentions, via_bot, entities, buttons, and global campaign reputation.

Network response

Delete and BanAll only on confirmed signatures. Quarantine ambiguous posts and record the reason.

BanAll

Exact admin-confirmed signature, known malicious handle/domain, or a signature already repeated by several distinct authors across groups.

Quarantine

Han script plus a mention, especially from a fresh account or through an inline bot. Delete the post, restrict briefly, and queue it for review.

Allow

No campaign signature. Broad metadata and language alone never trigger a ban.

Examples

Handles and wording below are synthetic and sanitized. “Match” means the classifier acts; it does not always mean a permanent ban.

InputDecisionWhy
聘群演每日600+ @cash_helper_47BanAllNormalizes to an admin-confirmed campaign template. Rotating digits and handle do not evade it.
小额收点赚 @work_channel_2QuarantineShort Han-script lure plus mention. Promote to BanAll only if the signature or handle is confirmed.
徕收歀一天300qoo @cash_agentQuarantineObfuscated campaign shape. Fresh account and inline-button metadata would raise confidence.
same signature · 3 authors · 2 chats · 10 minBanAllGlobal repetition proves coordinated distribution. The counter is across authors, not per user.
via_bot=known_bad · button=https://bad.example/…BanAllKnown campaign infrastructure is a high-confidence signal even when the visible text changes.
大家好,我是交换生,请问今天的课程在哪个教室?AllowLegitimate Chinese-language question. No lure, mention, button, or known signature.
请问 @mario 今天上课吗?AllowA normal conversational mention. It is not a confirmed signature and lacks supporting campaign signals.
Per l’esame usiamo π e Ω, giusto?AllowOrdinary course discussion. Mathematical Greek characters remain valid.
new account · numeric ID · no usernameAllowWeak metadata is never a standalone rejection rule.

Implementation shape

Keep the classifier deterministic and auditable. Start in log-only mode, then enable enforcement by decision tier.

src/middlewares/auto-moderation-stack/campaign-signature.ts
type Decision = "allow" | "quarantine" | "ban_all"

export function normalizeCampaignText(text: string) {
  return text
    .normalize("NFKC")
    .toLowerCase()
    .replace(/@[\p{L}\p{N}_]{3,}/gu, "<mention>")
    .replace(/\p{N}+/gu, "#")
    .replace(/\s+/g, " ")
    .trim()
}

export function decide(s: Signals): Decision {
  if (s.confirmedSignature) return "ban_all"
  if (s.knownBadHandle || s.knownBadButton) return "ban_all"
  if (s.globalBurst && s.distinctChats >= 2) return "ban_all"

  const suspiciousContent = s.hasHan && s.hasMention
  const supportingSignal = s.isFresh || s.viaBot || s.hasInlineKeyboard
  if (suspiciousContent && supportingSignal) return "quarantine"

  return "allow"
}
Global campaign reputation
const signature = normalizeCampaignText(text)
const key = `moderation:campaign:${sha256(signature)}`

await redis.sAdd(`${key}:authors`, String(ctx.from.id))
await redis.sAdd(`${key}:chats`, String(ctx.chat.id))
await redis.expire(`${key}:authors`, 600)
await redis.expire(`${key}:chats`, 600)

const [authors, chats] = await Promise.all([
  redis.sCard(`${key}:authors`),
  redis.sCard(`${key}:chats`),
])

const globalBurst = authors >= 3 && chats >= 2

Message fields to retain

  • via_bot.id and bot username
  • entity types and mention targets
  • reply_markup.inline_keyboard button URLs and domains
  • normalized signature hash and feature flags
  • decision, reason codes, model version, and first-seen time

Repository changes

  • Add the campaign classifier beside the existing auto-moderation stack.
  • Replace the per-user duplicate key with global author and chat sets.
  • Enable chat_join_request in src/bot.ts.
  • Add a join-request handler and a restricted-new-member state.
  • Add telemetry counters for allow, quarantine, BanAll, and moderator reversal.

Safe rollout

The numbers are strong enough to ship a deterministic first version, but permanent bans need a measured ramp.

Day 1

Observe

  • Log signals and decisions
  • Store button and via-bot metadata
  • Compare with moderator BanAll actions
Days 2–3

Quarantine

  • Delete high-signal first posts
  • Restrict the sender briefly
  • Expose one-click confirm and release
Day 4+

Enforce

  • BanAll confirmed signatures
  • Decline exact denylisted joins
  • Keep generic Han-plus-mention on review

Release gates

  • False-positive rate below 0.5% on quarantines.
  • No automatic BanAll from profile metadata alone.
  • Every action carries stable reason codes.
  • Moderator reversal restores permissions and updates reputation.

Success metrics

  • Campaign posts visible for less than 30 seconds.
  • Median messages before action falls from 22 to 1.
  • Manual BanAll delay falls from 164 minutes to under 5.
  • Track entry declines separately from first-post catches.

Recommended first release

Ship global signature normalization, rich message-field capture, and first-post quarantine first. Enable join requests where group operations permit it. Automatically BanAll only exact confirmed signatures and campaign infrastructure. Review the broader Han-script-plus-mention pattern until live false positives are known.

Feature hashes can stay for 30 days. Raw message bodies can keep the current shorter retention window.