Track pending browsing upvotes
This commit is contained in:
1 parent
540590f43e
commit
1fd154e1eb
13 files changed
+73
-27
No files matched your search
@@ -3,6 +3,7 @@ import type { GrowthQuietHours } from './growth-repository.js';
|
||||
|
||||
export type BrowsingThingKind = 'post' | 'comment';
|
||||
export type BrowsingDecisionKind = 'simulated-upvote' | 'skipped' | 'rejected';
|
||||
export type BrowsingVoteStatus = 'simulated' | 'pending' | 'submitted' | 'failed';
|
||||
|
||||
export interface AccountBrowsingSettingsRecord {
|
||||
accountId: string;
|
||||
@@ -47,6 +48,7 @@ export interface BrowsingDecisionRecord {
|
||||
decidedAt: number;
|
||||
scheduledSessionId: string;
|
||||
voteSubmitted: boolean;
|
||||
voteStatus: BrowsingVoteStatus;
|
||||
}
|
||||
|
||||
export interface BrowsingDecisionCounts {
|
||||
@@ -136,13 +138,13 @@ export class AccountBrowsingSimulationRepository {
|
||||
return this.db.prepare(`
|
||||
INSERT OR IGNORE INTO account_browsing_decisions (
|
||||
account_id, thing_fullname, thing_kind, post_id, subreddit, author,
|
||||
permalink, decision, reason, discovered_at, decided_at, scheduled_session_id, vote_submitted
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
permalink, decision, reason, discovered_at, decided_at, scheduled_session_id, vote_submitted, vote_status
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`).run(
|
||||
decision.accountId, decision.thingFullname, decision.thingKind, decision.postId,
|
||||
decision.subreddit, decision.author, decision.permalink, decision.decision,
|
||||
decision.reason, decision.discoveredAt, decision.decidedAt, decision.scheduledSessionId,
|
||||
decision.voteSubmitted ? 1 : 0,
|
||||
decision.voteSubmitted ? 1 : 0, decision.voteStatus,
|
||||
).changes === 1;
|
||||
}
|
||||
|
||||
@@ -154,11 +156,18 @@ export class AccountBrowsingSimulationRepository {
|
||||
|
||||
markVoteSubmitted(accountId: string, thingFullname: string): boolean {
|
||||
return this.db.prepare(`
|
||||
UPDATE account_browsing_decisions SET vote_submitted = 1
|
||||
WHERE account_id = ? AND thing_fullname = ? AND vote_submitted = 0
|
||||
UPDATE account_browsing_decisions SET vote_submitted = 1, vote_status = 'submitted', reason = 'Reddit upvote submitted'
|
||||
WHERE account_id = ? AND thing_fullname = ? AND vote_status = 'pending'
|
||||
`).run(accountId, thingFullname).changes === 1;
|
||||
}
|
||||
|
||||
markVoteFailed(accountId: string, thingFullname: string, reason: string): boolean {
|
||||
return this.db.prepare(`
|
||||
UPDATE account_browsing_decisions SET vote_status = 'failed', reason = ?
|
||||
WHERE account_id = ? AND thing_fullname = ? AND vote_status = 'pending'
|
||||
`).run(reason, accountId, thingFullname).changes === 1;
|
||||
}
|
||||
|
||||
hasAccountDecision(accountId: string, thingFullname: string): boolean {
|
||||
return this.db.prepare(`
|
||||
SELECT 1 FROM account_browsing_decisions WHERE account_id = ? AND thing_fullname = ?
|
||||
@@ -305,5 +314,6 @@ function mapDecision(row: Record<string, unknown>): BrowsingDecisionRecord {
|
||||
decidedAt: row.decided_at as number,
|
||||
scheduledSessionId: row.scheduled_session_id as string,
|
||||
voteSubmitted: row.vote_submitted === 1,
|
||||
voteStatus: row.vote_status as BrowsingVoteStatus,
|
||||
};
|
||||
}
|
||||
@@ -612,6 +612,10 @@ export function runMigrations(db: Database.Database): void {
|
||||
addColumnIfNotExists(db, 'account_browsing_settings', 'auto_vote_enabled', 'INTEGER NOT NULL DEFAULT 0 CHECK(auto_vote_enabled IN (0, 1))');
|
||||
addColumnIfNotExists(db, 'account_browsing_decisions', 'vote_submitted', 'INTEGER NOT NULL DEFAULT 0 CHECK(vote_submitted IN (0, 1))');
|
||||
},
|
||||
() => {
|
||||
addColumnIfNotExists(db, 'account_browsing_decisions', 'vote_status', "TEXT NOT NULL DEFAULT 'simulated' CHECK(vote_status IN ('simulated', 'pending', 'submitted', 'failed'))");
|
||||
db.exec("UPDATE account_browsing_decisions SET vote_status = 'submitted' WHERE vote_submitted = 1");
|
||||
},
|
||||
];
|
||||
|
||||
let currentVersion = db.pragma('user_version', { simple: true }) as number;
|
||||
|
||||
@@ -120,17 +120,27 @@ export class AccountBrowsingSimulationService {
|
||||
if (candidates.length === 0) return { kind: 'scheduled', nextDueAt };
|
||||
|
||||
const selected = candidates[Math.floor(this.random() * candidates.length)];
|
||||
const decision = toDecision(this.options.accountId, selected, 'simulated-upvote', 'Eligible browsing selection', sessionId, now);
|
||||
const voter = settings.autoVoteEnabled && allowVoting && !this.options.dryRun
|
||||
? this.options.voter
|
||||
: undefined;
|
||||
const decision = toDecision(
|
||||
this.options.accountId, selected, 'simulated-upvote',
|
||||
voter ? 'Upvote pending Reddit confirmation' : 'Eligible browsing selection',
|
||||
sessionId, now, voter ? 'pending' : 'simulated',
|
||||
);
|
||||
await this.options.sink.recordSimulation(decision);
|
||||
if (settings.autoVoteEnabled && allowVoting && !this.options.dryRun && this.options.voter) {
|
||||
const result = await this.options.voter.upvote(selected.fullname);
|
||||
if (voter) {
|
||||
const result = await voter.upvote(selected.fullname);
|
||||
if (result.status === 'submitted') {
|
||||
this.options.repository.markVoteSubmitted(this.options.accountId, selected.fullname);
|
||||
decision.voteSubmitted = true;
|
||||
decision.voteStatus = 'submitted';
|
||||
decision.reason = 'Reddit upvote submitted';
|
||||
this.options.logger.info(`Submitted browsing upvote for ${selected.fullname} in r/${selected.subreddit}`);
|
||||
} else {
|
||||
decision.reason = `Reddit upvote not submitted: ${result.reason ?? result.status}`;
|
||||
decision.voteStatus = 'failed';
|
||||
this.options.repository.markVoteFailed(this.options.accountId, selected.fullname, decision.reason);
|
||||
this.options.logger.warn(`Browsing upvote for ${selected.fullname} was not submitted: ${result.reason ?? result.status}`);
|
||||
}
|
||||
}
|
||||
@@ -165,7 +175,7 @@ export class AccountBrowsingSimulationService {
|
||||
}
|
||||
if (!this.options.repository.hasAccountDecision(this.options.accountId, thing.fullname)) {
|
||||
await this.options.sink.recordSimulation(
|
||||
toDecision(this.options.accountId, thing, 'rejected', reason, sessionId, now),
|
||||
toDecision(this.options.accountId, thing, 'rejected', reason, sessionId, now, 'simulated'),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -293,6 +303,7 @@ function toDecision(
|
||||
reason: string,
|
||||
sessionId: string,
|
||||
now: number,
|
||||
voteStatus: BrowsingDecisionRecord['voteStatus'],
|
||||
): BrowsingDecisionRecord {
|
||||
return {
|
||||
accountId,
|
||||
@@ -308,5 +319,6 @@ function toDecision(
|
||||
decidedAt: now,
|
||||
scheduledSessionId: sessionId,
|
||||
voteSubmitted: false,
|
||||
voteStatus,
|
||||
};
|
||||
}
|
||||
@@ -47,7 +47,7 @@ export const BrowsingStatsSchema = z.strictObject({
|
||||
export const BrowsingDecisionSchema = z.strictObject({
|
||||
thingFullname: z.string(), thingKind: z.enum(['post', 'comment']), postId: z.string(),
|
||||
subreddit: z.string(), author: z.string(), permalink: z.string(),
|
||||
decision: z.enum(['simulated-upvote', 'skipped', 'rejected']), reason: z.string(), voteSubmitted: z.boolean(), decidedAt: z.number(),
|
||||
decision: z.enum(['simulated-upvote', 'skipped', 'rejected']), reason: z.string(), voteSubmitted: z.boolean(), voteStatus: z.enum(['simulated', 'pending', 'submitted', 'failed']), decidedAt: z.number(),
|
||||
});
|
||||
|
||||
export const BrowsingSummarySchema = z.strictObject({
|
||||
|
||||
@@ -334,6 +334,7 @@ function presentBrowsing(
|
||||
decision: decision.decision,
|
||||
reason: decision.reason,
|
||||
voteSubmitted: decision.voteSubmitted,
|
||||
voteStatus: decision.voteStatus,
|
||||
decidedAt: decision.decidedAt,
|
||||
})),
|
||||
} : {}),
|
||||
|
||||
Reference in new issue
Block a user