Skip to content

Commit 64e9d8d

Browse files
committed
✨ Implement icloud contacts service
1 parent de2d219 commit 64e9d8d

File tree

3 files changed

+89
-1
lines changed

3 files changed

+89
-1
lines changed

src/index.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { iCloudDriveService } from "./services/drive";
1212
import { iCloudFindMyService } from "./services/findMy";
1313
import { iCloudPhotosService } from "./services/photos";
1414
import { iCloudUbiquityService } from "./services/ubiquity";
15+
import { iCloudContactsService } from "./services/contacts";
1516
import { AccountInfo } from "./types";
1617

1718
export type { iCloudAuthenticationStore } from "./auth/authStore";
@@ -476,7 +477,8 @@ export default class iCloudService extends EventEmitter {
476477
ubiquity: iCloudUbiquityService,
477478
drivews: iCloudDriveService,
478479
calendar: iCloudCalendarService,
479-
photos: iCloudPhotosService
480+
photos: iCloudPhotosService,
481+
contacts: iCloudContactsService
480482
};
481483

482484
// Returns an instance of the 'account' (Account Details) service.
@@ -494,6 +496,8 @@ export default class iCloudService extends EventEmitter {
494496
getService(service: "calendar"): iCloudCalendarService
495497
// Returns an instance of the 'photos' (iCloud Photos) service.
496498
getService(service: "photos"): iCloudPhotosService
499+
// Returns an instance of the 'contacts' (iCloud Contacts) service.
500+
getService(service: "contacts"): iCloudContactsService
497501
/**
498502
* Returns an instance of the specified service. Results are cached, so subsequent calls will return the same instance.
499503
* @param service The service name to return an instance of. Must be one of the keys in {@link iCloudService.serviceConstructors}.

src/services/contacts.ts

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import fetch from "node-fetch";
2+
import iCloudService from "..";
3+
interface iCloudFieldLabel{
4+
field: string;
5+
label: string;
6+
}
7+
interface iCloudContact{
8+
firstName?: string;
9+
lastName?: string;
10+
isGuardianApproved: boolean;
11+
emailAddresses?: Array<iCloudFieldLabel>;
12+
contactId: string;
13+
normalized: string;
14+
phones?: Array<iCloudFieldLabel>;
15+
urls?: Array<iCloudFieldLabel>;
16+
dates?: Array<iCloudFieldLabel>;
17+
relatedNames?: Array<iCloudFieldLabel>;
18+
notes?: string;
19+
birthday?: string;
20+
etag: string;
21+
whitelisted: boolean;
22+
isCompany: boolean;
23+
profiles?: { field: string; label: string; user: string }[];
24+
IMs?: { field: { IMService: string; userName: string; }; label: string; }[];
25+
photo?: { signature: string; url: string; crop: { x: number; y: number; width: number; height: number; } }
26+
}
27+
interface iCloudContactsStartupResponse {
28+
syncToken: string;
29+
prefToken: string;
30+
contacts: Array<iCloudContact>;
31+
}
32+
interface iCloudContactsContactsResponse {
33+
contacts: Array<iCloudContact>;
34+
}
35+
36+
export class iCloudContactsService {
37+
service: iCloudService;
38+
serviceUri: string;
39+
constructor(service: iCloudService, serviceUri: string) {
40+
this.service = service;
41+
this.serviceUri = serviceUri;
42+
}
43+
// fetch contacts
44+
async contacts() {
45+
const params = {
46+
locale: "en_US",
47+
order: "last,first"
48+
};
49+
const url = new URL("/co/startup", this.serviceUri);
50+
url.search = new URLSearchParams({ ...params, clientVersion: "2.1" }).toString();
51+
52+
const request = await fetch(url.href, {
53+
headers: this.service.authStore.getHeaders()
54+
});
55+
const json = await request.json() as iCloudContactsStartupResponse;
56+
57+
const paramsNext = {
58+
...params,
59+
prefToken: json.prefToken,
60+
syncToken: json.syncToken,
61+
limit: "0",
62+
offset: "0"
63+
};
64+
const urlNext = new URL("/co/contacts", this.serviceUri);
65+
urlNext.search = new URLSearchParams({ ...paramsNext, clientVersion: "2.1" }).toString();
66+
const nextRequest = await fetch(urlNext.href, {
67+
headers: this.service.authStore.getHeaders()
68+
});
69+
const nextJson = await nextRequest.json()as iCloudContactsContactsResponse;
70+
71+
return nextJson.contacts;
72+
}
73+
}

test/contacts.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
const authenticate = require("./authenticate");
2+
3+
authenticate.then(async(icloud) => {
4+
const contactsService = icloud.getService("contacts");
5+
const contacts = await contactsService.contacts();
6+
7+
console.log(`You have ${contacts.length} contacts`);
8+
const firstContact = contacts[0];
9+
console.log(`Let's get first your contact detail: ${firstContact.lastName}`);
10+
console.log(JSON.stringify(firstContact, null, 4));
11+
});

0 commit comments

Comments
 (0)