Bluesky app fork with some witchin' additions 馃挮
witchsky.app
bluesky
fork
client
1import {type AppBskyContactDefs} from '@atproto/api'
2
3import {type CountryCode} from '#/lib/international-telephone-codes'
4import {normalizePhoneNumber} from './phone-number'
5import {type Contact, type Match} from './state'
6
7/**
8 * Filters out contacts that do not have any associated phone numbers,
9 * as well as businesses
10 */
11export function contactsWithPhoneNumbersOnly(contacts: Contact[]) {
12 return contacts.filter(
13 contact =>
14 contact.phoneNumbers &&
15 contact.phoneNumbers.length > 0 &&
16 contact.contactType !== 'company',
17 )
18}
19
20/**
21 * Takes the raw contact book and returns a plain list of numbers in E.164 format, along
22 * with a mapping to retrieve the contact ID when we get the results back.
23 *
24 * `countryCode` is used as a fallback for local numbers that don't have a country code associated with them.
25 * I'm making the assumption that most local numbers in someone's phone book will be the same as theirs.
26 */
27export function normalizeContactBook(
28 contacts: Contact[],
29 countryCode: CountryCode,
30 ownNumber: string,
31): {
32 phoneNumbers: string[]
33 indexToContactId: Map<number, Contact['id']>
34} {
35 const phoneNumbers: string[] = []
36 const indexToContactId = new Map<number, Contact['id']>()
37
38 for (const contact of contacts) {
39 for (const number of contact.phoneNumbers ?? []) {
40 let rawNumber: string
41
42 if (number.number) {
43 rawNumber = number.number
44 } else if (number.digits) {
45 rawNumber = number.digits
46 } else {
47 continue
48 }
49
50 const normalized = normalizePhoneNumber(
51 rawNumber,
52 number.countryCode,
53 countryCode,
54 )
55 if (normalized === null) continue
56
57 // skip if it's your own number
58 if (normalized === ownNumber) continue
59
60 phoneNumbers.push(normalized)
61 indexToContactId.set(phoneNumbers.length - 1, contact.id)
62 }
63 }
64
65 return {
66 phoneNumbers,
67 indexToContactId,
68 }
69}
70
71export function filterMatchedNumbers(
72 contacts: Contact[],
73 results: AppBskyContactDefs.MatchAndContactIndex[],
74 mapping: Map<number, Contact['id']>,
75) {
76 const filteredIds = new Set<Contact['id']>()
77
78 for (const result of results) {
79 const id = mapping.get(result.contactIndex)
80 if (id !== undefined) {
81 filteredIds.add(id)
82 }
83 }
84
85 return contacts.filter(contact => !filteredIds.has(contact.id))
86}
87
88export function getMatchedContacts(
89 contacts: Contact[],
90 results: AppBskyContactDefs.MatchAndContactIndex[],
91 mapping: Map<number, Contact['id']>,
92): Array<Match> {
93 const contactsById = new Map(contacts.map(c => [c.id, c]))
94
95 return results.map(result => {
96 const id = mapping.get(result.contactIndex)
97 const contact = id !== undefined ? contactsById.get(id) : undefined
98 return {profile: result.match, contact}
99 })
100}