From 71654dcbc885cc38329922fd19c31f04d32d4b4c Mon Sep 17 00:00:00 2001 From: ccbikai Date: Sun, 16 Mar 2025 12:57:05 +0800 Subject: [PATCH] feat: add bot traffic filtering for access logs Adds configuration option NUXT_DISABLE_BOT_ACCESS_LOG to exclude bot/crawler traffic from access statistics - Updates ua-parser-js to stable v2.0.3 - Detects bots via CloudFlare bot management and user agent analysis - Documents new config option in configuration.md and FAQs --- docs/configuration.md | 4 + docs/faqs.md | 4 + nuxt.config.ts | 1 + package.json | 2 +- pnpm-lock.yaml | 167 +++++++++++++++++++++++++++++++++++-- server/utils/access-log.ts | 14 +++- 6 files changed, 183 insertions(+), 9 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index 1f3a891..d6a4ddf 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -51,3 +51,7 @@ Set URL case sensitivity. ## `NUXT_LIST_QUERY_LIMIT` Set the maximum query data volume for the Metric list. + +## `NUXT_DISABLE_BOT_ACCESS_LOG` + +Access statistics do not count bot traffic. diff --git a/docs/faqs.md b/docs/faqs.md index 1b99866..de5b5b5 100644 --- a/docs/faqs.md +++ b/docs/faqs.md @@ -46,3 +46,7 @@ Newly generated links will be case-sensitive, treating `MyLink` and `mylink` as ## 7. Why does the Metric list only show the top 500 data entries? To improve query performance, we have limited the amount of data. If you need to query more data, you can adjust it through `NUXT_LIST_QUERY_LIMIT`. + +## 8. I don't want to count bot or crawler traffic + +Set `NUXT_DISABLE_BOT_ACCESS_LOG` to `true`. diff --git a/nuxt.config.ts b/nuxt.config.ts index db33f40..eefaa1b 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -30,6 +30,7 @@ export default defineNuxtConfig({ aiPrompt: `You are a URL shortening assistant, please shorten the URL provided by the user into a SLUG. The SLUG information must come from the URL itself, do not make any assumptions. A SLUG is human-readable and should not exceed three words and can be validated using regular expressions {slugRegex} . Only the best one is returned, the format must be JSON reference {"slug": "example-slug"}`, caseSensitive: false, listQueryLimit: 500, + disableBotAccessLog: false, public: { previewMode: '', slugDefaultLength: '6', diff --git a/package.json b/package.json index afd48be..4e85a68 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "pluralize": "^8.0.0", "qr-code-styling": "1.6.0-rc.1", "radix-vue": "^1.9.11", - "ua-parser-js": "next", + "ua-parser-js": "^2.0.3", "vaul-vue": "^0.3.0", "vee-validate": "^4.15.0", "virtua": "^0.39.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e881cf1..f7c4d3e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -48,8 +48,8 @@ importers: specifier: ^1.9.11 version: 1.9.11(vue@3.5.13(typescript@5.7.2)) ua-parser-js: - specifier: next - version: 2.0.0-beta.3 + specifier: ^2.0.3 + version: 2.0.3(encoding@0.1.13) vaul-vue: specifier: ^0.3.0 version: 0.3.0(reka-ui@2.0.2(typescript@5.7.2)(vue@3.5.13(typescript@5.7.2)))(vue@3.5.13(typescript@5.7.2)) @@ -2256,6 +2256,9 @@ packages: '@types/ms@0.7.34': resolution: {integrity: sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==} + '@types/node-fetch@2.6.12': + resolution: {integrity: sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==} + '@types/node-forge@1.3.11': resolution: {integrity: sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==} @@ -2772,6 +2775,9 @@ packages: async@3.2.5: resolution: {integrity: sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==} + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + at-least-node@1.0.0: resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==} engines: {node: '>= 4.0.0'} @@ -2891,6 +2897,10 @@ packages: resolution: {integrity: sha512-IKufZ1o4Ut42YUrZSo8+qnMTrFuKkvyoLXUywKz9GJ5BrhOFGhLdkx9sG4KAnVvbY6kEcSFjLQul+DVmBm2bgA==} engines: {node: '>= 6.0.0'} + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + callsites@3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} @@ -3013,6 +3023,10 @@ packages: colorette@2.0.20: resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + commander@12.1.0: resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==} engines: {node: '>=18'} @@ -3445,6 +3459,10 @@ packages: delaunator@5.0.1: resolution: {integrity: sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==} + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + delegates@1.0.0: resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==} @@ -3471,6 +3489,9 @@ packages: resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + detect-europe-js@0.1.2: + resolution: {integrity: sha512-lgdERlL3u0aUdHocoouzT10d9I89VVhk0qNRmll7mXdGfJT1/wqZ2ZLA4oJAjeACPY5fT1wsbq2AT+GkuInsow==} + detect-libc@1.0.3: resolution: {integrity: sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==} engines: {node: '>=0.10'} @@ -3529,6 +3550,10 @@ packages: resolution: {integrity: sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==} engines: {node: '>=12'} + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + duplexer@0.1.2: resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==} @@ -3591,9 +3616,25 @@ packages: errx@0.1.0: resolution: {integrity: sha512-fZmsRiDNv07K6s2KkKFTiD2aIvECa7++PKyD5NC32tpRw46qZA3sOz+aM+/V9V0GDHxVTKLziveV4JhzBHDp9Q==} + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + es-module-lexer@1.5.4: resolution: {integrity: sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==} + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + esbuild@0.17.19: resolution: {integrity: sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==} engines: {node: '>=12'} @@ -3976,6 +4017,10 @@ packages: resolution: {integrity: sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==} engines: {node: '>=14'} + form-data@4.0.2: + resolution: {integrity: sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==} + engines: {node: '>= 6'} + fraction.js@4.3.7: resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} @@ -4034,9 +4079,17 @@ packages: resolution: {integrity: sha512-2nk+7SIVb14QrgXFHcm84tD4bKQz0RxPuMT8Ag5KPOq7J5fEmAg0UbXdTOSHqNuHSU28k55qnceesxXRZGzKWA==} engines: {node: '>=18'} + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + get-port-please@3.1.2: resolution: {integrity: sha512-Gxc29eLs1fbn6LQ4jSU4vXjlwyZhF5HsGuMAa7gqBP4Rw4yxxltyDUuF5MBclFzDTXO+ACchGQoeela4DSfzdQ==} + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + get-source@2.0.12: resolution: {integrity: sha512-X5+4+iD+HoSeEED+uwrQ07BOQr0kEDFMVqqpBuI+RaZBpBpHCuXxo70bjar6f0b0u/DQJsJ7ssurpP0V60Az+w==} @@ -4128,6 +4181,10 @@ packages: resolution: {integrity: sha512-s3Fq41ZVh7vbbe2PN3nrW7yC7U7MFVc5c98/iTl9c2GawNMKx/J648KQRW6WKkuU8GIbbh2IXfIRQjOZnXcTnw==} engines: {node: '>=18'} + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} @@ -4156,6 +4213,10 @@ packages: resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} engines: {node: '>= 0.4'} + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + has-tostringtag@1.0.2: resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} engines: {node: '>= 0.4'} @@ -4375,6 +4436,9 @@ packages: is-ssh@1.4.0: resolution: {integrity: sha512-x7+VxdxOdlV3CYpjvRLBv5Lo9OJerlYanjwFrPR9fuGPjCiNiCzFgAWpiLAohSbsnH4ZAys3SBh+hq5rJosxUQ==} + is-standalone-pwa@0.1.1: + resolution: {integrity: sha512-9Cbovsa52vNQCjdXOzeQq5CnCbAcRk05aU62K20WO372NrTv0NxibLFCK6lQ4/iZEFdEA3p3t2VNOn8AJ53F5g==} + is-stream@2.0.1: resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} engines: {node: '>=8'} @@ -4667,6 +4731,10 @@ packages: markdown-table@3.0.4: resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==} + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + mdast-util-find-and-replace@3.0.1: resolution: {integrity: sha512-SG21kZHGC3XRTSUhtofZkBzZTJNM5ecCi0SK2IMKmSXR8vO3peL+kb1O0z7Zl83jKtutG4k5Wv/W7V3/YHvzPA==} @@ -6191,8 +6259,11 @@ packages: engines: {node: '>=14.17'} hasBin: true - ua-parser-js@2.0.0-beta.3: - resolution: {integrity: sha512-vZkEG/GplLRxj4XVrjLUQpSNg9Qj9RdnIqrOy8dqrLP1e5x7i2N8RfVYlWRbKEZdDSWfeWNcWS38gmieX03/5A==} + ua-is-frozen@0.1.2: + resolution: {integrity: sha512-RwKDW2p3iyWn4UbaxpP2+VxwqXh0jpvdxsYpZ5j/MLLiQOfbsV5shpgQiw93+KMYQPcteeMQ289MaAFzs3G9pw==} + + ua-parser-js@2.0.3: + resolution: {integrity: sha512-LZyXZdNttONW8LjzEH3Z8+6TE7RfrEiJqDKyh0R11p/kxvrV2o9DrT2FGZO+KVNs3k+drcIQ6C3En6wLnzJGpw==} hasBin: true ufo@1.5.4: @@ -8996,6 +9067,11 @@ snapshots: '@types/ms@0.7.34': {} + '@types/node-fetch@2.6.12': + dependencies: + '@types/node': 20.12.11 + form-data: 4.0.2 + '@types/node-forge@1.3.11': dependencies: '@types/node': 20.12.11 @@ -9689,6 +9765,8 @@ snapshots: async@3.2.5: {} + asynckit@0.4.0: {} + at-least-node@1.0.0: {} autoprefixer@10.4.20(postcss@8.4.49): @@ -9784,7 +9862,7 @@ snapshots: giget: 1.2.3 jiti: 1.21.6 mlly: 1.7.1 - ohash: 1.1.3 + ohash: 1.1.4 pathe: 1.1.2 perfect-debounce: 1.0.0 pkg-types: 1.1.3 @@ -9833,6 +9911,11 @@ snapshots: mime-types: 2.1.35 ylru: 1.4.0 + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + callsites@3.1.0: {} camelcase-css@2.0.1: {} @@ -9953,6 +10036,10 @@ snapshots: colorette@2.0.20: {} + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + commander@12.1.0: {} commander@2.20.3: {} @@ -10363,6 +10450,8 @@ snapshots: dependencies: robust-predicates: 3.0.2 + delayed-stream@1.0.0: {} + delegates@1.0.0: {} denque@2.1.0: {} @@ -10377,6 +10466,8 @@ snapshots: destroy@1.2.0: {} + detect-europe-js@0.1.2: {} + detect-libc@1.0.3: {} detect-libc@2.0.3: {} @@ -10427,6 +10518,12 @@ snapshots: dotenv@16.4.7: {} + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + duplexer@0.1.2: {} earcut@2.2.4: {} @@ -10476,8 +10573,23 @@ snapshots: errx@0.1.0: {} + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + es-module-lexer@1.5.4: {} + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + esbuild@0.17.19: optionalDependencies: '@esbuild/android-arm': 0.17.19 @@ -11064,6 +11176,13 @@ snapshots: cross-spawn: 7.0.3 signal-exit: 4.1.0 + form-data@4.0.2: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + mime-types: 2.1.35 + fraction.js@4.3.7: {} fresh@0.5.2: {} @@ -11116,8 +11235,26 @@ snapshots: get-east-asian-width@1.2.0: {} + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 + get-port-please@3.1.2: {} + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + get-source@2.0.12: dependencies: data-uri-to-buffer: 2.0.2 @@ -11232,6 +11369,8 @@ snapshots: slash: 5.1.0 unicorn-magic: 0.1.0 + gopd@1.2.0: {} + graceful-fs@4.2.11: {} graphemer@1.4.0: {} @@ -11276,6 +11415,8 @@ snapshots: has-symbols@1.0.3: {} + has-symbols@1.1.0: {} + has-tostringtag@1.0.2: dependencies: has-symbols: 1.0.3 @@ -11472,6 +11613,8 @@ snapshots: dependencies: protocols: 2.0.1 + is-standalone-pwa@0.1.1: {} + is-stream@2.0.1: {} is-stream@3.0.0: {} @@ -11837,6 +11980,8 @@ snapshots: markdown-table@3.0.4: {} + math-intrinsics@1.1.0: {} + mdast-util-find-and-replace@3.0.1: dependencies: '@types/mdast': 4.0.4 @@ -13746,7 +13891,17 @@ snapshots: typescript@5.7.2: {} - ua-parser-js@2.0.0-beta.3: {} + ua-is-frozen@0.1.2: {} + + ua-parser-js@2.0.3(encoding@0.1.13): + dependencies: + '@types/node-fetch': 2.6.12 + detect-europe-js: 0.1.2 + is-standalone-pwa: 0.1.1 + node-fetch: 2.7.0(encoding@0.1.13) + ua-is-frozen: 0.1.2 + transitivePeerDependencies: + - encoding ufo@1.5.4: {} diff --git a/server/utils/access-log.ts b/server/utils/access-log.ts index dc6ea37..d3b60cf 100644 --- a/server/utils/access-log.ts +++ b/server/utils/access-log.ts @@ -10,7 +10,7 @@ import { Fetchers, InApps, MediaPlayers, - Modules, + Vehicles, } from 'ua-parser-js/extensions' import { parseURL } from 'ufo' @@ -69,13 +69,23 @@ export function useAccessLog(event: H3Event) { const userAgent = getHeader(event, 'user-agent') || '' const uaInfo = (new UAParser(userAgent, { - browser: [Crawlers.browser || [], CLIs.browser || [], Emails.browser || [], Fetchers.browser || [], InApps.browser || [], MediaPlayers.browser || [], Modules.browser || []].flat(), + browser: [Crawlers.browser || [], CLIs.browser || [], Emails.browser || [], Fetchers.browser || [], InApps.browser || [], MediaPlayers.browser || [], Vehicles.browser || []].flat(), device: [ExtraDevices.device || []].flat(), })).getResult() const { request: { cf } } = event.context.cloudflare const link = event.context.link || {} + const isBot = cf?.botManagement?.verifiedBot + || ['crawler', 'fetcher'].includes(uaInfo?.browser?.type || '') + || ['spider', 'bot'].includes(uaInfo?.browser?.name?.toLowerCase() || '') + + const { disableBotAccessLog } = useRuntimeConfig(event) + if (isBot && disableBotAccessLog) { + console.log('bot access log disabled:', userAgent) + return Promise.resolve() + } + const regionNames = new Intl.DisplayNames(['en'], { type: 'region' }) const countryName = regionNames.of(cf?.country || 'WD') // fallback to "Worldwide" const accessLogs = {