0

我正在设置 Google Contacts CardDAV API 客户端。

OAuth 2.0 使用oauth2client.
请求使用requests.

from oauth2client import file, client, tools
import requests

SCOPES = 'https://www.googleapis.com/auth/carddav'
store = file.Storage('credentials.json')
creds = store.get()
if not creds or creds.invalid:
    flow = client.flow_from_clientsecrets('client_secret.json', SCOPES)
    creds = tools.run_flow(flow, store)
print(creds.access_token)

hed = {'Authorization': 'Bearer ' + creds.access_token}

response = requests.request('PROPFIND', 'https://www.googleapis.com/.well-known/carddav', headers=hed, allow_redirects=False)

if response.status_code == 301:
    location = response.headers['location']
    response = requests.request('PROPFIND', 'https://www.googleapis.com' + location, headers=hed)
    print(response.text)

但是当我请求获取地址簿的 url 时(我从Location第一个请求的标头中获取它),它返回错误:

{
  "error": {
    "code": 400,
    "message": "Request contains an invalid argument.",
    "status": "INVALID_ARGUMENT"
  }
}

完整的请求信息

第一个请求

requests.request('PROPFIND', 'https://www.googleapis.com/.well-known/carddav', headers=hed, allow_redirects=False)

REQUEST
=======
endpoint: PROPFIND https://www.googleapis.com/.well-known/carddav
headers:
  User-Agent: python-requests/2.22.0
  Accept-Encoding: gzip, deflate
  Accept: */*
  Connection: keep-alive
  Authorization: Bearer ya29.***********************************************
  Content-Length: 0
=======
RESPONSE
========
status_code: 301
headers:
  Content-Type: text/plain; charset=UTF-8
  X-XSS-Protection: 1; mode=block
  X-Content-Type-Options: nosniff
  Expires: Mon, 01 Jan 1990 00:00:00 GMT
  Cache-Control: no-cache, no-store, max-age=0, must-revalidate
  X-Frame-Options: SAMEORIGIN
  Location: /carddav/v1/principals/<my_email>/lists/default/
  Pragma: no-cache
  Vary: Origin, X-Origin, Referer
  Date: Fri, 21 Jun 2019 11:43:23 GMT
  Server: ESF
  Content-Length: 0
  Alt-Svc: quic=":443"; ma=2592000; v="46,44,43,39"
========

第二个请求

response = requests.request('PROPFIND', 'https://www.googleapis.com' + location, headers=hed)

REQUEST
=======
endpoint: PROPFIND https://www.googleapis.com/carddav/v1/principals/<my_email>/lists/default/
headers:
  User-Agent: python-requests/2.22.0
  Accept-Encoding: gzip, deflate
  Accept: */*
  Connection: keep-alive
  Authorization: Bearer ya29.***********************************************
  Content-Length: 0
=======
RESPONSE
========
status_code: 400
headers:
  Vary: Origin, X-Origin, Referer
  Content-Type: application/json; charset=UTF-8
  Date: Fri, 21 Jun 2019 11:43:23 GMT
  Server: ESF
  Content-Length: 127
  X-XSS-Protection: 0
  X-Frame-Options: SAMEORIGIN
  X-Content-Type-Options: nosniff
  Alt-Svc: quic=":443"; ma=2592000; v="46,44,43,39"
body:
  {
    "error": {
      "code": 400,
      "message": "Request contains an invalid argument.",
      "status": "INVALID_ARGUMENT"
    }
  }
========
4

1 回答 1

0

简短的回答:该PROPFIND方法是通用的,并且必须包含一个主体,该主体指定服务器应返回的信息。您必须在请求正文中传递一个 XML 有效负载,以标识您请求的属性。

获取地址簿 URI

根据Google 的 CardDav API 文档,您的第一个请求是完美的,会将您重定向到当前用户的通讯簿资源。以下是谷歌对下一步的描述:

然后,您的客户端程序可以通过在 上执行 PROPFINDaddressbook-home-set并查找addressbookcollection资源来发现主体地址簿。

让我解压缩:您的第二个请求应该查询在您从第一个请求获得的位置找到的用户资源列表。要正确执行此查询,您需要将 XML 正文与PROPFIND请求一起传递,如下所示:

    PROPFIND https://www.googleapis.com/carddav/v1/principals/<my_email>/lists/default/
    Authorization: Bearer ya29.***********************************************
    Depth: 1
    Content-Type: application/xml; charset=utf-8

    <D:propfind xmlns:D="DAV:">
      <D:prop>
         <D:resourcetype />
         <D:displayname />
      </D:prop>
    </D:propfind>

您可以在此处指定希望服务器响应的属性。您指定resourcetype属性是因为您只对包含联系人的资源addressbook或资源感兴趣。collection

此请求将返回资源的 URI 列表,您可以从中选择任何资源类型为addressbook或的资源collection

此时,您没有任何联系人,甚至没有联系人的 URI。您有一个用于用户地址簿或联系人集合的 URI 列表。(通常,只有其中一个,但可能有很多。)

您没有询问如何获取用户的联系人,但我假设这是您的最终目标,并将继续执行后续步骤。

获取联系人 URI

您的下一组请求将查询每个通讯簿 URI 以获取其联系人的 URI。循环上一个查询的每个结果,并PROPFIND使用此有效负载在 URI 上发出另一个请求:

    REPORT <addressbook_uri>
    Authorization: Bearer ya29.***********************************************
    Content-Type: application/xml; charset=utf-8

    <D:propfind xmlns:D="DAV:">
      <D:prop>
        <D:getetag />
        <D:getcontenttype />
      </D:prop>
    </D:propfind>

这里我们查询每个项目的内容类型,以便我们可以确定它是否是 VCard 类型。VCard 是合法的联系记录。

现在,您可以过滤这组结果,contenttype == 'text/vcard'以获取指向用户地址簿中每个联系人的新 URI 列表。

哦,伙计,我们越来越近了。

获取联系人电子名片

最后,将您的 URI 列表组装成实际的联系人数据并从服务器查询数据。

在这里,您将addressbook-multiget REPORT请求检索列表中的一批联系人。Google 没有说明您可以在请求中包含多少个联系 URI。我通常一次将我的请求限制在几百个。

例如

    REPORT <addressbook_uri>
    Authorization: Bearer ya29.***********************************************
    Content-Type: application/xml; charset=utf-8

    <C:addressbook-multiget xmlns:D="DAV:" xmlns:C="urn:ietf:params:xml:ns:carddav">
      <D:prop>
        <D:getetag/>
        <C:address-data>
          <C:allprop/>
        </C:address-data>
      </D:prop>
      <D:href>/carddav/v1/principals/<my_email>/lists/default/contact1.vcf</D:href>
      <D:href>/carddav/v1/principals/<my_email>/lists/default/contact2.vcf</D:href>
      ...
    </C:addressbook-multiget>

响应将包含每个联系人的 VCard 数据,打包在 XML 中。解析出 XML 文本,然后解析 VCard 数据以最终检索您的联系方式。

完毕!


资源:

于 2019-06-21T21:13:21.507 回答