NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript==
// @name Amazon Books AI Review Detector (Deep Scan + Titles)
// @namespace blackfalcon1210
// @version 2.3
// @description Scans 1 & 2-star review bodies and titles on Amazon for AI red flags and reports the percentage of flagged reviews.
// @author Blackfalcon1210
// @license MIT
// @include /^https?:\/\/(www|smile)\.amazon\.(cn|in|co\.jp|sg|se|ae|fr|de|pl|it|nl|es|co\.uk|ca|com(\.(mx|au|br|tr|be))?)\/.*(dp|gp\/(product|video)|exec\/obidos\/ASIN|o\/ASIN|product-reviews)\/.*$/
// @grant GM_xmlhttpRequest
// @connect amazon.com
// @connect amazon.co.uk
// @connect amazon.ca
// @connect amazon.de
// @connect amazon.fr
// @connect amazon.it
// @connect amazon.es
// @connect amazon.co.jp
// @connect amazon.in
// @noframes
// @run-at document-end
// ==/UserScript==
/* jshint esversion: 6 */
(function(doc) {
'use strict';
// Edit the terms you want this script to look for in reviews.
const redFlags = [
"ai",
"ai slop",
"ai written",
"written with ai",
"programmed ai",
"much ai",
"ai writer",
"ai generated",
"its ai",
"it's ai",
"ai trash",
"ai writing",
"work of ai"
];
const maxPagesToScan = 10;
let statusWidget = null;
let scanInitiated = false;
let totalReviewsScanned = 0;
let flaggedReviews = 0;
// Prevent the same review from being counted twice.
const scannedReviewIds = new Set();
function getLeftColumnTarget() {
return doc.getElementById('litb-canvas-click-wrapper') ||
doc.getElementById('imgBlkFront') ||
doc.getElementById('imageBlock') ||
doc.getElementById('booksImageBlock_feature_div');
}
function createStatusWidget() {
if (statusWidget) return;
const targetAnchor = getLeftColumnTarget();
if (!targetAnchor) return;
statusWidget = doc.createElement('div');
statusWidget.id = 'amazon-ai-detector-status-card';
Object.assign(statusWidget.style, {
width: '100%',
backgroundColor: '#FEF3C7',
border: '2px solid #F59E0B',
borderRadius: '8px',
padding: '10px 14px',
margin: '10px 0 15px 0',
fontFamily: '"Amazon Ember", Arial, sans-serif',
boxSizing: 'border-box',
fontSize: '13px',
fontWeight: 'bold',
color: '#92400E',
textAlign: 'center',
display: 'block',
clear: 'both',
lineHeight: '1.5'
});
statusWidget.textContent =
"🔍 Scanning 1 & 2-star reviews for AI terms...";
targetAnchor.parentNode.insertBefore(
statusWidget,
targetAnchor.nextSibling
);
}
function updateScanningStatus(page) {
if (!statusWidget) return;
statusWidget.textContent =
`🔍 Scanning 1 & 2-star reviews... ` +
`Page ${page} of ${maxPagesToScan} | ` +
`${totalReviewsScanned} reviews checked`;
}
function showFinalStatus(scanIncomplete = false) {
if (!statusWidget) return;
const percentage = totalReviewsScanned > 0
? (flaggedReviews / totalReviewsScanned) * 100
: 0;
/*
* Show one decimal place, except when there are no flags.
* Examples: 0%, 2.5%, 10.0%
*/
const percentageText = flaggedReviews === 0
? "0%"
: `${percentage.toFixed(1)}%`;
if (flaggedReviews > 0) {
statusWidget.style.backgroundColor = '#FEF2F2';
statusWidget.style.border = '2px solid #F87171';
statusWidget.style.color = '#991B1B';
statusWidget.innerHTML =
`⚠️ AI Usage Mentioned in Reviews<br>` +
`${flaggedReviews} of ${totalReviewsScanned} scanned reviews ` +
`were flagged (${percentageText})`;
} else {
statusWidget.style.backgroundColor = '#F0FDF4';
statusWidget.style.border = '2px solid #4ADE80';
statusWidget.style.color = '#166534';
statusWidget.innerHTML =
`✅ No AI Flags Found<br>` +
`0 of ${totalReviewsScanned} scanned reviews ` +
`were flagged (${percentageText})`;
}
if (scanIncomplete) {
statusWidget.innerHTML +=
`<br><span style="font-size:11px;font-weight:normal;">` +
`Scan ended early because Amazon did not return another review page.` +
`</span>`;
}
}
function extractAsin() {
const asinInput =
doc.getElementById('ASIN') ||
doc.querySelector('[data-asin]');
if (asinInput) {
const asin =
asinInput.value ||
asinInput.getAttribute('data-asin');
if (asin && /^[A-Z0-9]{10}$/i.test(asin)) {
return asin;
}
}
const match = window.location.href.match(
/\/(?:dp|gp\/product|exec\/obidos\/ASIN|o\/ASIN)\/([A-Z0-9]{10})/i
);
// Return only the captured ASIN, not the entire regex result.
return match ? match[1] : null;
}
function checkTextForFlags(text) {
const cleanText = text
.toLowerCase()
.replace(/\s+/g, ' ')
.trim();
return redFlags.some(flag => {
const cleanFlag = flag.toLowerCase();
/*
* "ai" by itself needs word boundaries. Otherwise words such as
* "said", "paid", "Gail", and "detail" could create false flags.
*/
if (cleanFlag === "ai") {
return /\bai\b/i.test(cleanText);
}
return cleanText.includes(cleanFlag);
});
}
function getReviewIdentifier(review, page, index) {
return review.getAttribute('id') ||
review.getAttribute('data-review-id') ||
`${page}-${index}-${(review.textContent || "").slice(0, 100)}`;
}
function scanAllReviewPages(asin, page = 1) {
updateScanningStatus(page);
const domain = window.location.hostname;
const reviewsUrl =
`https://${domain}/product-reviews/${asin}/` +
`?pageNumber=${page}&sortBy=recent&filterByStar=critical`;
GM_xmlhttpRequest({
method: "GET",
url: reviewsUrl,
onload: function(response) {
if (response.status !== 200) {
showFinalStatus(true);
return;
}
const parser = new DOMParser();
const htmlDoc = parser.parseFromString(
response.responseText,
"text/html"
);
// Includes both the review title and review body.
const reviews = htmlDoc.querySelectorAll(
'[data-hook="review"]'
);
if (reviews.length === 0) {
showFinalStatus(page > 1);
return;
}
reviews.forEach((review, index) => {
const reviewId = getReviewIdentifier(
review,
page,
index
);
if (scannedReviewIds.has(reviewId)) return;
scannedReviewIds.add(reviewId);
totalReviewsScanned++;
if (checkTextForFlags(review.textContent || "")) {
flaggedReviews++;
}
});
if (page < maxPagesToScan) {
updateScanningStatus(page);
setTimeout(() => {
scanAllReviewPages(asin, page + 1);
}, 200);
} else {
showFinalStatus(false);
}
},
onerror: function() {
showFinalStatus(true);
}
});
}
function initScan() {
if (scanInitiated) return;
const asin = extractAsin();
if (!asin) return;
scanInitiated = true;
createStatusWidget();
scanAllReviewPages(asin, 1);
}
const layoutObserver = new MutationObserver(() => {
if (getLeftColumnTarget() && !scanInitiated) {
initScan();
layoutObserver.disconnect();
}
});
layoutObserver.observe(doc.documentElement, {
childList: true,
subtree: true
});
if (getLeftColumnTarget()) {
initScan();
}
})(document);