Add account browsing simulation

This commit is contained in:
jjbrunton committed 2026-08-11 21:40:31 +01:00
1 parent e2d7dc4b82
commit 27012399ec
31 files changed
+1476 -28

No files matched your search

+37 -2
View File
@@ -63,6 +63,8 @@ import { RecipientAccountStatusService } from './recipient-account-status.js';
import { AccountEstablishmentService } from './account-establishment.js';
import { AccountGrowthService, type GrowthActivityEvent } from './account-growth.js';
import { AccountGrowthRepository } from '../database/growth-repository.js';
import { AccountBrowsingSimulationRepository } from '../database/browsing-simulation-repository.js';
import { AccountBrowsingSimulationService } from './account-browsing-simulation.js';
import { allowsConversationSync, allowsSending, type BotMode } from './bot-mode.js';
export interface ConcreteRuntimeFactoryOptions {
@@ -268,10 +270,11 @@ async function buildRuntime(
},
},
);
const growthRepository = new AccountGrowthRepository(db);
const growthService = new AccountGrowthService({
accountId: session.accountId,
redditUsername: session.redditUsername,
repository: new AccountGrowthRepository(db),
repository: growthRepository,
reddit: {
getHotPosts: (subreddit, limit) => redditApi.getHotPosts(subreddit, limit),
getThreadTopComments: (subreddit, postId, limit) =>
@@ -284,6 +287,26 @@ async function buildRuntime(
dryRun: accountConfig.browser.dryRun,
onActivity: (event) => recordGrowthActivity(activityLogRepo, session.redditUsername, event),
});
const browsingRepository = new AccountBrowsingSimulationRepository(db);
const browsingService = new AccountBrowsingSimulationService({
accountId: session.accountId,
repository: browsingRepository,
growthSubreddits: (now) => growthRepository.listActiveSubreddits(session.accountId, now),
reddit: {
getHotPosts: (subreddit, limit) => redditApi.getHotPosts(subreddit, limit),
getBrowsingComments: (subreddit, postId, limit) =>
redditApi.getBrowsingComments(subreddit, postId, limit),
},
sink: browsingRepository,
logger,
onActivity: (decision) => activityLogRepo.insert({
timestamp: decision.decidedAt,
type: 'response',
message: `Simulated ${decision.thingKind} upvote in r/${decision.subreddit}; no Reddit vote was submitted`,
postUrl: `https://www.reddit.com${decision.permalink}`,
postTitle: `r/${decision.subreddit}`,
}),
});
const rateLimiter = new RateLimiter(accountConfig, logger, undefined, {
sentMessageHistory: async (since) => messagesRepo.getSentTimestampsSince(since),
});
@@ -332,7 +355,7 @@ async function buildRuntime(
activityLogRepo,
monitorService, processorService, responseChecker, conversationAutomation, chatService, mediaService,
mediaSyncService, mediaExchange, rateLimiter,
recipientAccountStatusService, establishmentService, growthService,
recipientAccountStatusService, establishmentService, growthService, browsingService,
};
const lifecycle = new AccountRuntimeLoop(
resources, accountConfig, logger, getBotMode, recipientDeduplicator,
@@ -418,6 +441,7 @@ export class AccountRuntimeLoop {
let nextReplyCheckAt = 0;
let nextPerformanceCheckAt = 0;
let nextGrowthCheckAt = 0;
let nextBrowsingCheckAt = 0;
const replyInterval = 30_000;
const performanceInterval = Math.max(this.config.reddit.pollIntervalMs * 5, 60_000);
// The pacer decides whether a comment is actually due; this is just how
@@ -478,6 +502,17 @@ export class AccountRuntimeLoop {
this.logger.error('Account growth tick failed:', error);
}
}
// Browsing simulation is strictly below comment growth. It records a
// hypothetical decision only and runs in both scan and running modes.
if (this.resources.browsingService && Date.now() >= nextBrowsingCheckAt) {
nextBrowsingCheckAt = Date.now() + growthInterval;
try {
await this.resources.browsingService.tick(signal);
} catch (error) {
if (signal.aborted) return;
this.logger.error('Browsing simulation tick failed:', error);
}
}
await delay(5_000, signal);
} catch (error) {
if (signal.aborted) return;