Class: Pikuri::Thunderbird::Gloda::Contacts
- Inherits:
-
Object
- Object
- Pikuri::Thunderbird::Gloda::Contacts
- Defined in:
- lib/pikuri/thunderbird/gloda/contacts.rb
Overview
The people half of the Gloda corpus (its mail counterpart is Mail): resolve a name to an address, and check recipient novelty. Both read the user's own correspondence graph, so they belong together and away from mail search/read.
contacts = Gloda::Contacts.new(gloda: gloda)
contacts.resolve(query: 'jon', limit: 5) # name → ranked addresses
contacts.domains_seen(['acme.com', 'paypa1.com']) # => ['acme.com']
It owns no resources: it queries Pikuri::Thunderbird::Gloda's live snapshot through #with_fresh_db (so a rebuild under it is transparent), and there is nothing to close — the Pikuri::Thunderbird::Gloda it holds is closed by its own owner.
Two graphs, one purpose
#resolve reads the derived graph — the decoded c3author /
c4recipients columns, where display name and address sit together —
because Gloda's contacts table is dead for this (frecency/popularity
unpopulated, name unreliable). #domains_seen reads the identity
graph (the identities table). Both answer "who has this user actually
corresponded with?" — #resolve to fill a recipient, #domains_seen
to flag a novel one.
Constant Summary collapse
- LOGGER =
Pikuri.logger_for('Thunderbird::Gloda::Contacts')
- SCAN =
Returns max matched messages scanned when tallying contact frequency in #resolve — bounds work on a very common name.
2000- SENT_WEIGHT =
Returns extra weight a Sent-folder appearance adds over a received one. "I have mailed this person" is the strongest real-contact signal, and a spoofer is never in your Sent — so this is what sinks a spoofed one-off below the genuine contact.
3- SENT_PRIORITY =
Returns Gloda's
indexingPriorityfor a Sent folder. 60- EMAIL_RE =
Bracket-agnostic email token: angle brackets, when present, fall outside the class, so
Jon <jon@x>and a barejon@x(name trailing) extract to the samejon@x. Commas/semicolons/quotes bound it too, so it never swallows the next recipient or a nickname quote. /[^\s<>,;"']+@[^\s<>,;"']+/
Instance Method Summary collapse
-
#domains_seen(domains) ⇒ Array<String>
Which of
domainsthe user has correspondence history with, per Gloda's identity graph (+identities+ collects the email of every author and recipient across all indexed messages, Sent included — so it answers "have I ever exchanged mail with this domain?" in both directions). - #initialize(gloda:) ⇒ Contacts constructor
-
#resolve(query:, limit:) ⇒ Array<Hash>
Resolve a name (or partial address) to ranked email addresses drawn from the user's own correspondence graph — the decoded author / recipient columns.
Constructor Details
#initialize(gloda:) ⇒ Contacts
54 55 56 |
# File 'lib/pikuri/thunderbird/gloda/contacts.rb', line 54 def initialize(gloda:) @gloda = gloda end |
Instance Method Details
#domains_seen(domains) ⇒ Array<String>
Which of domains the user has correspondence history with, per
Gloda's identity graph (+identities+ collects the email of every
author and recipient across all indexed messages, Sent included —
so it answers "have I ever exchanged mail with this domain?" in both
directions). Powers ComposeGuard's recipient-novelty warn.
contacts.domains_seen(['acme.com', 'paypa1.com']) # => ['acme.com']
113 114 115 116 117 118 119 120 121 122 123 124 125 126 |
# File 'lib/pikuri/thunderbird/gloda/contacts.rb', line 113 def domains_seen(domains) return [] if domains.empty? @gloda.with_fresh_db do |db| domains.select do |domain| db.get_first_value(<<~SQL, [domain]) SELECT 1 FROM identities WHERE kind = 'email' AND lower(substr(value, instr(value, '@') + 1)) = ? LIMIT 1 SQL end end end |
#resolve(query:, limit:) ⇒ Array<Hash>
Resolve a name (or partial address) to ranked email addresses drawn from the user's own correspondence graph — the decoded author / recipient columns. Feeds MailCompose: the model has a name, compose needs an address.
contacts.resolve(query: 'martin', limit: 5)
# => [{address: 'martin@vysny.me', name: 'Martin Vysny',
# count: 42, sent: 12, score: 78}, …]
query terms match (recall-first OR, token-wise) against the author
and recipient columns only, via Pikuri::Thunderbird::Gloda's shared FTS5 index; every
email token in a matching entry is extracted bracket-agnostically, grouped by lower-cased address, and ranked by
appearance count with a Sent-folder bonus. Never
auto-picks — several people/addresses come back and disambiguation is
the caller's job (silently choosing one is the mis-send
ComposeGuard exists to prevent). Reverse lookup (address → who?) is
the same call with the address as query.
82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 |
# File 'lib/pikuri/thunderbird/gloda/contacts.rb', line 82 def resolve(query:, limit:) terms = query.to_s.scan(/[[:alnum:]]+/) return [] if terms.empty? rows = @gloda.with_fresh_db do |db| db.execute(<<~SQL, [match_expr(terms), SCAN]) SELECT c.c3author, c.c4recipients, fl.indexingPriority FROM fts JOIN messages m ON m.id = fts.rowid JOIN messagesText_content c ON c.docid = fts.rowid LEFT JOIN folderLocations fl ON fl.id = m.folderID WHERE fts MATCH ? AND m.deleted = 0 LIMIT ? SQL end tally(rows, terms).first(limit) end |