Cookiebot

v1 feature map

Every v1 feature traced to the code that implements it, with the defects found on the way

Links every QA scenario (Cookiebot-QA/features/*.feature) to the code that implements it in COOKIEBOT-Telegram-Group-Bot/Bot/* and COOKIEBOT-backend.

Legend: QA = spec exists · BOT = python handler · API = backend endpoint/collection.


1. Core (moderation / config) — 10 specs, 22 scenarios

QA featureTrigger (spec)Trigger (code)Bot handlerBackendStatus
core_botskins3 skins5 personas via is_alternate_bot 0-4universal_funcs.py:39-52 get_bot_token—⚠ skins = separate OS processes + tokens, not a runtime config
core_groupguardianjoin captchajoin eventGroupShield.py:231-265 captcha_message, :313-344 solve_captchaconfigs.timeCaptcha⚠ state in flat Captcha.txt, no real lock
core_listcommand/commands/comandos,/commandsMiscellaneous.py:124-127—✅ (reads Static/locales/*/Cookiebot_functions.txt)
core_mediarestrictnew-user media blockphoto/video handlersCOOKIEBOT.py:167-172 + configs.timeWithoutSendingImagesGET /configs/{id}✅
core_musicdetectionvoice w/ musicvoice messageAudio.py:6-20 identify_music (ShazamAPI)—⚠ unofficial reverse-eng API
core_privacy/privacy/privacy,/privacidade,/privacidadMiscellaneous.py:60-63—✅
core_rules/rules,/newrulessameGroupShield.py:49-63, Configurations.py:281-283,:269-279GET/PUT /rules/{id}✅
core_setlangweb settings page/configurar menuConfigurations.py:242-251 set_languageconfigs.language, PUT /bff/group/{id}/config⚠ spec says web UI, bot does in-chat menu
core_stickerspamsticker floodsticker eventCooldowns.py:12-22 sticker_anti_spamconfigs.stickerSpamLimit⚠ counter in unlocked process-local dict
core_welcome/newwelcome + joinsameGroupShield.py:140-171, Configurations.py:265-267,:253-263GET/PUT /welcomes/{id}✅

2. Fun — 10 specs, 21 scenarios (+7 authored locally for x_conversational_ai, not counted against upstream QA)

QA featureTrigger (spec)Trigger (code)Bot handlerBackendStatus
fun_battle/battle/batalha,/battle,/batallaSocialContent.py:294-379—❌ QA's "tags another user" (singular) is v1's one-tag path (person vs. a random "fighter" image), not a two-person battle. That path ships now — the Fight/ prefixes are exported — and QA's "Option A"/"Option B" wording still does not name the real poll options, which are the two display names — see docs/contracts/fun_battle.md. The telegram.me HTML scrape (:279-292) this row used to flag is fixed, not preserved: v2 resolves photos through cb_core.members.roster + the Bot API's getUserProfilePhotos instead.
fun_complaint/complaint/milton,/reclamacao,/complaint,/quejaMiscellaneous.py:240-248,:250-259—❌ 3 QA/v1 conflicts: QA says message+picture, v1 sends one photo whose caption is the message; QA says "after some minutes", v1 waits 10-20s; QA omits both message deletions, which are in-scope v1 behaviour — ported per v1, see docs/contracts/fun_complaint.md
fun_death/death/morte,/muerte,/deathMiscellaneous.py:335-357—✅
fun_diceroll 6 / roll 20/dado,/dice,/d<N>Miscellaneous.py:160-183—❌ spec/code trigger mismatch
fun_firecracker/firecracker/rojao,/acende,/fogos,/firecrackerMiscellaneous.py:226-238—✅
fun_memememe cmd/memeSocialContent.py:224-277—✅
fun_partneredcons/bff,/patas,/fursmeet,/trex,/furcamp,/pawstral/patas,/bff,/furcamp,/fursmeet,/pawstralMiscellaneous.py:261-323 event_countdown—❌ 2 QA/v1 conflicts, both resolved in v2's favour of v1's code: (a) /trex is in QA and in no v1 code path — it ships as a caption-less poster off the Countdown/Trex prefix v1 never listed, because no date for the event exists in any reference repo; (b) QA lists its /fursmeet scenario twice, byte-identical, an authoring slip not reproduced in qa/features/. QA also says nothing at all about the countdown caption, which is where every line of v1's behaviour is — see docs/contracts/fun_partneredcons.md
fun_random/random/aleatorio,/randomSocialContent.py:198-206GET/POST /randomdatabase⚠ backend loads whole collection to pick 1
fun_ship/shipp/shippar,/shipUserRegisters.py:216-250GET /registers/{id}/users❌ spec expects a lone @user1 to be used; v1 discards it (:219) — ported per v1, see docs/contracts/fun_ship.md
x_conversational_ainone — no upstream QA scenario existsmention the skin's name/@username, or reply to a bot messageNaturalLanguage.py:65-77—⚠ no Cookiebot-QA scenario, confirmed against the full listing — qa/features/x_conversational_ai.feature's 7 scenarios are authored locally against .specs/features/x_conversational_ai/spec.md, not ported. Four recorded behavioural changes: the DAN jailbreak system prompt is not ported (D-AI-1, persona kept, fabrication instruction inverted); the dead api.simsimi.vn NSFW branch is dropped (D-AI-6, unreachable in v1); the trigger is derived per-skin (tenancy.registry.by_skin) rather than hardcoded to "cookiebot"/"@CookieMWbot" (D-AI-7); v1's per-user streak counter is ported as-is and a per-group rate limit plus the tenant's monthly_llm_budget_usd hard cap are added on top (D-AI-8). See docs/contracts/x_conversational_ai.md.

3. Util — 13 specs, 18 scenarios (+5 authored locally for x_speech_to_text, not counted against upstream QA)

QA featureTrigger (spec)Trigger (code)Bot handlerBackendStatus
util_birthday/birthday/aniversário,/birthday,/cumpleañosBirthdays.py:14-61GET /users?birthdate=❌ QA's bare /birthday expects a montage; v1's own first check (len(msg['text'].split()) == 1) replies a "type usernames" prompt instead and never looks up who has a birthday — only /birthday <anything else> reaches the real lookup — ported per v1, see docs/contracts/util_birthday.md. Also: unattended daily broadcast to every group (manual_chat_id=None shape) has no caller anywhere in this checkout — unverified whether it runs in production, not built, not resolved. $expr month/day scan fixed via GENERATED columns + index (v2).
util_calladms/adm/adm,@admin,/reportUserRegisters.py:168-176,:178-203—✅
util_config/config/configurar,/configureConfigurations.py:139-167,:213-240GET/PUT /configs/{id}❌ trigger mismatch (/config vs /configurar)
util_deletereposts/deletereposts/deleteposts,/apagarpostsPublisher.py:316-327local Publisher.db❌ trigger mismatch (all three spellings resolve in v2). Also ❌ QA asserts the wrong refusal: the scenario expects "You don't have permission to use this command or are in anonymous mode" plus an anonymous-mode video, which is /configurar's branch (Configurations.py:139-143) — cancel_posts answers "You are not a group admin!" and sends no video. And ❌ QA's "all posts … are deleted" reads as deleting delivered messages; v1 removes scheduled, not-yet-sent rows only. Ported per v1, both recorded in docs/contracts/util_deletereposts.md. D-DR-2 fixed: v1 accepted any anonymous sender, v2 requires an anonymous admin.
util_doomlistjoin gatejoin eventGroupShield.py:172-229 check_cas/check_banlist/check_banlist_publicGET /blacklist/{id}, ext api.cas.chat, ext burrbot.xyz⚠ 2 external deps in join hot path
util_embeddersocial linkany messageSocialContent.py:79-84 check_reply_embed—✅
util_everyone/ping everyone/everyone,@everyoneUserRegisters.py:97-146GET /registers/{id}/users + per-user GET /users?username=❌ trigger mismatch; ⚠ N+1 backend calls
util_isalive/isalive/tavivo,/isaliveMiscellaneous.py:65-69—✅
util_nextbirthday/nextbirthday/proximosaniversarios,/nextbirthdaysBirthdays.py:104-117GET /users⚠ plural/singular mismatch
util_postforwardercross-group forward/divulgar,/publish,/publicar,/repost,/repostar,/reenviarPublisher.py:46-92,:223-286,:288-314,:329-369configs.publisherPost/Ask/MembersOnly⚠ shared unlocked SQLite conn (D5) — replaced by the distributed scheduled_posts table. Also ❌ both QA scenarios describe the wrong mechanism: they read as though forwarding a post to the bot delivers it to group b, but v1 requires an owner approval in between and delivers a day later on a randomised schedule. Ported with the approval press and the sweep as explicit steps. ⚠ the approval workflow itself has no Gherkin anywhere — six scenarios authored. Four more defects fixed (D-PF-2 anyone could press approve, D-PF-7 order-dependent max_posts trim, D-PF-8 one 5xx killed a campaign, D-PF-12 substring member match); translation moved off Google Translate onto cb_core.llm.router(). See docs/contracts/util_postforwarder.md.
util_postgetterreceive forwardedchannel-forward eventPublisher.py:46-55 ask_publisherconfigs.threadPosts,maxPosts⚠ QA has no scenario for publisher_ask — the prompt this row maps to — so that one is authored. v1 prompts an es group in English (no Spanish arm at Publisher.py:48); preserved, and expressed by omitting the key from the es catalog so the gap stays visible. D-PG-1 fixed: v1 passed its "9999" no-topic sentinel straight into message_thread_id, so a forum group that never set a topic got a failing forward — which the scheduler's catch-all then punished by deleting the row. See docs/contracts/util_postgetter.md.
util_youtube/youtube <q>/youtubeSocialContent.py:172-189—✅ (YouTube Data API)
x_reverse_searchnone — no upstream QA scenario exists/buscarfonte,/searchsource,/buscarfuenteSocialContent.py:113-142—⚠ no Cookiebot-QA scenario — §4 already listed reverse search among the features the spec never covered; qa/features/x_reverse_search.feature's 6 scenarios are authored locally from v1's behaviour. 🔴 D14: v1 hands SauceNAO a Telegram file URL containing the bot token (:89,119-120) — fixed, v2 uploads the image bytes and never builds that URL. Four more defects fixed (no timeout, an outage was silence, a reply with no image killed the update, and the answer was machine-translated a second time over its own already-localised string). See docs/contracts/x_reverse_search.md.
x_speech_to_textnone — no upstream QA scenario existsvoice reply to the bot (shape a); /transcribe,/transcrever,/transcribir (shape b)Audio.py:22-32 speech_to_text—⚠ no Cookiebot-QA scenario — the only voice-adjacent QA file is core_musicdetection.feature (Shazam, a different function in the same v1 file). qa/features/x_speech_to_text.feature's 5 scenarios are authored locally. Shape (b), the standalone /transcribe command, is net-new and has no v1 equivalent at all — the same asymmetry /trex records for fun_partneredcons, just inverted: here the code is ahead of any spec rather than a spec ahead of the code. Shape (a) fixes six defects on the way (D-ST-1 through D-ST-6: no more disk-written temp file, a bounded call, a duration cap, no .capitalize(), a language hint, and every failure path now replies). See docs/contracts/x_speech_to_text.md.
publicador(PTBR).mdapproval workflow/repost schedulingPublisher.py:288-314,:329-357 schedulerPublisher.db⚠ prose only, no Gherkin — ported as part of util_postforwarder, with scenarios authored. v1's 300 s threading.Timer in the primary process only (D-PF-11: a crash between ticks stopped every scheduled post forever, silently) is now an arq cron.

4. Implemented but NOT spec'd in QA (spec debt — 19 features remaining, 21 originally)

Two of the original 21 (Conversational AI, Speech-to-text) now have a v2 port with a locally authored acceptance suite — see their rows in §2/§3 above and docs/contracts/x_conversational_ai.md / docs/contracts/x_speech_to_text.md. They stay listed below too, since the underlying fact this table tracks — no scenario in ../Cookiebot-QA/ features/ — is still true; only their v2 build status changed.

FeatureTriggerCode
Giveaways/giveaway + 4 callbacksGiveaways.py:25-173, Giveaways.db
Reverse image search/buscarfonte,/searchsourceSocialContent.py:113-142 (SauceNao)
Age guess/idade,/ageMiscellaneous.py:185-202 (agify.io)
Gender guess/genero,/genderMiscellaneous.py:204-224 (genderize.io)
Unearth/desenterrar,/unearthMiscellaneous.py:325-333
Fortune cookie/sorte,/fortunecookieMiscellaneous.py:359-375
Destroy/distort/zoar,/destroyMiscellaneous.py:377-432 + Distortioner.py
Image search/qualquercoisa,/anything + fallbackSocialContent.py:144-170
Drawing idea/ideiadesenho,/drawingideaMiscellaneous.py:137-143
Analysis/analise,/analysisMiscellaneous.py:71-81
Sticker DB auto-replysticker/doc replySocialContent.py:208-222
Conversational AI — v2 done, see x_conversational_ai in §2mention "cookiebot" / replyNaturalLanguage.py:65-77 (OpenAI)
Speech-to-text — v2 done, see x_speech_to_text in §3voice msgAudio.py:22-32 (Whisper API)
Custom commands — blocked, the command names are GCS folder names (Miscellaneous.py:23), see .specs/features/x_custom_commands/spec.mdGCS Custom/ prefixMiscellaneous.py:145-158
Owner: list groups/gruposMiscellaneous.py:83-112
Owner: broadcast/broadcastMiscellaneous.py:114-122
Owner: leave+blacklist/leaveuniversal_funcs.py:320-329
Owner: (un)blacklist/blacklist,/unblacklistuniversal_funcs.py:307-313
Owner: stop / restart/stop,/restartCOOKIEBOT.py:89-94
Reload caches/reload,/recarregarCOOKIEBOT.py:197-201
WebHub JWT loginHTTP POST /loginServer.py:25-52 (Telegram Login Widget → RS256 JWT)

Backend-only, no bot/QA coverage: Events CRUD + BFF (EventResource, bff/events), Groups/admins (GroupResource), Raffle domain orphan (entity exists, no repo/service/controller — but docs/openapi.json still documents /raffles/* = stale generated spec).

5. Spec'd but NOT implemented

  • /trex partnered-con command (fun_partneredcons.feature:20-23).
  • core_setlang as a web settings page — only in-chat /configurar exists.
  • Entire QA automation layer: tests/ and pages/ are empty __init__.py; conftest.py has an orphaned @pytest.fixture over a commented-out Playwright function. 61 scenarios, 0 executable.

6. Cross-cutting defects worth carrying into v2 as regression tests

#DefectLocation
D1Write calls (POST/PUT/DELETE) share the 60s read memo-cache → duplicate writes silently no-opuniversal_funcs.py:106,117,128
D2verify=False on every backend call → TLS validation disableduniversal_funcs.py:100,111,122,133
D3while SEMAPHORE_VIDEOS: pass busy-wait burns a core; serializes all distortion globallyDistortioner.py:114-115,145-146,155-156
D4Fixed temp filenames (meme.png,CAPTCHA.png,temp.jpg,user1.jpg…) shared across 50 threads → cross-chat file clobberSocialContent.py:275, GroupShield.py:242, Birthdays.py:92, …
D5Publisher.db single shared conn, check_same_thread=False, no lockPublisher.py:15-16 — fixed: migration 0005's scheduled_posts, distributed on the target group_id
D6Caches unbounded + unlocked, never expire (5 processes = 5 divergent views)Configurations.py:9-12, UserRegisters.py:11-12, Cooldowns.py:8-10
D7JWT signing key regenerated in memory every restart → all tokens invalidatedServer.py:22-23
D8/grupos, /broadcast, birthday() loop all groups with sleep(0.4/0.5/3) on a worker threadMiscellaneous.py:96-103,114-122, Birthdays.py:61
D9GroupResource.deleteAdmins missing @RequestBody → body never bindsGroupResource.java:135-139
D10No index on Event.groupId, User.username, User.birthdate; $expr birthday query un-indexableEvent.java, User.java, UserRepository.java:16
D11Zero pagination — every list endpoint returns the full collectionall *Service.findAll()
D12/actuator/health + /actuator/prometheus are .anonymous() = publicSpringSecurityConfig.java:56-64
D13CORS allowed-origins:"*" + allow-credentials:true on /bff/**application.yml:2-12
D14Bot token leaked to a third party. reverse_search builds https://api.telegram.org/file/bot{TOKEN}/{path} and hands that URL to SauceNAO, which fetches it — the token lands in an external service's access logs. Anyone holding it controls the bot. Fixed in v2: the image bytes are uploaded, the URL is never constructed, and a unit test asserts the outgoing request carries no url part.SocialContent.py:89,119-120

On this page