[Feat]: Add bot detection note

Just use my API, smh
This commit is contained in:
ClaraCrazy 2026-01-10 07:51:50 +01:00
parent a48ae65885
commit b2433c29e3
No known key found for this signature in database
GPG key ID: EBBC896ACB497011
7 changed files with 448 additions and 4 deletions

View file

@ -0,0 +1,279 @@
// Exhaustive bot detection middleware for Express
// Flags likely bots and sets res.locals.suspectedBot
// Uses multiple signals: User-Agent, Accept, Referer, Accept-Language, cookies, IP, and request rate
const knownBotUserAgents = [
/HeadlessChrome/i,
/PhantomJS/i,
/Puppeteer/i,
/node\.js/i,
/curl/i,
/wget/i,
/python/i,
/Go-http-client/i,
/Java\//i,
/libwww-perl/i,
/scrapy/i,
/httpclient/i,
/http_request2/i,
/lwp::simple/i,
/okhttp/i,
/mechanize/i,
/axios/i,
/rest-client/i,
/httpie/i,
/powershell/i,
/http.rb/i,
/fetch/i,
/httpclient/i,
/spider/i,
/bot/i,
/spider/i,
/crawler/i,
/slurp/i,
/bingbot/i,
/yandex/i,
/duckduckgo/i,
/baiduspider/i,
/sogou/i,
/exabot/i,
/facebot/i,
/ia_archiver/i,
/Google-Read-Aloud/i, // Google Read Aloud
/Google-Structured-Data-Testing-Tool/i,
/Google-PageRenderer/i,
/Google Favicon/i,
/Googlebot/i,
/AdsBot-Google/i,
/Feedfetcher-Google/i,
/APIs-Google/i,
/bingpreview/i,
/facebookexternalhit/i,
/WhatsApp/i,
/TelegramBot/i,
/Slackbot/i,
/Discordbot/i,
/Applebot/i,
/DuckDuckBot/i,
/embedly/i,
/LinkedInBot/i,
/outbrain/i,
/pinterest/i,
/quora link preview/i,
/rogerbot/i,
/showyoubot/i,
/SkypeUriPreview/i,
/Slack-ImgProxy/i,
/Twitterbot/i,
/vkShare/i,
/W3C_Validator/i,
/redditbot/i,
/FlipboardProxy/i,
/Qwantify/i,
/SEMrushBot/i,
/AhrefsBot/i,
/MJ12bot/i,
/DotBot/i,
/BLEXBot/i,
/YandexBot/i,
/Screaming Frog/i,
/SiteAuditBot/i,
/UptimeRobot/i,
/Pingdom/i,
/StatusCake/i,
/ZoominfoBot/i,
/Google-Safety/i,
/Lighthouse/i,
/Accessibility/i,
/NVDA/i,
/JAWS/i,
/VoiceOver/i,
/ScreenReader/i,
/axe-core/i,
/pa11y/i,
/waveapi/i,
/tenon/i,
/Siteimprove/i,
/SiteAnalyzer/i,
/Sitebulb/i,
/SEO PowerSuite/i,
/SEOsitecheckup/i,
/SEO Crawler/i,
/SEO-Checker/i,
/SEO-Tool/i,
/SEO-Analyzer/i,
/SEO-Tester/i,
/SEO-SpyGlass/i,
/SEO-Toolkit/i,
/SEO-Tools/i,
/SEO-Profiler/i,
/SEO-Checker/i,
/SEO-Tool/i,
/SEO-Analyzer/i,
/SEO-Tester/i,
/SEO-SpyGlass/i,
/SEO-Toolkit/i,
/SEO-Tools/i,
/SEO-Profiler/i
];
const knownHeadlessIndicators = [
'Headless',
'PhantomJS',
'Puppeteer',
'Selenium',
'Nightmare',
'SlimerJS',
'Zombie',
'CasperJS',
'TrifleJS',
'HtmlUnit',
'Splash',
'Playwright'
];
// Additional bypass and automation checks
function hasSuspiciousHeaders(req) {
// Some automation tools set these headers
if (req.get('X-Requested-With') && req.get('X-Requested-With').toLowerCase() !== 'xmlhttprequest') return true;
if (req.get('X-Purpose')) return true;
if (req.get('X-Moz')) return true;
if (req.get('X-ATT-DeviceId')) return true;
if (req.get('X-Wap-Profile')) return true;
if (req.get('X-OperaMini-Phone-UA')) return true;
if (req.get('X-OperaMini-Features')) return true;
if (req.get('X-Device-User-Agent')) return true;
if (req.get('X-Original-User-Agent')) return true;
if (req.get('X-Device-Id')) return true;
if (req.get('X-Forwarded-For') && req.get('X-Forwarded-For').split(',').length > 3) return true;
return false;
}
// In-memory request rate tracking (per IP)
const requestLog = {};
const RATE_WINDOW_MS = 10 * 1000; // 10 seconds
const MAX_REQUESTS_PER_WINDOW = 30;
function isRapidRequester(ip) {
const now = Date.now();
if (!requestLog[ip]) requestLog[ip] = [];
// Remove old entries
requestLog[ip] = requestLog[ip].filter(ts => now - ts < RATE_WINDOW_MS);
requestLog[ip].push(now);
return requestLog[ip].length > MAX_REQUESTS_PER_WINDOW;
}
module.exports = function botDetect(req, res, next) {
// If suppression cookie is set, skip detection
if (req.cookies && req.cookies.bot_check_passed) {
res.locals.suspectedBot = false;
return next();
}
let score = 0;
const reasons = [];
// Header and request info (declare all before use)
const ua = req.get('User-Agent') || '';
const accept = req.get('Accept') || '';
const referer = req.get('Referer') || '';
const acceptLang = req.get('Accept-Language') || '';
const hasCookies = !!req.headers.cookie;
const ip = req.ip || req.connection.remoteAddress;
const path = req.path || '';
// Check for suspicious/bypass headers
if (hasSuspiciousHeaders(req)) {
score += 2;
reasons.push('Suspicious/bypass headers');
}
// Google Read Aloud and similar tools: look for Accept header with 'application/ssml+xml' or 'text/speech'
if (accept.includes('ssml+xml') || accept.includes('text/speech')) {
score += 2;
reasons.push('Speech synthesis Accept header');
}
// Accessibility Accept headers (screen readers, etc)
if (accept.includes('application/x-nvda') || accept.includes('application/x-jaws')) {
score += 1;
reasons.push('Accessibility Accept header');
}
// Check for automation framework cookies (common for Selenium, Puppeteer, etc)
if (req.headers.cookie && (req.headers.cookie.includes('puppeteer') || req.headers.cookie.includes('selenium'))) {
score += 2;
reasons.push('Automation framework cookie');
}
// User-Agent checks
if (!ua) {
score += 2;
reasons.push('Missing User-Agent');
} else {
if (knownBotUserAgents.some(pat => pat.test(ua))) {
score += 3;
reasons.push('Known bot User-Agent');
}
if (knownHeadlessIndicators.some(ind => ua.includes(ind))) {
score += 2;
reasons.push('Headless browser indicator');
}
if (ua.length < 10) {
score += 1;
reasons.push('Suspiciously short User-Agent');
}
}
// Accept header
if (!accept || accept === '*/*') {
score += 1;
reasons.push('Suspicious Accept header');
}
// Referer
if (!referer && req.method === 'POST') {
score += 1;
reasons.push('Missing Referer on POST');
}
// Accept-Language
if (!acceptLang) {
score += 1;
reasons.push('Missing Accept-Language');
}
// Cookies
if (!hasCookies) {
score += 1;
reasons.push('No cookies sent');
}
// IP checks (basic, not using blocklists)
if (isRapidRequester(ip)) {
score += 2;
reasons.push('Rapid request rate');
}
// HTTP method
if (req.method && !['GET', 'POST', 'HEAD'].includes(req.method)) {
score += 1;
reasons.push('Unusual HTTP method');
}
// Path checks (bots often hit /robots.txt, /admin, etc)
if (['/robots.txt', '/admin', '/wp-login.php', '/xmlrpc.php'].includes(path)) {
score += 2;
reasons.push('Bot-targeted path');
}
// If score is high, flag as bot
const threshold = 3;
if (score >= threshold) {
res.locals.suspectedBot = true;
res.locals.botDetectionReasons = reasons;
} else {
res.locals.suspectedBot = false;
}
next();
}

View file

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 46 KiB

View file

@ -1,3 +1,71 @@
/* Bot detection popup */
.bot-detect-popup-overlay {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background: var(--overlay-purple-50, rgba(155, 77, 202, 0.5));
z-index: 99999;
display: flex;
align-items: center;
justify-content: center;
transition: background 0.2s;
}
.bot-detect-popup-modal {
background: var(--color-bg-medium, #2F2B36);
color: var(--color-text-primary, #cccccc);
padding: 2em 2.5em;
border-radius: 14px;
max-width: 420px;
width: 90vw;
text-align: center;
box-shadow: 0 2px 24px var(--overlay-black-45, rgba(0, 0, 0, 0.45));
font-size: 1.1em;
border: 1.5px solid var(--color-accent-purple, #9b4dca);
}
.bot-detect-popup-modal h2 {
margin-top: 0;
color: var(--color-accent-purple, #9b4dca);
font-weight: 700;
font-size: 1.6em;
}
.bot-detect-popup-modal a {
color: var(--color-accent-purple-bright, #6c5ce7);
text-decoration: underline;
font-weight: 500;
}
.bot-detect-popup-modal a:hover {
color: var(--color-accent-orange, #ca5414);
}
.bot-detect-popup-modal button {
margin-top: 1.5em;
font-size: 1em;
border-radius: 6px;
background: var(--color-accent-purple, #9b4dca);
color: #fff;
border: none;
cursor: pointer;
font-weight: 600;
box-shadow: 0 1px 4px var(--overlay-black-30, rgba(0, 0, 0, 0.3));
transition: background 0.15s;
}
.bot-detect-popup-modal button:hover {
background: var(--color-accent-purple-bright, #6c5ce7);
}
.bot-popup-no-scroll {
overflow: hidden !important;
height: 100vh !important;
}
:root { :root {
/* Base colors */ /* Base colors */
--color-bg-dark: #131516; --color-bg-dark: #131516;

View file

@ -0,0 +1,78 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Automation Detected | 48hr.email</title>
<link rel="icon" type="image/x-icon" href="/images/favicon.ico">
<link rel="shortcut icon" href="/images/favicon.ico">
<link rel="apple-touch-icon" href="/images/logo.png">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimal-ui">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Automation Detected | 48hr.email</title>
<!-- SEO Meta Tags -->
<meta name="description" content="Your temporary Inbox. Create instant throwaway email addresses to protect your privacy. No registration required. Emails auto-delete after 48 hours.">
<meta name="keywords" content="temporary email, disposable email, throwaway email, fake email, temp mail, anonymous email, 48hr email, privacy protection, burner email">
<meta name="author" content="CrazyCo">
<meta name="robots" content="index, follow">
<meta name="googlebot" content="index, follow">
<link rel="canonical" href="https://48hr.email/">
<!-- Open Graph / Facebook -->
<meta property="og:type" content="website">
<meta property="og:url" content="https://48hr.email/">
<meta property="og:title" content="48hr.email - Your temporary Inbox">
<meta property="og:description" content="Protect your privacy with free temporary email addresses. No registration required. Emails auto-delete after 48 hours.">
<meta property="og:image" content="/images/logo.png">
<meta property="og:site_name" content="48hr.email">
<!-- Twitter Card -->
<meta name="twitter:card" content="summary">
<meta name="twitter:url" content="https://48hr.email/">
<meta name="twitter:title" content="48hr.email - Your temporary Inbox">
<meta name="twitter:description" content="Free temporary email service. Protect your privacy with disposable email addresses.">
<meta name="twitter:image" content="/images/logo.png">
<!-- Additional Meta Tags -->
<meta name="theme-color" content="#9b4dca">
<meta name="darkreader-lock">
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="apple-mobile-web-app-title" content="48hr.email">
<!-- Favicon -->
<link rel="icon" type="image/x-icon" href="/images/favicon.ico">
<link rel="shortcut icon" href="/images/favicon.ico">
<link rel="apple-touch-icon" href="/images/logo.png">
<link rel="stylesheet" href="/stylesheets/custom.css">
</head>
<body class="bot-popup-no-scroll">
<div class="bot-detect-popup-overlay" id="bot-detect-popup">
<div class="bot-detect-popup-modal">
<h2>Automation Detected</h2>
<p>We have detected that you may be using automation or a bot to access this site.<br><br>
If you want to automate things, please use our <a href="/api" target="_blank">official API</a>.<br><br>
See the <a href="https://github.com/Crazyco-xyz/48hr.email/wiki" target="_blank"><b>API Documentation</b></a> for details.<br><br>
To continue, please close this popup manually. If you think you're seeing this by mistake, let us know!</p>
<button id="bot-popup-close">I Understand</button>
</div>
</div>
<script>
function setCookie(name, value, days) {
var expires = '';
if (days) {
var date = new Date();
date.setTime(date.getTime() + (days*24*60*60*1000));
expires = '; expires=' + date.toUTCString();
}
document.cookie = name + '=' + (value || '') + expires + '; path=/';
}
document.getElementById('bot-popup-close').onclick = function() {
setCookie('bot_check_passed', 'true', 7);
window.location.reload();
};
// Prevent all interaction with the rest of the page
document.addEventListener('DOMContentLoaded', function() {
document.body.classList.add('bot-popup-no-scroll');
});
</script>
</body>
</html>

View file

@ -41,8 +41,8 @@
<meta name="apple-mobile-web-app-title" content="48hr.email"> <meta name="apple-mobile-web-app-title" content="48hr.email">
<!-- Favicon --> <!-- Favicon -->
<link rel="icon" type="image/x-icon" href="/images/logo.ico"> <link rel="icon" type="image/x-icon" href="/images/favicon.ico">
<link rel="shortcut icon" href="/images/logo.ico"> <link rel="shortcut icon" href="/images/favicon.ico">
<link rel="apple-touch-icon" href="/images/logo.png"> <link rel="apple-touch-icon" href="/images/logo.png">
<!-- Stylesheets --> <!-- Stylesheets -->

View file

@ -1,3 +1,4 @@
const botDetect = require('./middleware/bot-detect')
const path = require('path') const path = require('path')
const http = require('http') const http = require('http')
const debug = require('debug')('48hr-email:server') const debug = require('debug')('48hr-email:server')
@ -58,6 +59,24 @@ app.use(session({
cookie: { maxAge: 24 * 60 * 60 * 1000 } // 24 hours cookie: { maxAge: 24 * 60 * 60 * 1000 } // 24 hours
})) }))
// Bot detection middleware (after cookies/session, before routes)
app.use(botDetect)
// If bot detected and not suppressed, render only the popup page and halt further processing
app.use((req, res, next) => {
// Allow static assets (css, js, images, favicon, etc) and all /api/* routes even if bot detected
if (res.locals.suspectedBot && !(req.cookies && req.cookies.bot_check_passed)) {
const asset = req.path.match(/\.(css|js|png|jpg|jpeg|gif|svg|ico|woff2?|ttf|eot)$/i);
if (asset) return next();
if (req.path.startsWith('/api/')) return next();
// For non-asset, non-API requests, render only the popup page
return res.status(200).render('bot-popup');
}
next();
});
// Clear lock session data when user goes Home (but preserve authentication) // Clear lock session data when user goes Home (but preserve authentication)
app.get('/', (req, res, next) => { app.get('/', (req, res, next) => {
if (req.session && req.session.lockedInbox) { if (req.session && req.session.lockedInbox) {
@ -243,4 +262,4 @@ server.on('listening', () => {
server.emit('ready') server.emit('ready')
}) })
module.exports = { app, io, server } module.exports = { app, io, server }

View file

@ -1,6 +1,6 @@
{ {
"name": "48hr.email", "name": "48hr.email",
"version": "2.3.2", "version": "2.3.3",
"private": false, "private": false,
"description": "48hr.email is your favorite open-source tempmail client.", "description": "48hr.email is your favorite open-source tempmail client.",
"keywords": [ "keywords": [