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
17 changes: 11 additions & 6 deletions Sources/IMsgCore/MessageStore+Accounts.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,26 @@ import SQLite
public struct LocalAccount: Sendable, Equatable {
/// The account login (typically your Apple ID email or phone), from `chat.account_login`.
public let login: String
/// The full account identifier (e.g. `iMessage;+;you@icloud.com`), from `chat.account_id`.
/// The opaque account identifier from `chat.account_id`.
public let accountID: String
/// The messaging service from `chat.service_name` (for example, `iMessage` or `SMS`).
public let service: String
/// Number of local chats routed through this account (useful to rank the primary account).
public let chatCount: Int

public init(login: String, accountID: String, chatCount: Int) {
public init(login: String, accountID: String, chatCount: Int, service: String = "") {
self.login = login
self.accountID = accountID
self.chatCount = chatCount
self.service = service
}
}

extension MessageStore {
/// Enumerate the messaging accounts seen in the local `chat.db`.
///
/// This reads `chat.account_login` / `chat.account_id` recorded on each chat,
/// grouped and ranked by how many chats use them. It reflects accounts
/// This reads `chat.account_login`, `chat.account_id`, and `chat.service_name`
/// recorded on each chat, grouped and ranked by how many chats use them. It reflects accounts
/// *observed in history*, not a live "currently signed-in" list — for a
/// single-account Mac these are equivalent, and the result is factual rather
/// than inferred. Returns an empty array on schemas without account columns.
Expand All @@ -34,9 +37,10 @@ extension MessageStore {
let sql = """
SELECT \(loginColumn) AS account_login,
\(accountIDColumn) AS account_id,
IFNULL(c.service_name, '') AS service_name,
COUNT(*) AS chat_count
FROM chat c
GROUP BY account_login, account_id
GROUP BY account_login, account_id, service_name
HAVING account_login != '' OR account_id != ''
ORDER BY chat_count DESC
"""
Expand All @@ -46,9 +50,10 @@ extension MessageStore {
while let row = try rows.failableNext() {
let login = try stringValue(row, "account_login")
let accountID = try stringValue(row, "account_id")
let service = try stringValue(row, "service_name")
let count = try intValue(row, "chat_count") ?? 0
accounts.append(
LocalAccount(login: login, accountID: accountID, chatCount: count)
LocalAccount(login: login, accountID: accountID, chatCount: count, service: service)
)
}
return accounts
Expand Down
34 changes: 34 additions & 0 deletions Tests/IMsgCoreTests/MessageStoreAccountsTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import SQLite
import Testing

@testable import IMsgCore

@Test
func localAccountsPreservesOpaqueAccountIDAndReportsChatService() throws {
let db = try Connection(.inMemory)
try MessageDatabaseFixture.createSchema(db)
try db.run(
"""
INSERT INTO chat(
ROWID, chat_identifier, guid, service_name, account_id, account_login
)
VALUES
(1, '+111', 'iMessage;-;+111', 'iMessage',
'9E876D56-1542-4DB5-A437-A4052D038DB9', 'E:me@example.com'),
(2, '+222', 'SMS;-;+222', 'SMS',
'34CD7B53-36C2-48ED-8FC3-7226D8BC538A', 'P:+15555550123'),
(3, '+333', 'iMessage;-;+333', 'iMessage',
'9E876D56-1542-4DB5-A437-A4052D038DB9', 'E:me@example.com')
"""
)
let store = try MessageStore(connection: db, path: ":memory:")

let accounts = try store.localAccounts()

#expect(accounts.map(\.service) == ["iMessage", "SMS"])
#expect(
accounts.map(\.accountID) == [
"9E876D56-1542-4DB5-A437-A4052D038DB9",
"34CD7B53-36C2-48ED-8FC3-7226D8BC538A",
])
}
Loading