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 Reporting feature allows you to submit suspicious accounts to Botnadzor administrators for review. Reports help improve the bot detection database and protect the VK community.
How to Report an Account
- Open the Account Inspector for the suspicious account
- Switch to the Report tab
- Fill out the report form with:
- Tag suggestion (bot category)
- Detailed description of suspicious behavior
- Trigger information (where you found the account)
- Submit the report
CollectingService
The extension includes a sophisticated system for collecting comment data to help with bot detection. This service runs in the background and is managed by CollectingService (src/entrypoints/background/@services/collecting-service.ts).
When enabled, the extension automatically collects metadata about comments you encounter:
type CommentToCollect = {
wallVkId: VkId; // Wall owner ID (e.g., wall-123_456 -> -123)
postVkId: PositiveVkId; // Post ID (e.g., wall-123_456 -> 456)
commentVkId: PositiveVkId; // Comment ID (e.g., ?reply=789 -> 789)
commenterVkDomain: VkDomain; // Commenter's VK domain
postCommentCount: number | undefined; // Total comments on post
};
Collection Flow
async collectCommentIfNeeded(
commentToCollect: CommentToCollect,
): Promise<void> {
const [userOptedIn, wallSkipped] = await Promise.all([
this.checkIfUserOptedIn(),
this.checkIfWallSkipped(commentToCollect.wallVkId),
]);
if (!userOptedIn) {
logger.debug(
"Comment {commentSlug} was ignored: user not opted in",
{ commentSlug },
);
return;
}
if (wallSkipped) {
logger.debug(
"Comment {commentSlug} was ignored: wall skipped",
{ commentSlug },
);
return;
}
this.notYetPersistedComments.push(commentToCollect);
this.scheduleNextPersist();
}
Collection only happens when:
- User has opted into comment collection in settings
- User has valid authentication
- Wall is not in the skip list
- Page is not an archived snapshot
Opt-In Required
Comment collection is entirely opt-in:
private async doCheckIfUserOptedIn(): Promise<boolean | "maybe"> {
const authStatus = this.authService.getAuthStatus();
if (authStatus.state === "empty") {
return false;
}
const userConfig = await this.userConfigService.get();
if (!userConfig.collectingComments) {
return false;
}
if (authStatus.state === "valid") {
return true;
}
return false;
}
To enable:
- Configure authentication in extension settings
- Enable “Collecting Comments” option
- Extension will automatically start collecting data
Data Storage and Upload
IndexedDB Storage
Collected comments are stored locally in IndexedDB:
this.db = new Dexie("collecting");
this.db.version(1).stores({
[commentsTableName]: "[wallVkId+postVkId+commentVkId], persistedAt, uploadedAt",
[postsTableName]: "[wallVkId+postVkId]",
});
Storage schema:
type PersistedComment = {
wallVkId: VkId;
postVkId: PositiveVkId;
commentVkId: PositiveVkId;
commenterVkIdOrNickname: PositiveVkId | VkNickname;
persistedAt: IsoDateTime;
uploadedAt: IsoDateTime | "-";
};
type PersistedPost = {
wallVkId: VkId;
postVkId: PositiveVkId;
postCommentCount: number;
persistedAt: IsoDateTime;
};
Persistence Throttling
Comments are batched and persisted periodically:
const persistingThrottleInterval = 5000; // 5 seconds
private scheduleNextPersist(): void {
if (this.persistingThrottleTimeout) {
return; // Already scheduled
}
this.persistingThrottleTimeout = setTimeout(() => {
this.persistingThrottleTimeout = undefined;
void this.persistRegisteredCommentsIfNeeded();
}, persistingThrottleInterval);
}
This ensures:
- Minimal performance impact
- Batch processing for efficiency
- Automatic deduplication
Automatic Upload
Stored comments are automatically uploaded to Botnadzor servers:
const uploadingThrottleInterval = 5 * 60 * 1000; // 5 minutes
const uploadedCommentsMinCount = 5;
const uploadedCommentsMaxCount = 1000;
async uploadPersistedCommentsIfNeeded(): Promise<void> {
const commentsToUpload = rawNotUploadedComments
.map((rawItem) => this.parsePersistedComment(rawItem))
.filter((item): item is PersistedComment => item !== undefined)
.toSorted((itemA, itemB) =>
itemA.persistedAt.localeCompare(itemB.persistedAt),
)
.slice(0, uploadedCommentsMaxCount);
if (commentsToUpload.length < uploadedCommentsMinCount) {
logger.debug(
"Not enough comments to upload ({count} < {min}), skipping",
{ count: commentsToUpload.length, min: uploadedCommentsMinCount },
);
return;
}
const outcome = await this.authService.fetchFromDynamicApiWithAccessCode(
"collect",
{ groupedComments },
);
}
Upload behavior:
- Runs every 5 minutes
- Requires minimum 5 comments
- Uploads up to 1000 comments per batch
- Groups comments by post for efficiency
- Marks uploaded comments to prevent duplicates
Comments are grouped by post before upload:
const groupedComments: Array<{
wallVkId: VkId;
postVkId: PositiveVkId;
commentCount?: number;
comments: Array<{
commentVkId: PositiveVkId;
commenterVkIdOrNickname: PositiveVkId | VkNickname;
}>;
}> = [];
This structure:
- Reduces API payload size
- Provides context (total comment count on post)
- Helps identify bot activity patterns
- Enables better analysis by administrators
Manual Reporting via Inspector
You can manually report accounts using the Inspector:
// src/entrypoints/background/@services/inspector-service.ts
async submitReport(payload: {
tagSuggestion: TagSuggestion;
text: string;
trigger: InspectorTrigger;
vkDomain: VkDomain;
}): Promise<ResultOfReportAccount> {
const { vkDomain, tagSuggestion, text, trigger } = payload;
if (text.length < reportTextMinLength) {
return {
problem: true,
type: "bn:ext:invalid-payload",
description: `Минимальная длина текста: ${reportTextMinLength} символов`,
fields: ["text"],
};
}
if (text.length > reportTextMaxLength) {
return {
problem: true,
type: "bn:ext:invalid-payload",
description: `Максимальная длина текста: ${reportTextMaxLength} символов`,
fields: ["text"],
};
}
const vkId = await this.vkDomainResolver.resolve(vkDomain);
if (!vkId) {
return {
problem: true,
type: "bn:ext:unforeseen-error",
description: "Не получилось получить ID аккаунта",
};
}
const outcome = await this.authService.fetchFromDynamicApiWithAccessCode(
"reportAccount",
{ tagSuggestion, text, trigger, vkId },
);
return outcome;
}
Report Requirements
Tag Suggestion: Category for the bot (e.g., spam, propaganda, fake account)
Text Description:
- Minimum length: Defined by
reportTextMinLength
- Maximum length: Defined by
reportTextMaxLength
- Should describe suspicious behavior patterns
- Should include relevant context
Trigger: How you discovered the account
- Type: “comment” for comment-based discovery
- Includes post/comment identifiers for reference
Report Result
type ResultOfReportAccount =
| { problem: false; /* success */ }
| { problem: true; type: string; description: string; fields?: string[]; };
Success: Report submitted successfully
Error Types:
bn:ext:invalid-payload - Invalid report text length or missing data
bn:ext:unforeseen-error - Failed to resolve account ID
bn:ext:invalid-access-code - Authentication failed
bn:ext:missing-permission - Insufficient permissions
Data Pruning
The service automatically manages storage:
const persistedCommentMaxCount = 10_000;
private async prunePersistedDataIfNeeded(): Promise<void> {
const persistedCommentCount = await commentsTable.count();
if (persistedCommentCount <= persistedCommentMaxCount) {
return;
}
const countToDelete = persistedCommentCount - persistedCommentMaxCount;
// Delete oldest comments
const commentsToDelete = await commentsTable
.orderBy("persistedAt")
.limit(countToDelete)
.toArray();
await commentsTable.bulkDelete(bulkDeleteCommentKeys);
}
Pruning behavior:
- Maximum 10,000 comments stored
- Deletes oldest comments first
- Removes associated post data
- Cleans up data for skipped walls
- Runs after each upload cycle
Wall Skip List
Some walls are excluded from collection:
private async doCheckIfWallSkipped(wallVkId: VkId): Promise<boolean> {
const wall = await this.staticListsService.findItem(
"walls",
"vkId",
wallVkId,
);
return wall?.skip ?? false;
}
Skip list features:
- Cached (LRU cache, max 1000 entries)
- Synchronized with Botnadzor server
- Prevents collection from private/sensitive walls
- Automatically updated
Privacy and Data Control
User Control
You have complete control over data collection:
private resetWithDebounce(): void {
if (this.resettingDebounceTimeout) {
clearTimeout(this.resettingDebounceTimeout);
}
this.resettingDebounceTimeout = setTimeout(
() => void this.resetIfNeeded(),
resetDebounceTimeout, // 10 seconds
);
}
async resetIfNeeded(): Promise<void> {
const userOptedIn = await this.checkIfUserOptedIn();
if (userOptedIn === true || userOptedIn === "maybe") {
return;
}
// Clear all data
await this.getCommentsTable().clear();
await this.getPostsTable().clear();
this.notYetPersistedComments = [];
}
When you opt out:
- Service waits 10 seconds (in case of accidental toggle)
- Deletes all stored comment data
- Clears pending uploads
- Stops collecting new data
- You can opt back in anytime
What Data is Collected
Collected:
- VK post/comment identifiers (wall/post/comment IDs)
- Commenter VK domain (ID or nickname)
- Post comment count
- Timestamp of collection
NOT Collected:
- Comment content/text
- Your identity as the collector
- Comment context or replies
- Private messages
- Personal information
Data Purpose
Collected data helps Botnadzor:
- Identify coordinated bot activity
- Detect spam patterns
- Build better bot detection models
- Track bot network evolution
- Improve community protection
Best Practices
When Reporting Accounts
- Gather evidence - Use Inspector to analyze behavior first
- Check registration date - New accounts with high activity are suspicious
- Be specific - Describe exact suspicious patterns observed
- Include context - Where did you find the account? What were they doing?
- Choose appropriate tag - Select the category that best fits the behavior
Writing Good Reports
Good report example:
Account posts identical comments across multiple unrelated posts within minutes.
Comments contain links to suspicious websites.
Account created 3 days ago but has 200+ comments.
Poor report example:
- Understand what’s collected - Review privacy section above
- Configure authentication - Required for upload
- Enable in settings - Toggle “Collecting Comments”
- Browse normally - Collection happens automatically
- Monitor storage - Check extension to see collection stats