import { checkBotId } from "botid/server"; import type { IncomingHttpHeaders } from "http"; import type { EventTypeRepository } from "@calcom/features/eventtypes/repositories/eventTypeRepository"; import type { FeaturesRepository } from "@calcom/features/flags/features.repository"; import { HttpError } from "@calcom/lib/http-error"; import logger from "@calcom/lib/logger"; interface BotDetectionConfig { eventTypeId?: number; headers: IncomingHttpHeaders; } const log = logger.getSubLogger({ prefix: ["[BotDetectionService]"] }); export class BotDetectionService { constructor( private featuresRepository: FeaturesRepository, private eventTypeRepository: EventTypeRepository ) {} private instanceHasBotIdEnabled() { return process.env.NEXT_PUBLIC_VERCEL_USE_BOTID_IN_BOOKER === "1"; } async checkBotDetection(config: BotDetectionConfig): Promise { if (!this.instanceHasBotIdEnabled()) return; const { eventTypeId, headers } = config; // If no eventTypeId provided, skip bot detection if (!eventTypeId) { return; } if (!Number.isInteger(eventTypeId) || eventTypeId <= 0) { throw new Error(`Invalid eventTypeId: ${eventTypeId}. Must be a positive integer.`); } // Fetch only the teamId from the event type const eventType = await this.eventTypeRepository.getTeamIdByEventTypeId({ id: eventTypeId, }); // Only check for team events if (!eventType?.teamId) { return; } // Check if BotID feature is enabled for this team (also checks global scope - enabling on all teams) const isBotIDEnabled = await this.featuresRepository.checkIfTeamHasFeature( eventType.teamId, "booker-botid" ); if (!isBotIDEnabled) { return; } // Perform bot detection const verification = await checkBotId({ advancedOptions: { headers, }, }); // Log verification results with detailed information const verificationDetails = { isBot: verification.isBot, isHuman: verification.isHuman, isVerifiedBot: verification.isVerifiedBot, verifiedBotName: verification.verifiedBotName, verifiedBotCategory: verification.verifiedBotCategory, bypassed: verification.bypassed, classificationReason: verification.classificationReason, teamId: eventType.teamId, eventTypeId, }; if (verification.isBot) { log.warn("Bot detected - blocking request", verificationDetails); throw new HttpError({ statusCode: 403, message: "Access denied" }); } } }