feat: add persistent state manager to track last mention time across restarts

This commit is contained in:
2025-12-20 19:52:45 +00:00
parent 60d325a970
commit 3bd2edba80
4 changed files with 63 additions and 9 deletions
+3
View File
@@ -123,11 +123,14 @@ extension Grokii {
temperature: temperature
)
let stateManager = StateManager()
let bot = MentionBot(
api: api,
token: token,
ai: ai,
logger: logger,
stateManager: stateManager,
botUsername: options.username,
dryRun: options.dryRun
)
+14 -6
View File
@@ -41,16 +41,18 @@ actor MentionBot {
private let token: AuthToken
private let ai: OpenRouterClient
private let logger: BotLogger
private let stateManager: StateManager
private let botUsername: String
private let dryRun: Bool
private var processedNotificationIds: Set<String> = []
private var processedThisSession: Set<String> = []
init(
api: DevRantRequest,
token: AuthToken,
ai: OpenRouterClient,
logger: BotLogger,
stateManager: StateManager,
botUsername: String,
dryRun: Bool = false
) {
@@ -58,6 +60,7 @@ actor MentionBot {
self.token = token
self.ai = ai
self.logger = logger
self.stateManager = stateManager
self.botUsername = botUsername.lowercased()
self.dryRun = dryRun
}
@@ -80,9 +83,12 @@ actor MentionBot {
do {
let mentions = try await fetchExternalMentions()
let lastTime = await stateManager.lastMentionTime
let newMentions = mentions.filter { mention in
mention.to.lowercased() == botUsername && !processedNotificationIds.contains(mention.identifiers.guid)
mention.to.lowercased() == botUsername &&
mention.createdTime > lastTime &&
!processedThisSession.contains(mention.identifiers.guid)
}
if newMentions.isEmpty {
@@ -92,7 +98,7 @@ actor MentionBot {
await logger.info("Found \(newMentions.count) new mention(s)")
for mention in newMentions {
for mention in newMentions.sorted(by: { $0.createdTime < $1.createdTime }) {
await processMention(mention)
}
@@ -116,12 +122,12 @@ actor MentionBot {
}
private func processMention(_ mention: ExternalMention) async {
guard !processedNotificationIds.contains(mention.identifiers.guid) else {
guard !processedThisSession.contains(mention.identifiers.guid) else {
await logger.debug("Already processed mention: \(mention.identifiers.guid)")
return
}
processedNotificationIds.insert(mention.identifiers.guid)
processedThisSession.insert(mention.identifiers.guid)
do {
let (rant, comments) = try await api.getRant(
@@ -151,6 +157,8 @@ actor MentionBot {
response: response
)
await stateManager.updateLastMentionTime(mention.createdTime)
} catch {
await logger.error("Failed to process mention: \(error)")
}
@@ -215,7 +223,7 @@ actor MentionBot {
}
func getStats() -> Int {
return processedNotificationIds.count
return processedThisSession.count
}
}
+43
View File
@@ -0,0 +1,43 @@
// retoor <retoor@molodetz.nl>
import Foundation
actor StateManager {
private struct State: Codable {
var lastMentionTime: Int
}
private let stateURL: URL
private var state: State
init() {
let homeDir = FileManager.default.homeDirectoryForCurrentUser
let grokiiDir = homeDir.appendingPathComponent(".grokii")
try? FileManager.default.createDirectory(at: grokiiDir, withIntermediateDirectories: true)
self.stateURL = grokiiDir.appendingPathComponent("state.json")
if let data = try? Data(contentsOf: stateURL),
let loaded = try? JSONDecoder().decode(State.self, from: data) {
self.state = loaded
} else {
self.state = State(lastMentionTime: 0)
}
}
var lastMentionTime: Int {
state.lastMentionTime
}
func updateLastMentionTime(_ time: Int) {
guard time > state.lastMentionTime else { return }
state.lastMentionTime = time
save()
}
private func save() {
guard let data = try? JSONEncoder().encode(state) else { return }
try? data.write(to: stateURL, options: .atomic)
}
}