Created
June 27, 2025 19:42
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
the delete-history.exp file is needed, and chmod +x on it. .. here are the contents.