Skip to content

Instantly share code, notes, and snippets.

@koad
Created June 27, 2025 19:42
Show Gist options
  • Save koad/9299adad4e1442fbe64ea2ef4f1bb54c to your computer and use it in GitHub Desktop.
Save koad/9299adad4e1442fbe64ea2ef4f1bb54c to your computer and use it in GitHub Desktop.
The digital equivalent of a bouncer with OCD - automatically evicts unwanted private chat messages from your Keybase like they never existed.
/**
* 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();
@koad
Copy link
Author

koad commented Jun 27, 2025

the delete-history.exp file is needed, and chmod +x on it. .. here are the contents.

#!/usr/bin/expect -f

# Get username from argument
set username [lindex $argv 0]

spawn keybase chat delete-history --private $username
expect "Hit Enter to confirm"
send "\r"
expect eof

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment