Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/botnadzor/extension/llms.txt

Use this file to discover all available pages before exploring further.

The insertion system is Botnadzor’s core mechanism for modifying the VK.com DOM to display bot detection UI elements. It uses a config-driven approach where insertion configurations are fetched from static lists and applied dynamically to matching elements.

Overview

The insertion system:
  • Monitors the DOM for elements matching insertion configs
  • Extracts data from markup (account names, avatars, identifiers)
  • Fetches service data (affiliations, registration dates)
  • Renders UI components (action bars, badges, highlights)
  • Manages lifecycle (mount, render, unmount)

Config-Driven Architecture

Insertions are defined as configuration objects stored in the insertions static list. Each config specifies:
  • Selector — CSS selector to find target elements
  • Variant — Type of insertion (account, comment, review, replyForm)
  • Markup Config — How to extract data and where to place UI
  • Applies To — Which VK variant (desktop/mobile)

Insertion Config Example

type InsertionConfig = {
  id: string;
  disabled: boolean;
  variant: "account" | "comment" | "review" | "replyForm";
  appliesTo: "desktopVkWebsite" | "mobileVkWebsite" | "desktopAndMobileVkWebsite";
  selector: {
    query: string;
    descendantMatcher?: string;
  };
  markup: {
    data: { /* selectors for extracting data */ };
    edits: { /* DOM modifications */ };
    ui: { /* UI placement configs */ };
  };
};

Insertion Variants

The system supports four insertion variants, each registered in src/entrypoints/content/insertion-variants.ts:
src/entrypoints/content/insertion-variants.ts
import type { InsertionVariant } from "@/shared/@model/insertion-configs";
import type { BaseInsertionVariantDefinition } from "./insertion-variant-typings";

import account from "./insertion-variants/=account";
import comment from "./insertion-variants/=comment";
import replyForm from "./insertion-variants/=reply-form";
import review from "./insertion-variants/=review";

export const insertionVariantLookup: Record<
  InsertionVariant,
  BaseInsertionVariantDefinition
> = {
  account,
  comment,
  replyForm,
  review,
};

Account Variant

Used for user profile pages, account cards, and user lists. Key Features:
  • Extracts account identifier, name, and avatar
  • Displays affiliation badges (bot/spam indicators)
  • Shows registration date
  • Provides action bar with inspector tools
src/entrypoints/content/insertion-variants/=account.ts
export default defineInsertionVariant<
  AccountInsertionConfig,
  AccountInnerData,
  AccountMarkupData,
  AccountServiceData
>({
  defaultInnerData: {},

  getMarkupData: async ({ config, instanceLogger, rootElement }) => {
    const accountAvatarUrl = extractAccountAvatarUrlFromMarkup(
      rootElement,
      config.markup.data.accountAvatar,
    );

    const accountIdentifier = await extractAccountIdentifierFromMarkup(
      rootElement,
      config.markup.data.accountIdentifier,
    );

    const accountName = await extractAccountNameFromMarkup(
      rootElement,
      config.markup.data.accountName,
    );

    return {
      accountAvatarUrl,
      accountIdentifier,
      accountName,
    };
  },

  getServiceData: async ({ markupData, serviceLookup }) => {
    const [accountAffiliation, frontendBaseUrl] = await Promise.all([
      serviceLookup.affiliationService.checkAccount(markupData.accountIdentifier),
      serviceLookup.frontendService.getBaseUrl(),
    ]);

    return { accountAffiliation, frontendBaseUrl };
  },

  mount: ({ config, rootElement, serviceLookup, updateInnerData }) => {
    const actionBarUi = mountUiWithActionBar({
      placement: config.markup.ui.actionBar,
      rootElement,
      serviceLookup,
      onRegDateInfoChange: (regDateInfo) => {
        updateInnerData((draft) => {
          if (regDateInfo) {
            draft.regDateInfo = regDateInfo;
          } else {
            delete draft.regDateInfo;
          }
        });
      },
    });

    return {
      render: ({ innerData, markupData, serviceData }) => {
        actionBarUi?.render({
          accountAffiliation: serviceData.accountAffiliation,
          accountIdentifier: markupData.accountIdentifier,
          regDateInfo: innerData.regDateInfo,
        });
      },
      unmount: () => {
        actionBarUi?.unmount();
      },
    };
  },
});

Comment Variant

Used for comments on posts, photos, videos, and wall posts. Key Features:
  • Extracts comment identifier for tracking
  • Collects comment data for analysis
  • Displays affiliation indicators
  • Provides inspector trigger
src/entrypoints/content/insertion-variants/=comment.ts
render: ({ innerData, markupData, serviceData }) => {
  // Collect comment for analysis
  if (markupData.commentIdentifier && !derivedPageInfo.archivedSnapshot) {
    void serviceLookup.collectingService.collectCommentIfNeeded({
      wallVkId: markupData.commentIdentifier.wallVkId,
      postVkId: markupData.commentIdentifier.postVkId,
      commentVkId: markupData.commentIdentifier.commentVkId,
      commenterVkDomain: stringifyAccountIdentifier(
        markupData.accountIdentifier,
      ),
      postCommentCount: undefined,
    });
  }

  actionBarUi?.render({
    accountAffiliation: serviceData.accountAffiliation,
    inspectorTrigger: markupData.commentIdentifier
      ? { type: "comment", ...markupData.commentIdentifier }
      : undefined,
    regDateInfo: innerData.regDateInfo,
  });
};

Review Variant

Used for application and community reviews.

Reply Form Variant

Used for comment reply forms.

Insertion Management

The startManagingInsertions function orchestrates the entire insertion lifecycle:
src/entrypoints/content/insertion-management.ts
export async function startManagingInsertions({
  archivedSnapshot,
  contentId,
  websiteVariant,
}: DerivedPageInfo & { contentId: ContentId }): Promise<void> {
  // Inject insertion styles
  const style = document.createElement("style");
  style.textContent = insertionStyling;
  document.head.append(style);

  const derivedPageInfo: DerivedPageInfo = { archivedSnapshot, websiteVariant };
  const instanceMap: InsertionInstanceMap = new Map();
  let currentConfigs: InsertionConfig[] = [];

  // Fetch configs from static lists
  const initialItems = await staticListsService.getItems("insertions");
  const initialDxConfig = await dxConfigService.get();

  updateConfigs(initialDxConfig.insertionsRemoved ? [] : initialItems);

  // Mount initial insertions
  mountNewInsertions({
    configs: currentConfigs,
    contentId,
    derivedPageInfo,
    instanceMap,
  });

  // Observe DOM changes and mount new insertions
  const mutationObserver = new MutationObserver(() => {
    // Throttled to 100ms
    mountNewInsertions({
      configs: currentConfigs,
      derivedPageInfo,
      instanceMap,
      contentId,
    });
  });

  mutationObserver.observe(document.body, {
    childList: true,
    subtree: true,
  });

  // Poll for config changes
  const stopPolling = startGlobalRerenderPolling({
    derivedPageInfo,
    instanceMap,
    contentId,
    getConfigs: () => currentConfigs,
    onConfigsChanged: updateConfigs,
  });
}

Tailwind bn: Prefix Convention

All Tailwind classes used in insertion UI components must use the bn: prefix.
This convention prevents style conflicts with VK.com’s existing styles:
// ✅ Correct: Use bn: prefix in insertions
const element = document.createElement("div");
element.className = "bn:flex bn:items-center bn:gap-2 bn:px-3 bn:py-1.5";

// ❌ Wrong: Don't use unprefixed classes in insertions
element.className = "flex items-center gap-2 px-3 py-1.5";

Why the Prefix?

VK.com uses its own CSS framework with classes like flex, text-center, etc. Without prefixing, Botnadzor’s Tailwind classes would conflict with VK’s styles, causing layout issues.

Where to Use It

  • Insertion variants (src/entrypoints/content/insertion-variants/**) — Required
  • Popup UI (src/entrypoints/popup/**) — Not required (isolated context)
  • Inspector UINot required (shadow DOM isolation)

Configuration

The prefix is configured in tailwind.config.js:
tailwind.config.js
module.exports = {
  prefix: 'bn:',
  // ... other config
};

UI Components

Insertion variants can mount several types of UI components:

Action Bar

Provides interactive buttons for:
  • Fetching registration dates
  • Triggering the inspector
  • Opening account profiles

Affiliation Badge

Displays a colored badge when an account is affiliated with bot/spam lists.

Affiliation Highlight

Highlights the entire element with a colored border based on affiliation.

Registration Date Display

Shows the VK registration date for an account.

Icons in Insertions

Import icons from lucide-static, not lucide-react.
Insertion UI is rendered using vanilla DOM manipulation, not React:
// ✅ Correct: Use lucide-static
import { Calendar } from "lucide-static";

const icon = document.createElement("div");
icon.innerHTML = Calendar;

// ❌ Wrong: Don't use lucide-react
import { Calendar } from "lucide-react";
return <Calendar />; // React components don't work in insertions

Class Utilities

Botnadzor provides two utility functions for managing classes:

cn() — Class Names

Combines class names with conditional logic:
import { cn } from "@/shared/tailwindcss-helpers";

const className = cn(
  "bn:px-2 bn:py-1",
  isActive && "bn:bg-blue-500",
  "bn:rounded"
);

cnt() — Class Names with Tailwind prefix

Automatically adds the bn: prefix to unprefixed classes:
import { cnt } from "@/shared/tailwindcss-helpers";

// Automatically prefixes all classes
const className = cnt("flex items-center gap-2 px-3");
// Result: "bn:flex bn:items-center bn:gap-2 bn:px-3"

Lifecycle Phases

1. Discovery

DOM observer detects elements matching insertion config selectors.

2. Markup Data Extraction

getMarkupData() extracts data from the DOM element using CSS selectors and React Fiber introspection.

3. Service Data Fetching

getServiceData() fetches data from background services (affiliation checks, base URLs).

4. Mounting

mount() creates UI components and attaches them to the DOM.

5. Rendering

render() updates UI components when data changes.

6. Unmounting

unmount() cleans up UI components when the target element is removed from the DOM.

Next Steps

Proxy Services

Learn how insertions communicate with background services

Static Lists

Understand how insertion configs are stored and fetched

Build docs developers (and LLMs) love