You are browsing as a guest. Sign up (or log in) to start making projects!

BigBrother Bot

  • 15 Devlogs
  • 25 Total hours

A 1984-themed Slack bot that surveils the Hack Club Stardance workspace. BigBrother monitors messages, analyzes sentiment, rewrites news headlines in Orwellian doublespeak, and responds to DMs in character — because he is always watching.

Ship #1

BigBrother is a 1984-themed Slack bot that turns your workspace into Oceania. It runs five modules:
Surveillance tracks message activity across channels and maintains a leaderboard of who’s been most “active” (watched). Telescreen broadcasts Orwellian announcements to the workspace. Police runs sentiment analysis on messages and flags thoughtcrime — positivity is suspicious. Truth rewrites real news headlines in Party-approved doublespeak using NewsAPI + Groq. DM lets you talk directly to Big Brother, who responds in full authoritarian character via LLM.
Built with Node.js + Bolt.js over Hack Club Stardance. Big Brother is always watching — especially in #1984.

  • 15 devlogs
  • 25h
  • 15.87x multiplier
  • 395 Stardust
Try project → See source code →
Open comments for this post

34m 51s logged

Wrote the README and stopped the prerequisites file from lying about itself

The repo had no README and prerequisites.md was three stray lines (axios, node-cron, fs) that didn’t actually match what package.json declared or what main.js fails fast on at boot. Rewrote prerequisites.md as a pure library install reference — packages, versions, and one npm install command — then wrote a README that follows the structure the Stardance judges actually want: one-sentence description, hero image slot, a try-it link, quick start, features, local setup, and a “how it works” section that earns its keep by naming real decisions (Socket Mode over HTTP so it runs behind any network, self-rescheduling crons with re-rolling fire times, file-locked JSON as a tiny DB instead of SQLite, sequential Groq calls to dodge rate limits).

The friction was the Node version. There’s no engines field in package.json and no .nvmrc, so I didn’t want to just assert “Node 18+” without evidence. Grepping package-lock.json for "engines" turned up ~100 matches and the binding constraint is @slack/bolt‘s transitive deps all declaring "node": ">=18" — so that’s the honest floor, stated as such.

Also swapped the watched channel ID in Code/main.js from C0BBBCER55W to C09F6AAP1PF to match the workspace in the invite link I was handed. Caught one real issue while reviewing: the “Try it” link was a channel permalink (/archives/C09F6...), which a non-member hits a sign-in wall on, flagged it, left it in since it’s an internal members link.

0
0
5
Open comments for this post

1h 58m 11s logged

Made Big Brother stop lying about your loyalty score

The /bigbrother-loyalty command was silently corrupting its own scoreboard. Every call overwrote the user’s stored score with whatever the current calculation produced — which meant a citizen who hit 200 points last week would see their displayed rank drop to 50 after a quiet few days. The scoreboard was a ledger of “most recent” instead of “best ever”, which is the opposite of how a loyalty panopticon should work.

The fix in Code/Surveillance.js was small but mattered: scoreboard() now only writes when the new score strictly beats the stored high, and stamps an achievedAt ISO timestamp on each new record. While in there, I also wired /bigbrother-scoreboard to refresh the caller’s score before displaying, so running it actually updates your entry — previously it just read stale data. The refresh is wrapped in try/catch so a transient Slack or HuggingFace failure doesn’t break the leaderboard read.

That scoreboard bug was the tip of a much longer audit. The same session surfaced and fixed ~50 defects across the codebase: Surveillance.presenceCheck was returning undefined (not 0) on unknown presence values, producing NaN loyalty scores that then broke the sort; Telescreen.propaganda() would crash with TypeError if broadcasts.json was empty; Truth.getNews() called .map() on a potentially-missing articles array; concurrent readFile/writeFile pairs on the same JSON file were last-writer-wins. Added a 12-line per-file async mutex in Code/lock.js and wrapped every read-merge-write critical section. Also added .env validation at boot — previously a missing HF_TOKEN would surface as an undefined auth header 45 minutes into a Slack session instead of failing at startup.

0
0
4
Open comments for this post

1h 3m 33s logged

Added DM support: BigBrother can now talk back

Users can now DM the bot and get a response in character as BigBrother, powered by Llama 3.3 on Groq. The bot keeps a per-user conversation history (capped at 30 messages) so it actually remembers what you said earlier in the conversation. History lives in memory, so it resets on restart, intentional, didn’t feel worth the complexity of persisting it.
The interesting part was Slack’s permission system. Adding the im:history, im:read, and im:write scopes wasn’t enough on its own, you also need to separately subscribe to the message.im event under Event Subscriptions. The scopes let the bot act on DMs, the event subscription is what actually delivers them. Spent a while staring at logs with no “DM received” entries before finding that.

0
0
37
Open comments for this post

22m 38s logged

Replaced every console.log in the bot with a leveled logger.

The old setup was five files each printing things like “Message received!”, “Responding…”, and “Messages saved!” with no way to tell which file was talking or which run produced which line. Replaced it all with a single Code/Logger.js that adds timestamps, severity (debug/info/warn/error), a subsystem tag per file, and structured metadata. Errors now print a full stack trace on a second dimmed line instead of [object Object].
The visual difference is the whole point. Before, /bigbrother-loyalty produced four console.log lines with no correlation. After, it produces one timestamped line per stage — fetch, score, scoreboard write — and you can grep by subsystem or level.
The interesting bug: my first Logger.js had a guard that re-checked whether meta was undefined, but I also had a fallback that promoted a non-Error second argument into meta. The two branches collided and swallowed the error stack entirely. Caught it in a two-line test snippet before staging, would have been invisible until the first real API failure.
Also stopped logging the full Hugging Face response on every sentiment check. Now it only prints the per-label scores rounded to three decimals, same diagnostic value, way less noise.

0
0
2
Open comments for this post

3h 17m 15s logged

Two Minute Hate, now interactive

Built a /bigbrother-hate command for the Slack bot that escalates the existing 11:00 Two Minute Hate through 3 stages (Whisper → Roar → Frenzy) based on call count, with in-character stage-transition announcements and increasing-intensity bot posts.
The interesting part was the difficulty curve. Base thresholds are 5 and 15 calls, but they get multiplied each day by yesterday / 7-day-mean, clamped 0.5–2.0. So if Monday had 20 calls and Tuesday only 15, Wednesday’s threshold drops to ~62% of the base — easier to escalate on quiet days. First pass forgot to reset usageCount and stage on the daily recompute, so the bot stayed pinned at Frenzy forever after one good session. Caught it on the second test run.
Wired it to the existing 11:00–11:02 cron window so calls outside it get an in-character refusal. Also added hateLevel.json to .gitignore since it’s pure runtime state.
Next: DMing the bot, probably tackling that before adding more interactive bits.

0
0
1
Open comments for this post

1h 0m 16s logged

The /bigbrother-loyalty command now doubles as a live leaderboard, every call re-ranks and displays the top 10 most loyal Party members. Plugging into the existing loyalty score pipeline meant the scoreboard itself was straightforward to build, but getting the Slack Block Kit formatting right for a clean ranked list took longer than expected. Ended up iterating through a few layouts before settling on one that doesn’t look terrible.

0
0
1
Open comments for this post

1h 26m 27s logged

Loyalty scoring now factors in message sentiment.

I added positive/negative sentiment saving by reusing the existing 7-day message cache logic, then wired the Surveillance module to read those sentiment files the same way it already reads other JSON, no new infrastructure needed. The score itself now reflects not just how active a user is, but whether they’re saying good or bad things about the Party.

0
0
4
Open comments for this post

1h 14m 22s logged

Stopped reloading 10 channels every time someone checks their loyalty score

The surveillance system was fetching all messages from all 10 channels on every single /bigbrother-loyalty call. That meant a ~50 second wait every time. Not great when Big Brother is supposed to feel instant and omniscient.
The fix: the bot now keeps a local JSON file of all messages. When someone requests their score, it only fetches messages newer than the newest one already saved, then deletes anything older than 7 days to keep the file lean. On top of that, a cron job rewrites the whole file once a day so it never drifts out of sync.
Score lookups that took a minute now finish in a couple seconds. The panopticon is finally fast enough to be scary.

0
0
4
Open comments for this post

58m 14s logged

Get more than one 1984 themed headline

The /bigbrother-news command could technically accept a count, but it always returned exactly one rewrite no matter what you passed.
First, rewriteHeadline was looping over the headlines array but always sending headline[0] to Groq, so it was rewriting the same headline count times. Second, the function returned an array but respond() was getting that array passed directly as text, which Slack rejects with a 500 because it expects a string. Fixed both, headline[i] in the loop, .join("\n") before responding. Also added a .replace(/^[""]|[""]$/g, "") strip because the model randomly decides whether to wrap its output in quotes or not and I’d rather it just didn’t.
The original code also only ever called rewriteHeadline with a single headline and had no loop at all, scaling it up to handle multiple rewrites sequentially meant restructuring the whole function.

0
0
2
Open comments for this post

1h 22m 16s logged

Added the Ministry of Truth module

Fetches real news headlines via NewsAPI, pipes them through Llama 3.3 on Groq, and rewrites them as state propaganda. The results are genuinely unsettling because they’re based on actual news. A Tesla autopilot crash became “Citizen Misalignment with Vehicle Guidance Systems Results in Regrettable Isolated Incident in Texas Residential Sector” which I think is funnier than anything I could have written myself.
The bug that got me: I passed the respond function itself as the text value instead of the actual result. Slack received an empty object, returned invalid_payload, and I spent way too long staring at the Groq response before noticing the slash command handler was the problem, not the API call.

0
0
2
Open comments for this post

1h 51m 26s logged

Built the Thought Police module for my 1984 themed Slack bot

Added a Thought Police module that watches messages in a specific channel, scores their sentiment using HuggingFace’s RoBERTa model, and fires a Big Brother-themed response if the message is negative or positive enough. The bot replies in-thread which feels way more unsettling than posting standalone, like it’s whispering directly at you.
Hit a bunch of friction getting here though. The Perspective API I originally planned to use got sunset, then the HuggingFace inference endpoint URL changed mid-build and started returning ENOTFOUND, turned out the old api-inference.huggingface.co domain was dead and needed to be swapped to router.huggingface.co. Then the response labels came back lowercase (negative instead of Negative) which broke the score parsing silently, the bot was running but returning undefined for everything.

0
0
6
Open comments for this post

1h 52m logged

Automated propaganda broadcasts for my 1984-themed Slack bot, random timing every day, plus a daily Two Minutes Hate at 11:00.

Imagine you are the head of Propaganda of an authoritarian state and you need to automate propaganda releases. Well, this is how I felt today after trying to automate this exact process. In the book 1984 propaganda is everywhere, so I replicated that. The hardest part for me was the writing of the propaganda. This is because I, as unexpected as it sounds, am not the head of Propaganda in an authoritarian state, so after around 10 minutes I asked my good friend Claude if he could write me some propaganda. Claude generated 50 different messages.

If anybody has some propaganda messages I could use just DM me on Slack or write it in the comments!

The next step for me is to become the head of police and automate the flagging of negative sentiments.

0
0
2
Open comments for this post

1h 22m 55s logged

Slash commands in Slack don’t convert @mentions to user IDs and I learned that the hard way. My /bigbrother-loyalty command was getting @jachym.fukal as plain text instead of <@U09L3LVPJ82>, so the regex I wrote was completely useless. The workspace also has 30k+ users so just fetching the whole user list isn’t an option either. For now the command just takes a raw user ID, which isn’t pretty but works. Next step is figuring out a cleaner way to handle this.

0
0
1
Open comments for this post

1h 31m 51s logged

Sieving through all channels would have taken hours. Now I am only using 10, which are the most used ones on the Hackclub workspace.
So I wrote a code that scans these 10 channels for messages of a specific user, which I want to use for a loyalty score in the future. But the problem isn’t completely solved, because the script needs around 50 seconds to look through these 10 channels.
So now I am in a bit of a pickle.
I am thinking about saving the data from the last scan and then only retrieving new data from the channels.

0
0
2
Open comments for this post

4h 55m 31s logged

Added the first version of the bot’s surveillance system. My original plan was to scan every channel in the Hack Club workspace weekly and fetch message history from all channels whenever a slash command was used. After looking into it, I realized the workspace has so many channels that a single command could take hours to finish, making the approach impractical. For now, I switched to a manually curated list of channels that the bot will monitor while I investigate a more scalable solution.

0
0
2

Delete project?

Are you sure you want to permanently delete this project? This action cannot be undone.

All devlogs, followers, and associated data will be removed.

Followers

Loading…