aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLorenz Diener <[email protected]>2019-10-12 22:55:17 +0200
committerLorenz Diener <[email protected]>2019-10-12 22:55:17 +0200
commit45908b6f4e9c66fe102b30764e6d153a8d0339a5 (patch)
treeb8e1caaa65c0b43a181cbd672199c7e8b919616c
parent1f36deb11950a627b52a587fc3ec30c37f9bb456 (diff)
downloadmastodon.py-45908b6f4e9c66fe102b30764e6d153a8d0339a5.tar.gz
Add, test and document last-read markers. Fixes #192
-rw-r--r--docs/index.rst35
-rw-r--r--mastodon/Mastodon.py50
-rw-r--r--tests/cassettes/test_markers.yaml117
-rw-r--r--tests/test_markers.py17
4 files changed, 216 insertions, 3 deletions
diff --git a/docs/index.rst b/docs/index.rst
index ef94b2a..1e64927 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -773,6 +773,20 @@ Featured tag dicts
773 # (can be None if there are none) 773 # (can be None if there are none)
774 } 774 }
775 775
776Read marker dicts
777~~~~~~~~~~~~~~~~~
778.. _read marker dict:
779
780.. code-block:: python
781
782 mastodon.markers_get()["home"]
783 # Returns the following dictionary:
784 {
785 'last_read_id': # ID of the last read object in the timeline
786 'version': # A counter that is incremented whenever the marker is set to a new status
787 'updated_at': # The time the marker was last set, as a datetime object
788 }
789
776Admin account dicts 790Admin account dicts
777~~~~~~~~~~~~~~~~~~~ 791~~~~~~~~~~~~~~~~~~~
778.. _admin account dict: 792.. _admin account dict:
@@ -990,11 +1004,20 @@ muted or blocked by the logged in user.
990.. automethod:: Mastodon.mutes 1004.. automethod:: Mastodon.mutes
991.. automethod:: Mastodon.blocks 1005.. automethod:: Mastodon.blocks
992 1006
993Reading data: Reports (REMOVED IN 2.5.0) 1007Reading data: Reports
994---------------------------------------- 1008---------------------
1009In Mastodon versions before 2.5.0 this function allowed for the retrieval
1010of reports filed by the logged in user. It has since been removed.
995 1011
996.. automethod:: Mastodon.reports 1012.. automethod:: Mastodon.reports
997 1013
1014
1015Writing data: Last-read markers
1016--------------------------
1017This function allows you to set get last read position for timelines.
1018
1019.. automethod:: Mastodon.markers_get
1020
998Reading data: Domain blocks 1021Reading data: Domain blocks
999--------------------------- 1022---------------------------
1000 1023
@@ -1142,6 +1165,14 @@ Writing data: Reports
1142 1165
1143.. automethod:: Mastodon.report 1166.. automethod:: Mastodon.report
1144 1167
1168Writing data: Last-read markers
1169--------------------------
1170This function allows you to set the last read position for timelines to
1171allow for persisting where the user was reading a timeline between sessions
1172and clients / devices.
1173
1174.. automethod:: Mastodon.markers_set
1175
1145Writing data: Domain blocks 1176Writing data: Domain blocks
1146--------------------------- 1177---------------------------
1147These functions allow you to block and unblock all statuses from a domain 1178These functions allow you to block and unblock all statuses from a domain
diff --git a/mastodon/Mastodon.py b/mastodon/Mastodon.py
index d07bae7..b8ad976 100644
--- a/mastodon/Mastodon.py
+++ b/mastodon/Mastodon.py
@@ -213,6 +213,7 @@ class Mastodon:
213 __DICT_VERSION_PREFERENCES = "2.8.0" 213 __DICT_VERSION_PREFERENCES = "2.8.0"
214 __DICT_VERSION_ADMIN_ACCOUNT = "2.9.1" 214 __DICT_VERSION_ADMIN_ACCOUNT = "2.9.1"
215 __DICT_VERSION_FEATURED_TAG = "3.0.0" 215 __DICT_VERSION_FEATURED_TAG = "3.0.0"
216 __DICT_VERSION_MARKER = "3.0.0"
216 217
217 ### 218 ###
218 # Registering apps 219 # Registering apps
@@ -1590,6 +1591,25 @@ class Mastodon:
1590 """ 1591 """
1591 return self.__api_request('GET', '/api/v1/preferences') 1592 return self.__api_request('GET', '/api/v1/preferences')
1592 1593
1594 ##
1595 # Reading data: Read markers
1596 ##
1597 @api_version("3.0.0", "3.0.0", __DICT_VERSION_MARKER)
1598 def markers_get(self, timeline=["home"]):
1599 """
1600 Get the last-read-location markers for the specified timelines. Valid timelines
1601 are the same as in `timeline()`_
1602
1603 Note that despite the singular name, `timeline` can be a list.
1604
1605 Returns a dict of `read marker dicts`_, keyed by timeline name.
1606 """
1607 if not isinstance(timeline, (list, tuple)):
1608 timeline = [timeline]
1609 params = self.__generate_params(locals())
1610
1611 return self.__api_request('GET', '/api/v1/markers', params)
1612
1593 ### 1613 ###
1594 # Writing data: Statuses 1614 # Writing data: Statuses
1595 ### 1615 ###
@@ -2450,6 +2470,34 @@ class Mastodon:
2450 params = self.__generate_params(locals()) 2470 params = self.__generate_params(locals())
2451 self.__api_request('DELETE', '/api/v1/domain_blocks', params) 2471 self.__api_request('DELETE', '/api/v1/domain_blocks', params)
2452 2472
2473 ##
2474 # Writing data: Read markers
2475 ##
2476 @api_version("3.0.0", "3.0.0", __DICT_VERSION_MARKER)
2477 def markers_set(self, timelines, last_read_ids):
2478 """
2479 Set the "last read" marker(s) for the given timeline(s) to the given id(s)
2480
2481 Note that if you give an invalid timeline name, this will silently do nothing.
2482
2483 Returns a dict with the updated `read marker dicts`_, keyed by timeline name.
2484 """
2485 if not isinstance(timelines, (list, tuple)):
2486 timelines = [timelines]
2487
2488 if not isinstance(last_read_ids, (list, tuple)):
2489 last_read_ids = [last_read_ids]
2490
2491 if len(last_read_ids) != len(timelines):
2492 raise MastodonIllegalArgumentError("Number of specified timelines and ids must be the same")
2493
2494 params = collections.OrderedDict()
2495 for timeline, last_read_id in zip(timelines, last_read_ids):
2496 params[timeline] = collections.OrderedDict()
2497 params[timeline]["last_read_id"] = self.__unpack_id(last_read_id)
2498
2499 return self.__api_request('POST', '/api/v1/markers', params, use_json=True)
2500
2453 ### 2501 ###
2454 # Writing data: Push subscriptions 2502 # Writing data: Push subscriptions
2455 ### 2503 ###
@@ -3071,7 +3119,7 @@ class Mastodon:
3071 """ 3119 """
3072 Converts json string numerals to native python bignums. 3120 Converts json string numerals to native python bignums.
3073 """ 3121 """
3074 for key in ('id', 'week', 'in_reply_to_id', 'in_reply_to_account_id', 'logins', 'registrations', 'statuses', 'day'): 3122 for key in ('id', 'week', 'in_reply_to_id', 'in_reply_to_account_id', 'logins', 'registrations', 'statuses', 'day', 'last_read_id'):
3075 if (key in json_object and isinstance(json_object[key], six.text_type)): 3123 if (key in json_object and isinstance(json_object[key], six.text_type)):
3076 try: 3124 try:
3077 json_object[key] = int(json_object[key]) 3125 json_object[key] = int(json_object[key])
diff --git a/tests/cassettes/test_markers.yaml b/tests/cassettes/test_markers.yaml
new file mode 100644
index 0000000..eceda6d
--- /dev/null
+++ b/tests/cassettes/test_markers.yaml
@@ -0,0 +1,117 @@
1interactions:
2- request:
3 body: status=Toot%21
4 headers:
5 Accept: ['*/*']
6 Accept-Encoding: ['gzip, deflate']
7 Authorization: [Bearer __MASTODON_PY_TEST_ACCESS_TOKEN]
8 Connection: [keep-alive]
9 Content-Length: ['14']
10 Content-Type: [application/x-www-form-urlencoded]
11 User-Agent: [python-requests/2.18.4]
12 method: POST
13 uri: http://localhost:3000/api/v1/statuses
14 response:
15 body: {string: '{"id":"102951400589982753","created_at":"2019-10-12T20:55:05.296Z","in_reply_to_id":null,"in_reply_to_account_id":null,"sensitive":false,"spoiler_text":"","visibility":"public","language":"ja","uri":"http://localhost/users/mastodonpy_test/statuses/102951400589982753","url":"http://localhost/@mastodonpy_test/102951400589982753","replies_count":0,"reblogs_count":0,"favourites_count":0,"favourited":false,"reblogged":false,"muted":false,"pinned":false,"content":"\u003cp\u003eToot!\u003c/p\u003e","reblog":null,"application":{"name":"Mastodon.py
16 test suite","website":null},"account":{"id":"1234567890123456","username":"mastodonpy_test","acct":"mastodonpy_test","display_name":"","locked":false,"bot":false,"created_at":"2019-06-22T23:11:52.441Z","note":"\u003cp\u003e\u003c/p\u003e","url":"http://localhost/@mastodonpy_test","avatar":"http://localhost/avatars/original/missing.png","avatar_static":"http://localhost/avatars/original/missing.png","header":"http://localhost/headers/original/missing.png","header_static":"http://localhost/headers/original/missing.png","followers_count":0,"following_count":1,"statuses_count":3,"last_status_at":"2019-10-12T20:55:05.311Z","emojis":[],"fields":[]},"media_attachments":[],"mentions":[],"tags":[],"emojis":[],"card":null,"poll":null}'}
17 headers:
18 Cache-Control: ['no-cache, no-store']
19 Content-Type: [application/json; charset=utf-8]
20 Referrer-Policy: [strict-origin-when-cross-origin]
21 Transfer-Encoding: [chunked]
22 Vary: ['Accept-Encoding, Origin']
23 X-Content-Type-Options: [nosniff]
24 X-Download-Options: [noopen]
25 X-Frame-Options: [SAMEORIGIN]
26 X-Permitted-Cross-Domain-Policies: [none]
27 X-Request-Id: [d6e097f3-e4e8-4d83-8c30-c4fe5ed88a87]
28 X-Runtime: ['0.151706']
29 X-XSS-Protection: [1; mode=block]
30 content-length: ['1280']
31 status: {code: 200, message: OK}
32- request:
33 body: '{"home": {"last_read_id": 102951400589982753}}'
34 headers:
35 Accept: ['*/*']
36 Accept-Encoding: ['gzip, deflate']
37 Authorization: [Bearer __MASTODON_PY_TEST_ACCESS_TOKEN]
38 Connection: [keep-alive]
39 Content-Length: ['46']
40 Content-Type: [application/json]
41 User-Agent: [python-requests/2.18.4]
42 method: POST
43 uri: http://localhost:3000/api/v1/markers
44 response:
45 body: {string: '{"home":{"last_read_id":"102951400589982753","version":2,"updated_at":"2019-10-12T20:55:05.457Z"}}'}
46 headers:
47 Cache-Control: ['no-cache, no-store']
48 Content-Type: [application/json; charset=utf-8]
49 Referrer-Policy: [strict-origin-when-cross-origin]
50 Transfer-Encoding: [chunked]
51 Vary: ['Accept-Encoding, Origin']
52 X-Content-Type-Options: [nosniff]
53 X-Download-Options: [noopen]
54 X-Frame-Options: [SAMEORIGIN]
55 X-Permitted-Cross-Domain-Policies: [none]
56 X-Request-Id: [2e3c3ede-edf6-43c9-9477-fddca87ffed3]
57 X-Runtime: ['0.036295']
58 X-XSS-Protection: [1; mode=block]
59 content-length: ['98']
60 status: {code: 200, message: OK}
61- request:
62 body: null
63 headers:
64 Accept: ['*/*']
65 Accept-Encoding: ['gzip, deflate']
66 Authorization: [Bearer __MASTODON_PY_TEST_ACCESS_TOKEN]
67 Connection: [keep-alive]
68 User-Agent: [python-requests/2.18.4]
69 method: GET
70 uri: http://localhost:3000/api/v1/markers?timeline%5B%5D=home
71 response:
72 body: {string: '{"home":{"last_read_id":"102951400589982753","version":2,"updated_at":"2019-10-12T20:55:05.457Z"}}'}
73 headers:
74 Cache-Control: ['no-cache, no-store']
75 Content-Type: [application/json; charset=utf-8]
76 Referrer-Policy: [strict-origin-when-cross-origin]
77 Transfer-Encoding: [chunked]
78 Vary: ['Accept-Encoding, Origin']
79 X-Content-Type-Options: [nosniff]
80 X-Download-Options: [noopen]
81 X-Frame-Options: [SAMEORIGIN]
82 X-Permitted-Cross-Domain-Policies: [none]
83 X-Request-Id: [2d3ddb8a-b93c-48e2-bdda-c4e0edf29adf]
84 X-Runtime: ['0.022064']
85 X-XSS-Protection: [1; mode=block]
86 content-length: ['98']
87 status: {code: 200, message: OK}
88- request:
89 body: null
90 headers:
91 Accept: ['*/*']
92 Accept-Encoding: ['gzip, deflate']
93 Authorization: [Bearer __MASTODON_PY_TEST_ACCESS_TOKEN]
94 Connection: [keep-alive]
95 Content-Length: ['0']
96 User-Agent: [python-requests/2.18.4]
97 method: DELETE
98 uri: http://localhost:3000/api/v1/statuses/102951400589982753
99 response:
100 body: {string: '{"id":"102951400589982753","created_at":"2019-10-12T20:55:05.296Z","in_reply_to_id":null,"in_reply_to_account_id":null,"sensitive":false,"spoiler_text":"","visibility":"public","language":"ja","uri":"http://localhost/users/mastodonpy_test/statuses/102951400589982753","url":"http://localhost/@mastodonpy_test/102951400589982753","replies_count":0,"reblogs_count":0,"favourites_count":0,"favourited":false,"reblogged":false,"muted":false,"pinned":false,"text":"Toot!","reblog":null,"application":{"name":"Mastodon.py
101 test suite","website":null},"account":{"id":"1234567890123456","username":"mastodonpy_test","acct":"mastodonpy_test","display_name":"","locked":false,"bot":false,"created_at":"2019-06-22T23:11:52.441Z","note":"\u003cp\u003e\u003c/p\u003e","url":"http://localhost/@mastodonpy_test","avatar":"http://localhost/avatars/original/missing.png","avatar_static":"http://localhost/avatars/original/missing.png","header":"http://localhost/headers/original/missing.png","header_static":"http://localhost/headers/original/missing.png","followers_count":0,"following_count":1,"statuses_count":3,"last_status_at":"2019-10-12T20:55:05.311Z","emojis":[],"fields":[]},"media_attachments":[],"mentions":[],"tags":[],"emojis":[],"card":null,"poll":null}'}
102 headers:
103 Cache-Control: ['no-cache, no-store']
104 Content-Type: [application/json; charset=utf-8]
105 Referrer-Policy: [strict-origin-when-cross-origin]
106 Transfer-Encoding: [chunked]
107 Vary: ['Accept-Encoding, Origin']
108 X-Content-Type-Options: [nosniff]
109 X-Download-Options: [noopen]
110 X-Frame-Options: [SAMEORIGIN]
111 X-Permitted-Cross-Domain-Policies: [none]
112 X-Request-Id: [526d76d7-5428-45d2-9a84-4f520b09524c]
113 X-Runtime: ['0.121781']
114 X-XSS-Protection: [1; mode=block]
115 content-length: ['1250']
116 status: {code: 200, message: OK}
117version: 1
diff --git a/tests/test_markers.py b/tests/test_markers.py
new file mode 100644
index 0000000..a35c56c
--- /dev/null
+++ b/tests/test_markers.py
@@ -0,0 +1,17 @@
1import pytest
2
3@pytest.mark.vcr()
4def test_markers(api, status):
5 marker_a = api.markers_set("home", status)
6 assert marker_a
7 assert marker_a["home"]
8
9 marker_b = api.markers_get("home")
10 assert marker_b
11 assert marker_b["home"]
12
13 assert marker_a.home.version == marker_b.home.version
14 assert marker_a.home.last_read_id == status.id
15 assert marker_b.home.last_read_id == status.id
16
17
Powered by cgit v1.2.3 (git 2.41.0)