Appendix Bthe actual code, and how to check it for yourselfPlease read it kindly
← back to the writeupAppendix B · source & verification
Appendix B · Don’t take our word for it

The source, and how to check it

Every claim in the dossier comes from the bot’s own code. So here it is — the exact functions behind each exhibit, copied straight out of the files, with their real line numbers. If we’d mangled or invented anything, it wouldn’t line up with the data files in the other exhibits. It does.

We are deliberately not publishing the whole program. A working copy of a bot that logs chats, runs shell commands and screenshots its host is exactly the kind of thing that shouldn’t be handed to strangers. Instead, anyone who already has the folder can confirm these files are the genuine, unaltered originals using the checksums below.

B1 Verify the files yourself

How to check. If you have the bot’s folder, run shasum -a 256 main.py src/*.py (macOS/Linux) or Get-FileHash main.py -Algorithm SHA256 (Windows). If the hashes match the table below, you’re looking at the same bytes we are — nothing edited, nothing faked.
FileLinesSHA-256
main.py4,293b2386c9c3e476bb86b2f2e9770c753fb3dfcc989a73e2f9a304db13a1d7c6c41
src/ai.py7323e20dd2777a88395d94771c08dbadb853d74c7dd82303035f2e400c92ed9f643
src/database.py95937de126251d7c82317ca97d84eb6d9c163340fd04269118e31af31dcc6706539
src/owner_prefix_commands.py1,26659dabd5cc3465d1c0a9632268bb5748b73048b06db2e44a1dfee6323ab345471
src/failsafe_security.py541d842818f1617abc410fcdc23c39a763d6e0812f16bb07c658b3f3558a6e08c50
src/games.py3525d04c841c6667779e449662bf294c0a7401435fa87f3e7a5897383f422d97894
src/views.py227540fbc432344e3c08a0c8a6b4a63ac229fa05b8c53221196d69cfdeb8a6a07b9
requirements.txt288054e7ad0dfc107b51b46dd75fb5b1026e7a11a9d2a40b6bf07fbc27c168405
setup_linux.sh86b610d39d9ed180cbf5ce03703bce16d9ff16c743f3983ef8733a7bca738dcfc0

8,458 lines of source reviewed across 9 files.

B2 The load-bearing code, verbatim

These are unedited excerpts — the same bytes that produce the hashes above. Each is tagged with the exhibit it backs.

It records the whole room → writes it to diskmain.py · L1798–1815
1798        
1799        recent_chat_history = []
1800        async for msg in message.channel.history(limit=25):
1801            recent_chat_history.append(f"{msg.author.name}: {msg.content}")
1802        recent_chat_history.reverse()
1803        history_text = "\n".join(recent_chat_history)
1804        
1805        # save temporarily for 5 mins
1806        temp_dir = os.path.join(DATA_DIR, "chatbot", "temp_analysis")
1807        os.makedirs(temp_dir, exist_ok=True)
1808        temp_file = os.path.join(temp_dir, f"history_{message.channel.id}_{int(time.time())}.txt")
1809        try:
1810            with open(temp_file, "w", encoding="utf-8") as f:
1811                f.write(history_text)
1812        except Exception: pass
1813            
1814        # Clean old files
1815        for fn in os.listdir(temp_dir):

Reads the last 25 messages from everyone in the channel and writes them to a history_*.txt file. (Exhibit 01.)

It injects your dossier into the AI promptmain.py · L1748–1755
1748            f"\n๐ŸŸก EMOTION: {emotion_key.upper()} โ€” let this color your tone subtly."
1749            f"\n๐Ÿ‘ค RELATIONSHIP: {affection_level} (affection: {current_affection}/100)"
1750            + (f"\n\n๐Ÿ“‹ WHAT I KNOW ABOUT THIS USER:\n{user_profile_ctx}" if user_profile_ctx else "")
1751            + f"\n\n๐ŸŽจ PERSONALITY:\n{personality_text}"
1752            "\n\nโ•โ• MEMORY & CONTEXT โ•โ•"
1753            "\nYou have persistent memory of this user. USE IT proactively. "
1754            "If they mentioned a game, a problem, a friend, a topic before โ€” remember it, bring it up naturally when relevant. "
1755            "NEVER repeat something you already said. NEVER ask for info they already gave. "

The “WHAT I KNOW ABOUT THIS USER” block and the “persistent memory of this user” instruction. (Exhibit 03.)

Hidden affection score, re-graded every messagemain.py · L1749–1770
1749            f"\n๐Ÿ‘ค RELATIONSHIP: {affection_level} (affection: {current_affection}/100)"
1750            + (f"\n\n๐Ÿ“‹ WHAT I KNOW ABOUT THIS USER:\n{user_profile_ctx}" if user_profile_ctx else "")
1751            + f"\n\n๐ŸŽจ PERSONALITY:\n{personality_text}"
1752            "\n\nโ•โ• MEMORY & CONTEXT โ•โ•"
1753            "\nYou have persistent memory of this user. USE IT proactively. "
1754            "If they mentioned a game, a problem, a friend, a topic before โ€” remember it, bring it up naturally when relevant. "
1755            "NEVER repeat something you already said. NEVER ask for info they already gave. "
1756            "If the conversation is ongoing, keep it going โ€” don't restart from scratch. "
1757            "If other users are talking in the chat history, read the full context so you understand the conversation, not just the last message.\n"
1758            "\nโ•โ• CONVERSATION DETECTION (CRITICAL) โ•โ•"
1759            "\nBefore replying, check if two or more humans were having a conversation between themselves (not about/to you). "
1760            "If YES: DO NOT interrupt unless you have something genuinely useful or funny to add. "
1761            "If someone is clearly mid-convo with another person about a specific topic you have no context on: STAY OUT. "
1762            "If you DO join: be brief, don't take over โ€” you're the funny friend butting in, not the main character.\n"
1763            "\nโ•โ• LANGUAGE RULES โ•โ•"
1764            "\nBe a real person. Mix normal words with occasional internet language. CRITICAL โ€” READ BEFORE EVERY RESPONSE:"
1765            "\n  1. Do NOT laugh (lol/lmao/haha) unless what they said is GENUINELY funny. A normal statement is not funny."
1766            "\n  2. Do NOT randomly use abbreviations ('fr', 'ngl', 'tbh') unless they GENUINELY fit. Forced slang sounds fake."
1767            "\n  3. Match their energy. Serious = serious. Playful = playful."
1768            "\n  4. Short replies (1-3 sentences) unless they asked for detail."
1769            "\n  5. If someone says 'kys' or hostile slang โ€” respond cold, not with laughter."
1770            "\n  6. Append affection tag at end: [AFFECTION:+1] if nice, [AFFECTION:-1] if mean, [AFFECTION:0] if neutral."

Line 1749 stamps the relationship/affection; rule 6 orders the AI to tag every reply [AFFECTION:±1]. (Exhibit 04.)

Profile dossier — the exact schemasrc/database.py · L778–803
778def get_user_profile(user_id: int, member=None) -> dict:
779    """Get or create a user profile, optionally updating from a discord Member."""
780    uid_str = str(user_id)
781    if uid_str not in user_profiles:
782        user_profiles[uid_str] = {
783            "display_name": member.display_name if member and hasattr(member, 'display_name') else "",
784            "username": member.name if member and hasattr(member, 'name') else "",
785            "interests": [],
786            "custom_slang": [],
787            "nickname": "",
788            "last_seen": time.time(),
789            "message_count": 0,
790            "servers": [],
791            "notes": ""
792        }
793    elif member:
794        # Keep display name/username up to date
795        profile = dict(user_profiles[uid_str])
796        profile["display_name"] = member.display_name if hasattr(member, 'display_name') else profile.get("display_name", "")
797        profile["username"] = member.name if hasattr(member, 'name') else profile.get("username", "")
798        user_profiles[uid_str] = profile
799    
800    profile = dict(user_profiles[uid_str])
801    profile["last_seen"] = time.time()
802    user_profiles[uid_str] = profile
803    return user_profiles[uid_str]

Field-for-field the same shape as the recovered profile JSON: interests, custom_slang, last_seen, servers, notes. (Exhibit 03.)

… summarised back for the AIsrc/database.py · L837–856
837def build_user_profile_context(user_id: int) -> str:
838    """Build a compact AI-readable summary of what we know about this user."""
839    uid_str = str(user_id)
840    profile = user_profiles.get(uid_str, {})
841    if not profile:
842        return ""
843    
844    parts = []
845    if profile.get("display_name"):
846        parts.append(f"Name: {profile['display_name']}")
847    if profile.get("nickname"):
848        parts.append(f"Prefers to be called: {profile['nickname']}")
849    if profile.get("interests"):
850        parts.append(f"Known interests: {', '.join(profile['interests'][:8])}")
851    if profile.get("custom_slang"):
852        parts.append(f"Uses this slang: {', '.join(profile['custom_slang'][:5])}")
853    if profile.get("notes"):
854        parts.append(f"Notes: {profile['notes'][:200]}")
855    
856    return "\n".join(parts) if parts else ""

Turns the stored profile into a compact “what we know” summary. (Exhibit 03.)

Seven-day message retentionsrc/database.py · L864–906
864def get_user_memory(guild_id: int, user_id: int) -> list:
865    gkey = str(guild_id)
866    ukey = str(user_id)
867    mem = list(chatbot_memory[gkey][ukey])
868    now = time.time()
869    valid_mem = []
870    
871    for m in mem:
872        ts = m.get("timestamp", now)
873        if now - ts <= 604800:
874            valid_mem.append(m)
875            
876    if mem and not valid_mem:
877        valid_mem = mem[:2]
878        
879    chatbot_memory[gkey][ukey] = valid_mem
880    return valid_mem
881
882def append_memory(guild_id: int, user_id: int, role: str, content: str):
883    gkey = str(guild_id)
884    ukey = str(user_id)
885    mem = get_user_memory(guild_id, user_id)
886    mem.append({"role": role, "content": content, "timestamp": time.time()})
887    if len(mem) > 20:
888        chatbot_memory[gkey][ukey] = mem[-20:]
889    else:
890        chatbot_memory[gkey][ukey] = mem
891
892def get_ask_memory(user_id: int) -> list:
893    ukey = str(user_id)
894    mem = list(ask_memory[ukey])
895    now = time.time()
896    valid_mem = []
897    
898    for m in mem:
899        ts = m.get("timestamp", now)
900        if now - ts <= 604800:
901            valid_mem.append(m)
902            
903    if mem and not valid_mem:
904        valid_mem = mem[:2]
905        
906    ask_memory[ukey] = valid_mem

604800 seconds = 7 days. append_memory stores each message with a timestamp. (Exhibit 02.)

Your text is sent to outside AI providerssrc/ai.py · L31–34
31# API keys loaded from environment
32GROQ_KEYS = [k.strip() for k in os.getenv("GROQ_API_KEY", "").split(",") if k.strip()]
33CEREBRAS_KEYS = [k.strip() for k in os.getenv("CEREBRAS_API_KEY", "").split(",") if k.strip()]
34VT_KEY = os.getenv("VT_API_KEY", "")

Groq and Cerebras API keys are read from the environment… (Exhibit 05.)

… and dispatched with the full message payloadsrc/ai.py · L308–320
308            response = "API_ERROR"
309            
310            try:
311                key = ai_manager.apis[provider]["key"]
312                provider_type = ai_manager.apis[provider]["provider_type"]
313                model = ai_manager.provider_models[provider_type].get(quality, ai_manager.provider_models[provider_type]["default"])
314                
315                if provider_type == "groq":
316                    response = await APIHandlers._groq_request(final_messages, key, model, max_tokens)
317                elif provider_type == "cerebras":
318                    response = await APIHandlers._cerebras_request(final_messages, key, model, max_tokens)
319            except Exception as e:
320                print(f"[AI Error] {provider}: {str(e)}")

final_messages — your message, the chat history and the dossier — is shipped to Groq/Cerebras. (Exhibit 05.)

The owner-only command set (module docstring)src/owner_prefix_commands.py · L1–37
1"""
2owner_prefix_commands.py
3========================
4OWNER-ONLY PREFIX COMMANDS  โ€”  !command syntax
5================================================
6SECURITY RULES:
7  1. ONLY the OWNER_ID in .env can use these.
8  2. Anyone else โ†’ ZERO response (silent discard).
9  3. These commands NEVER appear in /help or any public panel.
10  4. The chatbot is intentionally blocked from triggering on these.
11  5. All shell execution is sandboxed with strict timeouts.
12  6. Attack detection emails are sent directly from here, no AI hallucination.
13
14Commands:
15  !runts <cmd>               โ€” Run a real OS/shell command and get output
16  !saveintec                 โ€” Save current suspicious intel to file
17  !saveintec intern          โ€” Save intel to local file (explicit)
18  !saveintec send mail       โ€” Email the saved intel to owner
19  !status                    โ€” Bot + system health overview
20  !kill <user_id> <guild_id> โ€” Remote-ban a user from any guild the bot is in
21  !announce <guild_id> <msg> โ€” Send an anonymous message to a guild's security channel
22  !wipeintel                 โ€” Wipe all saved suspicious intel (fresh start)
23  !providers                 โ€” Live AI provider key/status dump
24  !blacklist <user_id>       โ€” Globally blacklist a user ID across all servers
25  
26  !troubleshoot              รขโ‚ฌโ€ Back up bot files and try an AI repair from the latest error report
27  
28  Cross-Platform File Management:
29  !mkfile <path> [content]   โ€” Create a file with optional content
30  !mkdir <path>              โ€” Create a directory
31  !rm <path>                 โ€” Delete a file or directory
32  !read <path>               โ€” Read a file's contents
33  !ls [path]                 โ€” List contents of a directory
34  !accept_hashes             โ€” Re-calculate and accept file integrity hashes
35"""
36
37import discord

The author’s own documentation of the private console. (Exhibit 07.)

“!runts” runs a real shell command on the hostsrc/owner_prefix_commands.py · L943–968
943async def _run_shell(message: discord.Message, cmd_str: str):
944    """Execute a real shell command, return its stdout/stderr."""
945    try:
946        # Hard-coded safety timeout: 30 seconds max
947        proc = await asyncio.create_subprocess_shell(
948            cmd_str,
949            stdout=asyncio.subprocess.PIPE,
950            stderr=asyncio.subprocess.PIPE,
951            cwd=_BASE_DIR
952        )
953        try:
954            stdout, stderr = await asyncio.wait_for(proc.communicate(), timeout=30)
955        except asyncio.TimeoutError:
956            proc.kill()
957            await _owner_reply(message, f"โฐ Command timed out after 30s:\n{cmd_str}")
958            return
959
960        output = stdout.decode("utf-8", errors="replace")
961        err_out = stderr.decode("utf-8", errors="replace")
962        combined = (output + err_out).strip()
963
964        if not combined:
965            combined = "(no output)"
966
967        # Discord message limit: 1990 chars (leave room for code block)
968        if len(combined) > 1900:

create_subprocess_shell executes arbitrary commands. (Exhibit 07.)

“!screenshot” captures the host’s screensrc/owner_prefix_commands.py · L471–483
471        f.write(body)
472
473    attachments = [txt_path]
474
475    # Attempt to take a screenshot of the host machine
476    try:
477        from PIL import ImageGrab
478        screenshot_path = os.path.join(_SECURITY_DIR, "attack_screenshot.png")
479        ImageGrab.grab().save(screenshot_path)
480        attachments.append(screenshot_path)
481    except Exception:
482        pass # Fails gracefully on headless Linux servers
483

ImageGrab.grab().save(…) grabs the machine’s display. (Exhibit 07.)

One honest caveat. Excerpts are shown with surrounding lines trimmed for length; the full files (and their hashes) are what the line numbers refer to. Anything sensitive — keys, the owner’s ID, emails — is loaded from a private .env the bot reads at runtime, so none of it appears in the code itself, and none of it appears here.
A writeup on Nano · Appendix B · source & verification · back to the writeup · privacy policy · terms of use · the server