Skip to content

Instantly share code, notes, and snippets.

@koad
Created June 27, 2025 19:42
Show Gist options
  • Select an option

  • Save koad/9299adad4e1442fbe64ea2ef4f1bb54c to your computer and use it in GitHub Desktop.

Select an option

Save koad/9299adad4e1442fbe64ea2ef4f1bb54c to your computer and use it in GitHub Desktop.

Revisions

  1. koad created this gist Jun 27, 2025.
    219 changes: 219 additions & 0 deletions keybase-janitor-supreme.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,219 @@
    /**
    * Keybase Janitor Supreme
    *
    * The digital equivalent of a bouncer with OCD - automatically evicts unwanted
    * private chat messages from your Keybase like they never existed.
    *
    * Mission Briefing:
    * This script stalks your chat like a caffeinated security guard, watching for
    * messages from your personal "naughty list" of users. When these digital
    * troublemakers send you anything, this script:
    *
    * 1. Ghosts the conversation (hides it from UI)
    * 2. Sets a randomized "popcorn timer" (waits for the chatter to die down)
    * 3. Nukes the entire chat history into the digital void
    *
    * Think of it as Marie Kondo for your chat history - if it doesn't spark joy,
    * it gets yeeted into oblivion. The randomized timing makes you look human
    * instead of like a cold, calculating deletion bot (which you totally are).
    *
    * ⚠️ Warning: This script has commitment issues - it deletes EVERYTHING
    * from both sides of the conversation. No take-backsies!
    *
    * Author: koad
    * Version: 0.0.1
    * License: MIT
    */

    const { spawn } = require('child_process');
    const readline = require('readline');

    // Configuration Constants (aka "The Knobs You Can Twist")
    const DEBUG = false; // Set to true if you enjoy watching paint dry (verbose logging)
    const SECONDS = 1000; // Because JavaScript thinks time is measured in milliseconds like a weirdo
    const MINUTES = 60 * SECONDS; // Math is hard, let's make it easier

    // Timing Configuration (The Art of Strategic Procrastination)
    // Base delay - like counting to 10 before responding to drama
    const TIMEOUT_BASE = 15 * MINUTES;

    // Random jitter to avoid looking like a robot overlord
    const TIMEOUT_VARIANCE = (TIMEOUT_BASE / 3) * 2;

    // Color Palette (Because plain text is for peasants)
    const COLORS = { RED: '\x1b[31m', GREEN: '\x1b[32m', YELLOW: '\x1b[33m', CYAN: '\x1b[36m', RESET: '\x1b[0m' };

    // Environment Validation (Making sure you did your homework)
    if (!process.env.ASSHOLES_ON_KEYBASE) {
    console.error(`${COLORS.RED}[FATAL ERROR]${COLORS.RESET} Missing ASSHOLES_ON_KEYBASE in .env file`);
    console.error(`${COLORS.CYAN}Pro tip:${COLORS.RESET} ASSHOLES_ON_KEYBASE=spammer1,troll2,exboyfriend`);
    console.error('This script is useless without targets. Exiting with prejudice.');
    process.exit(1);
    };

    // Global Variables (The Script's Memory Palace)
    const janitorAgent = process.env.ENTITY || 'Anonymous Janitor'; // Who's on duty today?
    const troublemakerList = process.env.ASSHOLES_ON_KEYBASE
    .split(',')
    .map(username => username.trim())
    .filter(username => username.length > 0); // Remove empty strings like we remove bad vibes

    // Timer Tracking (Our Digital Hitlist)
    const activeTimers = new Map(); // Keeps track of who's getting the axe and when

    /**
    * 💀 The Nuclear Option - Deletes chat history with extreme prejudice
    *
    * Spawns an expect script because Keybase is paranoid and wants confirmation
    * before nuking conversations. We automate the "Are you sure?" dance.
    *
    * @param {string} username - The digital persona about to be memory-holed
    */
    function obliterateConversation(username) {
    console.log(`${COLORS.RED}🚀 Launching deletion missile${COLORS.RESET} at ${COLORS.CYAN}${username}${COLORS.RESET}`);

    const deleteProcess = spawn('expect', ['delete-history.exp', username]);

    // Silent but deadly - we don't need to see the carnage 🤫
    deleteProcess.stdout.on('data', () => {
    // Shh... the adults are working
    });

    deleteProcess.stderr.on('data', (data) => {
    if (DEBUG) console.error(`${COLORS.RED}[DELETE ERROR]${COLORS.RESET} ${username}: ${data.toString().trim()}`);
    });

    // Victory celebration when the deed is done
    deleteProcess.on('close', (exitCode) => {
    if (exitCode === 0) {
    // hide the cleared conversation too since the UI looks at the clear command as a new message
    spawn('keybase', ['chat', 'hide', username]);
    console.log(`${COLORS.GREEN}✨ Mission accomplished!${COLORS.RESET} ${COLORS.CYAN}${username}${COLORS.RESET}'s digital footprint has been sanitized`);
    } else {
    console.error(`${COLORS.RED}💥 Deletion failed${COLORS.RESET} for ${COLORS.CYAN}${username}${COLORS.RESET} (exit code: ${exitCode})`);
    }
    });
    };


    /**
    * Main Event - The Chat Surveillance Theater
    *
    * This is where the magic happens. We tap into Keybase's chat stream like
    * digital wiretappers, parsing JSON events faster than gossip spreads.
    */
    function initializeChatSurveillance() {
    console.log(`${COLORS.GREEN}🚀 Booting up the surveillance state...${COLORS.RESET}`);

    // Tap into the Keybase chat matrix
    const keybaseListener = spawn('keybase', ['chat', 'api-listen']);

    // Set up our JSON event reader (because raw streams are for masochists)
    const eventReader = readline.createInterface({
    input: keybaseListener.stdout,
    crlfDelay: Infinity
    });

    // Listen for the digital whispers
    eventReader.on('line', (jsonLine) => {
    try {
    const chatEvent = JSON.parse(jsonLine);

    // 🚫 Ignore the boring stuff (we only care about actual messages)
    if (chatEvent.type !== 'chat') return;

    processChatMessage(chatEvent.msg);

    } catch (parseError) {
    console.error(`${COLORS.RED}🤖 JSON parsing failed${COLORS.RESET} (probably Keybase having a moment):`, parseError.message);
    if (DEBUG) console.error('Problematic line:', jsonLine);
    }
    });

    // Handle the inevitable technical difficulties
    if (DEBUG) {
    keybaseListener.stderr.on('data', (errorData) => {
    console.error(`${COLORS.RED}[Keybase Tantrum]${COLORS.RESET} ${errorData.toString().trim()}`);
    });
    }

    keybaseListener.on('close', (exitCode) => {
    console.log(`${COLORS.RED}💀 Keybase listener died${COLORS.RESET} (exit code: ${exitCode})`);
    console.log('The surveillance state has fallen. Anarchy reigns.');
    });
    };

    /**
    * Message Processing Central Command
    *
    * Analyzes incoming messages with the intensity of a conspiracy theorist
    * examining blurry UFO photos.
    *
    * @param {Object} message - The intercepted chat message
    */
    function processChatMessage(message) {
    const { channel, id: messageId, sender } = message;
    const senderUsername = sender.username;
    const channelName = channel.name;

    console.log(`📨 Obeserved message #${COLORS.YELLOW}${messageId}${COLORS.RESET} from ${COLORS.CYAN}${senderUsername}${COLORS.RESET} in ${COLORS.CYAN}${channelName}${COLORS.RESET}`);

    // Check if this sender is on our naughty list
    if (!troublemakerList.includes(senderUsername)) {
    return; // Not our problem, move along citizen
    }

    // 🚨 DEFCON 1: Troublemaker detected!
    handleTroublemakerMessage(senderUsername, channelName);
    };

    /**
    * Troublemaker Handler - The Business End of the Operation
    *
    * When a known troublemaker sends a message, this function springs into action
    * like a digital ninja with anger management issues.
    *
    * @param {string} username - The digital delinquent
    * @param {string} channelKey - The conversation identifier
    */
    function handleTroublemakerMessage(username, channelKey) {
    console.log(`${COLORS.RED}🚨 TROUBLEMAKER ALERT!${COLORS.RESET} ${COLORS.CYAN}${username}${COLORS.RESET} is stirring up trouble`);

    // Immediately ghost the conversation (blink and you'll miss it)
    spawn('keybase', ['chat', 'hide', username]);
    console.log(`${COLORS.GREEN}👻 Conversation ghosted${COLORS.RESET} - ${COLORS.CYAN}${username}${COLORS.RESET} is now talking to the void`);

    // Calculate a random delay (the "popcorn timer" algorithm)
    const randomDelay = TIMEOUT_BASE + Math.floor(Math.random() * TIMEOUT_VARIANCE * 2) - TIMEOUT_VARIANCE;
    const delayInMinutes = (randomDelay / MINUTES).toFixed(1);

    // Reset existing timer if this troublemaker is being chatty
    if (activeTimers.has(channelKey)) {
    clearTimeout(activeTimers.get(channelKey));
    console.log(`${COLORS.CYAN}⏰ Popcorn timer reset${COLORS.RESET} for ${COLORS.CYAN}${username}${COLORS.RESET} - new countdown: ${delayInMinutes} minutes`);
    } else {
    console.log(`${COLORS.CYAN}⏰ Popcorn timer started${COLORS.RESET} for ${COLORS.CYAN}${username}${COLORS.RESET} - countdown: ${delayInMinutes} minutes`);
    }

    // Schedule the digital execution
    const destructionTimer = setTimeout(() => {
    activeTimers.delete(channelKey); // Clean up our tracking
    obliterateConversation(username);
    }, randomDelay);

    // Keep track of our pending doom
    activeTimers.set(channelKey, destructionTimer);
    };

    // Lets start the show!
    console.log(`${COLORS.GREEN}🧹 Keybase Janitor Supreme${COLORS.RESET} reporting for duty!`);
    if (janitorAgent !== 'Anonymous Janitor') {
    console.log(`${COLORS.GREEN}👤 Agent on duty:${COLORS.RESET} ${COLORS.CYAN}${janitorAgent}${COLORS.RESET}`);
    };

    console.log(`${COLORS.RED}🎯 Surveillance targets:${COLORS.RESET} ${COLORS.CYAN}${troublemakerList.join(', ')}${COLORS.RESET}`);
    console.log(`${COLORS.CYAN}⏱️ Base timeout:${COLORS.RESET} ${TIMEOUT_BASE / MINUTES} minutes (±${TIMEOUT_VARIANCE / MINUTES} minutes of chaos)`);
    console.log('');

    // Action!
    initializeChatSurveillance();