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.
Botnadzor Extension uses Zod for runtime type validation and TypeScript type inference. All models are defined in src/shared/@model/ and use zod/mini for smaller bundle size.
Import Convention
import { z } from "zod/mini";
// Define schema
export const mySchema = z.readonly(z.object({ ... }));
// Infer TypeScript type
export type MyType = z.infer<typeof mySchema>;
Note: Use z.exactOptional() instead of z.optional() for cleaner types.
RootConfig
Central configuration fetched from the static API that defines extension version ranges and remote system URLs.
Schema: src/shared/@model/root-config.ts:16-53
type RootConfig = {
extensionVersionRange: SemverRange; // e.g., ">=2.0.0 <3.0.0"
generatedAt: IsoDateTime;
remoteSystemLookup: {
dynamicApi: {
aliasLookup: Record<string, { role?: "primary" }>;
};
frontend: {
aliasLookup: Record<string, { role?: "primary" }>;
};
staticApi: {
aliasLookup: Record<string, { role?: "primary" }>;
listLookup: Record<StaticListId, {
generatedAt: IsoDateTime;
itemCount: number;
}>;
};
};
};
Usage:
const rootConfig = await rootConfigService.get();
const isSupported = semverSatisfies(
currentVersion,
rootConfig.extensionVersionRange
);
Seed:
The extension includes a seed config (root-config/seed.json) that’s used on first launch before fetching from the server.
UserConfig
User preferences stored in sync storage.
Schema: src/shared/@model/user-config.ts:14-20
type UserConfig = {
tagOverrideLookup: Record<string, {
colorForHighlight?: HexColor; // Override tag color
hidden?: true; // Hide this tag's accounts
}>;
fansDisplay: "default" | "table"; // How to display reaction lists
collectingComments?: true; // Opt-in to comment collection
};
Default:
const defaultUserConfig: UserConfig = {
tagOverrideLookup: {},
fansDisplay: "default"
};
Usage:
const userConfig = await userConfigService.get();
if (userConfig.collectingComments) {
await collectingService.collectCommentIfNeeded(comment);
}
Auth Models
Authentication state management types.
Schema: src/shared/@model/auth.ts
PermissionLookup
type PermissionLookup = {
getRegDate?: true; // Can fetch registration dates
inspectAccount?: true; // Can inspect accounts
reportAccount?: true; // Can report accounts
};
AuthStatus
type AuthStatus =
| {
state: "empty"; // No access code entered
accessCode: string;
accessCodeEnteredAt: IsoDateTime;
}
| {
state: "invalid"; // Access code rejected
accessCode: string;
accessCodeEnteredAt: IsoDateTime;
accessCodeRecognized: boolean; // true if code exists but expired
errorMessage: string;
}
| {
state: "valid"; // Successfully authenticated
expiresAt?: IsoDateTime;
accessLevel: number;
pointCount: number;
permissionLookup: PermissionLookup;
}
| {
state: "unknown"; // API unavailable, can't verify
};
AuthCheck
type AuthCheck =
| { state: "idle"; lastFinishedAt?: IsoDateTime }
| { state: "ongoing"; startedAt: IsoDateTime };
Usage:
const authStatus = authService.getAuthStatus();
if (authStatus.state === "valid" && authStatus.permissionLookup.inspectAccount) {
// User can inspect accounts
}
StaticLists
Data structures for the five static lists: accounts, tags, walls, announcements, and insertions.
Schema: src/shared/@model/static-lists.ts
StaticListId
type StaticListId = "accounts" | "tags" | "walls" | "announcements" | "insertions";
Static List Items
AccountListItem
type AccountListItem = {
vkId?: number; // VK ID (if known)
vkNickname?: string; // VK nickname (if known)
tagIds: string[]; // Associated tag IDs
};
TagListItem
type TagListItem = {
id: string; // Unique tag ID
name: string; // Display name (e.g., "Бот")
description?: string; // Full description
color?: HexColor; // Badge color
colorForHighlight?: HexColor; // Highlight color
botnadzorPage?: true; // Has Botnadzor page link
botnadzorCard?: true; // Has Botnadzor card link
};
WallListItem
type WallListItem = {
vkId: number; // Wall/community ID
skip?: true; // Skip comment collection for this wall
};
AnnouncementListItem
type AnnouncementListItem = {
createdAt: IsoDateTime;
extensionVersionRange: SemverRange;
extensionVersionRangeForToast?: SemverRange; // Narrower range for toasts
text: string; // Markdown content
title: string;
updatedAt: IsoDateTime;
};
InsertionListItem
type InsertionListItem = InsertionConfig & {
id: string;
extensionVersionRange?: SemverRange; // Optional version constraint
};
StaticListSummary
Each list has a summary type that provides aggregate statistics:
// Example for accounts list
type AccountsListSummary = {
itemCount: number;
vkIdCount: number;
vkNicknameCount: number;
};
// Example for tags list
type TagsListSummary = {
itemCount: number;
};
Usage:
const account = await staticListsService.findItem(
"accounts",
"vkId",
123456
);
const tags = await Promise.all(
account.tagIds.map(tagId =>
staticListsService.findItem("tags", "id", tagId)
)
);
InsertionConfigs
Configuration schemas for each insertion variant.
Base Schema: src/shared/@model/insertion-configs/shared/schema.ts
Common Types
type StringDataSelector =
| { selector: string; attribute: string }
| { selector: string; textContent: true }
| { fiberKey: string } // React fiber key
| false; // Disabled
type ElementSelector =
| { selector: string }
| false;
type UiPlacement =
| { target: string; position: "beforebegin" | "afterend" | "beforeend" }
| { target: string } // For wrapping
| false; // Disabled
type MarkupEdit =
| { type: "hide"; target: string }
| { type: "show"; target: string };
AccountInsertionConfig
Schema: src/shared/@model/insertion-configs/account.ts
type AccountInsertionConfig = {
variant: "account";
observeSelector: string;
markup: {
data: {
accountAvatar: ElementSelector | StringDataSelector;
accountIdentifier: StringDataSelector;
accountName: StringDataSelector;
};
ui: {
actionBar: UiPlacement;
affiliationBadge: UiPlacement;
affiliationHighlight: UiPlacement;
regDate: UiPlacement;
};
edits: MarkupEdit[];
};
};
Schema: src/shared/@model/insertion-configs/comment.ts
type CommentInsertionConfig = {
variant: "comment";
observeSelector: string;
markup: {
data: {
accountAvatar: ElementSelector | StringDataSelector;
accountIdentifier: StringDataSelector;
accountName: StringDataSelector;
commentIdentifier: StringDataSelector | false;
postCommentCount: StringDataSelector | false;
};
ui: {
actionBar: UiPlacement;
affiliationBadge: UiPlacement;
affiliationHighlight: UiPlacement;
regDate: UiPlacement;
};
edits: MarkupEdit[];
};
};
Schema: src/shared/@model/insertion-configs/reply-form.ts
type ReplyFormInsertionConfig = {
variant: "replyForm";
observeSelector: string;
markup: {
data: {
accountIdentifier: StringDataSelector;
};
ui: {
bnCardAttachmentButton: UiPlacement;
};
edits: MarkupEdit[];
};
};
ReviewInsertionConfig
Schema: src/shared/@model/insertion-configs/review.ts
type ReviewInsertionConfig = {
variant: "review";
observeSelector: string;
markup: {
data: {
accountAvatar: ElementSelector | StringDataSelector;
accountIdentifier: StringDataSelector;
accountName: StringDataSelector;
reviewIdentifier: StringDataSelector | false;
};
ui: {
actionBar: UiPlacement;
affiliationBadge: UiPlacement;
affiliationHighlight: UiPlacement;
regDate: UiPlacement;
};
edits: MarkupEdit[];
};
};
Union Type:
type InsertionConfig =
| AccountInsertionConfig
| CommentInsertionConfig
| ReplyFormInsertionConfig
| ReviewInsertionConfig;
type InsertionVariant = InsertionConfig["variant"];
AccountAffiliation
Represents an account’s affiliation with tags (bot, spam, etc.).
Schema: src/shared/@model/account-affiliation.ts:4-14
type AccountAffiliation = {
color: HexColor; // Primary color from first tag
colorForHighlight: HexColor; // Highlight color
tags: [TagListItem, ...TagListItem[]]; // Non-empty array of tags
hidden?: boolean; // User has hidden this tag
botnadzorPage?: true; // Has Botnadzor page link
botnadzorCard?: true; // Has Botnadzor card link
};
Fallback Color:
const fallbackHexColor: HexColor = "#888888";
Usage:
const affiliation = await affiliationService.checkAccount("id123");
if (affiliation && !affiliation.hidden) {
// Show colored badge with first tag
showBadge(affiliation.tags[0].name, affiliation.color);
}
Inspector Models
Data structures for the account inspector feature.
Schema: src/shared/@model/inspector.ts
InspectorAccountInfo
type InspectorAccountInfo = {
vkDomain: VkDomain; // e.g., "id123" or "nickname"
name: string; // Display name
avatarUrl: string; // Avatar URL
};
InspectorTrigger
type InspectorTrigger =
| {
type: "comment";
postType: "photo" | "video" | "wall";
wallVkId: VkId;
postVkId: VkId;
commentVkId: VkId;
}
| {
type: "review";
wallVkId: VkId;
reviewVkId: PositiveVkId;
};
InspectorInstanceConfig
type InspectorInstanceConfig = {
accountInfo: InspectorAccountInfo;
trigger: InspectorTrigger;
tab: "activity" | "report";
triggeredAt: IsoDateTime;
};
Report Constraints:
const reportTextMinLength = 10;
const reportTextMaxLength = 200;
Usage:
await inspectorService.trigger(contentId, {
accountInfo: {
vkDomain: "id123",
name: "John Doe",
avatarUrl: "https://..."
},
trigger: {
type: "comment",
postType: "wall",
wallVkId: -123,
postVkId: 456,
commentVkId: 789
}
});
Additional Primitives
VK Types
Schema: src/shared/@primitives/vk.ts
type VkId = number; // Can be negative for communities
type PositiveVkId = number; // Must be > 0 (users only)
type VkDomain = string; // e.g., "id123" or "nickname"
type VkNickname = string; // Alphanumeric + underscore
type AccountIdentifier =
| { kind: "vkId"; value: PositiveVkId }
| { kind: "vkNickname"; value: VkNickname };
Temporal Types
Schema: src/shared/@primitives/temporal.ts
type IsoDate = string; // YYYY-MM-DD
type IsoDateTime = string; // ISO 8601 with timezone
Misc Types
Schema: src/shared/@primitives/misc.ts
type HexColor = string; // #RRGGBB format
type ContentId = string; // Unique ID for content instances
type TagId = string; // Tag identifier
type TagSuggestion = string; // Tag suggestion for reports
Model Best Practices
Schema Design
- Always use readonly: Wrap schemas with
z.readonly() for immutability
- Use exactOptional: Prefer
z.exactOptional() over z.optional()
- Validate enums: Use
z.enum() or z.literal() for string constants
- Document types: Export both schema and inferred type
Type Safety
// Good: Explicit type inference
export const mySchema = z.readonly(z.object({ ... }));
export type MyType = z.infer<typeof mySchema>;
// Good: Discriminated unions
export const statusSchema = z.discriminatedUnion("state", [
z.object({ state: z.literal("idle") }),
z.object({ state: z.literal("loading") })
]);
Validation
// Parse with error handling
const result = mySchema.safeParse(data);
if (!result.success) {
logger.error("Invalid data: {error}", { error: result.error });
return;
}
const validData = result.data;
Schema Evolution
When updating schemas:
- Use
z.exactOptional() for new fields
- Provide sensible defaults
- Test with old data to ensure backwards compatibility
- Version schemas when breaking changes are needed
// Adding a new optional field
export const mySchemaV2 = z.readonly(
z.extend(mySchemaV1, {
newField: z.exactOptional(z.string())
})
);