UNPKG

tezx

Version:

TezX is a high-performance, lightweight JavaScript framework designed for speed, scalability, and flexibility. It enables efficient routing, middleware management, and static file serving with minimal configuration. Fully compatible with Node.js, Deno, an

99 lines (98 loc) 3.89 kB
import { GlobalConfig } from "../core/config.js"; export const detectBot = (options = {}) => { const { botUserAgents = ["bot", "spider", "crawl", "slurp"], maxRequests = 30, windowMs = 60000, isBlacklisted = async () => false, queryKeyBot = "bot", onBotDetected = "block", enableRateLimiting = false, customBotDetector = async () => false, customBlockedResponse = (ctx, { reason }) => { ctx.setStatus = 403; return ctx.json({ error: `Bot detected: ${reason}` }); }, storage, confidenceThreshold = 0.5, } = options; let store = storage; if (enableRateLimiting) { store = createRateLimitDefaultStorage(); } return async function detectBot(ctx, next) { const detectionResult = { isBot: false, indicators: [], }; const userAgent = ctx.headers.get("user-agent")?.toLowerCase() || ""; const remoteAddress = `${ctx.req.remoteAddress?.address}:${ctx.req.remoteAddress?.port}` || "unknown"; const isBotQuery = ctx.req.query[queryKeyBot] === "true"; if (botUserAgents.some((agent) => userAgent.includes(agent))) { detectionResult.indicators.push("User-Agent"); } if (await isBlacklisted(ctx, remoteAddress)) { detectionResult.indicators.push("Blacklisted IP"); } if (isBotQuery) { detectionResult.indicators.push("Query Parameter"); } const key = `${ctx.req.remoteAddress.address}:${ctx.req.remoteAddress.port}`; if (enableRateLimiting && isRateLimit(ctx, key, store, maxRequests, windowMs).check) { detectionResult.indicators.push("Rate Limiting"); } if (await customBotDetector(ctx)) { detectionResult.indicators.push("Custom Detector"); } detectionResult.isBot = detectionResult.indicators.length > 0; if (detectionResult.indicators.length > 1) { detectionResult.reason = "Multiple Indicators"; const confidence = Math.min(0.3 * detectionResult.indicators.length, 1); detectionResult.isBot = confidence >= confidenceThreshold; } else if (detectionResult.indicators.length === 1) { detectionResult.reason = detectionResult.indicators[0]; } if (detectionResult.isBot) { GlobalConfig.debugging.warn(`Bot detected: ${detectionResult.reason}`, { ip: remoteAddress, userAgent, indicators: detectionResult.indicators, }); if (onBotDetected === "block") { return customBlockedResponse(ctx, detectionResult); } else if (typeof onBotDetected === "function") { return onBotDetected(ctx, detectionResult); } } return await next(); }; }; export function createRateLimitDefaultStorage() { const store = new Map(); return { get: (key) => store.get(key), set: (key, value) => store.set(key, value), clearExpired: () => { const now = Date.now(); for (const [key, entry] of store.entries()) { if (now >= entry.resetTime) { store.delete(key); } } }, }; } export function isRateLimit(ctx, key, store, maxRequests, windowMs) { store.clearExpired(); const now = Date.now(); let entry = store.get(key) || { count: 0, resetTime: now + windowMs }; if (now < entry.resetTime) { entry.count++; if (entry.count > maxRequests) { return { check: true, entry: entry, }; } } else { entry = { count: 1, resetTime: now + windowMs }; } store.set(key, entry); return { check: false, entry: entry, }; }