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.

Insertion variants are the core mechanism for modifying VK.com pages. Each variant defines how to extract data from the page, what services to call, and what UI elements to inject.

Overview

The insertion system is:
  • Config-driven: Behavior is defined by JSON configs loaded from static lists
  • Type-safe: Full TypeScript typing for configs, data, and services
  • Reactive: Uses Pollable pattern for real-time updates
  • Modular: Each variant is independent with clear data flow

Architecture

Each insertion variant follows this data flow:
Page Markup → Markup Data → Service Data → UI Rendering
      ↓           ↓              ↓              ↓
  DOM selectors  Extraction   Background     DOM injection
                 logic        services

Available Variants

Botnadzor includes four insertion variants:

1. Account Variant

Purpose: Enhances VK profile pages and account links throughout the site. File: src/entrypoints/content/insertion-variants/=account.ts Markup Data Extracted:
  • accountAvatarUrl: Profile picture URL
  • accountIdentifier: VK domain (e.g., id123 or nickname)
  • accountName: Display name
Service Data:
  • accountAffiliation: Tags and colors from static lists
  • frontendBaseUrl: Base URL for Botnadzor links
UI Components:
  • Action Bar: Inspector trigger, reg date fetch button
  • Affiliation Badge: Colored tag indicator
  • Affiliation Highlight: Background color overlay
  • Reg Date Display: Shows registration date when fetched
Example Config:
{
  "variant": "account",
  "observeSelector": ".profile_info",
  "markup": {
    "data": {
      "accountAvatar": { "selector": ".profile_photo img", "attribute": "src" },
      "accountIdentifier": { "selector": "[data-page-id]", "attribute": "data-page-id" },
      "accountName": { "selector": ".page_name", "textContent": true }
    },
    "ui": {
      "actionBar": { "target": ".profile_actions", "position": "beforebegin" },
      "affiliationBadge": { "target": ".page_name", "position": "afterend" },
      "affiliationHighlight": { "target": ".profile_info" },
      "regDate": { "target": ".profile_info", "position": "beforeend" }
    },
    "edits": []
  }
}

2. Comment Variant

Purpose: Enhances comments on posts, photos, and videos. File: src/entrypoints/content/insertion-variants/=comment.ts Markup Data Extracted:
  • accountAvatarUrl: Commenter’s avatar
  • accountIdentifier: Commenter’s VK domain
  • accountName: Commenter’s name
  • commentIdentifier: Comment URL parts (wallVkId, postVkId, commentVkId)
  • postCommentCount: Total comments on the post
Service Data:
  • accountAffiliation: Tags and colors
  • frontendBaseUrl: Base URL for links
Special Features:
  • Comment Collection: Automatically collects comment metadata if user has opted in (via CollectingService)
  • Inspector Trigger: Can open inspector with comment context
Comment Identifier Parsing: Supports multiple formats:
  • /wall-123_456?reply=789
  • https://vk.com/photo-123_456?reply=789
  • replyClick('wall-123_456', 789)
  • replyClick('-123_photo456', 789)
Source: src/entrypoints/content/insertion-variants/=comment.ts:62-148

3. Reply Form Variant

Purpose: Adds a “quick attach bot card” button to reply forms when replying to known bots. File: src/entrypoints/content/insertion-variants/=reply-form.ts Markup Data Extracted:
  • accountIdentifier: The account being replied to
Service Data:
  • accountAffiliation: Checks if account has botnadzorCard: true
  • frontendBaseUrl: For generating card links
Behavior:
  • Monitors reply form for changes (MutationObserver)
  • Shows button only when replying to an account with botnadzorCard: true
  • Clicking button injects a Botnadzor card link into the reply
Implementation Detail: The button uses caret position preservation to insert the link without disrupting user typing:
const savedOffset = getCaretCharOffset(contentEditable);
// ... paste content ...
setCaretCharOffset(contentEditable, savedOffset);
Source: src/entrypoints/content/insertion-variants/=reply-form.ts:17-70

4. Review Variant

Purpose: Enhances VK community reviews (separate from comments). File: src/entrypoints/content/insertion-variants/=review.ts Markup Data Extracted:
  • accountAvatarUrl: Reviewer’s avatar
  • accountIdentifier: Reviewer’s VK domain
  • accountName: Reviewer’s name
  • reviewIdentifier: Review ID parts (wallVkId, reviewVkId)
Service Data:
  • accountAffiliation: Tags and colors
  • frontendBaseUrl: Base URL for links
Review Identifier Format: Parses review-123_456 format:
const match = /^review(-?\d+)_(\d+)$/.exec(value);
Source: src/entrypoints/content/insertion-variants/=review.ts:49-71

Variant Structure

All variants are defined using defineInsertionVariant<Config, InnerData, MarkupData, ServiceData>():
export default defineInsertionVariant<
  CommentInsertionConfig,
  CommentInnerData,
  CommentMarkupData,
  CommentServiceData
>({
  defaultInnerData: {},

  getMarkupData: async ({ config, instanceLogger, rootElement }) => {
    // Extract data from DOM using config selectors
    return { accountIdentifier, accountName, ... };
  },

  getServiceData: async ({ markupData, serviceLookup }) => {
    // Call background services
    return { accountAffiliation, frontendBaseUrl };
  },

  mount: ({ config, rootElement, serviceLookup, updateInnerData }) => {
    // Mount UI components and set up event listeners
    
    return {
      render: ({ innerData, markupData, serviceData }) => {
        // Update UI with current data
      },
      
      unmount: () => {
        // Clean up UI and event listeners
      }
    };
  }
});

Configuration Schema

Each variant has a config schema defined in src/shared/@model/insertion-configs/:

Common Config Properties

type BaseInsertionConfig = {
  variant: "account" | "comment" | "replyForm" | "review";
  observeSelector: string;  // CSS selector to watch for new elements
  markup: {
    data: { ... };          // Selectors for extracting data
    ui: { ... };            // Placement for UI components
    edits: Array<...>;      // DOM modifications (hide elements, etc.)
  };
};

Data Selectors

Data is extracted using StringDataSelector or ElementSelector:
type StringDataSelector = 
  | { selector: string; attribute: string }  // Read attribute
  | { selector: string; textContent: true }  // Read text content
  | { fiberKey: string }                    // Read from React fiber
  | false;                                   // Disabled

type ElementSelector = 
  | { selector: string }                     // Find element
  | false;                                   // Disabled

UI Placement

UI components use UiPlacement:
type UiPlacement = 
  | { target: string; position: "beforebegin" | "afterend" | "beforeend" }
  | { target: string }  // For highlights (wraps target)
  | false;              // Disabled

Markup Edits

type MarkupEdit = 
  | { type: "hide"; target: string }  // Hides elements
  | { type: "show"; target: string }  // Shows elements

Shared UI Components

Variants use shared UI components from src/entrypoints/content/insertion-variants/shared/@markup-ui/:

Action Bar

mountUiWithActionBar({
  contentId,
  derivedPageInfo,
  instanceLogger,
  placement,
  rootElement,
  serviceLookup,
  onRegDateInfoChange
})
Provides:
  • Inspector trigger button
  • Registration date fetch button
  • Account card link

Affiliation Badge

mountUiWithAffiliationBadge({ rootElement, placement })
Shows colored tag label next to account names.

Affiliation Highlight

mountUiWithAffiliationHighlight({ instanceLogger, placement, rootElement })
Applies colored background to account elements.

Reg Date Display

mountUiWithRegDate({ instanceLogger, placement, rootElement })
Shows registration date when available.

Registering New Variants

To add a new variant:
  1. Create the variant file in src/entrypoints/content/insertion-variants/:
    // =my-variant.ts
    export default defineInsertionVariant<...>({
      defaultInnerData: {},
      getMarkupData: async (...) => { ... },
      getServiceData: async (...) => { ... },
      mount: (...) => { ... }
    });
    
  2. Define the config schema in src/shared/@model/insertion-configs/:
    // my-variant.ts
    export const myVariantInsertionConfigSchema = z.readonly(
      z.extend(baseInsertionConfigSchema, {
        variant: z.literal("myVariant"),
        markup: z.readonly(
          z.object({
            data: z.readonly(z.object({ ... })),
            ui: z.readonly(z.object({ ... })),
            edits: markupEditsSchema
          })
        )
      })
    );
    
  3. Register in the union type in src/shared/@model/insertion-configs.ts:
    export const insertionConfigSchema = z.union([
      accountInsertionConfigSchema,
      commentInsertionConfigSchema,
      replyFormInsertionConfigSchema,
      reviewInsertionConfigSchema,
      myVariantInsertionConfigSchema  // Add here
    ]);
    
  4. Add to insertion system (handled automatically by the variant registry)
  5. Deploy configs via static lists (insertions list)

Best Practices

Selector Stability

  • Prefer data-* attributes over class names (VK frequently changes classes)
  • Use observeSelector that matches the insertion scope precisely
  • Test with React DevTools to find stable selectors

Performance

  • Extract markup data asynchronously in parallel
  • Use revalidateMarkupData() sparingly (triggers full re-extraction)
  • Cache service data when possible

Error Handling

  • Always validate extracted data with warnAboutUndefinedFields()
  • Return undefined from getMarkupData() when required fields are missing
  • Use omitUndefined() to clean up service data

Tailwind CSS

  • Use bn: prefix for all Tailwind classes in insertion variants
  • This prevents style conflicts with VK’s CSS
  • Example: bn:text-red-500 bn:font-bold

Icons

  • Import from lucide-static, not lucide-react
  • Example: import { UserPlus } from "lucide-static"
  • This avoids React hydration issues in content scripts

Build docs developers (and LLMs) love