Fix commenter-targeted outreach attribution
This commit is contained in:
1 parent
b5304e24e3
commit
f77157d303
19 files changed
+328
-49
No files matched your search
@@ -0,0 +1,45 @@
|
||||
export interface CommentTargetedOpenerValidation {
|
||||
safe: boolean;
|
||||
reason?: string;
|
||||
}
|
||||
|
||||
const COMMENT_GROUNDING = /\b(?:comment|said|wrote|mentioned|wanted|wanting|wish(?:ed)?|liked the idea|you(?:'d| would) love)\b/i;
|
||||
|
||||
const RECIPIENT_APPEARANCE_PATTERNS: ReadonlyArray<{ pattern: RegExp; reason: string }> = [
|
||||
{
|
||||
pattern: /\byour\s+(?:tits?|boobs?|breasts?|ass|pussy|body|curves?|figure|face|smile|eyes?|pose|photo|pic(?:ture)?|image|post|shot|selfie)\b/i,
|
||||
reason: 'attributes the source post or depicted appearance to the commenter',
|
||||
},
|
||||
{
|
||||
pattern: /\byou(?:'re| are| were)?\s+(?:look(?:ing|ed)?|pos(?:e|ing|ed)|smil(?:e|ing|ed)|kneel(?:ing|ed)|ly(?:ing)?|stand(?:ing)?|sit(?:ting)?)\b/i,
|
||||
reason: 'describes the commenter as though they were depicted',
|
||||
},
|
||||
{
|
||||
pattern: /(?:\byou\b.{0,60}\b(?:camera|photo|picture|image|shot)\b|\b(?:camera|photo|picture|image|shot)\b.{0,60}\byou\b)/i,
|
||||
reason: 'connects the commenter to visual details from the source post',
|
||||
},
|
||||
{
|
||||
pattern: /\b(?:you\s+(?:posted|shared|uploaded)|your\s+(?:upload|submission))\b/i,
|
||||
reason: 'claims the commenter created the source post',
|
||||
},
|
||||
{
|
||||
pattern: /\b(?:send|show|share|upload)(?:\s+\w+){0,8}\s+(?:another|more|a|some|your)?\s*(?:pic(?:ture)?s?|photos?|images?|shots?|selfies?)\b/i,
|
||||
reason: 'asks the commenter for an image based on somebody else\'s post',
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* Fail-closed guard for openers aimed at a commenter on somebody else's post.
|
||||
* It deliberately permits desired actions addressed to "you", but rejects
|
||||
* visual or ownership claims that can only have come from the source post.
|
||||
*/
|
||||
export function validateCommentTargetedOpener(message: string): CommentTargetedOpenerValidation {
|
||||
const trimmed = message.trim();
|
||||
if (!COMMENT_GROUNDING.test(trimmed)) {
|
||||
return { safe: false, reason: 'does not ground the opener in what the commenter expressed' };
|
||||
}
|
||||
for (const rule of RECIPIENT_APPEARANCE_PATTERNS) {
|
||||
if (rule.pattern.test(trimmed)) return { safe: false, reason: rule.reason };
|
||||
}
|
||||
return { safe: true };
|
||||
}
|
||||
+24
-17
@@ -25,6 +25,8 @@ export interface ProcessedPost {
|
||||
sourceType: 'image' | 'chat';
|
||||
toneId: ToneKey;
|
||||
scripted: boolean;
|
||||
/** Identifies the generation contract so stale commenter drafts can be re-prepared safely. */
|
||||
openerContract?: 'post-author-v1' | 'comment-author-v2';
|
||||
/** True when an operator replaced the generated opener before dispatch. */
|
||||
operatorEdited?: boolean;
|
||||
}
|
||||
@@ -200,23 +202,27 @@ export class ProcessorService {
|
||||
.replace('{author}', post.author)
|
||||
.replace('{flair}', post.link_flair_text || '(none)')
|
||||
: selectedStrategy?.promptOverride;
|
||||
const messageArgs = [
|
||||
analysis.description,
|
||||
post.title,
|
||||
post.author,
|
||||
this.successfulExamples,
|
||||
strategyPrompt,
|
||||
analysis.attractivenessScore,
|
||||
post.link_flair_text ?? undefined,
|
||||
tone.scripted ? undefined : tone.profile.promptGuidance || undefined,
|
||||
this.recentMessageProvider('image'),
|
||||
] as const;
|
||||
const message = post.targetComment
|
||||
? await this.aiClient.generateMessage(
|
||||
...messageArgs,
|
||||
`<<<UNTRUSTED PUBLIC COMMENT>>>\n${post.targetComment.body.slice(0, 500)}\n<<<END COMMENT>>>`,
|
||||
)
|
||||
: await this.aiClient.generateMessage(...messageArgs);
|
||||
? await this.aiClient.generateCommentTargetedMessage({
|
||||
recipient: post.author,
|
||||
comment: post.targetComment.body,
|
||||
sourcePostTitle: post.title,
|
||||
sourceImageAnalysis: analysis.description,
|
||||
strategyGuidance: tone.scripted ? selectedStrategy?.promptOverride : strategyPrompt,
|
||||
toneGuidance: tone.profile.promptGuidance || undefined,
|
||||
recentMessages: this.recentMessageProvider('image'),
|
||||
})
|
||||
: await this.aiClient.generateMessage(
|
||||
analysis.description,
|
||||
post.title,
|
||||
post.author,
|
||||
this.successfulExamples,
|
||||
strategyPrompt,
|
||||
analysis.attractivenessScore,
|
||||
post.link_flair_text ?? undefined,
|
||||
tone.scripted ? undefined : tone.profile.promptGuidance || undefined,
|
||||
this.recentMessageProvider('image'),
|
||||
);
|
||||
|
||||
if (!message || message.length < 10) {
|
||||
this.logger.warn(
|
||||
@@ -241,7 +247,8 @@ export class ProcessorService {
|
||||
modelId: this.config.openrouter.imageAnalysisModel,
|
||||
sourceType: 'image',
|
||||
toneId: tone.profile.id,
|
||||
scripted: tone.scripted !== null,
|
||||
scripted: post.targetComment ? false : tone.scripted !== null,
|
||||
openerContract: post.targetComment ? 'comment-author-v2' : 'post-author-v1',
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user