Apple iCloud 访问 uwp 应用程序中的联系人

Apple iCloud access to contacts in uwp application

我正在尝试在我的 UWP 应用程序中访问我的 iCloud 联系人。 我知道我可以通过 Google's People API and my Outlook contacts through Microsoft's Graph api OR Outook people api 访问我的 Gmail 联系人。

Apple 是否提供可用于获取、更新、添加、删除联系人的 API(Rest 或其他)?如果是,是否有教程介绍如何设置访问 iCloud api?

在花了很多天试图解决这个问题之后,我想我开始破解这个问题了。我不确定(可能是我)Apple 是否故意让这变得困难,但这比应该的更困难。 这是我什至需要做的来获取联系人(完全不知道如何添加、更新、删除它们):

Step 1> Request a token using apple id and password. If 2FA is activated, you will not receive the auth token and auth user cookies in the response header. But that is okay, we will get to that in the next step. Note that most probably the extraction of the AUTH-TOKEN and AUTH-USER will fail for now. Also, we will receive the locale information and the contact link in the response which we will need later on.

public sealed partial class MainPage : Page
{
    String contactLink = "";
    String authToken = "";
    String authUser = "";

    public MainPage()
    {
        this.InitializeComponent();
    }

    private async void IcloudBtn_Click(object sender, RoutedEventArgs e)
    {
        HttpResponseHeaderCollection respHeaders = await ContactApple();
        ContactsBlock.Text += "CONTACT LINK: " + contactLink + "\n";
        ContactsBlock.Text += authToken + "\n";
        ContactsBlock.Text += authUser + "\n";
    }

    private async void GetToken_Click(object sender, RoutedEventArgs e)
    {
        HttpResponseHeaderCollection respHeaders = await ContactApple(AuthenticationCode.Text);
        ContactsBlock.Text += "CONTACT LINK: " + contactLink + "\n";
        ContactsBlock.Text += authToken + "\n";
        ContactsBlock.Text += authUser + "\n";
    }

    private async void GetContacts_Click(object sender, RoutedEventArgs e)
    {
        Uri contactUri = new Uri(contactLink + "/co/startup?locale=en_US&order=last%2Cfirst");
        HttpClient client = new HttpClient();
        client.DefaultRequestHeaders.Add("Origin", "https://www.icloud.com");
        client.DefaultRequestHeaders.Add("Cookie", authToken + ";" + authUser + ";");

        HttpResponseMessage hrm = await client.GetAsync(contactUri);
        JObject contactJson = JObject.Parse(await hrm.Content.ReadAsStringAsync());
        ContactsBlock.Text += contactJson["contacts"];
    }


    private async Task<HttpResponseHeaderCollection> ContactApple(String authentication = "")
    {
        // The apple id and the password
        String appleId = "AppleID";
        String password = "Password" + authentication;

        // 
        // Post request will have this in the content
        String data = "{\"apple_id\":" + appleId + ", \"password\":" + password + ", \"extended_login\":true}";
        HttpStringContent content = new HttpStringContent(data, UnicodeEncoding.Utf8);

        // The URI to get the tokens from:
        Uri requestUri = new Uri("https://setup.icloud.com/setup/ws/1/accountLogin");

        // Create an instance of the HttpClient (Windows.Web.Http)
        HttpClient client = new HttpClient();

        // Add Origin = https://www.icloud.com in the header.
        client.DefaultRequestHeaders.Add("Origin", "https://www.icloud.com");

        // Post request and read response as JSON object (NewtonSoft)
        HttpResponseMessage hrm = await client.PostAsync(requestUri, content);
        JObject resp = JObject.Parse(await hrm.Content.ReadAsStringAsync());

        // Get the URL to the contacts
        contactLink = (String)resp["webservices"]["contacts"]["url"];

        // Read the headers for AUTH-TOKEN and AUTH-USER Cookies,
        HttpResponseHeaderCollection headers = hrm.Headers;
        if (headers.ContainsKey("Set-Cookie"))
        {
            String cookie = headers["Set-Cookie"];
            char[] separators = { ';', ',' };
            String[] tokens = cookie.Split(separators);
            foreach (String token in tokens)
            {
                int length = token.Length;
                if (token.Contains("X-APPLE-WEBAUTH-TOKEN"))
                {
                    authToken = token;
                }
                if (token.Contains("X-APPLE-WEBAUTH-USER"))
                {
                    authUser = token;
                }
            }
        }
        return headers;
    }
}

Step 2> If 2FA is activated, you will receive a request on your Apple Device to authorize the login and to use the provided code for logging in. Once you have the code, then repeat the same step as above only append the code to your password by providing in the authorization code text field.

请注意,您需要输入授权码。

Step 3> Hopefully, you would have now received tokens necessary for fetching the contacts. Now, you can fetch the contacts by using the contact link obtained in the response body while fetching the tokens and appending "/co/startup?locale=XXXXX&order=last%2Cfirst"

请注意,您必须使用适合您需要的语言环境。我需要使用 "en_US" 因为那是我在我的语言环境中的响应。您将需要使用请求中返回的语言环境。

XAML:

<Page
    x:Class="AddressBook.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:AddressBook"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">

    <StackPanel x:Name="MainStack">
        <Button x:Name="IcloudBtn" Content="Request Code" Click="IcloudBtn_Click"/>
        <TextBox x:Name="AuthenticationCode" Text="Authentication Code here." TextWrapping="Wrap"/>
        <Button x:Name="GetToken" Content="Get Token" Click="GetToken_Click"/>
        <Button x:Name="GetContacts" Content="Get Contacts" Click="GetContacts_Click"/>
        <TextBox x:Name="ContactsBlock" TextWrapping="Wrap"/>
    </StackPanel>
</Page>

但是,我仍然不知道如何实际操作联系人列表。我想如果我发现了,我会在这里更新。

因此,如果您只想获取联系人(并且采用 JSON 格式),我之前的回答可能是可行的方法。 但是,由于我希望能够对Apple帐户进行CRUD操作,更好的方法是使用icloud/apple支持的CARDDAV协议。

  1. Get the principle user using basic authentication (the password used in this basic authentication would have to be generated as an app password here) by firing a PROPFIND request at https://contacts.icloud.com with the request content:
<propfind
xmlns="DAV:">
<prop>
    <current-user-principal/>
</prop>
</propfind> 
  1. The previous step will let you retrieve a principle which will be of the format /1437425399/principal/. Now, you can fire a PROPFIND query the home-set for this user at the link https://contacts.icloud.com/1437425399/principal with the following request content:
<propfind
    xmlns="DAV:"
    xmlns:c="urn:ietf:params:xml:ns:carddav">
    <prop>
        <c:addressbook-home-set/>
    </prop>
</propfind>
  1. From the previous request, you will get the link to the homeset in the format https://p48-contacts.icloud.com:443/1437425399/carddavhome/. You can query where the vcards for the user exist using the following PROPFIND request at the home-set link:
<propfind
xmlns="DAV:">
<prop>
    <resourcetype/>
</prop>
</propfind>
  1. You will receive the place where all the cards are placed (eg. /1437425399/carddavhome/card/). Using a addressbook query, now you can initiate a REPORT request at the endpoint received in the previous link:
<c:addressbook-query
    xmlns="DAV:"
    xmlns:c="urn:ietf:params:xml:ns:carddav">
    <prop>
    </prop>
</c:addressbook-query>
  1. This will give you all the cards in the tag. You can then fetch multiple cards using a addressbook-multiget REPORT request:
<c:addressbook-multiget
    xmlns="DAV:"
    xmlns:c="urn:ietf:params:xml:ns:carddav">
    <prop>
        <getetag />
        <c:address-data />
    </prop>
    <href>/1437425399/carddavhome/card/somecard.vcf</href>
    <href>/1437425399/carddavhome/card/anothercard.vcf</href>
</c:addressbook-multiget>

为了更新、创建和删除卡片,您可以在“/1437425399/carddavhome/card/cardtobemanipulated.vcf”端点使用我们之前讨论的基本身份验证在将 Content-Type 设置为 [=24 后触发 PUT 请求=] 并在更新和创建请求的内容中发送 VCard。