What is CyberPUP?
CyberPUP is Frisky Dev's Telegram group protection stack — two complementary bots that together handle automated moderation, ML-based content screening, reputation tracking, and community management for the Haus of Howl community and partner groups.
/setlog log channel. Think of it as the tool belt that Security's log channel receives events into.64.23.130.34) inside separate Docker containers: cyberpup-security and cyberpup-core.
Architecture
Two independent processes, one droplet, shared logging destination.
DB: SQLite (aiosqlite) / PostgreSQL via DB_URL
ML: Image classifier (NSFW), text spam model
Config:
config.toml + environment variablesDeployment: Docker,
/opt/cyberpup-securityHealth: HTTP healthcheck on port 8080
DB: MongoDB (
claw-mongo motor async)Modules: ~50 auto-discovered Python modules
Config: Environment variables + MongoDB
Deployment: Docker,
/opt/cyberpup-coreHot-patch:
docker cp + restart (no rebuild)
Reputation System
CyberPUP Security tracks every member's message count and reputation points. Clean messages earn points; violations cost them. Thresholds gate which actions the bot applies to each user.
Member Levels
Level is determined by total message count. Admins and the group owner are always exempt from automated restrictions regardless of level.
Reputation Thresholds
These rep-point thresholds control which automatic restrictions apply. All values are configurable in config.toml under [spam].
Reputation Labels
Rep Changes
-20Link spam:
-10Forward violation:
-10Cross-chat reply:
-10Location blocked:
-10Voice messages:
-10Invisible spacing:
-10Single emoji:
-5CJK spam:
-5ML spam:
-5Promo violation:
-5
Spam Detections
Security's message pipeline runs a cascade of checks in order. Admins and group owners bypass every check. Trusted users (100+ clean messages, high rep) skip the expensive ML check.
Active Content Checks
| Check | Condition | Action | Rep Penalty |
|---|---|---|---|
| CJK Spam | Message contains Chinese characters (CJK Unified Ideographs) | Delete, log to channel | -5 |
| Invisible Spacing | Hangul filler chars (U+115F, U+1160, U+3164, U+FFA0) present; user rep < 50 | Delete, log to channel | -10 |
| Profanity | Built-in bad-word list matches any member regardless of rep | Delete, log to channel | -20 |
| Single Emoji | Message is exactly one emoji; user rep < 30 | Delete, log to channel | -5 |
| Link Spam | Message has URL or text_link entity; user rep < 50; not a promo slot | Delete, log to channel | -10 |
| ML Spam Model | Text classified as spam; user not trusted; low message count or low rep | Delete (no log entry by itself) | -5 |
Message Type Checks
| Content Type | Rule | Action |
|---|---|---|
| Media (photos, videos, documents, audio, video notes, animations) | User rep < 20 | Delete silently |
| Forwards (external, non-auto-forward) | User rep < 30 AND not a channel forward | Delete, log Forward Filter, -10 |
| Channel Forwards (external channels) | Counted as promo — see Promo Guard section | Promo Guard flow |
| Cross-chat replies ("Reply in Another Chat") | User rep < 50 | Delete, log Cross-Chat Reply Filter, -10 |
| Voice messages | 75% probability trigger (all users) | Reply discouraging message, -10 |
| Contacts | Sender is not admin | Delete silently |
| Locations | Sender rep < 50 | Delete, log Location Filter, -10 |
| Early comments | Reply to linked channel post within 30s; user rep < 50 | Delete, log Early Comment Filter |
| Join notifications | All NEW_CHAT_MEMBERS service messages | Delete; log member joined |
NSFW Detection
CyberPUP Security runs an ML image classifier on photos from low-rep users. Both in-chat images and profile photos are checked. Results are cached to avoid re-downloading the same file.
Classification Categories
What Gets Checked
file_unique_id to avoid repeated downloads. If NSFW: message deleted, review sent to log channel with action buttons.Log Channel Review
When NSFW is detected, two action buttons appear in the log channel:
Promo Guard
Each user gets one promotional post per 24-hour rolling window per group. Admins and owners are always exempt. Reposts of the same content (same URL or same channel) reuse the existing slot without consuming a new one.
What Counts as a Promo?
Regular messages, stickers, voice notes, photos without links, and forwards from linked channels are not counted as promos.
How the Limit Works
wbb.promo_limits collection). If MongoDB is unavailable, an in-memory fallback is used (lost on restart). The limit defaults to 1/day and is configurable via PROMO_LIMIT_PER_DAY or config.toml [promo].
Configuration
| Key | Default | Description |
|---|---|---|
PROMO_ENABLED | true | Enable/disable promo guard entirely |
PROMO_LIMIT_PER_DAY | 1 | Max promo slots per user per group per 24h |
Auto-Ban
When a user accumulates enough spam violations while maintaining low reputation, CyberPUP Security automatically bans them without waiting for manual action.
Both conditions must be true simultaneously. A user with 200 violations but high reputation won't be auto-banned. A user with 50 violations and low rep also won't. Only when violations cross 100 AND rep is below 100 does the ban fire.
violations_count_spam.
autoban_enabled = false in [spam] in config.toml, or set AUTOBAN_ENABLED=false env var.
Security Commands
CyberPUP Security uses a ! prefix for owner commands and / prefix for member commands. All owner commands require the sender to be configured as an owner in config.toml or BOT_OWNER env var.
Member Commands (in main groups)
| Command | Description |
|---|---|
/me /info /я /лвл /whoami /neofetch /fastfetch | Show your member signal snapshot — level, reputation, message count. Reply to another member to see their info. Throttled: once per minute per user per group. |
/rules /правила | Show the group rules message. Throttled: once per minute per group (all aliases share the throttle). |
!бу /бу | Fun command — the bot pretends to be startled. Random response from the bot's "bu-responses" string set. |
Owner Commands (in main groups)
| Command | Usage | Description |
|---|---|---|
!spam | Reply to a message | Flag message as spam. Deletes it (if user isn't admin), creates DB record, sends to log channel with ban / mark-safe / cleanup buttons. |
!setlvl | !setlvl 500 (reply) | Override a member's message count and boost their reputation by the same value. Useful for manually promoting trusted members. |
!reward | !reward 100 (reply) | Award N reputation points to the replied member. Max 100 000 per call. |
!punish | !punish 50 (reply) | Remove N reputation points from the replied member. Max 100 000 per call. |
!rreset | Reply to a message | Reset a member's reputation to equal their message count (clean slate). |
Owner System Commands
| Command | Usage | Description |
|---|---|---|
!setmain | !setmain -100xxxx | Set authorized main group(s) — comma-separated IDs. Only groups in this list will be moderated. |
!setreports | !setreports -100xxxx | Set the channel where reported messages are sent. |
!setlogs | !setlogs -100xxxx | Set the channel where moderation events are logged. |
!ping | In any authorized group | Confirm the bot is monitoring this group. |
!menu | DM or any group | Open the inline navigation menu (Status / Moderation / Reports / Help / Configure). |
Auto-Activation
groups.main, persisted to MongoDB, and a confirmation panel is posted. No !setmain needed. If added by anyone else, the bot stays silent and posts a message explaining how the owner can activate it.
Admin Commands (Core)
CyberPUP Core provides the full moderation toolkit. Commands require the calling user to have the relevant admin permission in the group (e.g., can_restrict_members for bans). The Sudoers list can bypass permission checks.
Ban & Kick
| Command | Description |
|---|---|
/ban | Remove a user from the chat permanently. |
/dban | Delete the replied message and ban its sender. |
/tban | Ban a user for a limited time (e.g., /tban 2h). |
/unban | Restore a banned user's access. |
/listban | Ban a user across all chats listed in a replied message. |
/listunban | Remove a cross-chat ban applied by /listban. |
/kick | Remove a user and allow them to rejoin. |
/dkick | Delete the replied message and kick its sender. |
/ban_ghosts | Remove all deleted Telegram accounts from the chat. |
Warn System
| Command | Description |
|---|---|
/warn | Add a warning to a user. Reply to their message. |
/dwarn | Delete the replied message and warn its sender. |
/warns | Show a user's current warning count. |
/rmwarns | Clear all warnings for a user. |
Mute
| Command | Description |
|---|---|
/mute | Put a user in read-only mode. |
/tmute | Mute a user for a limited time (e.g., /tmute 30m). |
/unmute | Restore sending access to a muted user. |
Messages & Promotion
| Command | Description |
|---|---|
/del | Delete the replied message. |
/purge | Clear messages from the replied point upward. |
/purge N | Clear exactly N messages from the replied point. |
/promote | Promote a member to admin with standard rights. |
/fullpromote | Promote a member with every available admin right. |
/demote | Remove admin rights from a member. |
/pin | Pin or unpin the replied message. |
/invite | Share the group or supergroup invite link. |
/report @admins @admin | Signal a message to the admin team. |
Blacklist & Filters
CyberPUP Core separates two concepts: the Blacklist (explicit phrases that trigger enforcement) and Filters (keyword-triggered bot responses). Both are per-chat.
/blacklist are enforced. General cursing passes freely.
Blacklist Commands
| Command | Description |
|---|---|
/blacklist [WORD|PHRASE] | Add a phrase to this chat's blacklist. Requires can_change_info admin right. |
/whitelist [WORD|PHRASE] | Remove a phrase from the blacklist. |
/blacklisted | List all blacklisted phrases. Large lists are paginated; very large lists sent as a text file to avoid flooding. |
8581086019 is hard-coded as a never-restrict user — the blacklist enforcer will never delete their messages regardless of content.
Filters Commands
Filters let you save a Telegram message (text, photo, video, audio, animation, etc.) as a trigger. When any member sends a message matching the filter name, the bot responds with the saved content.
| Command | Description |
|---|---|
/filter [NAME] | Save a filter, replying to the message you want as the response. Use underscores for multi-word names: /filter Hey_there. |
/stop [NAME] | Remove one filter. |
/stopall | Permanently remove all filters in this chat. |
/filters | List all saved filters in this chat. |
Federations
Federations let you synchronize bans across multiple groups. A spammer banned in one group can be auto-banned in all others in the same federation. Federation admins can ban across all member groups without being an admin in each one individually.
/feds help. Creating a federation, inviting groups, and designating fed admins requires the Sudoers list or fed ownership. Support channel: Frisky Signal.
/setlog — Log Channels (Dual-Emit)
CyberPUP uses a dual-channel log architecture — every moderation event is sent to two destinations simultaneously:
- INDIVIDUAL — the group's own private log channel (set with
/setlogfrom inside that group). Only events from that specific group land here. - GENERAL — a master aggregate feed shared by all groups (set with
/setlogfrom DM). Every group's events arrive here, giving a unified view across the network.
If a group has no INDIVIDUAL channel set, its events still appear in GENERAL — nothing goes dark. If INDIVIDUAL == GENERAL for a group, the message is sent only once (de-duped). Three privilege tiers can run /setlog, each with different scope.
Access Tiers
off, no-arg status, @handle, numeric ID), any context (DM, group, inside channel)./setlog -100xxxx, /setlog @handle, or bare /setlog (forward mode) from within a group they administer — sets the INDIVIDUAL channel for that group only. May also use /setlog off to clear the INDIVIDUAL mapping (group still appears in GENERAL). Cannot change the GENERAL channel. Cannot run from a group where they are not an admin — no cross-group leakage.Usage Reference
| Syntax | Who | Description |
|---|---|---|
/setlog (bare, in group) ★ INDIVIDUAL | Owner / Sudoer / Group Admin | Activates forward mode (5 min TTL) — sets the INDIVIDUAL channel for this group. Forward any post from the target log channel. Events from this group will go to BOTH this channel AND the GENERAL channel. Other groups are unaffected. Security reads the mapping at event time (instant, no delay). |
/setlog (bare, in DM) ★ GENERAL | Owner / Sudoer only | Activates forward mode (5 min TTL) — sets the GENERAL (aggregate) channel. All groups' events land here regardless of their INDIVIDUAL setting. Also writes to Security's MongoDB — Security applies within ~30 s, no restart needed. |
/setlog -100xxxx | Owner / Sudoer / Group Admin | Set by numeric ID. In a group → sets INDIVIDUAL for that group. In DM → sets GENERAL. Group admins must send from within their group. |
/setlog @channelname | Owner / Sudoer / Group Admin | Set by @username. Same INDIVIDUAL vs. GENERAL routing as above. Bot must already be a member of the target channel. |
/setlog (inside channel) | Owner / Sudoer only | Auto-register this channel as the GENERAL aggregate log channel. Bot probes and sets immediately. |
/setlog off | Owner / Sudoer / Group Admin | In a group → clears the INDIVIDUAL mapping (group still appears in GENERAL). In DM → disables the GENERAL channel for Core and Security (Owner/Sudoer only). |
/logstatus | Owner / Sudoer / Group Admin | Reports both channels: this group's INDIVIDUAL (if set) and the GENERAL aggregate channel — for both Core and Security — in one reply. |
wbb.log_channels._id=<group_id>) is written by /setlog in a group — Core caches it in-memory, Security queries it at event time (no delay, no restart). GENERAL (core_settings.log_group_id / security_settings.groups_logs) is written by /setlog in DM — Security applies within ~30 s. If a group has no INDIVIDUAL set, only GENERAL is emitted (cutover-safe). If INDIVIDUAL == GENERAL, the event is sent exactly once. Run /logstatus in a group to see both layers.
How it Works
/setlog is invoked, the handler checks message.chat.type. If it's a group/supergroup → INDIVIDUAL path (stores source_group_id in the pending dict). If it's DM or channel → GENERAL path (source_group_id = None)./setlog enters forward mode (5 min TTL). _pending_setlog[user_id] stores (expiry, source_group_id_or_None). When a forwarded channel post arrives, the handler checks the stored context to determine INDIVIDUAL vs. GENERAL write./setlog -100xxxx or /setlog @handle resolves the channel and calls _apply_log_channel(log_channel_id, ..., source_group_id=<int|None>).wbb.utils.log_channels.set_log_channel(source_group_id, log_channel_id). Upserts wbb.log_channels._id=source_group_id in MongoDB and updates in-memory cache. At log time, Core calls get_log_destinations(chat_id) which returns [individual, general] deduped — both messages are sent in a loop. Security calls get_group_log_channel(chat_id) on the same collection and dedupes vs. config.groups.logs inline.core_settings.log_group_id and patches every loaded wbb submodule via sys.modules iteration. Also writes security_settings.groups_logs — Security's config-reload loop applies within ~30 s. Groups with no INDIVIDUAL set emit to GENERAL only (cutover-safe, nothing goes dark).@@channelname → @channelname, and a bare channelname (no @) → @channelname. Double-@ typos (a known UX trap when copy-pasting) no longer cause "Can't find @@…" errors. Error messages now distinguish two failure modes: "username doesn't exist" (UsernameNotOccupied) vs. "bot is not yet a member" (PeerIdInvalid).
Sudoers (Global Ops)
The Sudoers list in CyberPUP Core is the highest privilege tier. Sudoers can execute global operations across all groups the bot serves.
| Command | Description |
|---|---|
/gban [user] [reason] | Ban a user across every served group. Requires a reason. Sends the user a DM explaining the ban. |
/gunban [user] | Reverse a global ban, removing the ban across all served groups. |
/stats | Live system stats: uptime, CPU, RAM, disk, bot process memory. |
/gstats | Network-wide stats: total served chats, served users, global ban count. |
/broadcast | Send a replied message to all served groups. |
/ubroadcast | Send a replied message to all served users. |
/setlogs [id] | Set the primary log group ID (Sudoers version — integer-only). |
/setgbanlogs [id] | Set the global ban log group. |
/setdump [id] | Set the message dump chat. |
/eval | Execute Python code in the bot's process. |
/sh | Execute shell commands on the host. |
/update | Pull latest changes and restart. |
/restart | Restart the bot. |
/clean_db | Remove stale served-chat records from the database. |
/servedchats | List every group and channel the bot is serving, with chat IDs and type. Sent as a .txt file if > 60 lines. Sudoers + owner only. |
Initial Setup
Both bots run as Docker containers on a DigitalOcean droplet. Here's how each is brought live for a new group.
CyberPUP Security — New Group
!setmain.!ping in the group to confirm monitoring is active.!setreports -100xxxx and !setlogs -100xxxx to point the reports and log channels.CyberPUP Core — New Group
/setlog and forward a post from your master log channel — this sets the GENERAL aggregate feed that all groups write to. (2) From inside each group, run /setlog and forward from that group's private channel — this sets the INDIVIDUAL channel. Events from that group then arrive in both places. Repeat step 2 for each group. Run /logstatus in the group to confirm both INDIVIDUAL and GENERAL are set correctly./blacklist WORD and filters with /filter NAME as needed.Configuration
CyberPUP Security is configured via config.toml and environment variables. Environment variables take precedence over the file.
Key Environment Variables
| Variable | Description |
|---|---|
BOT_TOKEN | Telegram bot token (required) |
BOT_OWNER | Primary owner Telegram ID |
BOT_OWNERS | Comma-separated additional owner IDs |
GROUPS_MAIN | Comma-separated main group IDs to moderate |
GROUPS_REPORTS | Reports channel ID |
GROUPS_LOGS | Log channel ID |
LINKED_CHANNELS | Comma-separated linked channel IDs (forwards allowed) |
DB_URL | Database URL (default: SQLite) |
PROMO_ENABLED | true/false — enable promo guard |
PROMO_LIMIT_PER_DAY | Promo slots per user per 24h (default: 1) |
LOG_LEVEL | Python logging level (default: INFO) |
HONEYBADGER_API_KEY | Optional: Honeybadger error reporting |
config.toml Sections
owner, owners, token, version, version_codenamemain, reports, logs, linked_channels, new_users_nomediaenabled, limit_per_daydata/bootstrap-state.json) stores the owner_id, group_id, reports_id, and logs_id from the web activation portal. This file is loaded on startup and fills in any config values that weren't explicitly set.
Troubleshooting
Common issues and their resolutions.
2. Confirm the group ID is in
GROUPS_MAIN (or was auto-activated).3. Check the bot has admin rights (Delete + Ban).
4. Run
!ping in the group to confirm the bot responds.
docker logs cyberpup-core --tail 50 on the droplet. Confirm the module table shows the relevant module (e.g., admin, blacklist). If the container crashed, docker start cyberpup-core.
parse_mode="html" string instead of ParseMode.HTML enum). Fixed in v2+. If you still see it, verify the deployed setlog.py contains from pyrogram.enums import … ParseMode and uses parse_mode=ParseMode.HTML.
filters.command() already rejects commands addressed to another bot — /ban@OtherBot won't trigger the ban handler. The one gap is the keyword filter engine (filters_re): if a saved filter word matches a command name (e.g., a saved filter "ban"), it would fire on /ban@OtherBot because filters_re scans raw text. Fix deployed in v4: filters_re now skips any message that starts with a slash command whose @mention targets a different bot's username.
/opt/cyberpup-core/wbb/modules/<module>.py (host source — survives rebuild).2.
docker cp /opt/cyberpup-core/wbb/modules/<module>.py cyberpup-core:/wbb/wbb/modules/<module>.py3.
docker restart cyberpup-core
claw-mem Docker network. If the claw-mongo container is down: docker start claw-mongo. The promo guard falls back to in-memory (lost on restart). The log channel setting also persists in MongoDB — run /setlog again after recovery.
ssh -o ControlMaster=auto -o ControlPath=/tmp/cyberpup-ssh root@64.23.130.34.
/logstatus in the group — if either shows "not set", run /setlog from DM (for GENERAL) or from the group (for INDIVIDUAL). Core uses wbb.utils.log_channels.get_log_destinations(chat_id) which returns [individual, general] deduped — both messages are sent in a loop. Security reads wbb.log_channels for the individual and config.groups.logs for general inline at each call site. If only one channel receives events, verify the other is configured.For the GENERAL path:
from wbb import LOG_GROUP_ID at module import creates a local copy. setlog.py uses sys.modules iteration (_patch_all_modules) to update every loaded submodule. Verify this function is present in the deployed setlog.py if GENERAL writes aren't taking effect.
FAQ
Can group admins use /setlog?
/setlog -100xxxx, /setlog @handle, or bare /setlog (forward mode) from within a group they administer — this writes an INDIVIDUAL mapping (wbb.log_channels) that affects only that group. Events from that group then go to both their INDIVIDUAL channel AND the GENERAL aggregate channel. They may also run /setlog off to clear the INDIVIDUAL mapping (group still appears in GENERAL). They cannot change the GENERAL channel, run it from a group where they are not an admin, or affect other groups. Sudoers and the owner have full access including GENERAL writes. Non-authorized users are silently ignored.Why does the Security bot delete Chinese text even from trusted members?
A user says they're getting their messages deleted unfairly. How do I check?
/me as a reply to one of their messages in the group (Security bot). This shows their rep, level, and message count. If rep is very low, the appropriate thresholds are active. The owner can use !reward to boost rep or !setlvl to raise their level if they're a trusted member who joined recently.What is the difference between the reports channel and the log channel?
/report, @admins, or @admin — user-initiated. Log channel receives automatic moderation events (bans, NSFW detections, spam removals, member joins, global bans, etc.) — bot-initiated. Set them separately with !setreports and !setlogs in Security, or /setlog in Core.Does CyberPUP Core automatically block profanity like Security does?
AUTO_PROFANITY_FILTER = False). Core ships with no built-in wordlist. The blacklist only enforces words you explicitly add with /blacklist WORD. General cursing passes freely in Core. CyberPUP Security has its own built-in profanity detection which is separate.How do I add a second owner to CyberPUP Security?
BOT_OWNERS=id1,id2 in the environment or add owners = [id1, id2] to [bot] in config.toml. Settings loaded from MongoDB at startup merge with these — Mongo's list is never allowed to replace the env/config list to prevent a stale Mongo doc from locking out configured owners.What happens if MongoDB is down?
wbb.log_channels) also live in Mongo — if Mongo was down when you ran /setlog from a group, the in-memory cache update still works for the current session but the INDIVIDUAL mapping won't survive a restart. The GENERAL channel (core_settings.log_group_id) also persists in Mongo. Security uses SQLite for member/spam data so isn't affected by Mongo outages for moderation, but both INDIVIDUAL and GENERAL log channel lookups require Mongo for Security. If Mongo recovers, mappings resume automatically; re-run /setlog if any were lost. While Mongo is down, Core emits to GENERAL only (in-memory value); Security emits to config.groups.logs only.The NSFW filter flagged a clean anime image. What do I do?
[nsfw] can be raised.