Exempt repeat AI comments on same post
This commit is contained in:
@@ -1171,11 +1171,36 @@ function getAIAutoCommentOldestEventSince(profileNumber, sinceIso) {
|
||||
`).get(normalizedProfileNumber, sinceIso) || null;
|
||||
}
|
||||
|
||||
function buildAIAutoCommentRateLimitStatus(profileNumber, settings = getAIAutoCommentRateLimitSettings(), now = new Date()) {
|
||||
function sanitizeAIAutoCommentPostKey(value) {
|
||||
if (typeof value !== 'string') {
|
||||
return null;
|
||||
}
|
||||
const trimmed = truncateString(value.trim(), 1000);
|
||||
return trimmed || null;
|
||||
}
|
||||
|
||||
function hasAIAutoCommentEventForPost(profileNumber, postKey) {
|
||||
const normalizedProfileNumber = sanitizeProfileNumber(profileNumber);
|
||||
const normalizedPostKey = sanitizeAIAutoCommentPostKey(postKey);
|
||||
if (!normalizedProfileNumber || !normalizedPostKey) {
|
||||
return false;
|
||||
}
|
||||
const row = db.prepare(`
|
||||
SELECT 1
|
||||
FROM ai_auto_comment_rate_limit_events
|
||||
WHERE profile_number = ?
|
||||
AND post_key = ?
|
||||
LIMIT 1
|
||||
`).get(normalizedProfileNumber, normalizedPostKey);
|
||||
return Boolean(row);
|
||||
}
|
||||
|
||||
function buildAIAutoCommentRateLimitStatus(profileNumber, settings = getAIAutoCommentRateLimitSettings(), now = new Date(), options = {}) {
|
||||
const normalizedProfileNumber = sanitizeProfileNumber(profileNumber);
|
||||
if (!normalizedProfileNumber) {
|
||||
return null;
|
||||
}
|
||||
const normalizedPostKey = sanitizeAIAutoCommentPostKey(options.postKey);
|
||||
|
||||
clearExpiredAIAutoCommentProfileCooldown(normalizedProfileNumber, now);
|
||||
|
||||
@@ -1192,12 +1217,13 @@ function buildAIAutoCommentRateLimitStatus(profileNumber, settings = getAIAutoCo
|
||||
const lastEvent = getAIAutoCommentLatestEvent(normalizedProfileNumber);
|
||||
const profileState = getAIAutoCommentProfileState(normalizedProfileNumber);
|
||||
const activeHours = getAIAutoCommentActiveHoursState(settings, now);
|
||||
const samePostExempt = Boolean(normalizedPostKey && hasAIAutoCommentEventForPost(normalizedProfileNumber, normalizedPostKey));
|
||||
|
||||
let blocked = false;
|
||||
let blockedReason = null;
|
||||
let blockedUntil = null;
|
||||
|
||||
if (settings.enabled) {
|
||||
if (settings.enabled && !samePostExempt) {
|
||||
const blockingCandidates = [];
|
||||
const addBlockingCandidate = (reason, untilIso) => {
|
||||
if (!untilIso) {
|
||||
@@ -1282,6 +1308,7 @@ function buildAIAutoCommentRateLimitStatus(profileNumber, settings = getAIAutoCo
|
||||
blocked,
|
||||
blocked_reason: blockedReason,
|
||||
blocked_until: blockedUntil,
|
||||
same_post_exempt: samePostExempt,
|
||||
cooldown_until: profileState?.cooldown_until || null,
|
||||
cooldown_reason: profileState?.cooldown_reason || null,
|
||||
usage: {
|
||||
@@ -1313,15 +1340,18 @@ function listAIAutoCommentRateLimitStatuses(settings = getAIAutoCommentRateLimit
|
||||
));
|
||||
}
|
||||
|
||||
function checkAIAutoCommentActionAvailability(profileNumber, settings = getAIAutoCommentRateLimitSettings()) {
|
||||
function checkAIAutoCommentActionAvailability(profileNumber, settings = getAIAutoCommentRateLimitSettings(), postKey = null) {
|
||||
const normalizedProfileNumber = sanitizeProfileNumber(profileNumber);
|
||||
if (!normalizedProfileNumber) {
|
||||
return { ok: false, status: null };
|
||||
}
|
||||
const normalizedPostKey = sanitizeAIAutoCommentPostKey(postKey);
|
||||
|
||||
const check = db.transaction(() => {
|
||||
purgeOldAIAutoCommentRateLimitEvents();
|
||||
const currentStatus = buildAIAutoCommentRateLimitStatus(normalizedProfileNumber, settings, new Date());
|
||||
const currentStatus = buildAIAutoCommentRateLimitStatus(normalizedProfileNumber, settings, new Date(), {
|
||||
postKey: normalizedPostKey
|
||||
});
|
||||
if (!currentStatus || currentStatus.blocked) {
|
||||
return {
|
||||
ok: false,
|
||||
@@ -1345,11 +1375,12 @@ function checkAIAutoCommentActionAvailability(profileNumber, settings = getAIAut
|
||||
return check();
|
||||
}
|
||||
|
||||
function recordAIAutoCommentAction(profileNumber, settings = getAIAutoCommentRateLimitSettings(), occurredAt = new Date()) {
|
||||
function recordAIAutoCommentAction(profileNumber, settings = getAIAutoCommentRateLimitSettings(), occurredAt = new Date(), postKey = null) {
|
||||
const normalizedProfileNumber = sanitizeProfileNumber(profileNumber);
|
||||
if (!normalizedProfileNumber) {
|
||||
return null;
|
||||
}
|
||||
const normalizedPostKey = sanitizeAIAutoCommentPostKey(postKey);
|
||||
|
||||
const eventDate = occurredAt instanceof Date && !Number.isNaN(occurredAt.getTime())
|
||||
? occurredAt
|
||||
@@ -1357,12 +1388,19 @@ function recordAIAutoCommentAction(profileNumber, settings = getAIAutoCommentRat
|
||||
|
||||
const record = db.transaction(() => {
|
||||
purgeOldAIAutoCommentRateLimitEvents(eventDate);
|
||||
if (normalizedPostKey && hasAIAutoCommentEventForPost(normalizedProfileNumber, normalizedPostKey)) {
|
||||
return buildAIAutoCommentRateLimitStatus(normalizedProfileNumber, settings, eventDate, {
|
||||
postKey: normalizedPostKey
|
||||
});
|
||||
}
|
||||
db.prepare(`
|
||||
INSERT INTO ai_auto_comment_rate_limit_events (profile_number, created_at)
|
||||
VALUES (?, ?)
|
||||
`).run(normalizedProfileNumber, eventDate.toISOString());
|
||||
INSERT INTO ai_auto_comment_rate_limit_events (profile_number, post_key, created_at)
|
||||
VALUES (?, ?, ?)
|
||||
`).run(normalizedProfileNumber, normalizedPostKey, eventDate.toISOString());
|
||||
|
||||
return buildAIAutoCommentRateLimitStatus(normalizedProfileNumber, settings, eventDate);
|
||||
return buildAIAutoCommentRateLimitStatus(normalizedProfileNumber, settings, eventDate, {
|
||||
postKey: normalizedPostKey
|
||||
});
|
||||
});
|
||||
|
||||
return record();
|
||||
@@ -2159,6 +2197,7 @@ db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS ai_auto_comment_rate_limit_events (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
profile_number INTEGER NOT NULL,
|
||||
post_key TEXT,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
`);
|
||||
@@ -2655,8 +2694,13 @@ ensureColumn('ai_auto_comment_rate_limit_settings', 'burst_limit', 'burst_limit
|
||||
ensureColumn('ai_auto_comment_rate_limit_settings', 'cooldown_minutes', 'cooldown_minutes INTEGER NOT NULL DEFAULT 15');
|
||||
ensureColumn('ai_auto_comment_rate_limit_settings', 'active_hours_start', 'active_hours_start TEXT');
|
||||
ensureColumn('ai_auto_comment_rate_limit_settings', 'active_hours_end', 'active_hours_end TEXT');
|
||||
ensureColumn('ai_auto_comment_rate_limit_events', 'post_key', 'post_key TEXT');
|
||||
ensureColumn('ai_auto_comment_rate_limit_profile_state', 'cooldown_until', 'cooldown_until DATETIME');
|
||||
ensureColumn('ai_auto_comment_rate_limit_profile_state', 'cooldown_reason', 'cooldown_reason TEXT');
|
||||
db.exec(`
|
||||
CREATE INDEX IF NOT EXISTS idx_ai_auto_comment_rate_limit_events_profile_post
|
||||
ON ai_auto_comment_rate_limit_events(profile_number, post_key);
|
||||
`);
|
||||
ensureColumn('ai_credentials', 'is_active', 'is_active INTEGER DEFAULT 1');
|
||||
ensureColumn('ai_credentials', 'priority', 'priority INTEGER DEFAULT 0');
|
||||
ensureColumn('ai_credentials', 'base_url', 'base_url TEXT');
|
||||
@@ -7294,6 +7338,26 @@ app.get('/api/ai-settings', (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/api/ai/auto-comment-rate-limit-status', (req, res) => {
|
||||
try {
|
||||
const profileNumber = sanitizeProfileNumber(req.query.profileNumber);
|
||||
if (!profileNumber) {
|
||||
return res.status(400).json({ error: 'profileNumber is required' });
|
||||
}
|
||||
|
||||
const status = buildAIAutoCommentRateLimitStatus(
|
||||
profileNumber,
|
||||
getAIAutoCommentRateLimitSettings(),
|
||||
new Date(),
|
||||
{ postKey: req.query.postKey }
|
||||
);
|
||||
|
||||
res.json({ status });
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
app.put('/api/ai-settings', (req, res) => {
|
||||
try {
|
||||
const { active_credential_id, prompt_prefix, enabled, rate_limit_settings } = req.body;
|
||||
@@ -7933,10 +7997,12 @@ app.post('/api/ai/generate-comment', async (req, res) => {
|
||||
};
|
||||
|
||||
let autoCommentRateLimitStatus = null;
|
||||
let autoCommentRateLimitGlobalStatus = null;
|
||||
|
||||
try {
|
||||
const { postText, profileNumber, preferredCredentialId } = requestBody;
|
||||
const { postText, profileNumber, preferredCredentialId, postKey } = requestBody;
|
||||
const normalizedProfileNumber = sanitizeProfileNumber(profileNumber);
|
||||
const normalizedPostKey = sanitizeAIAutoCommentPostKey(postKey);
|
||||
|
||||
if (!postText) {
|
||||
return respondWithTrackedError(400, 'postText is required');
|
||||
@@ -7975,8 +8041,16 @@ app.post('/api/ai/generate-comment', async (req, res) => {
|
||||
}
|
||||
|
||||
const limitCheckStartedMs = timingStart();
|
||||
const availability = checkAIAutoCommentActionAvailability(normalizedProfileNumber, autoCommentRateLimitSettings);
|
||||
const availability = checkAIAutoCommentActionAvailability(
|
||||
normalizedProfileNumber,
|
||||
autoCommentRateLimitSettings,
|
||||
normalizedPostKey
|
||||
);
|
||||
autoCommentRateLimitStatus = availability.status || null;
|
||||
autoCommentRateLimitGlobalStatus = buildAIAutoCommentRateLimitStatus(
|
||||
normalizedProfileNumber,
|
||||
autoCommentRateLimitSettings
|
||||
);
|
||||
if (!availability.ok && autoCommentRateLimitStatus && autoCommentRateLimitStatus.blocked) {
|
||||
timingEnd('profileLimitCheckMs', limitCheckStartedMs);
|
||||
const blockedUntilText = autoCommentRateLimitStatus.blocked_until
|
||||
@@ -8079,7 +8153,12 @@ app.post('/api/ai/generate-comment', async (req, res) => {
|
||||
autoCommentRateLimitStatus = recordAIAutoCommentAction(
|
||||
normalizedProfileNumber,
|
||||
autoCommentRateLimitSettings,
|
||||
new Date()
|
||||
new Date(),
|
||||
normalizedPostKey
|
||||
);
|
||||
autoCommentRateLimitGlobalStatus = buildAIAutoCommentRateLimitStatus(
|
||||
normalizedProfileNumber,
|
||||
autoCommentRateLimitSettings
|
||||
);
|
||||
}
|
||||
|
||||
@@ -8097,7 +8176,8 @@ app.post('/api/ai/generate-comment', async (req, res) => {
|
||||
usedCredential: credential.name,
|
||||
usedCredentialId: credential.id,
|
||||
attempts: attemptDetails,
|
||||
autoCommentRateLimit: autoCommentRateLimitStatus
|
||||
autoCommentRateLimit: autoCommentRateLimitStatus,
|
||||
autoCommentRateLimitGlobal: autoCommentRateLimitGlobalStatus
|
||||
},
|
||||
totalDurationMs: backendTimings.totalMs
|
||||
});
|
||||
@@ -8112,6 +8192,7 @@ app.post('/api/ai/generate-comment', async (req, res) => {
|
||||
attempts: attemptDetails,
|
||||
rateLimitInfo: rateInfo || null,
|
||||
autoCommentRateLimitStatus,
|
||||
autoCommentRateLimitGlobalStatus,
|
||||
traceId,
|
||||
flowId,
|
||||
timings: {
|
||||
@@ -8129,6 +8210,7 @@ app.post('/api/ai/generate-comment', async (req, res) => {
|
||||
if (cooldownDecision) {
|
||||
setAIAutoCommentProfileCooldown(normalizedProfileNumber, cooldownDecision);
|
||||
autoCommentRateLimitStatus = buildAIAutoCommentRateLimitStatus(normalizedProfileNumber, autoCommentRateLimitSettings);
|
||||
autoCommentRateLimitGlobalStatus = buildAIAutoCommentRateLimitStatus(normalizedProfileNumber, autoCommentRateLimitSettings);
|
||||
}
|
||||
}
|
||||
credentialTimingDetails.push({
|
||||
@@ -8170,7 +8252,8 @@ app.post('/api/ai/generate-comment', async (req, res) => {
|
||||
attempts: error && error.attempts ? error.attempts : null,
|
||||
responseMeta: {
|
||||
attempts: error && error.attempts ? error.attempts : null,
|
||||
autoCommentRateLimit: autoCommentRateLimitStatus
|
||||
autoCommentRateLimit: autoCommentRateLimitStatus,
|
||||
autoCommentRateLimitGlobal: autoCommentRateLimitGlobalStatus
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user