Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ Built-in manifests ship for:
- `slacrawl`
- `discrawl`
- `telecrawl`
- `imsgcrawl`
- `notcrawl`
- `gogcli` through the `gog` executable
- `wacli`
Expand Down
36 changes: 36 additions & 0 deletions Sources/CrawlBarCore/BuiltInApps/BuiltInCrawlApps+Imsgcrawl.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import Foundation

public extension BuiltInCrawlApps {
static let imsgcrawl = CrawlAppManifest(
id: Self.imsgcrawlID,
displayName: "iMessage",
description: "Local-first iMessage archive crawler",
binary: .init(name: "imsgcrawl"),
branding: .init(
symbolName: "message.fill",
accentColor: "#34C759",
bundleIdentifier: "com.apple.MobileSMS"),
paths: .init(
defaultDatabase: "~/.imsgcrawl/archive.db",
defaultCache: "~/.imsgcrawl/cache",
defaultLogs: "~/.imsgcrawl/logs"),
commands: [
"metadata": ["--json", "metadata"],
"status": ["--json", "status"],
"refresh": ["--json", "sync"],
"search": ["--json", "search"],
],
capabilities: [.status, .refresh, .search],
statusRequiresSecrets: false,
privacy: .init(
containsPrivateMessages: true,
exportsSecrets: false,
localOnlyScopes: [
"apple-messages",
"sqlite",
"contact-handles",
"message-archive",
"message-text-search",
]))
.withSuggestion(Self.appSuggest("Messages", ["com.apple.MobileSMS"]))
}
2 changes: 2 additions & 0 deletions Sources/CrawlBarCore/BuiltInApps/BuiltInCrawlApps.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ public enum BuiltInCrawlApps {
public static let slacrawlID = CrawlAppID(rawValue: "slacrawl")
public static let discrawlID = CrawlAppID(rawValue: "discrawl")
public static let telecrawlID = CrawlAppID(rawValue: "telecrawl")
public static let imsgcrawlID = CrawlAppID(rawValue: "imsgcrawl")
public static let notcrawlID = CrawlAppID(rawValue: "notcrawl")
public static let gogcliID = CrawlAppID(rawValue: "gogcli")
public static let wacliID = CrawlAppID(rawValue: "wacli")
Expand All @@ -16,6 +17,7 @@ public enum BuiltInCrawlApps {
Self.slacrawl,
Self.discrawl,
Self.telecrawl,
Self.imsgcrawl,
Self.notcrawl,
Self.gogcli,
Self.wacli,
Expand Down
4 changes: 3 additions & 1 deletion Sources/CrawlBarCore/StatusMapperCrawlKit.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ extension CrawlStatusMapper {
share: self.shareStatus(in: object),
remote: remote,
sqliteObject: self.sqliteObjectStatus(in: object),
sqliteBundle: self.sqliteBundleStatus(in: object))
sqliteBundle: self.sqliteBundleStatus(in: object),
warnings: self.stringValues(["warnings"], in: object),
errors: self.stringValues(["errors"], in: object))
}

func isCrawlKitStatus(_ object: [String: Any]) -> Bool {
Expand Down
10 changes: 9 additions & 1 deletion Sources/CrawlBarCore/StatusMapperJSON.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,21 @@ extension CrawlStatusMapper {
return .current
case "warn", "warning", "degraded":
return .stale
case "failed", "failure":
case "failed", "failure", "source_error", "archive_error":
return .error
default:
return nil
}
}

func stringValues(_ keys: [String], in object: [String: Any]) -> [String] {
for key in keys {
guard let values = self.firstValue(key, in: object) as? [Any] else { continue }
return values.compactMap { ($0 as? String)?.nilIfBlank }
}
return []
}

func dateValue(_ keys: [String], in object: [String: Any]) -> Date? {
for key in keys {
guard let value = self.firstValue(key, in: object) else { continue }
Expand Down
10 changes: 10 additions & 0 deletions Sources/CrawlBarSelfTest/SelfTestConfig.swift
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,16 @@ extension CrawlBarSelfTest {
try Self.expect(BuiltInCrawlApps.telecrawl.install?.package == "steipete/tap/telecrawl", "telecrawl install metadata exists")
try Self.expect(BuiltInCrawlApps.telecrawl.paths.defaultConfig == "~/.telecrawl/backup.json", "telecrawl config path maps")
try Self.expect(BuiltInCrawlApps.graincrawl.availability == .available, "graincrawl is available")
try Self.expect(BuiltInCrawlApps.imsgcrawl.displayName == "iMessage", "imsgcrawl uses user-facing iMessage name")
try Self.expect(BuiltInCrawlApps.imsgcrawl.commands["status"] == ["--json", "status"], "imsgcrawl uses crawlkit status command")
try Self.expect(BuiltInCrawlApps.imsgcrawl.commands["refresh"] == ["--json", "sync"], "imsgcrawl sync is wired as refresh")
try Self.expect(BuiltInCrawlApps.imsgcrawl.commands["search"] == ["--json", "search"], "imsgcrawl search is wired")
try Self.expect(BuiltInCrawlApps.imsgcrawl.commands["chats"] == nil, "imsgcrawl chats stay outside persisted actions")
try Self.expect(BuiltInCrawlApps.imsgcrawl.commands["messages"] == nil, "imsgcrawl messages stay outside persisted actions")
try Self.expect(BuiltInCrawlApps.imsgcrawl.commands["contact-export"] == nil, "imsgcrawl contact export stays outside persisted actions")
try Self.expect(BuiltInCrawlApps.imsgcrawl.privacy.containsPrivateMessages, "imsgcrawl privacy metadata flags iMessage data")
try Self.expect(BuiltInCrawlApps.imsgcrawl.branding.bundleIdentifier == "com.apple.MobileSMS", "imsgcrawl uses native Messages app icon")
try Self.expect(BuiltInCrawlApps.imsgcrawl.suggestion?.name == "Messages", "imsgcrawl suggests from the native Messages app")
try Self.expect(BuiltInCrawlApps.graincrawl.commands["status"] == ["status", "--json"], "graincrawl uses crawlkit status command")
try Self.expect(
BuiltInCrawlApps.graincrawl.commands["refresh"] == ["sync", "--json"],
Expand Down
24 changes: 24 additions & 0 deletions Sources/CrawlBarSelfTest/SelfTestStatusMapping.swift
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,30 @@ extension CrawlBarSelfTest {
let failedStatus = CrawlStatusMapper().status(from: failedResult, manifest: BuiltInCrawlApps.graincrawl)
try Self.expect(failedStatus.state == .error, "crawlkit failed state maps to error")

let imsgcrawlSourceErrorResult = CrawlCommandResult(
appID: BuiltInCrawlApps.imsgcrawlID,
action: "status",
exitCode: 0,
stdout: """
{
"schema_version": "crawlkit.control.v1",
"app_id": "imsgcrawl",
"state": "source_error",
"summary": "Messages source could not be read.",
"warnings": ["archive has not been synced"],
"errors": ["Messages database access was denied"]
}
""",
stderr: "",
startedAt: Date(),
finishedAt: Date())
let imsgcrawlSourceErrorStatus = CrawlStatusMapper().status(
from: imsgcrawlSourceErrorResult,
manifest: BuiltInCrawlApps.imsgcrawl)
try Self.expect(imsgcrawlSourceErrorStatus.state == .error, "crawlkit source errors map to error")
try Self.expect(imsgcrawlSourceErrorStatus.warnings == ["archive has not been synced"], "crawlkit warnings are preserved")
try Self.expect(imsgcrawlSourceErrorStatus.errors == ["Messages database access was denied"], "crawlkit errors are preserved")

let githubAuthMessage = """
[github] request GET /repos/openclaw/openclaw
github GET /repos/openclaw/openclaw failed with status 401: {
Expand Down