feat: add persistent state management for mention processing
This commit is contained in:
parent
4554a8a58f
commit
eca01e8427
@ -4,7 +4,7 @@
|
||||
|
||||
## Version 0.1.0 - 2025-12-20
|
||||
|
||||
Adds Grokii, an AI-powered devRant bot that supports chat interactions and spam detection modes. Developers can now integrate this bot to enhance community engagement and moderation on devRant platforms.
|
||||
Adds persistent state management for mention processing, ensuring continuity across application restarts.
|
||||
|
||||
**Changes:** 9 files, 1684 lines
|
||||
**Languages:** Markdown (386 lines), Other (1298 lines)
|
||||
**Changes:** 3 files, 66 lines
|
||||
**Languages:** Other (66 lines)
|
||||
|
||||
@ -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
|
||||
)
|
||||
|
||||
@ -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
Sources/Grokii/StateManager.swift
Normal file
43
Sources/Grokii/StateManager.swift
Normal 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)
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user