Building an NSFW Detection Telegram Bot with Bun and TypeScript

Contai, West Bengal, India
Search for a command to run...

Contai, West Bengal, India
No comments yet. Be the first to comment.
How I generate per-page Open Graph images at build time using satori and resvg-js - no edge runtime, no external service, just a custom Astro integration.

Modern web and mobile applications typically use JWT (JSON Web Tokens) for authentication. These tokens expire after a certain period for security reasons, requiring a refresh mechanism to maintain us

When developing forms in React Native applications, we frequently encounter these challenges: Repetitive Code: Setting up input fields with proper styling and behavior for each form Validation Compl

Method chaining is a very useful and popular method in JavaScript and TypeScript which helps you write more readable and concise code. In this article, I will explain how method chaining works in JS/T

Ever wondered how you can keep your Telegram groups or online communities safe from inappropriate content automatically? It's actually easier than you might think! In this article, I'll walk you through building your own NSFW (Not Safe For Work) detection bot using the super-fast Bun runtime, TypeScript, and a couple of awesome libraries.
By the end of this guide, you'll have a bot that can automatically detect and delete NSFW images posted in a Telegram chat. Looks cool right?
Bun: A fast all-in-one JavaScript runtime. We'll use it to run our TypeScript code directly.
@mtkruto/node: A modern and easy-to-use Telegram MTProto client for Node.js that lets us build powerful user and bot clients.
@nsfwspy/node: A library that uses a pre-trained TensorFlow.js model to classify images and detect NSFW content right on your machine.
First things first, let's get our project set up. Make sure you have Bun installed.
Create a new directory for your project and initialize it.
bun init
Next, we need to install our dependencies.
bun add @mtkruto/node @nsfwspy/node
Your package.json should look something like this:
{
"name": "nsfw-bot",
"module": "index.ts",
"type": "module",
"dependencies": {
"@mtkruto/node": "^0.67.1",
"@nsfwspy/node": "^1.2.0"
},
"devDependencies": {
"bun-types": "latest"
}
}
We need a way to connect to Telegram's services. This is where @mtkruto/node comes in. Let's create a file src/bot.ts to handle our bot client.
// src/bot.ts
import { Client } from "@mtkruto/node";
import env from "./env"; // We'll create this file next!
const bot = new Client({
apiId: env.TELEGRAM_APP_ID,
apiHash: env.TELEGRAM_APP_HASH,
});
export default bot;
You'll notice we're importing env. You'll need to get your TELEGRAM_APP_ID and API_HASH from my.telegram.org. For the bot token, talk to @BotFather on Telegram.
Let's create the src/env.ts file to manage these secrets safely with validation:
// src/env.ts
import { cleanEnv, str, num } from "envalid";
const env = cleanEnv(Bun.env, {
TELEGRAM_APP_ID: num(),
TELEGRAM_APP_HASH: str(),
TELEGRAM_BOT_TOKEN: str(),
});
export default env;
You'll also need to install envalid for environment variable validation:
bun add envalid
This is the core of our bot! We'll create a simple class to wrap the @nsfwspy/node library. This makes our code cleaner and easier to manage.
Let's create a file at src/nsfw-detector.ts.
// src/nsfw-detector.ts
import { NsfwSpy } from "@nsfwspy/node";
import { join } from "path";
class NsfwDetector {
private nsfwSpy: NsfwSpy;
private modelLoaded = false;
constructor() {
// Make sure the model path is correct.
// You'll need to download the model files first!
const modelPath = `file://${join(
process.cwd(),
"models",
"mobilenet-v1.0.0",
"model.json"
)}`;
this.nsfwSpy = new NsfwSpy(modelPath);
}
// We need to load the model into memory before we can use it.
async initialize() {
if (!this.modelLoaded) {
console.log("Loading NSFW detection model...");
await this.nsfwSpy.load();
this.modelLoaded = true;
console.log("NSFW detection model loaded successfully!");
}
}
// This function will take an image and return if it's NSFW.
async analyzeImage(imageBuffer: Buffer) {
if (!this.modelLoaded) {
throw new Error("Model not loaded. Call initialize() first.");
}
const result = await this.nsfwSpy.classifyImageFromByteArray(imageBuffer);
const nsfwScore = result.pornography + result.sexy + result.hentai;
// We'll consider an image NSFW if the score is above 65%
const isNsfw = nsfwScore > 0.65;
return {
isNsfw,
confidence: Math.round(nsfwScore * 100),
result,
};
}
}
// We'll export a single instance of the class (Singleton Pattern)
export default new NsfwDetector();
So how does this work? The answer is pretty simple. The NsfwSpy class loads a pre-trained machine learning model. The analyzeImage method takes the raw data of an image (as a Buffer), runs it through the model, and gets back scores for different categories (pornography, sexy, hentai, neutral). We just add up the scores for the NSFW categories to get a final confidence score.
Now we need to connect our bot to our detector. Let's create a file at src/media/photos.ts that will listen for any photo messages sent to the chat.
// src/media/photos.ts
import bot from "../bot";
import nsfwDetector from "../nsfw-detector";
import { readFileSync } from "fs";
// Listen for any message that contains a photo
bot.on("message:photo", async (ctx) => {
console.log("Received a photo, analyzing...");
// Download the photo file from Telegram's servers
// We download the smallest thumbnail for faster processing
const file = await ctx.download("smallest");
if (!file) return;
const imageBuffer = readFileSync(file);
const analysis = await nsfwDetector.analyzeImage(imageBuffer);
console.log(`Analysis complete. NSFW: \({analysis.isNsfw}, Confidence: \){analysis.confidence}%`);
if (analysis.isNsfw) {
try {
// If it's NSFW, delete the message
await ctx.deleteMessage();
// And send a warning message to the user
await ctx.reply(
`Hey ${ctx.sender.firstName}! Please avoid sending inappropriate content.`
);
} catch (error) {
console.error("Failed to delete message or reply:", error);
}
}
});
In the code above, we set up a listener using bot.on("message:photo", ...). When a photo is posted, we download it, read it into a buffer, and pass it to our nsfwDetector. If it comes back as NSFW, we delete the original message and send a little warning. Pretty simple, right?
Finally, let's create our main entry point, src/index.ts, to start the bot.
// src/index.ts
import bot from "./bot";
import nsfwDetector from "./nsfw-detector";
import env from "./env";
// Import the photo handler so the listener gets registered
import "./media/photos";
async function main() {
// First, initialize the NSFW model
await nsfwDetector.initialize();
// Then, start the bot
await bot.start({ botToken: env.TELEGRAM_BOT_TOKEN });
console.log("Bot started successfully!");
}
main().catch((err) => console.error(err));
Now, all you have to do is run the bot!
bun run src/index.ts
Your bot will log in and immediately start watching for any new photos.
I hope you learned something new today and see how easy it can be to build powerful and practical bots. If you think I have made some mistakes or have any questions, please do let me know. Thanks, and see you again in my next post.