From caefd295ae31ca61d6b1c8b5f1d36cfe66b19825 Mon Sep 17 00:00:00 2001 From: Harmon Date: Thu, 27 Oct 2022 21:54:28 -0500 Subject: [PATCH] Add support for Twitter API v2 Direct Messages endpoints Resolves #1995 --- ...ent_manage_and_lookup_direct_messages.yaml | 410 +++++++++++++++++ ...est_manage_and_lookup_direct_messages.yaml | 422 ++++++++++++++++++ docs/asyncclient.rst | 419 +++++++++-------- docs/client.rst | 419 +++++++++-------- docs/v2_models.rst | 5 + tests/test_asyncclient.py | 23 + tests/test_client.py | 21 + tweepy/__init__.py | 1 + tweepy/asynchronous/client.py | 232 +++++++++- tweepy/client.py | 247 +++++++++- tweepy/direct_message_event.py | 74 +++ tweepy/tweet.py | 14 +- 12 files changed, 1899 insertions(+), 388 deletions(-) create mode 100644 cassettes/test_asyncclient_manage_and_lookup_direct_messages.yaml create mode 100644 cassettes/test_manage_and_lookup_direct_messages.yaml create mode 100644 tweepy/direct_message_event.py diff --git a/cassettes/test_asyncclient_manage_and_lookup_direct_messages.yaml b/cassettes/test_asyncclient_manage_and_lookup_direct_messages.yaml new file mode 100644 index 0000000..4d7ecda --- /dev/null +++ b/cassettes/test_asyncclient_manage_and_lookup_direct_messages.yaml @@ -0,0 +1,410 @@ +interactions: +- request: + body: + text: Testing 1 + headers: + Content-Type: + - application/json + User-Agent: + - Python/3.10.0 aiohttp/3.8.1 Tweepy/4.11.0 + method: POST + uri: https://api.twitter.com/2/dm_conversations/with/750362064426721281/messages + response: + body: + string: '{"data":{"dm_conversation_id":"750362064426721281-1072250532645998596","dm_event_id":"1585823814058971140"}}' + headers: + Cache-Control: + - no-cache, no-store, max-age=0 + Content-Disposition: + - attachment; filename=json.json + Content-Encoding: + - gzip + Content-Length: + - '116' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 28 Oct 2022 02:40:35 UTC + Location: + - https://api.twitter.com/2/dm_events/"1585823814058971140" + Server: + - tsa_b + Set-Cookie: + - guest_id_marketing=v1%3A166692483505142170; Max-Age=63072000; Expires=Sun, + 27 Oct 2024 02:40:35 GMT; Path=/; Domain=.twitter.com; Secure; SameSite=None + - guest_id_ads=v1%3A166692483505142170; Max-Age=63072000; Expires=Sun, 27 Oct + 2024 02:40:35 GMT; Path=/; Domain=.twitter.com; Secure; SameSite=None + - personalization_id="v1_s0sOrj9Aez25nmQGn2ueog=="; Max-Age=63072000; Expires=Sun, + 27 Oct 2024 02:40:35 GMT; Path=/; Domain=.twitter.com; Secure; SameSite=None + - guest_id=v1%3A166692483505142170; Max-Age=63072000; Expires=Sun, 27 Oct 2024 + 02:40:35 GMT; Path=/; Domain=.twitter.com; Secure; SameSite=None + api-version: + - '2.55' + perf: + - '7626143928' + strict-transport-security: + - max-age=631138519 + x-access-level: + - read-write-directmessages + x-connection-hash: + - e64aa05d988c6b12f356b0b777b16e58067fd9f4f5b76e0aa03a755ded9ac882 + x-content-type-options: + - nosniff + x-frame-options: + - SAMEORIGIN + x-rate-limit-limit: + - '200' + x-rate-limit-remaining: + - '196' + x-rate-limit-reset: + - '1666925626' + x-response-time: + - '99' + x-transaction-id: + - 1ba0e69dac2d14c9 + x-xss-protection: + - '0' + status: + code: 201 + message: Created + url: https://api.twitter.com/2/dm_conversations/with/750362064426721281/messages +- request: + body: + text: Testing 2 + headers: + Content-Type: + - application/json + User-Agent: + - Python/3.10.0 aiohttp/3.8.1 Tweepy/4.11.0 + method: POST + uri: https://api.twitter.com/2/dm_conversations/750362064426721281-1072250532645998596/messages + response: + body: + string: '{"data":{"dm_conversation_id":"750362064426721281-1072250532645998596","dm_event_id":"1585823814956482567"}}' + headers: + Cache-Control: + - no-cache, no-store, max-age=0 + Content-Disposition: + - attachment; filename=json.json + Content-Encoding: + - gzip + Content-Length: + - '118' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 28 Oct 2022 02:40:35 UTC + Location: + - https://api.twitter.com/2/dm_events/"1585823814956482567" + Server: + - tsa_b + Set-Cookie: + - guest_id_marketing=v1%3A166692483526981171; Max-Age=63072000; Expires=Sun, + 27 Oct 2024 02:40:35 GMT; Path=/; Domain=.twitter.com; Secure; SameSite=None + - guest_id_ads=v1%3A166692483526981171; Max-Age=63072000; Expires=Sun, 27 Oct + 2024 02:40:35 GMT; Path=/; Domain=.twitter.com; Secure; SameSite=None + - personalization_id="v1_vOAhj7Rgpg4+d8XWHyuGvQ=="; Max-Age=63072000; Expires=Sun, + 27 Oct 2024 02:40:35 GMT; Path=/; Domain=.twitter.com; Secure; SameSite=None + - guest_id=v1%3A166692483526981171; Max-Age=63072000; Expires=Sun, 27 Oct 2024 + 02:40:35 GMT; Path=/; Domain=.twitter.com; Secure; SameSite=None + api-version: + - '2.55' + perf: + - '7626143928' + strict-transport-security: + - max-age=631138519 + x-access-level: + - read-write-directmessages + x-connection-hash: + - 816b3d3d7fa28b220cbf9fd64979c7aa9444e638bd19fa1521543b2607132975 + x-content-type-options: + - nosniff + x-frame-options: + - SAMEORIGIN + x-rate-limit-limit: + - '200' + x-rate-limit-remaining: + - '195' + x-rate-limit-reset: + - '1666925626' + x-response-time: + - '92' + x-transaction-id: + - ab04790d9a0ad556 + x-xss-protection: + - '0' + status: + code: 201 + message: Created + url: https://api.twitter.com/2/dm_conversations/750362064426721281-1072250532645998596/messages +- request: + body: + conversation_type: Group + message: + text: Testing + participant_ids: + - '145336962' + - '750362064426721281' + headers: + Content-Type: + - application/json + User-Agent: + - Python/3.10.0 aiohttp/3.8.1 Tweepy/4.11.0 + method: POST + uri: https://api.twitter.com/2/dm_conversations + response: + body: + string: '{"data":{"dm_conversation_id":"1585823815904395264","dm_event_id":"1585823815904395268"}}' + headers: + Cache-Control: + - no-cache, no-store, max-age=0 + Content-Disposition: + - attachment; filename=json.json + Content-Encoding: + - gzip + Content-Length: + - '91' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 28 Oct 2022 02:40:35 UTC + Location: + - https://api.twitter.com/2/dm_events/"1585823815904395268" + Server: + - tsa_b + Set-Cookie: + - guest_id_marketing=v1%3A166692483548929307; Max-Age=63072000; Expires=Sun, + 27 Oct 2024 02:40:35 GMT; Path=/; Domain=.twitter.com; Secure; SameSite=None + - guest_id_ads=v1%3A166692483548929307; Max-Age=63072000; Expires=Sun, 27 Oct + 2024 02:40:35 GMT; Path=/; Domain=.twitter.com; Secure; SameSite=None + - personalization_id="v1_/SSSm+jQ8/m+T74h092ffw=="; Max-Age=63072000; Expires=Sun, + 27 Oct 2024 02:40:35 GMT; Path=/; Domain=.twitter.com; Secure; SameSite=None + - guest_id=v1%3A166692483548929307; Max-Age=63072000; Expires=Sun, 27 Oct 2024 + 02:40:35 GMT; Path=/; Domain=.twitter.com; Secure; SameSite=None + api-version: + - '2.55' + perf: + - '7626143928' + strict-transport-security: + - max-age=631138519 + x-access-level: + - read-write-directmessages + x-connection-hash: + - 419970fa1e4f08780874d727938e832f372b494fd2b990a9d354f157420f7c2e + x-content-type-options: + - nosniff + x-frame-options: + - SAMEORIGIN + x-rate-limit-limit: + - '200' + x-rate-limit-remaining: + - '194' + x-rate-limit-reset: + - '1666925626' + x-response-time: + - '111' + x-transaction-id: + - 95312ec9aed6941f + x-xss-protection: + - '0' + status: + code: 201 + message: Created + url: https://api.twitter.com/2/dm_conversations +- request: + body: null + headers: + User-Agent: + - Python/3.10.0 aiohttp/3.8.1 Tweepy/4.11.0 + method: GET + uri: https://api.twitter.com/2/dm_events + response: + body: + string: '{"meta":{"result_count":0}}' + headers: + Cache-Control: + - no-cache, no-store, max-age=0 + Content-Disposition: + - attachment; filename=json.json + Content-Encoding: + - gzip + Content-Length: + - '53' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 28 Oct 2022 02:40:36 UTC + Server: + - tsa_b + Set-Cookie: + - guest_id_marketing=v1%3A166692483571694274; Max-Age=63072000; Expires=Sun, + 27 Oct 2024 02:40:36 GMT; Path=/; Domain=.twitter.com; Secure; SameSite=None + - guest_id_ads=v1%3A166692483571694274; Max-Age=63072000; Expires=Sun, 27 Oct + 2024 02:40:36 GMT; Path=/; Domain=.twitter.com; Secure; SameSite=None + - personalization_id="v1_JqIhrGbVz9y65V8gt0I0nw=="; Max-Age=63072000; Expires=Sun, + 27 Oct 2024 02:40:36 GMT; Path=/; Domain=.twitter.com; Secure; SameSite=None + - guest_id=v1%3A166692483571694274; Max-Age=63072000; Expires=Sun, 27 Oct 2024 + 02:40:36 GMT; Path=/; Domain=.twitter.com; Secure; SameSite=None + api-version: + - '2.55' + perf: + - '7626143928' + strict-transport-security: + - max-age=631138519 + x-access-level: + - read-write-directmessages + x-connection-hash: + - c61ab77e6b2edde82e41279fe30614c360c83986f2486df3cd5379ff6ee81efc + x-content-type-options: + - nosniff + x-frame-options: + - SAMEORIGIN + x-rate-limit-limit: + - '300' + x-rate-limit-remaining: + - '295' + x-rate-limit-reset: + - '1666925575' + x-response-time: + - '450' + x-transaction-id: + - ca2238d8b3bb8180 + x-xss-protection: + - '0' + status: + code: 200 + message: OK + url: https://api.twitter.com/2/dm_events +- request: + body: null + headers: + User-Agent: + - Python/3.10.0 aiohttp/3.8.1 Tweepy/4.11.0 + method: GET + uri: https://api.twitter.com/2/dm_conversations/750362064426721281-1072250532645998596/dm_events + response: + body: + string: '{"data":[{"event_type":"MessageCreate","id":"1585823814956482567","text":"Testing + 2"},{"event_type":"MessageCreate","id":"1585823814058971140","text":"Testing + 1"}],"meta":{"result_count":2,"next_token":"18LAA5G1V9DLILOG0G00ZZZZ","previous_token":"1BLC45G1V9DOTLG00S00ZZZZ"}}' + headers: + Cache-Control: + - no-cache, no-store, max-age=0 + Content-Disposition: + - attachment; filename=json.json + Content-Encoding: + - gzip + Content-Length: + - '201' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 28 Oct 2022 02:40:36 UTC + Server: + - tsa_b + Set-Cookie: + - guest_id_marketing=v1%3A166692483630137659; Max-Age=63072000; Expires=Sun, + 27 Oct 2024 02:40:36 GMT; Path=/; Domain=.twitter.com; Secure; SameSite=None + - guest_id_ads=v1%3A166692483630137659; Max-Age=63072000; Expires=Sun, 27 Oct + 2024 02:40:36 GMT; Path=/; Domain=.twitter.com; Secure; SameSite=None + - personalization_id="v1_q7EvxLEzivBP+d8d8n5iwQ=="; Max-Age=63072000; Expires=Sun, + 27 Oct 2024 02:40:36 GMT; Path=/; Domain=.twitter.com; Secure; SameSite=None + - guest_id=v1%3A166692483630137659; Max-Age=63072000; Expires=Sun, 27 Oct 2024 + 02:40:36 GMT; Path=/; Domain=.twitter.com; Secure; SameSite=None + api-version: + - '2.55' + perf: + - '7626143928' + strict-transport-security: + - max-age=631138519 + x-access-level: + - read-write-directmessages + x-connection-hash: + - 3eaad847b13a27123db04d3bc781a841f4f3a73b2da9fcdb1063276020d63e72 + x-content-type-options: + - nosniff + x-frame-options: + - SAMEORIGIN + x-rate-limit-limit: + - '300' + x-rate-limit-remaining: + - '294' + x-rate-limit-reset: + - '1666925575' + x-response-time: + - '240' + x-transaction-id: + - 6f38f4836665784c + x-xss-protection: + - '0' + status: + code: 200 + message: OK + url: https://api.twitter.com/2/dm_conversations/750362064426721281-1072250532645998596/dm_events +- request: + body: null + headers: + User-Agent: + - Python/3.10.0 aiohttp/3.8.1 Tweepy/4.11.0 + method: GET + uri: https://api.twitter.com/2/dm_conversations/with/750362064426721281/dm_events + response: + body: + string: '{"data":[{"event_type":"MessageCreate","id":"1585823814956482567","text":"Testing + 2"},{"event_type":"MessageCreate","id":"1585823814058971140","text":"Testing + 1"}],"meta":{"result_count":2,"next_token":"18LAA5G1V9DLILOG0G00ZZZZ","previous_token":"1BLC45G1V9DOTLG00S00ZZZZ"}}' + headers: + Cache-Control: + - no-cache, no-store, max-age=0 + Content-Disposition: + - attachment; filename=json.json + Content-Encoding: + - gzip + Content-Length: + - '201' + Content-Type: + - application/json; charset=utf-8 + Date: + - Fri, 28 Oct 2022 02:40:36 UTC + Server: + - tsa_b + Set-Cookie: + - guest_id_marketing=v1%3A166692483667194018; Max-Age=63072000; Expires=Sun, + 27 Oct 2024 02:40:36 GMT; Path=/; Domain=.twitter.com; Secure; SameSite=None + - guest_id_ads=v1%3A166692483667194018; Max-Age=63072000; Expires=Sun, 27 Oct + 2024 02:40:36 GMT; Path=/; Domain=.twitter.com; Secure; SameSite=None + - personalization_id="v1_z5H6tv+pvYGBnAirlfFEDQ=="; Max-Age=63072000; Expires=Sun, + 27 Oct 2024 02:40:36 GMT; Path=/; Domain=.twitter.com; Secure; SameSite=None + - guest_id=v1%3A166692483667194018; Max-Age=63072000; Expires=Sun, 27 Oct 2024 + 02:40:36 GMT; Path=/; Domain=.twitter.com; Secure; SameSite=None + api-version: + - '2.55' + perf: + - '7626143928' + strict-transport-security: + - max-age=631138519 + x-access-level: + - read-write-directmessages + x-connection-hash: + - b46452f9f641533ca331b6f3fef745c7e077f6526cbca084d0042a1a9bbb9326 + x-content-type-options: + - nosniff + x-frame-options: + - SAMEORIGIN + x-rate-limit-limit: + - '300' + x-rate-limit-remaining: + - '293' + x-rate-limit-reset: + - '1666925575' + x-response-time: + - '248' + x-transaction-id: + - 91411ce0949616ad + x-xss-protection: + - '0' + status: + code: 200 + message: OK + url: https://api.twitter.com/2/dm_conversations/with/750362064426721281/dm_events +version: 1 diff --git a/cassettes/test_manage_and_lookup_direct_messages.yaml b/cassettes/test_manage_and_lookup_direct_messages.yaml new file mode 100644 index 0000000..f46eebd --- /dev/null +++ b/cassettes/test_manage_and_lookup_direct_messages.yaml @@ -0,0 +1,422 @@ +interactions: +- request: + body: '{"text": "Testing 1"}' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '21' + Content-Type: + - application/json + User-Agent: + - Python/3.10.0 Requests/2.27.1 Tweepy/4.11.0 + method: POST + uri: https://api.twitter.com/2/dm_conversations/with/750362064426721281/messages + response: + body: + string: !!binary | + H4sIAAAAAAAAACTLQQqEMAwF0LtkPULy26Spl5Fiu3BhhZniRrz7IL79u6iWUWi+qO7LevSzfX9l + bEdftkozJeVgYIsRliBwmYQToKwBFjVn12z0eXY7Wx9vE3V1ZpMADS4AnO77DwAA//8DAK1K7bhs + AAAA + headers: + api-version: + - '2.55' + cache-control: + - no-cache, no-store, max-age=0 + content-disposition: + - attachment; filename=json.json + content-encoding: + - gzip + content-length: + - '117' + content-type: + - application/json; charset=utf-8 + date: + - Fri, 28 Oct 2022 01:08:23 UTC + location: + - https://api.twitter.com/2/dm_events/"1585800613253812228" + perf: + - '7626143928' + server: + - tsa_b + set-cookie: + - guest_id_marketing=v1%3A166691930357241307; Max-Age=63072000; Expires=Sun, + 27 Oct 2024 01:08:23 GMT; Path=/; Domain=.twitter.com; Secure; SameSite=None + - guest_id_ads=v1%3A166691930357241307; Max-Age=63072000; Expires=Sun, 27 Oct + 2024 01:08:23 GMT; Path=/; Domain=.twitter.com; Secure; SameSite=None + - personalization_id="v1_Q7bH83VtVAsTu/8rDJi07A=="; Max-Age=63072000; Expires=Sun, + 27 Oct 2024 01:08:23 GMT; Path=/; Domain=.twitter.com; Secure; SameSite=None + - guest_id=v1%3A166691930357241307; Max-Age=63072000; Expires=Sun, 27 Oct 2024 + 01:08:23 GMT; Path=/; Domain=.twitter.com; Secure; SameSite=None + strict-transport-security: + - max-age=631138519 + x-access-level: + - read-write-directmessages + x-connection-hash: + - 6606c0b67f5c1549f59c56f029b51c3767fa3d7d2e303df11caf4fa499a0299e + x-content-type-options: + - nosniff + x-frame-options: + - SAMEORIGIN + x-rate-limit-limit: + - '200' + x-rate-limit-remaining: + - '199' + x-rate-limit-reset: + - '1666920203' + x-response-time: + - '82' + x-transaction-id: + - 8a5ee86aeacaa300 + x-xss-protection: + - '0' + status: + code: 201 + message: Created +- request: + body: '{"text": "Testing 2"}' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '21' + Content-Type: + - application/json + Cookie: + - guest_id=v1%3A166691930357241307; guest_id_ads=v1%3A166691930357241307; guest_id_marketing=v1%3A166691930357241307; + personalization_id="v1_Q7bH83VtVAsTu/8rDJi07A==" + User-Agent: + - Python/3.10.0 Requests/2.27.1 Tweepy/4.11.0 + method: POST + uri: https://api.twitter.com/2/dm_conversations/750362064426721281-1072250532645998596/messages + response: + body: + string: !!binary | + H4sIAAAAAAAAACTLQQqEMAwF0LtkPUKS9qepl5Fiu3BhhZniRrz7IL79u6iWUWi+qO7LevSzfX9l + bEdftkozJXAwZYtRLamoyyScVMEIahE5O7LR59ntbH28TeBwZpOQoO4iMLrvPwAAAP//AwC4jkA1 + bAAAAA== + headers: + api-version: + - '2.55' + cache-control: + - no-cache, no-store, max-age=0 + content-disposition: + - attachment; filename=json.json + content-encoding: + - gzip + content-length: + - '118' + content-type: + - application/json; charset=utf-8 + date: + - Fri, 28 Oct 2022 01:08:23 UTC + location: + - https://api.twitter.com/2/dm_events/"1585800613752881156" + perf: + - '7626143928' + server: + - tsa_b + strict-transport-security: + - max-age=631138519 + x-access-level: + - read-write-directmessages + x-connection-hash: + - 6606c0b67f5c1549f59c56f029b51c3767fa3d7d2e303df11caf4fa499a0299e + x-content-type-options: + - nosniff + x-frame-options: + - SAMEORIGIN + x-rate-limit-limit: + - '200' + x-rate-limit-remaining: + - '198' + x-rate-limit-reset: + - '1666920203' + x-response-time: + - '71' + x-transaction-id: + - 8d2db836db90cb70 + x-xss-protection: + - '0' + status: + code: 201 + message: Created +- request: + body: '{"conversation_type": "Group", "message": {"text": "Testing"}, "participant_ids": + ["145336962", "750362064426721281"]}' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '118' + Content-Type: + - application/json + Cookie: + - guest_id=v1%3A166691930357241307; guest_id_ads=v1%3A166691930357241307; guest_id_marketing=v1%3A166691930357241307; + personalization_id="v1_Q7bH83VtVAsTu/8rDJi07A==" + User-Agent: + - Python/3.10.0 Requests/2.27.1 Tweepy/4.11.0 + method: POST + uri: https://api.twitter.com/2/dm_conversations + response: + body: + string: !!binary | + H4sIAAAAAAAAAKpWSkksSVSyqlZKyY1Pzs8rSy0qTizJzM+Lz0xRslIyNLUwtTAwMDM0MTYzNTa2 + NDOxUNIBKU0tS80rwaHG1EipthYAAAD//wMA/NX3P1kAAAA= + headers: + api-version: + - '2.55' + cache-control: + - no-cache, no-store, max-age=0 + content-disposition: + - attachment; filename=json.json + content-encoding: + - gzip + content-length: + - '92' + content-type: + - application/json; charset=utf-8 + date: + - Fri, 28 Oct 2022 01:08:23 UTC + location: + - https://api.twitter.com/2/dm_events/"1585800614365339652" + perf: + - '7626143928' + server: + - tsa_b + strict-transport-security: + - max-age=631138519 + x-access-level: + - read-write-directmessages + x-connection-hash: + - 6606c0b67f5c1549f59c56f029b51c3767fa3d7d2e303df11caf4fa499a0299e + x-content-type-options: + - nosniff + x-frame-options: + - SAMEORIGIN + x-rate-limit-limit: + - '200' + x-rate-limit-remaining: + - '197' + x-rate-limit-reset: + - '1666920203' + x-response-time: + - '114' + x-transaction-id: + - bd8c8860e7589c88 + x-xss-protection: + - '0' + status: + code: 201 + message: Created +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Cookie: + - guest_id=v1%3A166691930357241307; guest_id_ads=v1%3A166691930357241307; guest_id_marketing=v1%3A166691930357241307; + personalization_id="v1_Q7bH83VtVAsTu/8rDJi07A==" + User-Agent: + - Python/3.10.0 Requests/2.27.1 Tweepy/4.11.0 + method: GET + uri: https://api.twitter.com/2/dm_events + response: + body: + string: !!binary | + H4sIAAAAAAAAAKpWyk0tSVSyqlYqSi0uzSmJT84vzStRsjKorQUAAAD//wMAMxjNjxsAAAA= + headers: + api-version: + - '2.55' + cache-control: + - no-cache, no-store, max-age=0 + content-disposition: + - attachment; filename=json.json + content-encoding: + - gzip + content-length: + - '53' + content-type: + - application/json; charset=utf-8 + date: + - Fri, 28 Oct 2022 01:08:24 UTC + perf: + - '7626143928' + server: + - tsa_b + strict-transport-security: + - max-age=631138519 + x-access-level: + - read-write-directmessages + x-connection-hash: + - 6606c0b67f5c1549f59c56f029b51c3767fa3d7d2e303df11caf4fa499a0299e + x-content-type-options: + - nosniff + x-frame-options: + - SAMEORIGIN + x-rate-limit-limit: + - '300' + x-rate-limit-remaining: + - '299' + x-rate-limit-reset: + - '1666920203' + x-response-time: + - '402' + x-transaction-id: + - 532b9bbaaab076f0 + x-xss-protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Cookie: + - guest_id=v1%3A166691930357241307; guest_id_ads=v1%3A166691930357241307; guest_id_marketing=v1%3A166691930357241307; + personalization_id="v1_Q7bH83VtVAsTu/8rDJi07A==" + User-Agent: + - Python/3.10.0 Requests/2.27.1 Tweepy/4.11.0 + method: GET + uri: https://api.twitter.com/2/dm_conversations/750362064426721281-1072250532645998596/dm_events + response: + body: + string: !!binary | + H4sIAAAAAAAAAJSOwQqCQBRFfyXe2sXM2NjgzlxIMFZUqyJE8iFSjeI8pRD/vZGgFq26y3sPhztA + kVMO4WkA7NFQRs8GIYQUrc1LjFvMCcGDqnAll0oqxgLuL6RQinMZuInwQW48oKXKlDMBo/efTEhf + cSGE+pVxGM8e3HG6OECLtrtRdqk74yDhgXF0RvUVzSRUOopkwvearXdyk7CEsaOLszYt9lXd2S+7 + 1PH8zW5TvWIfdhxfAAAA//8DAPQRV/YSAQAA + headers: + api-version: + - '2.55' + cache-control: + - no-cache, no-store, max-age=0 + content-disposition: + - attachment; filename=json.json + content-encoding: + - gzip + content-length: + - '198' + content-type: + - application/json; charset=utf-8 + date: + - Fri, 28 Oct 2022 01:08:24 UTC + perf: + - '7626143928' + server: + - tsa_b + strict-transport-security: + - max-age=631138519 + x-access-level: + - read-write-directmessages + x-connection-hash: + - 6606c0b67f5c1549f59c56f029b51c3767fa3d7d2e303df11caf4fa499a0299e + x-content-type-options: + - nosniff + x-frame-options: + - SAMEORIGIN + x-rate-limit-limit: + - '300' + x-rate-limit-remaining: + - '298' + x-rate-limit-reset: + - '1666920203' + x-response-time: + - '252' + x-transaction-id: + - 97e15c5a26d66f34 + x-xss-protection: + - '0' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Cookie: + - guest_id=v1%3A166691930357241307; guest_id_ads=v1%3A166691930357241307; guest_id_marketing=v1%3A166691930357241307; + personalization_id="v1_Q7bH83VtVAsTu/8rDJi07A==" + User-Agent: + - Python/3.10.0 Requests/2.27.1 Tweepy/4.11.0 + method: GET + uri: https://api.twitter.com/2/dm_conversations/with/750362064426721281/dm_events + response: + body: + string: !!binary | + H4sIAAAAAAAAAJSOwQqCQBRFfyXe2sXM2NjgzlxIMFZUqyJE8iFSjeI8pRD/vZGgFq26y3sPhztA + kVMO4WkA7NFQRs8GIYQUrc1LjFvMCcGDqnAll0oqxgLuL6RQinMZuInwQW48oKXKlDMBo/efTEhf + cSGE+pVxGM8e3HG6OECLtrtRdqk74yDhgXF0RvUVzSRUOopkwvearXdyk7CEsaOLszYt9lXd2S+7 + 1PH8zW5TvWIfdhxfAAAA//8DAPQRV/YSAQAA + headers: + api-version: + - '2.55' + cache-control: + - no-cache, no-store, max-age=0 + content-disposition: + - attachment; filename=json.json + content-encoding: + - gzip + content-length: + - '198' + content-type: + - application/json; charset=utf-8 + date: + - Fri, 28 Oct 2022 01:08:24 UTC + perf: + - '7626143928' + server: + - tsa_b + strict-transport-security: + - max-age=631138519 + x-access-level: + - read-write-directmessages + x-connection-hash: + - 6606c0b67f5c1549f59c56f029b51c3767fa3d7d2e303df11caf4fa499a0299e + x-content-type-options: + - nosniff + x-frame-options: + - SAMEORIGIN + x-rate-limit-limit: + - '300' + x-rate-limit-remaining: + - '297' + x-rate-limit-reset: + - '1666920203' + x-response-time: + - '249' + x-transaction-id: + - 445b49d7f86564e1 + x-xss-protection: + - '0' + status: + code: 200 + message: OK +version: 1 diff --git a/docs/asyncclient.rst b/docs/asyncclient.rst index 7d37f93..0f9f0f6 100644 --- a/docs/asyncclient.rst +++ b/docs/asyncclient.rst @@ -11,195 +11,213 @@ .. table:: :align: center - +--------------------------------------------------------------+---------------------------------------------+ - | Twitter API v2 Endpoint | :class:`AsyncClient` Method | - +==============================================================+=============================================+ - | .. centered:: :ref:`Tweets` | - +--------------------------------------------------------------+---------------------------------------------+ - | .. centered:: |Bookmarks|_ | - +--------------------------------------------------------------+---------------------------------------------+ - | `DELETE /2/users/:id/bookmarks/:tweet_id`_ | :meth:`AsyncClient.remove_bookmark` | - +--------------------------------------------------------------+---------------------------------------------+ - | `GET /2/users/:id/bookmarks`_ | :meth:`AsyncClient.get_bookmarks` | - +--------------------------------------------------------------+---------------------------------------------+ - | `POST /2/users/:id/bookmarks`_ | :meth:`AsyncClient.bookmark` | - +--------------------------------------------------------------+---------------------------------------------+ - | .. centered:: |Hide replies|_ | - +--------------------------------------------------------------+---------------------------------------------+ - | `PUT /2/tweets/:id/hidden`_ | :meth:`AsyncClient.hide_reply` | - +--------------------------------------------------------------+---------------------------------------------+ - | `PUT /2/tweets/:id/hidden`_ | :meth:`AsyncClient.unhide_reply` | - +--------------------------------------------------------------+---------------------------------------------+ - | .. centered:: |Likes|_ | - +--------------------------------------------------------------+---------------------------------------------+ - | `DELETE /2/users/:id/likes/:tweet_id`_ | :meth:`AsyncClient.unlike` | - +--------------------------------------------------------------+---------------------------------------------+ - | `GET /2/tweets/:id/liking_users`_ | :meth:`AsyncClient.get_liking_users` | - +--------------------------------------------------------------+---------------------------------------------+ - | `GET /2/users/:id/liked_tweets`_ | :meth:`AsyncClient.get_liked_tweets` | - +--------------------------------------------------------------+---------------------------------------------+ - | `POST /2/users/:id/likes`_ | :meth:`AsyncClient.like` | - +--------------------------------------------------------------+---------------------------------------------+ - | .. centered:: |Manage Tweets|_ | - +--------------------------------------------------------------+---------------------------------------------+ - | `DELETE /2/tweets/:id`_ | :meth:`AsyncClient.delete_tweet` | - +--------------------------------------------------------------+---------------------------------------------+ - | `POST /2/tweets`_ | :meth:`AsyncClient.create_tweet` | - +--------------------------------------------------------------+---------------------------------------------+ - | .. centered:: |Quote Tweets|_ | - +--------------------------------------------------------------+---------------------------------------------+ - | `GET /2/tweets/:id/quote_tweets`_ | :meth:`AsyncClient.get_quote_tweets` | - +--------------------------------------------------------------+---------------------------------------------+ - | .. centered:: |Retweets|_ | - +--------------------------------------------------------------+---------------------------------------------+ - | `DELETE /2/users/:id/retweets/:source_tweet_id`_ | :meth:`AsyncClient.unretweet` | - +--------------------------------------------------------------+---------------------------------------------+ - | `GET /2/tweets/:id/retweeted_by`_ | :meth:`AsyncClient.get_retweeters` | - +--------------------------------------------------------------+---------------------------------------------+ - | `POST /2/users/:id/retweets`_ | :meth:`AsyncClient.retweet` | - +--------------------------------------------------------------+---------------------------------------------+ - | .. centered:: |Search Tweets|_ | - +--------------------------------------------------------------+---------------------------------------------+ - | `GET /2/tweets/search/all`_ | :meth:`AsyncClient.search_all_tweets` | - +--------------------------------------------------------------+---------------------------------------------+ - | `GET /2/tweets/search/recent`_ | :meth:`AsyncClient.search_recent_tweets` | - +--------------------------------------------------------------+---------------------------------------------+ - | .. centered:: |Timelines|_ | - +--------------------------------------------------------------+---------------------------------------------+ - | `GET /2/users/:id/mentions`_ | :meth:`AsyncClient.get_users_mentions` | - +--------------------------------------------------------------+---------------------------------------------+ - | `GET /2/users/:id/timelines/reverse_chronological`_ | :meth:`AsyncClient.get_home_timeline` | - +--------------------------------------------------------------+---------------------------------------------+ - | `GET /2/users/:id/tweets`_ | :meth:`AsyncClient.get_users_tweets` | - +--------------------------------------------------------------+---------------------------------------------+ - | .. centered:: |Tweet counts|_ | - +--------------------------------------------------------------+---------------------------------------------+ - | `GET /2/tweets/counts/all`_ | :meth:`AsyncClient.get_all_tweets_count` | - +--------------------------------------------------------------+---------------------------------------------+ - | `GET /2/tweets/counts/recent`_ | :meth:`AsyncClient.get_recent_tweets_count` | - +--------------------------------------------------------------+---------------------------------------------+ - | .. centered:: |Tweet lookup|_ | - +--------------------------------------------------------------+---------------------------------------------+ - | `GET /2/tweets/:id`_ | :meth:`AsyncClient.get_tweet` | - +--------------------------------------------------------------+---------------------------------------------+ - | `GET /2/tweets`_ | :meth:`AsyncClient.get_tweets` | - +--------------------------------------------------------------+---------------------------------------------+ - | .. centered:: :ref:`Users` | - +--------------------------------------------------------------+---------------------------------------------+ - | .. centered:: |Blocks|_ | - +--------------------------------------------------------------+---------------------------------------------+ - | `DELETE /2/users/:source_user_id/blocking/:target_user_id`_ | :meth:`AsyncClient.unblock` | - +--------------------------------------------------------------+---------------------------------------------+ - | `GET /2/users/:id/blocking`_ | :meth:`AsyncClient.get_blocked` | - +--------------------------------------------------------------+---------------------------------------------+ - | `POST /2/users/:id/blocking`_ | :meth:`AsyncClient.block` | - +--------------------------------------------------------------+---------------------------------------------+ - | .. centered:: |Follows|_ | - +--------------------------------------------------------------+---------------------------------------------+ - | `DELETE /2/users/:source_user_id/following/:target_user_id`_ | :meth:`AsyncClient.unfollow_user` | - +--------------------------------------------------------------+---------------------------------------------+ - | `GET /2/users/:id/followers`_ | :meth:`AsyncClient.get_users_followers` | - +--------------------------------------------------------------+---------------------------------------------+ - | `GET /2/users/:id/following`_ | :meth:`AsyncClient.get_users_following` | - +--------------------------------------------------------------+---------------------------------------------+ - | `POST /2/users/:id/following`_ | :meth:`AsyncClient.follow_user` | - +--------------------------------------------------------------+---------------------------------------------+ - | .. centered:: |Mutes|_ | - +--------------------------------------------------------------+---------------------------------------------+ - | `DELETE /2/users/:source_user_id/muting/:target_user_id`_ | :meth:`AsyncClient.unmute` | - +--------------------------------------------------------------+---------------------------------------------+ - | `GET /2/users/:id/muting`_ | :meth:`AsyncClient.get_muted` | - +--------------------------------------------------------------+---------------------------------------------+ - | `POST /2/users/:id/muting`_ | :meth:`AsyncClient.mute` | - +--------------------------------------------------------------+---------------------------------------------+ - | .. centered:: |User lookup|_ | - +--------------------------------------------------------------+---------------------------------------------+ - | `GET /2/users/:id`_ | :meth:`AsyncClient.get_user` | - +--------------------------------------------------------------+---------------------------------------------+ - | `GET /2/users/by/username/:username`_ | :meth:`AsyncClient.get_user` | - +--------------------------------------------------------------+---------------------------------------------+ - | `GET /2/users`_ | :meth:`AsyncClient.get_users` | - +--------------------------------------------------------------+---------------------------------------------+ - | `GET /2/users/by`_ | :meth:`AsyncClient.get_users` | - +--------------------------------------------------------------+---------------------------------------------+ - | `GET /2/users/me`_ | :meth:`AsyncClient.get_me` | - +--------------------------------------------------------------+---------------------------------------------+ - | .. centered:: :ref:`Spaces` | - +--------------------------------------------------------------+---------------------------------------------+ - | .. centered:: |Search Spaces|_ | - +--------------------------------------------------------------+---------------------------------------------+ - | `GET /2/spaces/search`_ | :meth:`AsyncClient.search_spaces` | - +--------------------------------------------------------------+---------------------------------------------+ - | .. centered:: |Spaces lookup|_ | - +--------------------------------------------------------------+---------------------------------------------+ - | `GET /2/spaces`_ | :meth:`AsyncClient.get_spaces` | - +--------------------------------------------------------------+---------------------------------------------+ - | `GET /2/spaces/:id`_ | :meth:`AsyncClient.get_space` | - +--------------------------------------------------------------+---------------------------------------------+ - | `GET /2/spaces/:id/buyers`_ | :meth:`AsyncClient.get_space_buyers` | - +--------------------------------------------------------------+---------------------------------------------+ - | `GET /2/spaces/:id/tweets`_ | :meth:`AsyncClient.get_space_tweets` | - +--------------------------------------------------------------+---------------------------------------------+ - | `GET /2/spaces/by/creator_ids`_ | :meth:`AsyncClient.get_spaces` | - +--------------------------------------------------------------+---------------------------------------------+ - | .. centered:: :ref:`Lists` | - +--------------------------------------------------------------+---------------------------------------------+ - | .. centered:: |List Tweets lookup|_ | - +--------------------------------------------------------------+---------------------------------------------+ - | `GET /2/lists/:id/tweets`_ | :meth:`AsyncClient.get_list_tweets` | - +--------------------------------------------------------------+---------------------------------------------+ - | .. centered:: |List follows|_ | - +--------------------------------------------------------------+---------------------------------------------+ - | `DELETE /2/users/:id/followed_lists/:list_id`_ | :meth:`AsyncClient.unfollow_list` | - +--------------------------------------------------------------+---------------------------------------------+ - | `GET /2/lists/:id/followers`_ | :meth:`AsyncClient.get_list_followers` | - +--------------------------------------------------------------+---------------------------------------------+ - | `GET /2/users/:id/followed_lists`_ | :meth:`AsyncClient.get_followed_lists` | - +--------------------------------------------------------------+---------------------------------------------+ - | `POST /2/users/:id/followed_lists`_ | :meth:`AsyncClient.follow_list` | - +--------------------------------------------------------------+---------------------------------------------+ - | .. centered:: |List lookup|_ | - +--------------------------------------------------------------+---------------------------------------------+ - | `GET /2/lists/:id`_ | :meth:`AsyncClient.get_list` | - +--------------------------------------------------------------+---------------------------------------------+ - | `GET /2/users/:id/owned_lists`_ | :meth:`AsyncClient.get_owned_lists` | - +--------------------------------------------------------------+---------------------------------------------+ - | .. centered:: |List members|_ | - +--------------------------------------------------------------+---------------------------------------------+ - | `DELETE /2/lists/:id/members/:user_id`_ | :meth:`AsyncClient.remove_list_member` | - +--------------------------------------------------------------+---------------------------------------------+ - | `GET /2/lists/:id/members`_ | :meth:`AsyncClient.get_list_members` | - +--------------------------------------------------------------+---------------------------------------------+ - | `GET /2/users/:id/list_memberships`_ | :meth:`AsyncClient.get_list_memberships` | - +--------------------------------------------------------------+---------------------------------------------+ - | `POST /2/lists/:id/members`_ | :meth:`AsyncClient.add_list_member` | - +--------------------------------------------------------------+---------------------------------------------+ - | .. centered:: |Manage Lists|_ | - +--------------------------------------------------------------+---------------------------------------------+ - | `DELETE /2/lists/:id`_ | :meth:`AsyncClient.delete_list` | - +--------------------------------------------------------------+---------------------------------------------+ - | `PUT /2/lists/:id`_ | :meth:`AsyncClient.update_list` | - +--------------------------------------------------------------+---------------------------------------------+ - | `POST /2/lists`_ | :meth:`AsyncClient.create_list` | - +--------------------------------------------------------------+---------------------------------------------+ - | .. centered:: |Pinned Lists|_ | - +--------------------------------------------------------------+---------------------------------------------+ - | `DELETE /2/users/:id/pinned_lists/:list_id`_ | :meth:`AsyncClient.unpin_list` | - +--------------------------------------------------------------+---------------------------------------------+ - | `GET /2/users/:id/pinned_lists`_ | :meth:`AsyncClient.get_pinned_lists` | - +--------------------------------------------------------------+---------------------------------------------+ - | `POST /2/users/:id/pinned_lists`_ | :meth:`AsyncClient.pin_list` | - +--------------------------------------------------------------+---------------------------------------------+ - | .. centered:: :ref:`Compliance` | - +--------------------------------------------------------------+---------------------------------------------+ - | .. centered:: |Batch Compliance|_ | - +--------------------------------------------------------------+---------------------------------------------+ - | `GET /2/compliance/jobs`_ | :meth:`AsyncClient.get_compliance_jobs` | - +--------------------------------------------------------------+---------------------------------------------+ - | `GET /2/compliance/jobs/:id`_ | :meth:`AsyncClient.get_compliance_job` | - +--------------------------------------------------------------+---------------------------------------------+ - | `POST /2/compliance/jobs`_ | :meth:`AsyncClient.create_compliance_job` | - +--------------------------------------------------------------+---------------------------------------------+ + +--------------------------------------------------------------+--------------------------------------------------------+ + | Twitter API v2 Endpoint | :class:`AsyncClient` Method | + +==============================================================+========================================================+ + | .. centered:: :ref:`Tweets` | + +--------------------------------------------------------------+--------------------------------------------------------+ + | .. centered:: |Bookmarks|_ | + +--------------------------------------------------------------+--------------------------------------------------------+ + | `DELETE /2/users/:id/bookmarks/:tweet_id`_ | :meth:`AsyncClient.remove_bookmark` | + +--------------------------------------------------------------+--------------------------------------------------------+ + | `GET /2/users/:id/bookmarks`_ | :meth:`AsyncClient.get_bookmarks` | + +--------------------------------------------------------------+--------------------------------------------------------+ + | `POST /2/users/:id/bookmarks`_ | :meth:`AsyncClient.bookmark` | + +--------------------------------------------------------------+--------------------------------------------------------+ + | .. centered:: |Hide replies|_ | + +--------------------------------------------------------------+--------------------------------------------------------+ + | `PUT /2/tweets/:id/hidden`_ | :meth:`AsyncClient.hide_reply` | + +--------------------------------------------------------------+--------------------------------------------------------+ + | `PUT /2/tweets/:id/hidden`_ | :meth:`AsyncClient.unhide_reply` | + +--------------------------------------------------------------+--------------------------------------------------------+ + | .. centered:: |Likes|_ | + +--------------------------------------------------------------+--------------------------------------------------------+ + | `DELETE /2/users/:id/likes/:tweet_id`_ | :meth:`AsyncClient.unlike` | + +--------------------------------------------------------------+--------------------------------------------------------+ + | `GET /2/tweets/:id/liking_users`_ | :meth:`AsyncClient.get_liking_users` | + +--------------------------------------------------------------+--------------------------------------------------------+ + | `GET /2/users/:id/liked_tweets`_ | :meth:`AsyncClient.get_liked_tweets` | + +--------------------------------------------------------------+--------------------------------------------------------+ + | `POST /2/users/:id/likes`_ | :meth:`AsyncClient.like` | + +--------------------------------------------------------------+--------------------------------------------------------+ + | .. centered:: |Manage Tweets|_ | + +--------------------------------------------------------------+--------------------------------------------------------+ + | `DELETE /2/tweets/:id`_ | :meth:`AsyncClient.delete_tweet` | + +--------------------------------------------------------------+--------------------------------------------------------+ + | `POST /2/tweets`_ | :meth:`AsyncClient.create_tweet` | + +--------------------------------------------------------------+--------------------------------------------------------+ + | .. centered:: |Quote Tweets|_ | + +--------------------------------------------------------------+--------------------------------------------------------+ + | `GET /2/tweets/:id/quote_tweets`_ | :meth:`AsyncClient.get_quote_tweets` | + +--------------------------------------------------------------+--------------------------------------------------------+ + | .. centered:: |Retweets|_ | + +--------------------------------------------------------------+--------------------------------------------------------+ + | `DELETE /2/users/:id/retweets/:source_tweet_id`_ | :meth:`AsyncClient.unretweet` | + +--------------------------------------------------------------+--------------------------------------------------------+ + | `GET /2/tweets/:id/retweeted_by`_ | :meth:`AsyncClient.get_retweeters` | + +--------------------------------------------------------------+--------------------------------------------------------+ + | `POST /2/users/:id/retweets`_ | :meth:`AsyncClient.retweet` | + +--------------------------------------------------------------+--------------------------------------------------------+ + | .. centered:: |Search Tweets|_ | + +--------------------------------------------------------------+--------------------------------------------------------+ + | `GET /2/tweets/search/all`_ | :meth:`AsyncClient.search_all_tweets` | + +--------------------------------------------------------------+--------------------------------------------------------+ + | `GET /2/tweets/search/recent`_ | :meth:`AsyncClient.search_recent_tweets` | + +--------------------------------------------------------------+--------------------------------------------------------+ + | .. centered:: |Timelines|_ | + +--------------------------------------------------------------+--------------------------------------------------------+ + | `GET /2/users/:id/mentions`_ | :meth:`AsyncClient.get_users_mentions` | + +--------------------------------------------------------------+--------------------------------------------------------+ + | `GET /2/users/:id/timelines/reverse_chronological`_ | :meth:`AsyncClient.get_home_timeline` | + +--------------------------------------------------------------+--------------------------------------------------------+ + | `GET /2/users/:id/tweets`_ | :meth:`AsyncClient.get_users_tweets` | + +--------------------------------------------------------------+--------------------------------------------------------+ + | .. centered:: |Tweet counts|_ | + +--------------------------------------------------------------+--------------------------------------------------------+ + | `GET /2/tweets/counts/all`_ | :meth:`AsyncClient.get_all_tweets_count` | + +--------------------------------------------------------------+--------------------------------------------------------+ + | `GET /2/tweets/counts/recent`_ | :meth:`AsyncClient.get_recent_tweets_count` | + +--------------------------------------------------------------+--------------------------------------------------------+ + | .. centered:: |Tweet lookup|_ | + +--------------------------------------------------------------+--------------------------------------------------------+ + | `GET /2/tweets/:id`_ | :meth:`AsyncClient.get_tweet` | + +--------------------------------------------------------------+--------------------------------------------------------+ + | `GET /2/tweets`_ | :meth:`AsyncClient.get_tweets` | + +--------------------------------------------------------------+--------------------------------------------------------+ + | .. centered:: :ref:`Users` | + +--------------------------------------------------------------+--------------------------------------------------------+ + | .. centered:: |Blocks|_ | + +--------------------------------------------------------------+--------------------------------------------------------+ + | `DELETE /2/users/:source_user_id/blocking/:target_user_id`_ | :meth:`AsyncClient.unblock` | + +--------------------------------------------------------------+--------------------------------------------------------+ + | `GET /2/users/:id/blocking`_ | :meth:`AsyncClient.get_blocked` | + +--------------------------------------------------------------+--------------------------------------------------------+ + | `POST /2/users/:id/blocking`_ | :meth:`AsyncClient.block` | + +--------------------------------------------------------------+--------------------------------------------------------+ + | .. centered:: |Follows|_ | + +--------------------------------------------------------------+--------------------------------------------------------+ + | `DELETE /2/users/:source_user_id/following/:target_user_id`_ | :meth:`AsyncClient.unfollow_user` | + +--------------------------------------------------------------+--------------------------------------------------------+ + | `GET /2/users/:id/followers`_ | :meth:`AsyncClient.get_users_followers` | + +--------------------------------------------------------------+--------------------------------------------------------+ + | `GET /2/users/:id/following`_ | :meth:`AsyncClient.get_users_following` | + +--------------------------------------------------------------+--------------------------------------------------------+ + | `POST /2/users/:id/following`_ | :meth:`AsyncClient.follow_user` | + +--------------------------------------------------------------+--------------------------------------------------------+ + | .. centered:: |Mutes|_ | + +--------------------------------------------------------------+--------------------------------------------------------+ + | `DELETE /2/users/:source_user_id/muting/:target_user_id`_ | :meth:`AsyncClient.unmute` | + +--------------------------------------------------------------+--------------------------------------------------------+ + | `GET /2/users/:id/muting`_ | :meth:`AsyncClient.get_muted` | + +--------------------------------------------------------------+--------------------------------------------------------+ + | `POST /2/users/:id/muting`_ | :meth:`AsyncClient.mute` | + +--------------------------------------------------------------+--------------------------------------------------------+ + | .. centered:: |User lookup|_ | + +--------------------------------------------------------------+--------------------------------------------------------+ + | `GET /2/users/:id`_ | :meth:`AsyncClient.get_user` | + +--------------------------------------------------------------+--------------------------------------------------------+ + | `GET /2/users/by/username/:username`_ | :meth:`AsyncClient.get_user` | + +--------------------------------------------------------------+--------------------------------------------------------+ + | `GET /2/users`_ | :meth:`AsyncClient.get_users` | + +--------------------------------------------------------------+--------------------------------------------------------+ + | `GET /2/users/by`_ | :meth:`AsyncClient.get_users` | + +--------------------------------------------------------------+--------------------------------------------------------+ + | `GET /2/users/me`_ | :meth:`AsyncClient.get_me` | + +--------------------------------------------------------------+--------------------------------------------------------+ + | .. centered:: :ref:`Spaces` | + +--------------------------------------------------------------+--------------------------------------------------------+ + | .. centered:: |Search Spaces|_ | + +--------------------------------------------------------------+--------------------------------------------------------+ + | `GET /2/spaces/search`_ | :meth:`AsyncClient.search_spaces` | + +--------------------------------------------------------------+--------------------------------------------------------+ + | .. centered:: |Spaces lookup|_ | + +--------------------------------------------------------------+--------------------------------------------------------+ + | `GET /2/spaces`_ | :meth:`AsyncClient.get_spaces` | + +--------------------------------------------------------------+--------------------------------------------------------+ + | `GET /2/spaces/:id`_ | :meth:`AsyncClient.get_space` | + +--------------------------------------------------------------+--------------------------------------------------------+ + | `GET /2/spaces/:id/buyers`_ | :meth:`AsyncClient.get_space_buyers` | + +--------------------------------------------------------------+--------------------------------------------------------+ + | `GET /2/spaces/:id/tweets`_ | :meth:`AsyncClient.get_space_tweets` | + +--------------------------------------------------------------+--------------------------------------------------------+ + | `GET /2/spaces/by/creator_ids`_ | :meth:`AsyncClient.get_spaces` | + +--------------------------------------------------------------+--------------------------------------------------------+ + | .. centered:: :ref:`Direct Messages` | + +-----------------------------------------------------------------------------------------------------------------------+ + | .. centered:: |Direct Messages lookup|_ | + +--------------------------------------------------------------+--------------------------------------------------------+ + | `GET /2/dm_conversations/:dm_conversation_id/dm_events`_ | :meth:`AsyncClient.get_direct_message_events` | + +--------------------------------------------------------------+--------------------------------------------------------+ + | `GET /2/dm_conversations/with/:participant_id/dm_events`_ | :meth:`AsyncClient.get_direct_message_events` | + +--------------------------------------------------------------+--------------------------------------------------------+ + | `GET /2/dm_events`_ | :meth:`AsyncClient.get_direct_message_events` | + +--------------------------------------------------------------+--------------------------------------------------------+ + | .. centered:: |Manage Direct Messages|_ | + +--------------------------------------------------------------+--------------------------------------------------------+ + | `POST /2/dm_conversations`_ | :meth:`AsyncClient.create_direct_message_conversation` | + +--------------------------------------------------------------+--------------------------------------------------------+ + | `POST /2/dm_conversations/:dm_conversation_id/messages`_ | :meth:`AsyncClient.create_direct_message` | + +--------------------------------------------------------------+--------------------------------------------------------+ + | `POST /2/dm_conversations/with/:participant_id/messages`_ | :meth:`AsyncClient.create_direct_message` | + +--------------------------------------------------------------+--------------------------------------------------------+ + | .. centered:: :ref:`Lists` | + +--------------------------------------------------------------+--------------------------------------------------------+ + | .. centered:: |List Tweets lookup|_ | + +--------------------------------------------------------------+--------------------------------------------------------+ + | `GET /2/lists/:id/tweets`_ | :meth:`AsyncClient.get_list_tweets` | + +--------------------------------------------------------------+--------------------------------------------------------+ + | .. centered:: |List follows|_ | + +--------------------------------------------------------------+--------------------------------------------------------+ + | `DELETE /2/users/:id/followed_lists/:list_id`_ | :meth:`AsyncClient.unfollow_list` | + +--------------------------------------------------------------+--------------------------------------------------------+ + | `GET /2/lists/:id/followers`_ | :meth:`AsyncClient.get_list_followers` | + +--------------------------------------------------------------+--------------------------------------------------------+ + | `GET /2/users/:id/followed_lists`_ | :meth:`AsyncClient.get_followed_lists` | + +--------------------------------------------------------------+--------------------------------------------------------+ + | `POST /2/users/:id/followed_lists`_ | :meth:`AsyncClient.follow_list` | + +--------------------------------------------------------------+--------------------------------------------------------+ + | .. centered:: |List lookup|_ | + +--------------------------------------------------------------+--------------------------------------------------------+ + | `GET /2/lists/:id`_ | :meth:`AsyncClient.get_list` | + +--------------------------------------------------------------+--------------------------------------------------------+ + | `GET /2/users/:id/owned_lists`_ | :meth:`AsyncClient.get_owned_lists` | + +--------------------------------------------------------------+--------------------------------------------------------+ + | .. centered:: |List members|_ | + +--------------------------------------------------------------+--------------------------------------------------------+ + | `DELETE /2/lists/:id/members/:user_id`_ | :meth:`AsyncClient.remove_list_member` | + +--------------------------------------------------------------+--------------------------------------------------------+ + | `GET /2/lists/:id/members`_ | :meth:`AsyncClient.get_list_members` | + +--------------------------------------------------------------+--------------------------------------------------------+ + | `GET /2/users/:id/list_memberships`_ | :meth:`AsyncClient.get_list_memberships` | + +--------------------------------------------------------------+--------------------------------------------------------+ + | `POST /2/lists/:id/members`_ | :meth:`AsyncClient.add_list_member` | + +--------------------------------------------------------------+--------------------------------------------------------+ + | .. centered:: |Manage Lists|_ | + +--------------------------------------------------------------+--------------------------------------------------------+ + | `DELETE /2/lists/:id`_ | :meth:`AsyncClient.delete_list` | + +--------------------------------------------------------------+--------------------------------------------------------+ + | `PUT /2/lists/:id`_ | :meth:`AsyncClient.update_list` | + +--------------------------------------------------------------+--------------------------------------------------------+ + | `POST /2/lists`_ | :meth:`AsyncClient.create_list` | + +--------------------------------------------------------------+--------------------------------------------------------+ + | .. centered:: |Pinned Lists|_ | + +--------------------------------------------------------------+--------------------------------------------------------+ + | `DELETE /2/users/:id/pinned_lists/:list_id`_ | :meth:`AsyncClient.unpin_list` | + +--------------------------------------------------------------+--------------------------------------------------------+ + | `GET /2/users/:id/pinned_lists`_ | :meth:`AsyncClient.get_pinned_lists` | + +--------------------------------------------------------------+--------------------------------------------------------+ + | `POST /2/users/:id/pinned_lists`_ | :meth:`AsyncClient.pin_list` | + +--------------------------------------------------------------+--------------------------------------------------------+ + | .. centered:: :ref:`Compliance` | + +--------------------------------------------------------------+--------------------------------------------------------+ + | .. centered:: |Batch Compliance|_ | + +--------------------------------------------------------------+--------------------------------------------------------+ + | `GET /2/compliance/jobs`_ | :meth:`AsyncClient.get_compliance_jobs` | + +--------------------------------------------------------------+--------------------------------------------------------+ + | `GET /2/compliance/jobs/:id`_ | :meth:`AsyncClient.get_compliance_job` | + +--------------------------------------------------------------+--------------------------------------------------------+ + | `POST /2/compliance/jobs`_ | :meth:`AsyncClient.create_compliance_job` | + +--------------------------------------------------------------+--------------------------------------------------------+ .. |Bookmarks| replace:: *Bookmarks* .. _DELETE /2/users/:id/bookmarks/:tweet_id: https://developer.twitter.com/en/docs/twitter-api/tweets/bookmarks/api-reference/delete-users-id-bookmarks-tweet_id @@ -288,6 +306,14 @@ .. _GET /2/compliance/jobs: https://developer.twitter.com/en/docs/twitter-api/compliance/batch-compliance/api-reference/get-compliance-jobs .. _GET /2/compliance/jobs/:id: https://developer.twitter.com/en/docs/twitter-api/compliance/batch-compliance/api-reference/get-compliance-jobs-id .. _POST /2/compliance/jobs: https://developer.twitter.com/en/docs/twitter-api/compliance/batch-compliance/api-reference/post-compliance-jobs +.. |Direct Messages lookup| replace:: *Direct Messages lookup* +.. _GET /2/dm_conversations/:dm_conversation_id/dm_events: https://developer.twitter.com/en/docs/twitter-api/direct-messages/lookup/api-reference/get-dm_conversations-dm_conversation_id-dm_events +.. _GET /2/dm_conversations/with/:participant_id/dm_events: https://developer.twitter.com/en/docs/twitter-api/direct-messages/lookup/api-reference/get-dm_conversations-with-participant_id-dm_events +.. _GET /2/dm_events: https://developer.twitter.com/en/docs/twitter-api/direct-messages/lookup/api-reference/get-dm_events +.. |Manage Direct Messages| replace:: *Manage Direct Messages* +.. _POST /2/dm_conversations: https://developer.twitter.com/en/docs/twitter-api/direct-messages/manage/api-reference/post-dm_conversations +.. _POST /2/dm_conversations/:dm_conversation_id/messages: https://developer.twitter.com/en/docs/twitter-api/direct-messages/manage/api-reference/post-dm_conversations-dm_conversation_id-messages +.. _POST /2/dm_conversations/with/:participant_id/messages: https://developer.twitter.com/en/docs/twitter-api/direct-messages/manage/api-reference/post-dm_conversations-with-participant_id-messages Tweets ====== @@ -430,6 +456,21 @@ Spaces lookup .. automethod:: AsyncClient.get_space_tweets +Direct Messages +=============== + +Direct Messages lookup +---------------------- + +.. automethod:: AsyncClient.get_direct_message_events + +Manage Direct Messages +---------------------- + +.. automethod:: AsyncClient.create_direct_message + +.. automethod:: AsyncClient.create_direct_message_conversation + Lists ===== diff --git a/docs/client.rst b/docs/client.rst index fd4be3b..a32ac22 100644 --- a/docs/client.rst +++ b/docs/client.rst @@ -11,195 +11,213 @@ .. table:: :align: center - +--------------------------------------------------------------+----------------------------------------+ - | Twitter API v2 Endpoint | :class:`Client` Method | - +==============================================================+========================================+ - | .. centered:: :ref:`Tweets` | - +-------------------------------------------------------------------------------------------------------+ - | .. centered:: |Bookmarks|_ | - +--------------------------------------------------------------+----------------------------------------+ - | `DELETE /2/users/:id/bookmarks/:tweet_id`_ | :meth:`Client.remove_bookmark` | - +--------------------------------------------------------------+----------------------------------------+ - | `GET /2/users/:id/bookmarks`_ | :meth:`Client.get_bookmarks` | - +--------------------------------------------------------------+----------------------------------------+ - | `POST /2/users/:id/bookmarks`_ | :meth:`Client.bookmark` | - +--------------------------------------------------------------+----------------------------------------+ - | .. centered:: |Hide replies|_ | - +--------------------------------------------------------------+----------------------------------------+ - | `PUT /2/tweets/:id/hidden`_ | :meth:`Client.hide_reply` | - +--------------------------------------------------------------+----------------------------------------+ - | `PUT /2/tweets/:id/hidden`_ | :meth:`Client.unhide_reply` | - +--------------------------------------------------------------+----------------------------------------+ - | .. centered:: |Likes|_ | - +--------------------------------------------------------------+----------------------------------------+ - | `DELETE /2/users/:id/likes/:tweet_id`_ | :meth:`Client.unlike` | - +--------------------------------------------------------------+----------------------------------------+ - | `GET /2/tweets/:id/liking_users`_ | :meth:`Client.get_liking_users` | - +--------------------------------------------------------------+----------------------------------------+ - | `GET /2/users/:id/liked_tweets`_ | :meth:`Client.get_liked_tweets` | - +--------------------------------------------------------------+----------------------------------------+ - | `POST /2/users/:id/likes`_ | :meth:`Client.like` | - +--------------------------------------------------------------+----------------------------------------+ - | .. centered:: |Manage Tweets|_ | - +--------------------------------------------------------------+----------------------------------------+ - | `DELETE /2/tweets/:id`_ | :meth:`Client.delete_tweet` | - +--------------------------------------------------------------+----------------------------------------+ - | `POST /2/tweets`_ | :meth:`Client.create_tweet` | - +--------------------------------------------------------------+----------------------------------------+ - | .. centered:: |Quote Tweets|_ | - +--------------------------------------------------------------+----------------------------------------+ - | `GET /2/tweets/:id/quote_tweets`_ | :meth:`Client.get_quote_tweets` | - +--------------------------------------------------------------+----------------------------------------+ - | .. centered:: |Retweets|_ | - +--------------------------------------------------------------+----------------------------------------+ - | `DELETE /2/users/:id/retweets/:source_tweet_id`_ | :meth:`Client.unretweet` | - +--------------------------------------------------------------+----------------------------------------+ - | `GET /2/tweets/:id/retweeted_by`_ | :meth:`Client.get_retweeters` | - +--------------------------------------------------------------+----------------------------------------+ - | `POST /2/users/:id/retweets`_ | :meth:`Client.retweet` | - +--------------------------------------------------------------+----------------------------------------+ - | .. centered:: |Search Tweets|_ | - +--------------------------------------------------------------+----------------------------------------+ - | `GET /2/tweets/search/all`_ | :meth:`Client.search_all_tweets` | - +--------------------------------------------------------------+----------------------------------------+ - | `GET /2/tweets/search/recent`_ | :meth:`Client.search_recent_tweets` | - +--------------------------------------------------------------+----------------------------------------+ - | .. centered:: |Timelines|_ | - +--------------------------------------------------------------+----------------------------------------+ - | `GET /2/users/:id/mentions`_ | :meth:`Client.get_users_mentions` | - +--------------------------------------------------------------+----------------------------------------+ - | `GET /2/users/:id/timelines/reverse_chronological`_ | :meth:`Client.get_home_timeline` | - +--------------------------------------------------------------+----------------------------------------+ - | `GET /2/users/:id/tweets`_ | :meth:`Client.get_users_tweets` | - +--------------------------------------------------------------+----------------------------------------+ - | .. centered:: |Tweet counts|_ | - +--------------------------------------------------------------+----------------------------------------+ - | `GET /2/tweets/counts/all`_ | :meth:`Client.get_all_tweets_count` | - +--------------------------------------------------------------+----------------------------------------+ - | `GET /2/tweets/counts/recent`_ | :meth:`Client.get_recent_tweets_count` | - +--------------------------------------------------------------+----------------------------------------+ - | .. centered:: |Tweet lookup|_ | - +--------------------------------------------------------------+----------------------------------------+ - | `GET /2/tweets/:id`_ | :meth:`Client.get_tweet` | - +--------------------------------------------------------------+----------------------------------------+ - | `GET /2/tweets`_ | :meth:`Client.get_tweets` | - +--------------------------------------------------------------+----------------------------------------+ - | .. centered:: :ref:`Users` | - +-------------------------------------------------------------------------------------------------------+ - | .. centered:: |Blocks|_ | - +--------------------------------------------------------------+----------------------------------------+ - | `DELETE /2/users/:source_user_id/blocking/:target_user_id`_ | :meth:`Client.unblock` | - +--------------------------------------------------------------+----------------------------------------+ - | `GET /2/users/:id/blocking`_ | :meth:`Client.get_blocked` | - +--------------------------------------------------------------+----------------------------------------+ - | `POST /2/users/:id/blocking`_ | :meth:`Client.block` | - +--------------------------------------------------------------+----------------------------------------+ - | .. centered:: |Follows|_ | - +--------------------------------------------------------------+----------------------------------------+ - | `DELETE /2/users/:source_user_id/following/:target_user_id`_ | :meth:`Client.unfollow_user` | - +--------------------------------------------------------------+----------------------------------------+ - | `GET /2/users/:id/followers`_ | :meth:`Client.get_users_followers` | - +--------------------------------------------------------------+----------------------------------------+ - | `GET /2/users/:id/following`_ | :meth:`Client.get_users_following` | - +--------------------------------------------------------------+----------------------------------------+ - | `POST /2/users/:id/following`_ | :meth:`Client.follow_user` | - +--------------------------------------------------------------+----------------------------------------+ - | .. centered:: |Mutes|_ | - +--------------------------------------------------------------+----------------------------------------+ - | `DELETE /2/users/:source_user_id/muting/:target_user_id`_ | :meth:`Client.unmute` | - +--------------------------------------------------------------+----------------------------------------+ - | `GET /2/users/:id/muting`_ | :meth:`Client.get_muted` | - +--------------------------------------------------------------+----------------------------------------+ - | `POST /2/users/:id/muting`_ | :meth:`Client.mute` | - +--------------------------------------------------------------+----------------------------------------+ - | .. centered:: |User lookup|_ | - +--------------------------------------------------------------+----------------------------------------+ - | `GET /2/users/:id`_ | :meth:`Client.get_user` | - +--------------------------------------------------------------+----------------------------------------+ - | `GET /2/users/by/username/:username`_ | :meth:`Client.get_user` | - +--------------------------------------------------------------+----------------------------------------+ - | `GET /2/users`_ | :meth:`Client.get_users` | - +--------------------------------------------------------------+----------------------------------------+ - | `GET /2/users/by`_ | :meth:`Client.get_users` | - +--------------------------------------------------------------+----------------------------------------+ - | `GET /2/users/me`_ | :meth:`Client.get_me` | - +--------------------------------------------------------------+----------------------------------------+ - | .. centered:: :ref:`Spaces` | - +-------------------------------------------------------------------------------------------------------+ - | .. centered:: |Search Spaces|_ | - +--------------------------------------------------------------+----------------------------------------+ - | `GET /2/spaces/search`_ | :meth:`Client.search_spaces` | - +--------------------------------------------------------------+----------------------------------------+ - | .. centered:: |Spaces lookup|_ | - +--------------------------------------------------------------+----------------------------------------+ - | `GET /2/spaces`_ | :meth:`Client.get_spaces` | - +--------------------------------------------------------------+----------------------------------------+ - | `GET /2/spaces/:id`_ | :meth:`Client.get_space` | - +--------------------------------------------------------------+----------------------------------------+ - | `GET /2/spaces/:id/buyers`_ | :meth:`Client.get_space_buyers` | - +--------------------------------------------------------------+----------------------------------------+ - | `GET /2/spaces/:id/tweets`_ | :meth:`Client.get_space_tweets` | - +--------------------------------------------------------------+----------------------------------------+ - | `GET /2/spaces/by/creator_ids`_ | :meth:`Client.get_spaces` | - +--------------------------------------------------------------+----------------------------------------+ - | .. centered:: :ref:`Lists` | - +-------------------------------------------------------------------------------------------------------+ - | .. centered:: |List Tweets lookup|_ | - +--------------------------------------------------------------+----------------------------------------+ - | `GET /2/lists/:id/tweets`_ | :meth:`Client.get_list_tweets` | - +--------------------------------------------------------------+----------------------------------------+ - | .. centered:: |List follows|_ | - +--------------------------------------------------------------+----------------------------------------+ - | `DELETE /2/users/:id/followed_lists/:list_id`_ | :meth:`Client.unfollow_list` | - +--------------------------------------------------------------+----------------------------------------+ - | `GET /2/lists/:id/followers`_ | :meth:`Client.get_list_followers` | - +--------------------------------------------------------------+----------------------------------------+ - | `GET /2/users/:id/followed_lists`_ | :meth:`Client.get_followed_lists` | - +--------------------------------------------------------------+----------------------------------------+ - | `POST /2/users/:id/followed_lists`_ | :meth:`Client.follow_list` | - +--------------------------------------------------------------+----------------------------------------+ - | .. centered:: |List lookup|_ | - +--------------------------------------------------------------+----------------------------------------+ - | `GET /2/lists/:id`_ | :meth:`Client.get_list` | - +--------------------------------------------------------------+----------------------------------------+ - | `GET /2/users/:id/owned_lists`_ | :meth:`Client.get_owned_lists` | - +--------------------------------------------------------------+----------------------------------------+ - | .. centered:: |List members|_ | - +--------------------------------------------------------------+----------------------------------------+ - | `DELETE /2/lists/:id/members/:user_id`_ | :meth:`Client.remove_list_member` | - +--------------------------------------------------------------+----------------------------------------+ - | `GET /2/lists/:id/members`_ | :meth:`Client.get_list_members` | - +--------------------------------------------------------------+----------------------------------------+ - | `GET /2/users/:id/list_memberships`_ | :meth:`Client.get_list_memberships` | - +--------------------------------------------------------------+----------------------------------------+ - | `POST /2/lists/:id/members`_ | :meth:`Client.add_list_member` | - +--------------------------------------------------------------+----------------------------------------+ - | .. centered:: |Manage Lists|_ | - +--------------------------------------------------------------+----------------------------------------+ - | `DELETE /2/lists/:id`_ | :meth:`Client.delete_list` | - +--------------------------------------------------------------+----------------------------------------+ - | `PUT /2/lists/:id`_ | :meth:`Client.update_list` | - +--------------------------------------------------------------+----------------------------------------+ - | `POST /2/lists`_ | :meth:`Client.create_list` | - +--------------------------------------------------------------+----------------------------------------+ - | .. centered:: |Pinned Lists|_ | - +--------------------------------------------------------------+----------------------------------------+ - | `DELETE /2/users/:id/pinned_lists/:list_id`_ | :meth:`Client.unpin_list` | - +--------------------------------------------------------------+----------------------------------------+ - | `GET /2/users/:id/pinned_lists`_ | :meth:`Client.get_pinned_lists` | - +--------------------------------------------------------------+----------------------------------------+ - | `POST /2/users/:id/pinned_lists`_ | :meth:`Client.pin_list` | - +--------------------------------------------------------------+----------------------------------------+ - | .. centered:: :ref:`Compliance` | - +-------------------------------------------------------------------------------------------------------+ - | .. centered:: |Batch Compliance|_ | - +--------------------------------------------------------------+----------------------------------------+ - | `GET /2/compliance/jobs`_ | :meth:`Client.get_compliance_jobs` | - +--------------------------------------------------------------+----------------------------------------+ - | `GET /2/compliance/jobs/:id`_ | :meth:`Client.get_compliance_job` | - +--------------------------------------------------------------+----------------------------------------+ - | `POST /2/compliance/jobs`_ | :meth:`Client.create_compliance_job` | - +--------------------------------------------------------------+----------------------------------------+ + +--------------------------------------------------------------+---------------------------------------------------+ + | Twitter API v2 Endpoint | :class:`Client` Method | + +==============================================================+===================================================+ + | .. centered:: :ref:`Tweets` | + +------------------------------------------------------------------------------------------------------------------+ + | .. centered:: |Bookmarks|_ | + +--------------------------------------------------------------+---------------------------------------------------+ + | `DELETE /2/users/:id/bookmarks/:tweet_id`_ | :meth:`Client.remove_bookmark` | + +--------------------------------------------------------------+---------------------------------------------------+ + | `GET /2/users/:id/bookmarks`_ | :meth:`Client.get_bookmarks` | + +--------------------------------------------------------------+---------------------------------------------------+ + | `POST /2/users/:id/bookmarks`_ | :meth:`Client.bookmark` | + +--------------------------------------------------------------+---------------------------------------------------+ + | .. centered:: |Hide replies|_ | + +--------------------------------------------------------------+---------------------------------------------------+ + | `PUT /2/tweets/:id/hidden`_ | :meth:`Client.hide_reply` | + +--------------------------------------------------------------+---------------------------------------------------+ + | `PUT /2/tweets/:id/hidden`_ | :meth:`Client.unhide_reply` | + +--------------------------------------------------------------+---------------------------------------------------+ + | .. centered:: |Likes|_ | + +--------------------------------------------------------------+---------------------------------------------------+ + | `DELETE /2/users/:id/likes/:tweet_id`_ | :meth:`Client.unlike` | + +--------------------------------------------------------------+---------------------------------------------------+ + | `GET /2/tweets/:id/liking_users`_ | :meth:`Client.get_liking_users` | + +--------------------------------------------------------------+---------------------------------------------------+ + | `GET /2/users/:id/liked_tweets`_ | :meth:`Client.get_liked_tweets` | + +--------------------------------------------------------------+---------------------------------------------------+ + | `POST /2/users/:id/likes`_ | :meth:`Client.like` | + +--------------------------------------------------------------+---------------------------------------------------+ + | .. centered:: |Manage Tweets|_ | + +--------------------------------------------------------------+---------------------------------------------------+ + | `DELETE /2/tweets/:id`_ | :meth:`Client.delete_tweet` | + +--------------------------------------------------------------+---------------------------------------------------+ + | `POST /2/tweets`_ | :meth:`Client.create_tweet` | + +--------------------------------------------------------------+---------------------------------------------------+ + | .. centered:: |Quote Tweets|_ | + +--------------------------------------------------------------+---------------------------------------------------+ + | `GET /2/tweets/:id/quote_tweets`_ | :meth:`Client.get_quote_tweets` | + +--------------------------------------------------------------+---------------------------------------------------+ + | .. centered:: |Retweets|_ | + +--------------------------------------------------------------+---------------------------------------------------+ + | `DELETE /2/users/:id/retweets/:source_tweet_id`_ | :meth:`Client.unretweet` | + +--------------------------------------------------------------+---------------------------------------------------+ + | `GET /2/tweets/:id/retweeted_by`_ | :meth:`Client.get_retweeters` | + +--------------------------------------------------------------+---------------------------------------------------+ + | `POST /2/users/:id/retweets`_ | :meth:`Client.retweet` | + +--------------------------------------------------------------+---------------------------------------------------+ + | .. centered:: |Search Tweets|_ | + +--------------------------------------------------------------+---------------------------------------------------+ + | `GET /2/tweets/search/all`_ | :meth:`Client.search_all_tweets` | + +--------------------------------------------------------------+---------------------------------------------------+ + | `GET /2/tweets/search/recent`_ | :meth:`Client.search_recent_tweets` | + +--------------------------------------------------------------+---------------------------------------------------+ + | .. centered:: |Timelines|_ | + +--------------------------------------------------------------+---------------------------------------------------+ + | `GET /2/users/:id/mentions`_ | :meth:`Client.get_users_mentions` | + +--------------------------------------------------------------+---------------------------------------------------+ + | `GET /2/users/:id/timelines/reverse_chronological`_ | :meth:`Client.get_home_timeline` | + +--------------------------------------------------------------+---------------------------------------------------+ + | `GET /2/users/:id/tweets`_ | :meth:`Client.get_users_tweets` | + +--------------------------------------------------------------+---------------------------------------------------+ + | .. centered:: |Tweet counts|_ | + +--------------------------------------------------------------+---------------------------------------------------+ + | `GET /2/tweets/counts/all`_ | :meth:`Client.get_all_tweets_count` | + +--------------------------------------------------------------+---------------------------------------------------+ + | `GET /2/tweets/counts/recent`_ | :meth:`Client.get_recent_tweets_count` | + +--------------------------------------------------------------+---------------------------------------------------+ + | .. centered:: |Tweet lookup|_ | + +--------------------------------------------------------------+---------------------------------------------------+ + | `GET /2/tweets/:id`_ | :meth:`Client.get_tweet` | + +--------------------------------------------------------------+---------------------------------------------------+ + | `GET /2/tweets`_ | :meth:`Client.get_tweets` | + +--------------------------------------------------------------+---------------------------------------------------+ + | .. centered:: :ref:`Users` | + +------------------------------------------------------------------------------------------------------------------+ + | .. centered:: |Blocks|_ | + +--------------------------------------------------------------+---------------------------------------------------+ + | `DELETE /2/users/:source_user_id/blocking/:target_user_id`_ | :meth:`Client.unblock` | + +--------------------------------------------------------------+---------------------------------------------------+ + | `GET /2/users/:id/blocking`_ | :meth:`Client.get_blocked` | + +--------------------------------------------------------------+---------------------------------------------------+ + | `POST /2/users/:id/blocking`_ | :meth:`Client.block` | + +--------------------------------------------------------------+---------------------------------------------------+ + | .. centered:: |Follows|_ | + +--------------------------------------------------------------+---------------------------------------------------+ + | `DELETE /2/users/:source_user_id/following/:target_user_id`_ | :meth:`Client.unfollow_user` | + +--------------------------------------------------------------+---------------------------------------------------+ + | `GET /2/users/:id/followers`_ | :meth:`Client.get_users_followers` | + +--------------------------------------------------------------+---------------------------------------------------+ + | `GET /2/users/:id/following`_ | :meth:`Client.get_users_following` | + +--------------------------------------------------------------+---------------------------------------------------+ + | `POST /2/users/:id/following`_ | :meth:`Client.follow_user` | + +--------------------------------------------------------------+---------------------------------------------------+ + | .. centered:: |Mutes|_ | + +--------------------------------------------------------------+---------------------------------------------------+ + | `DELETE /2/users/:source_user_id/muting/:target_user_id`_ | :meth:`Client.unmute` | + +--------------------------------------------------------------+---------------------------------------------------+ + | `GET /2/users/:id/muting`_ | :meth:`Client.get_muted` | + +--------------------------------------------------------------+---------------------------------------------------+ + | `POST /2/users/:id/muting`_ | :meth:`Client.mute` | + +--------------------------------------------------------------+---------------------------------------------------+ + | .. centered:: |User lookup|_ | + +--------------------------------------------------------------+---------------------------------------------------+ + | `GET /2/users/:id`_ | :meth:`Client.get_user` | + +--------------------------------------------------------------+---------------------------------------------------+ + | `GET /2/users/by/username/:username`_ | :meth:`Client.get_user` | + +--------------------------------------------------------------+---------------------------------------------------+ + | `GET /2/users`_ | :meth:`Client.get_users` | + +--------------------------------------------------------------+---------------------------------------------------+ + | `GET /2/users/by`_ | :meth:`Client.get_users` | + +--------------------------------------------------------------+---------------------------------------------------+ + | `GET /2/users/me`_ | :meth:`Client.get_me` | + +--------------------------------------------------------------+---------------------------------------------------+ + | .. centered:: :ref:`Spaces` | + +------------------------------------------------------------------------------------------------------------------+ + | .. centered:: |Search Spaces|_ | + +--------------------------------------------------------------+---------------------------------------------------+ + | `GET /2/spaces/search`_ | :meth:`Client.search_spaces` | + +--------------------------------------------------------------+---------------------------------------------------+ + | .. centered:: |Spaces lookup|_ | + +--------------------------------------------------------------+---------------------------------------------------+ + | `GET /2/spaces`_ | :meth:`Client.get_spaces` | + +--------------------------------------------------------------+---------------------------------------------------+ + | `GET /2/spaces/:id`_ | :meth:`Client.get_space` | + +--------------------------------------------------------------+---------------------------------------------------+ + | `GET /2/spaces/:id/buyers`_ | :meth:`Client.get_space_buyers` | + +--------------------------------------------------------------+---------------------------------------------------+ + | `GET /2/spaces/:id/tweets`_ | :meth:`Client.get_space_tweets` | + +--------------------------------------------------------------+---------------------------------------------------+ + | `GET /2/spaces/by/creator_ids`_ | :meth:`Client.get_spaces` | + +--------------------------------------------------------------+---------------------------------------------------+ + | .. centered:: :ref:`Direct Messages` | + +------------------------------------------------------------------------------------------------------------------+ + | .. centered:: |Direct Messages lookup|_ | + +--------------------------------------------------------------+---------------------------------------------------+ + | `GET /2/dm_conversations/:dm_conversation_id/dm_events`_ | :meth:`Client.get_direct_message_events` | + +--------------------------------------------------------------+---------------------------------------------------+ + | `GET /2/dm_conversations/with/:participant_id/dm_events`_ | :meth:`Client.get_direct_message_events` | + +--------------------------------------------------------------+---------------------------------------------------+ + | `GET /2/dm_events`_ | :meth:`Client.get_direct_message_events` | + +--------------------------------------------------------------+---------------------------------------------------+ + | .. centered:: |Manage Direct Messages|_ | + +--------------------------------------------------------------+---------------------------------------------------+ + | `POST /2/dm_conversations`_ | :meth:`Client.create_direct_message_conversation` | + +--------------------------------------------------------------+---------------------------------------------------+ + | `POST /2/dm_conversations/:dm_conversation_id/messages`_ | :meth:`Client.create_direct_message` | + +--------------------------------------------------------------+---------------------------------------------------+ + | `POST /2/dm_conversations/with/:participant_id/messages`_ | :meth:`Client.create_direct_message` | + +--------------------------------------------------------------+---------------------------------------------------+ + | .. centered:: :ref:`Lists` | + +------------------------------------------------------------------------------------------------------------------+ + | .. centered:: |List Tweets lookup|_ | + +--------------------------------------------------------------+---------------------------------------------------+ + | `GET /2/lists/:id/tweets`_ | :meth:`Client.get_list_tweets` | + +--------------------------------------------------------------+---------------------------------------------------+ + | .. centered:: |List follows|_ | + +--------------------------------------------------------------+---------------------------------------------------+ + | `DELETE /2/users/:id/followed_lists/:list_id`_ | :meth:`Client.unfollow_list` | + +--------------------------------------------------------------+---------------------------------------------------+ + | `GET /2/lists/:id/followers`_ | :meth:`Client.get_list_followers` | + +--------------------------------------------------------------+---------------------------------------------------+ + | `GET /2/users/:id/followed_lists`_ | :meth:`Client.get_followed_lists` | + +--------------------------------------------------------------+---------------------------------------------------+ + | `POST /2/users/:id/followed_lists`_ | :meth:`Client.follow_list` | + +--------------------------------------------------------------+---------------------------------------------------+ + | .. centered:: |List lookup|_ | + +--------------------------------------------------------------+---------------------------------------------------+ + | `GET /2/lists/:id`_ | :meth:`Client.get_list` | + +--------------------------------------------------------------+---------------------------------------------------+ + | `GET /2/users/:id/owned_lists`_ | :meth:`Client.get_owned_lists` | + +--------------------------------------------------------------+---------------------------------------------------+ + | .. centered:: |List members|_ | + +--------------------------------------------------------------+---------------------------------------------------+ + | `DELETE /2/lists/:id/members/:user_id`_ | :meth:`Client.remove_list_member` | + +--------------------------------------------------------------+---------------------------------------------------+ + | `GET /2/lists/:id/members`_ | :meth:`Client.get_list_members` | + +--------------------------------------------------------------+---------------------------------------------------+ + | `GET /2/users/:id/list_memberships`_ | :meth:`Client.get_list_memberships` | + +--------------------------------------------------------------+---------------------------------------------------+ + | `POST /2/lists/:id/members`_ | :meth:`Client.add_list_member` | + +--------------------------------------------------------------+---------------------------------------------------+ + | .. centered:: |Manage Lists|_ | + +--------------------------------------------------------------+---------------------------------------------------+ + | `DELETE /2/lists/:id`_ | :meth:`Client.delete_list` | + +--------------------------------------------------------------+---------------------------------------------------+ + | `PUT /2/lists/:id`_ | :meth:`Client.update_list` | + +--------------------------------------------------------------+---------------------------------------------------+ + | `POST /2/lists`_ | :meth:`Client.create_list` | + +--------------------------------------------------------------+---------------------------------------------------+ + | .. centered:: |Pinned Lists|_ | + +--------------------------------------------------------------+---------------------------------------------------+ + | `DELETE /2/users/:id/pinned_lists/:list_id`_ | :meth:`Client.unpin_list` | + +--------------------------------------------------------------+---------------------------------------------------+ + | `GET /2/users/:id/pinned_lists`_ | :meth:`Client.get_pinned_lists` | + +--------------------------------------------------------------+---------------------------------------------------+ + | `POST /2/users/:id/pinned_lists`_ | :meth:`Client.pin_list` | + +--------------------------------------------------------------+---------------------------------------------------+ + | .. centered:: :ref:`Compliance` | + +------------------------------------------------------------------------------------------------------------------+ + | .. centered:: |Batch Compliance|_ | + +--------------------------------------------------------------+---------------------------------------------------+ + | `GET /2/compliance/jobs`_ | :meth:`Client.get_compliance_jobs` | + +--------------------------------------------------------------+---------------------------------------------------+ + | `GET /2/compliance/jobs/:id`_ | :meth:`Client.get_compliance_job` | + +--------------------------------------------------------------+---------------------------------------------------+ + | `POST /2/compliance/jobs`_ | :meth:`Client.create_compliance_job` | + +--------------------------------------------------------------+---------------------------------------------------+ .. |Bookmarks| replace:: *Bookmarks* .. _DELETE /2/users/:id/bookmarks/:tweet_id: https://developer.twitter.com/en/docs/twitter-api/tweets/bookmarks/api-reference/delete-users-id-bookmarks-tweet_id @@ -288,6 +306,14 @@ .. _GET /2/compliance/jobs: https://developer.twitter.com/en/docs/twitter-api/compliance/batch-compliance/api-reference/get-compliance-jobs .. _GET /2/compliance/jobs/:id: https://developer.twitter.com/en/docs/twitter-api/compliance/batch-compliance/api-reference/get-compliance-jobs-id .. _POST /2/compliance/jobs: https://developer.twitter.com/en/docs/twitter-api/compliance/batch-compliance/api-reference/post-compliance-jobs +.. |Direct Messages lookup| replace:: *Direct Messages lookup* +.. _GET /2/dm_conversations/:dm_conversation_id/dm_events: https://developer.twitter.com/en/docs/twitter-api/direct-messages/lookup/api-reference/get-dm_conversations-dm_conversation_id-dm_events +.. _GET /2/dm_conversations/with/:participant_id/dm_events: https://developer.twitter.com/en/docs/twitter-api/direct-messages/lookup/api-reference/get-dm_conversations-with-participant_id-dm_events +.. _GET /2/dm_events: https://developer.twitter.com/en/docs/twitter-api/direct-messages/lookup/api-reference/get-dm_events +.. |Manage Direct Messages| replace:: *Manage Direct Messages* +.. _POST /2/dm_conversations: https://developer.twitter.com/en/docs/twitter-api/direct-messages/manage/api-reference/post-dm_conversations +.. _POST /2/dm_conversations/:dm_conversation_id/messages: https://developer.twitter.com/en/docs/twitter-api/direct-messages/manage/api-reference/post-dm_conversations-dm_conversation_id-messages +.. _POST /2/dm_conversations/with/:participant_id/messages: https://developer.twitter.com/en/docs/twitter-api/direct-messages/manage/api-reference/post-dm_conversations-with-participant_id-messages Tweets ====== @@ -434,6 +460,21 @@ Spaces lookup .. automethod:: Client.get_space_tweets +Direct Messages +=============== + +Direct Messages lookup +---------------------- + +.. automethod:: Client.get_direct_message_events + +Manage Direct Messages +---------------------- + +.. automethod:: Client.create_direct_message + +.. automethod:: Client.create_direct_message_conversation + Lists ===== diff --git a/docs/v2_models.rst b/docs/v2_models.rst index dfe5332..f0da251 100644 --- a/docs/v2_models.rst +++ b/docs/v2_models.rst @@ -6,6 +6,11 @@ Models ****** +:class:`DirectMessageEvent` +=========================== + +.. autoclass:: DirectMessageEvent() + :class:`List` ============= diff --git a/tests/test_asyncclient.py b/tests/test_asyncclient.py index 4aaa6c5..f7fa1b8 100644 --- a/tests/test_asyncclient.py +++ b/tests/test_asyncclient.py @@ -185,6 +185,29 @@ class TweepyAsyncClientTests(IsolatedAsyncioTestCase): # TODO: Test AsyncClient.get_space_tweets + @tape.use_cassette( + "test_asyncclient_manage_and_lookup_direct_messages.yaml" + ) + async def test_manage_and_lookup_direct_messages(self): + user_ids = [145336962, 750362064426721281] + # User IDs for @Harmon758 and @Harmon758Public + response = await self.client.create_direct_message( + participant_id=user_ids[1], + text="Testing 1" + ) + dm_conversation_id = response.data["dm_conversation_id"] + await self.client.create_direct_message( + dm_conversation_id=dm_conversation_id, + text="Testing 2" + ) + await self.client.create_direct_message_conversation( + text="Testing", + participant_ids=user_ids + ) + await self.client.get_dm_events() + await self.client.get_dm_events(dm_conversation_id=dm_conversation_id) + await self.client.get_dm_events(participant_id=user_ids[1]) + @tape.use_cassette("test_asyncclient_get_list_tweets.yaml") async def test_get_list_tweets(self): list_id = 84839422 # List ID for Official Twitter Accounts (@Twitter) diff --git a/tests/test_client.py b/tests/test_client.py index 054f3d5..faa3a41 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -179,6 +179,27 @@ class TweepyClientTests(unittest.TestCase): # TODO: Test Client.get_space_tweets + @tape.use_cassette("test_manage_and_lookup_direct_messages.yaml") + def test_manage_and_lookup_direct_messages(self): + user_ids = [145336962, 750362064426721281] + # User IDs for @Harmon758 and @Harmon758Public + response = self.client.create_direct_message( + participant_id=user_ids[1], + text="Testing 1" + ) + dm_conversation_id = response.data["dm_conversation_id"] + self.client.create_direct_message( + dm_conversation_id=dm_conversation_id, + text="Testing 2" + ) + self.client.create_direct_message_conversation( + text="Testing", + participant_ids=user_ids + ) + self.client.get_dm_events() + self.client.get_dm_events(dm_conversation_id=dm_conversation_id) + self.client.get_dm_events(participant_id=user_ids[1]) + @tape.use_cassette("test_client_get_list_tweets.yaml") def test_get_list_tweets(self): list_id = 84839422 # List ID for Official Twitter Accounts (@Twitter) diff --git a/tweepy/__init__.py b/tweepy/__init__.py index 106921f..78af3b9 100644 --- a/tweepy/__init__.py +++ b/tweepy/__init__.py @@ -17,6 +17,7 @@ from tweepy.auth import ( from tweepy.cache import Cache, FileCache, MemoryCache from tweepy.client import Client, Response from tweepy.cursor import Cursor +from tweepy.direct_message_event import DirectMessageEvent from tweepy.errors import ( BadRequest, Forbidden, HTTPException, NotFound, TooManyRequests, TweepyException, TwitterServerError, Unauthorized diff --git a/tweepy/asynchronous/client.py b/tweepy/asynchronous/client.py index 27a2e74..be256ae 100644 --- a/tweepy/asynchronous/client.py +++ b/tweepy/asynchronous/client.py @@ -20,6 +20,7 @@ from yarl import URL import tweepy from tweepy.client import BaseClient, Response +from tweepy.direct_message_event import DirectMessageEvent from tweepy.errors import ( BadRequest, Forbidden, HTTPException, NotFound, TooManyRequests, TwitterServerError, Unauthorized @@ -138,7 +139,7 @@ class AsyncBaseClient(BaseClient): return response async def _make_request( - self, method, route, params={}, endpoint_parameters=None, json=None, + self, method, route, params={}, endpoint_parameters=(), json=None, data_type=None, user_auth=False ): request_params = self._process_params(params, endpoint_parameters) @@ -2569,6 +2570,235 @@ class AsyncClient(AsyncBaseClient): ), data_type=Tweet ) + # Direct Messages lookup + + async def get_direct_message_events( + self, *, dm_conversation_id=None, participant_id=None, user_auth=True, + **params + ): + """get_direct_message_events( \ + *, dm_conversation_id=None, participant_id=None, \ + dm_event_fields=None, event_types=None, expansions=None, \ + max_results=None, media_fields=None, pagination_token=None, \ + tweet_fields=None, user_fields=None, user_auth=True \ + ) + + If ``dm_conversation_id`` is passed, returns a list of Direct Messages + within the conversation specified. Messages are returned in reverse + chronological order. + + If ``participant_id`` is passed, returns a list of Direct Messages (DM) + events within a 1-1 conversation with the user specified. Messages are + returned in reverse chronological order. + + If neither is passed, returns a list of Direct Messages for the + authenticated user, both sent and received. Direct Message events are + returned in reverse chronological order. Supports retrieving events + from the previous 30 days. + + .. note:: + + There is an alias for this method named ``get_dm_events``. + + .. versionadded:: 4.12 + + Parameters + ---------- + dm_conversation_id : str | None + The ``id`` of the Direct Message conversation for which events are + being retrieved. + participant_id : int | str | None + The ``participant_id`` of the user that the authenicating user is + having a 1-1 conversation with. + dm_event_fields : list[str] | str | None + Extra fields to include in the event payload. ``id``, ``text``, and + ``event_type`` are returned by default. + event_types : str + The type of Direct Message event to returm. If not included, all + types are returned. + expansions : list[str] | str | None + :ref:`expansions_parameter` + max_results : int | None + The maximum number of results to be returned in a page. Must be + between 1 and 100. The default is 100. + media_fields : list[str] | str | None + :ref:`media_fields_parameter` + pagination_token : str | None + Contains either the ``next_token`` or ``previous_token`` value. + tweet_fields : list[str] | str | None + :ref:`tweet_fields_parameter` + user_fields : list[str] | str | None + :ref:`user_fields_parameter` + user_auth : bool + Whether or not to use OAuth 1.0a User Context to authenticate + + Raises + ------ + TypeError + If both ``dm_conversation_id`` and ``participant_id`` are passed + + Returns + ------- + dict | aiohttp.ClientResponse | Response + + References + ---------- + https://developer.twitter.com/en/docs/twitter-api/direct-messages/lookup/api-reference/get-dm_events + https://developer.twitter.com/en/docs/twitter-api/direct-messages/lookup/api-reference/get-dm_conversations-with-participant_id-dm_events + https://developer.twitter.com/en/docs/twitter-api/direct-messages/lookup/api-reference/get-dm_conversations-dm_conversation_id-dm_events + """ + if dm_conversation_id is not None and participant_id is not None: + raise TypeError( + "Expected DM conversation ID or participant ID, not both" + ) + elif dm_conversation_id is not None: + path = f"/2/dm_conversations/{dm_conversation_id}/dm_events" + elif participant_id is not None: + path = f"/2/dm_conversations/with/{participant_id}/dm_events" + else: + path = "/2/dm_events" + + return await self._make_request( + "GET", path, params=params, + endpoint_parameters=( + "dm_event.fields", "event_types", "expansions", "max_results", + "media.fields", "pagination_token", "tweet.fields", + "user.fields" + ), data_type=DirectMessageEvent, user_auth=user_auth + ) + + get_dm_events = get_direct_message_events + + # Manage Direct Messages + + async def create_direct_message( + self, *, dm_conversation_id=None, participant_id=None, media_id=None, + text=None, user_auth=True + ): + """If ``dm_conversation_id`` is passed, creates a Direct Message on + behalf of the authenticated user, and adds it to the specified + conversation. + + If ``participant_id`` is passed, creates a one-to-one Direct Message + and adds it to the one-to-one conversation. This method either creates + a new one-to-one conversation or retrieves the current conversation and + adds the Direct Message to it. + + .. note:: + + There is an alias for this method named ``create_dm``. + + .. versionadded:: 4.12 + + Parameters + ---------- + dm_conversation_id : str | None + The ``dm_conversation_id`` of the conversation to add the Direct + Message to. Supports both 1-1 and group conversations. + participant_id : int | str | None + The User ID of the account this one-to-one Direct Message is to be + sent to. + media_id : int | str | None + A single Media ID being attached to the Direct Message. This field + is required if ``text`` is not present. For this launch, only 1 + attachment is supported. + text : str | None + Text of the Direct Message being created. This field is required if + ``media_id`` is not present. Text messages support up to 10,000 + characters. + user_auth : bool + Whether or not to use OAuth 1.0a User Context to authenticate + + Raises + ------ + TypeError + If ``dm_conversation_id`` and ``participant_id`` are not passed or + both are passed + + Returns + ------- + dict | aiohttp.ClientResponse | Response + + References + ---------- + https://developer.twitter.com/en/docs/twitter-api/direct-messages/manage/api-reference/post-dm_conversations-dm_conversation_id-messages + https://developer.twitter.com/en/docs/twitter-api/direct-messages/manage/api-reference/post-dm_conversations-with-participant_id-messages + """ + if dm_conversation_id is not None and participant_id is not None: + raise TypeError( + "Expected DM conversation ID or participant ID, not both" + ) + elif dm_conversation_id is not None: + path = f"/2/dm_conversations/{dm_conversation_id}/messages" + elif participant_id is not None: + path = f"/2/dm_conversations/with/{participant_id}/messages" + else: + raise TypeError("DM conversation ID or participant ID is required") + + json = {} + if media_id is not None: + json["attachments"] = [{"media_id": str(media_id)}] + if text is not None: + json["text"] = text + + return await self._make_request( + "POST", path, json=json, user_auth=user_auth + ) + + create_dm = create_direct_message + + async def create_direct_message_conversation( + self, *, media_id=None, text=None, participant_ids, user_auth=True + ): + """Creates a new group conversation and adds a Direct Message to it on + behalf of the authenticated user. + + .. note:: + + There is an alias for this method named ``create_dm_conversation``. + + .. versionadded:: 4.12 + + Parameters + ---------- + media_id : int | str | None + A single Media ID being attached to the Direct Message. This field + is required if ``text`` is not present. For this launch, only 1 + attachment is supported. + text : str | None + Text of the Direct Message being created. This field is required if + ``media_id`` is not present. Text messages support up to 10,000 + characters. + participant_ids : list[int | str] + An array of User IDs that the conversation is created with. + Conversations can have up to 50 participants. + user_auth : bool + Whether or not to use OAuth 1.0a User Context to authenticate + + Returns + ------- + dict | aiohttp.ClientResponse | Response + + References + ---------- + https://developer.twitter.com/en/docs/twitter-api/direct-messages/manage/api-reference/post-dm_conversations + """ + json = { + "conversation_type": "Group", + "message": {}, + "participant_ids": list(map(str, participant_ids)) + } + if media_id is not None: + json["message"]["attachments"] = [{"media_id": str(media_id)}] + if text is not None: + json["message"]["text"] = text + + return await self._make_request( + "POST", "/2/dm_conversations", json=json, user_auth=user_auth + ) + + create_dm_conversation = create_direct_message_conversation + # List Tweets lookup async def get_list_tweets(self, id, *, user_auth=False, **params): diff --git a/tweepy/client.py b/tweepy/client.py index 6aace32..c7db560 100644 --- a/tweepy/client.py +++ b/tweepy/client.py @@ -20,6 +20,7 @@ import requests import tweepy from tweepy.auth import OAuth1UserHandler +from tweepy.direct_message_event import DirectMessageEvent from tweepy.errors import ( BadRequest, Forbidden, HTTPException, NotFound, TooManyRequests, TwitterServerError, Unauthorized @@ -119,8 +120,10 @@ class BaseClient: return response - def _make_request(self, method, route, params={}, endpoint_parameters=None, - json=None, data_type=None, user_auth=False): + def _make_request( + self, method, route, params={}, endpoint_parameters=(), json=None, + data_type=None, user_auth=False + ): request_params = self._process_params(params, endpoint_parameters) response = self.request(method, route, params=request_params, @@ -167,10 +170,17 @@ class BaseClient: return includes def _process_params(self, params, endpoint_parameters): + endpoint_parameters = { + endpoint_parameter.replace('.', '_'): endpoint_parameter + for endpoint_parameter in endpoint_parameters + } + request_params = {} for param_name, param_value in params.items(): - if param_name.replace('_', '.') in endpoint_parameters: - param_name = param_name.replace('_', '.') + try: + param_name = endpoint_parameters[param_name] + except KeyError: + log.warn(f"Unexpected parameter: {param_name}") if isinstance(param_value, list): request_params[param_name] = ','.join(map(str, param_value)) @@ -184,8 +194,6 @@ class BaseClient: elif param_value is not None: request_params[param_name] = param_value - if param_name not in endpoint_parameters: - log.warn(f"Unexpected parameter: {param_name}") return request_params @@ -2760,6 +2768,233 @@ class Client(BaseClient): ), data_type=Tweet ) + # Direct Messages lookup + + def get_direct_message_events( + self, *, dm_conversation_id=None, participant_id=None, user_auth=True, + **params + ): + """get_direct_message_events( \ + *, dm_conversation_id=None, participant_id=None, \ + dm_event_fields=None, event_types=None, expansions=None, \ + max_results=None, media_fields=None, pagination_token=None, \ + tweet_fields=None, user_fields=None, user_auth=True \ + ) + + If ``dm_conversation_id`` is passed, returns a list of Direct Messages + within the conversation specified. Messages are returned in reverse + chronological order. + + If ``participant_id`` is passed, returns a list of Direct Messages (DM) + events within a 1-1 conversation with the user specified. Messages are + returned in reverse chronological order. + + If neither is passed, returns a list of Direct Messages for the + authenticated user, both sent and received. Direct Message events are + returned in reverse chronological order. Supports retrieving events + from the previous 30 days. + + .. note:: + + There is an alias for this method named ``get_dm_events``. + + .. versionadded:: 4.12 + + Parameters + ---------- + dm_conversation_id : str | None + The ``id`` of the Direct Message conversation for which events are + being retrieved. + participant_id : int | str | None + The ``participant_id`` of the user that the authenicating user is + having a 1-1 conversation with. + dm_event_fields : list[str] | str | None + Extra fields to include in the event payload. ``id``, ``text``, and + ``event_type`` are returned by default. + event_types : str + The type of Direct Message event to returm. If not included, all + types are returned. + expansions : list[str] | str | None + :ref:`expansions_parameter` + max_results : int | None + The maximum number of results to be returned in a page. Must be + between 1 and 100. The default is 100. + media_fields : list[str] | str | None + :ref:`media_fields_parameter` + pagination_token : str | None + Contains either the ``next_token`` or ``previous_token`` value. + tweet_fields : list[str] | str | None + :ref:`tweet_fields_parameter` + user_fields : list[str] | str | None + :ref:`user_fields_parameter` + user_auth : bool + Whether or not to use OAuth 1.0a User Context to authenticate + + Raises + ------ + TypeError + If both ``dm_conversation_id`` and ``participant_id`` are passed + + Returns + ------- + dict | requests.Response | Response + + References + ---------- + https://developer.twitter.com/en/docs/twitter-api/direct-messages/lookup/api-reference/get-dm_events + https://developer.twitter.com/en/docs/twitter-api/direct-messages/lookup/api-reference/get-dm_conversations-with-participant_id-dm_events + https://developer.twitter.com/en/docs/twitter-api/direct-messages/lookup/api-reference/get-dm_conversations-dm_conversation_id-dm_events + """ + if dm_conversation_id is not None and participant_id is not None: + raise TypeError( + "Expected DM conversation ID or participant ID, not both" + ) + elif dm_conversation_id is not None: + path = f"/2/dm_conversations/{dm_conversation_id}/dm_events" + elif participant_id is not None: + path = f"/2/dm_conversations/with/{participant_id}/dm_events" + else: + path = "/2/dm_events" + + return self._make_request( + "GET", path, params=params, + endpoint_parameters=( + "dm_event.fields", "event_types", "expansions", "max_results", + "media.fields", "pagination_token", "tweet.fields", + "user.fields" + ), data_type=DirectMessageEvent, user_auth=user_auth + ) + + get_dm_events = get_direct_message_events + + # Manage Direct Messages + + def create_direct_message( + self, *, dm_conversation_id=None, participant_id=None, media_id=None, + text=None, user_auth=True + ): + """If ``dm_conversation_id`` is passed, creates a Direct Message on + behalf of the authenticated user, and adds it to the specified + conversation. + + If ``participant_id`` is passed, creates a one-to-one Direct Message + and adds it to the one-to-one conversation. This method either creates + a new one-to-one conversation or retrieves the current conversation and + adds the Direct Message to it. + + .. note:: + + There is an alias for this method named ``create_dm``. + + .. versionadded:: 4.12 + + Parameters + ---------- + dm_conversation_id : str | None + The ``dm_conversation_id`` of the conversation to add the Direct + Message to. Supports both 1-1 and group conversations. + participant_id : int | str | None + The User ID of the account this one-to-one Direct Message is to be + sent to. + media_id : int | str | None + A single Media ID being attached to the Direct Message. This field + is required if ``text`` is not present. For this launch, only 1 + attachment is supported. + text : str | None + Text of the Direct Message being created. This field is required if + ``media_id`` is not present. Text messages support up to 10,000 + characters. + user_auth : bool + Whether or not to use OAuth 1.0a User Context to authenticate + + Raises + ------ + TypeError + If ``dm_conversation_id`` and ``participant_id`` are not passed or + both are passed + + Returns + ------- + dict | requests.Response | Response + + References + ---------- + https://developer.twitter.com/en/docs/twitter-api/direct-messages/manage/api-reference/post-dm_conversations-dm_conversation_id-messages + https://developer.twitter.com/en/docs/twitter-api/direct-messages/manage/api-reference/post-dm_conversations-with-participant_id-messages + """ + if dm_conversation_id is not None and participant_id is not None: + raise TypeError( + "Expected DM conversation ID or participant ID, not both" + ) + elif dm_conversation_id is not None: + path = f"/2/dm_conversations/{dm_conversation_id}/messages" + elif participant_id is not None: + path = f"/2/dm_conversations/with/{participant_id}/messages" + else: + raise TypeError("DM conversation ID or participant ID is required") + + json = {} + if media_id is not None: + json["attachments"] = [{"media_id": str(media_id)}] + if text is not None: + json["text"] = text + + return self._make_request("POST", path, json=json, user_auth=user_auth) + + create_dm = create_direct_message + + def create_direct_message_conversation( + self, *, media_id=None, text=None, participant_ids, user_auth=True + ): + """Creates a new group conversation and adds a Direct Message to it on + behalf of the authenticated user. + + .. note:: + + There is an alias for this method named ``create_dm_conversation``. + + .. versionadded:: 4.12 + + Parameters + ---------- + media_id : int | str | None + A single Media ID being attached to the Direct Message. This field + is required if ``text`` is not present. For this launch, only 1 + attachment is supported. + text : str | None + Text of the Direct Message being created. This field is required if + ``media_id`` is not present. Text messages support up to 10,000 + characters. + participant_ids : list[int | str] + An array of User IDs that the conversation is created with. + Conversations can have up to 50 participants. + user_auth : bool + Whether or not to use OAuth 1.0a User Context to authenticate + + Returns + ------- + dict | requests.Response | Response + + References + ---------- + https://developer.twitter.com/en/docs/twitter-api/direct-messages/manage/api-reference/post-dm_conversations + """ + json = { + "conversation_type": "Group", + "message": {}, + "participant_ids": list(map(str, participant_ids)) + } + if media_id is not None: + json["message"]["attachments"] = [{"media_id": str(media_id)}] + if text is not None: + json["message"]["text"] = text + + return self._make_request( + "POST", "/2/dm_conversations", json=json, user_auth=user_auth + ) + + create_dm_conversation = create_direct_message_conversation + # List Tweets lookup def get_list_tweets(self, id, *, user_auth=False, **params): diff --git a/tweepy/direct_message_event.py b/tweepy/direct_message_event.py new file mode 100644 index 0000000..6ff037a --- /dev/null +++ b/tweepy/direct_message_event.py @@ -0,0 +1,74 @@ +# Tweepy +# Copyright 2009-2022 Joshua Roesslein +# See LICENSE for details. + +from tweepy.mixins import DataMapping, HashableID +from tweepy.tweet import ReferencedTweet +from tweepy.utils import parse_datetime + + +class DirectMessageEvent(HashableID, DataMapping): + """.. versionadded:: 4.12 + + Attributes + ---------- + data : dict + The JSON data representing the Direct Message event. + id : int + event_type : str + attachments : dict | None + created_at : datetime.datetime | None + dm_conversation_id : str | None + participant_ids : list[int] | None + referenced_tweets : list[ReferencedTweet] | None + sender_id : int | None + text : str | None + """ + + __slots__ = ( + "data", "id", "event_type", "attachments", "created_at", + "dm_conversation_id", "participant_ids", "referenced_tweets", + "sender_id", "text" + ) + + def __init__(self, data): + self.data = data + self.id = int(data["id"]) + self.event_type = data["event_type"] + + self.attachments = data.get("attachments") + + self.created_at = data.get("created_at") + if self.created_at is not None: + self.created_at = parse_datetime(self.created_at) + + self.dm_conversation_id = data.get("dm_conversation_id") + + self.participant_ids = data.get("participant_ids") + if self.participant_ids is not None: + self.participant_ids = list(map(int, self.participant_ids)) + + self.referenced_tweets = data.get("referenced_tweets") + if self.referenced_tweets is not None: + self.referenced_tweets = [ + ReferencedTweet(referenced_tweet) + for referenced_tweet in self.referenced_tweets + ] + + self.sender_id = data.get("sender_id") + if self.sender_id is not None: + self.sender_id = int(self.sender_id) + + self.text = data.get("text") + + def __repr__(self): + representation = ( + f"" + representation = f"