Building an NSFW Detection Telegram Bot with Bun and TypeScript

Arnab ParyaliArnab Paryali
5 min read

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?

What We'll Be Using

  1. Bun: A fast all-in-one JavaScript runtime. We'll use it to run our TypeScript code directly.

  2. @mtkruto/node: A modern and easy-to-use Telegram MTProto client for Node.js that lets us build powerful user and bot clients.

  3. @nsfwspy/node: A library that uses a pre-trained TensorFlow.js model to classify images and detect NSFW content right on your machine.

Step 1: Setting Up the Project

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"
  }
}

Step 2: Creating the Telegram Bot Client

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

Step 3: Building the NSFW Detector

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.

Step 4: Handling Incoming Photos

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?

Step 5: Bringing It All Together

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.

0
Subscribe to my newsletter

Read articles from Arnab Paryali directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Arnab Paryali
Arnab Paryali

Contai, West Bengal, India