aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLorenz Diener <[email protected]>2019-10-12 21:47:58 +0200
committerLorenz Diener <[email protected]>2019-10-12 21:47:58 +0200
commit63bf5afc61503ebd99a7e7bc36014b378db98d23 (patch)
treeb66daf9ec0727cd0c59a9ca8146958d74b508ea3
parent3194b1295e8f4a6d151d18ad4f23174c63408c05 (diff)
downloadmastodon.py-63bf5afc61503ebd99a7e7bc36014b378db98d23.tar.gz
Implement, test and document featured and suggested tags APIs (fixes #191)
-rw-r--r--docs/index.rst29
-rw-r--r--mastodon/Mastodon.py47
-rw-r--r--tests/cassettes/test_featured_tags.yaml113
-rw-r--r--tests/cassettes/test_suggested_tags.yaml90
-rw-r--r--tests/test_account.py34
5 files changed, 312 insertions, 1 deletions
diff --git a/docs/index.rst b/docs/index.rst
index c0aebb7..b9fcde1 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -756,6 +756,21 @@ Preference dicts
756 # content warnings by default 756 # content warnings by default
757 } 757 }
758 758
759Featured tag dicts
760~~~~~~~~~~~~~~~~~~
761.. _featured tag dict:
762
763.. code-block:: python
764
765 mastodon.featured_tags()[0]
766 # Returns the following dictionary:
767 {
768 'id': # The featured tags id
769 'name': # The featured tags name (without leading #)
770 'statuses_count': # Number of publicly visible statuses posted with this hashtag that this instance knows about
771 'last_status_at': # The last time a public status containing this hashtag was added to this instances database
772 # (can be None if there are none)
773 }
759 774
760Admin account dicts 775Admin account dicts
761~~~~~~~~~~~~~~~~~~~ 776~~~~~~~~~~~~~~~~~~~
@@ -907,6 +922,13 @@ their relationships.
907.. automethod:: Mastodon.account_relationships 922.. automethod:: Mastodon.account_relationships
908.. automethod:: Mastodon.account_search 923.. automethod:: Mastodon.account_search
909 924
925Reading data: Featured tags
926~~~~~~~~~~~~~~~~~~~~~~~~~~~
927These functions allow retrieving info about a users featured and suggested tags.
928
929.. automethod:: Mastodon.featured_tags
930.. automethod:: Mastodon.featured_tag_suggestions
931
910Reading data: Keyword filters 932Reading data: Keyword filters
911----------------------------- 933-----------------------------
912These functions allow you to get information about keyword filters. 934These functions allow you to get information about keyword filters.
@@ -1058,6 +1080,13 @@ These functions allow you to interact with other accounts: To (un)follow and
1058.. automethod:: Mastodon.account_unpin 1080.. automethod:: Mastodon.account_unpin
1059.. automethod:: Mastodon.account_update_credentials 1081.. automethod:: Mastodon.account_update_credentials
1060 1082
1083Writing data: Featured tags
1084~~~~~~~~~~~~~~~~~~~~~~~~~~~
1085These functions allow setting which tags are featured on a users profile.
1086
1087.. automethod:: Mastodon.featured_tag_create
1088.. automethod:: Mastodon.featured_tag_delete
1089
1061Writing data: Keyword filters 1090Writing data: Keyword filters
1062----------------------------- 1091-----------------------------
1063These functions allow you to manipulate keyword filters. 1092These functions allow you to manipulate keyword filters.
diff --git a/mastodon/Mastodon.py b/mastodon/Mastodon.py
index 899aae4..9c6f1ce 100644
--- a/mastodon/Mastodon.py
+++ b/mastodon/Mastodon.py
@@ -212,6 +212,7 @@ class Mastodon:
212 __DICT_VERSION_SCHEDULED_STATUS = bigger_version("2.7.0", __DICT_VERSION_STATUS) 212 __DICT_VERSION_SCHEDULED_STATUS = bigger_version("2.7.0", __DICT_VERSION_STATUS)
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 216
216 ### 217 ###
217 # Registering apps 218 # Registering apps
@@ -1145,6 +1146,28 @@ class Mastodon:
1145 return self.__api_request('GET', url, params) 1146 return self.__api_request('GET', url, params)
1146 1147
1147 ### 1148 ###
1149 # Reading data: Featured hashtags
1150 ###
1151 @api_version("3.0.0", "3.0.0", __DICT_VERSION_FEATURED_TAG)
1152 def featured_tags(self):
1153 """
1154 Return the hashtags the logged-in user has set to be featured on
1155 their profile as a list of `featured tag dicts`_.
1156
1157 Returns a list of `featured tag dicts`_.
1158 """
1159 return self.__api_request('GET', '/api/v1/featured_tags')
1160
1161 @api_version("3.0.0", "3.0.0", __DICT_VERSION_HASHTAG)
1162 def featured_tag_suggestions(self):
1163 """
1164 Returns the logged-in users 10 most commonly hashtags.
1165
1166 Returns a list of `hashtag dicts`_.
1167 """
1168 return self.__api_request('GET', '/api/v1/featured_tags/suggestions')
1169
1170 ###
1148 # Reading data: Keyword filters 1171 # Reading data: Keyword filters
1149 ### 1172 ###
1150 @api_version("2.4.3", "2.4.3", __DICT_VERSION_FILTER) 1173 @api_version("2.4.3", "2.4.3", __DICT_VERSION_FILTER)
@@ -2138,6 +2161,28 @@ class Mastodon:
2138 return self.__api_request('POST', url) 2161 return self.__api_request('POST', url)
2139 2162
2140 ### 2163 ###
2164 # Writing data: Featured hashtags
2165 ###
2166 @api_version("3.0.0", "3.0.0", __DICT_VERSION_FEATURED_TAG)
2167 def featured_tag_create(self, name):
2168 """
2169 Creates a new featured hashtag displayed on the logged-in users profile.
2170
2171 Returns a `featured tag dict`_ with the newly featured tag.
2172 """
2173 params = self.__generate_params(locals())
2174 return self.__api_request('POST', '/api/v1/featured_tags', params)
2175
2176 @api_version("3.0.0", "3.0.0", __DICT_VERSION_FEATURED_TAG)
2177 def featured_tag_delete(self, id):
2178 """
2179 Deletes one of the logged-in users featured hashtags.
2180 """
2181 id = self.__unpack_id(id)
2182 url = '/api/v1/featured_tags/{0}'.format(str(id))
2183 self.__api_request('DELETE', url)
2184
2185 ###
2141 # Writing data: Keyword filters 2186 # Writing data: Keyword filters
2142 ### 2187 ###
2143 @api_version("2.4.3", "2.4.3", __DICT_VERSION_FILTER) 2188 @api_version("2.4.3", "2.4.3", __DICT_VERSION_FILTER)
@@ -2982,7 +3027,7 @@ class Mastodon:
2982 """ 3027 """
2983 Parse dates in certain known json fields, if possible. 3028 Parse dates in certain known json fields, if possible.
2984 """ 3029 """
2985 known_date_fields = ["created_at", "week", "day", "expires_at", "scheduled_at", "updated_at"] 3030 known_date_fields = ["created_at", "week", "day", "expires_at", "scheduled_at", "updated_at", "last_status_at"]
2986 for k, v in json_object.items(): 3031 for k, v in json_object.items():
2987 if k in known_date_fields: 3032 if k in known_date_fields:
2988 if v != None: 3033 if v != None:
diff --git a/tests/cassettes/test_featured_tags.yaml b/tests/cassettes/test_featured_tags.yaml
new file mode 100644
index 0000000..8b1aa78
--- /dev/null
+++ b/tests/cassettes/test_featured_tags.yaml
@@ -0,0 +1,113 @@
1interactions:
2- request:
3 body: name=mastopytesttag
4 headers:
5 Accept: ['*/*']
6 Accept-Encoding: ['gzip, deflate']
7 Authorization: [Bearer __MASTODON_PY_TEST_ACCESS_TOKEN]
8 Connection: [keep-alive]
9 Content-Length: ['19']
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/featured_tags
14 response:
15 body: {string: '{"id":"1","name":"mastopytesttag","statuses_count":0,"last_status_at":null}'}
16 headers:
17 Cache-Control: ['no-cache, no-store']
18 Content-Type: [application/json; charset=utf-8]
19 Referrer-Policy: [strict-origin-when-cross-origin]
20 Transfer-Encoding: [chunked]
21 Vary: ['Accept-Encoding, Origin']
22 X-Content-Type-Options: [nosniff]
23 X-Download-Options: [noopen]
24 X-Frame-Options: [SAMEORIGIN]
25 X-Permitted-Cross-Domain-Policies: [none]
26 X-Request-Id: [319b60a5-1408-4515-9b41-91a9eb90b81c]
27 X-Runtime: ['0.122775']
28 X-XSS-Protection: [1; mode=block]
29 content-length: ['75']
30 status: {code: 200, message: OK}
31- request:
32 body: null
33 headers:
34 Accept: ['*/*']
35 Accept-Encoding: ['gzip, deflate']
36 Authorization: [Bearer __MASTODON_PY_TEST_ACCESS_TOKEN]
37 Connection: [keep-alive]
38 User-Agent: [python-requests/2.18.4]
39 method: GET
40 uri: http://localhost:3000/api/v1/featured_tags
41 response:
42 body: {string: '[{"id":"1","name":"mastopytesttag","statuses_count":0,"last_status_at":null}]'}
43 headers:
44 Cache-Control: ['no-cache, no-store']
45 Content-Type: [application/json; charset=utf-8]
46 Referrer-Policy: [strict-origin-when-cross-origin]
47 Transfer-Encoding: [chunked]
48 Vary: ['Accept-Encoding, Origin']
49 X-Content-Type-Options: [nosniff]
50 X-Download-Options: [noopen]
51 X-Frame-Options: [SAMEORIGIN]
52 X-Permitted-Cross-Domain-Policies: [none]
53 X-Request-Id: [6c35b860-5264-46a1-9219-388f2d65f038]
54 X-Runtime: ['0.030432']
55 X-XSS-Protection: [1; mode=block]
56 content-length: ['77']
57 status: {code: 200, message: OK}
58- request:
59 body: null
60 headers:
61 Accept: ['*/*']
62 Accept-Encoding: ['gzip, deflate']
63 Authorization: [Bearer __MASTODON_PY_TEST_ACCESS_TOKEN]
64 Connection: [keep-alive]
65 Content-Length: ['0']
66 User-Agent: [python-requests/2.18.4]
67 method: DELETE
68 uri: http://localhost:3000/api/v1/featured_tags/1
69 response:
70 body: {string: '{}'}
71 headers:
72 Cache-Control: ['no-cache, no-store']
73 Content-Type: [application/json; charset=utf-8]
74 Referrer-Policy: [strict-origin-when-cross-origin]
75 Transfer-Encoding: [chunked]
76 Vary: ['Accept-Encoding, Origin']
77 X-Content-Type-Options: [nosniff]
78 X-Download-Options: [noopen]
79 X-Frame-Options: [SAMEORIGIN]
80 X-Permitted-Cross-Domain-Policies: [none]
81 X-Request-Id: [7c062d77-90bd-4400-9476-42495d98d77b]
82 X-Runtime: ['0.031225']
83 X-XSS-Protection: [1; mode=block]
84 content-length: ['2']
85 status: {code: 200, message: OK}
86- request:
87 body: null
88 headers:
89 Accept: ['*/*']
90 Accept-Encoding: ['gzip, deflate']
91 Authorization: [Bearer __MASTODON_PY_TEST_ACCESS_TOKEN]
92 Connection: [keep-alive]
93 User-Agent: [python-requests/2.18.4]
94 method: GET
95 uri: http://localhost:3000/api/v1/featured_tags
96 response:
97 body: {string: '[]'}
98 headers:
99 Cache-Control: ['no-cache, no-store']
100 Content-Type: [application/json; charset=utf-8]
101 Referrer-Policy: [strict-origin-when-cross-origin]
102 Transfer-Encoding: [chunked]
103 Vary: ['Accept-Encoding, Origin']
104 X-Content-Type-Options: [nosniff]
105 X-Download-Options: [noopen]
106 X-Frame-Options: [SAMEORIGIN]
107 X-Permitted-Cross-Domain-Policies: [none]
108 X-Request-Id: [8e5eaf25-5e6a-4998-bb44-188b123b726c]
109 X-Runtime: ['0.020343']
110 X-XSS-Protection: [1; mode=block]
111 content-length: ['2']
112 status: {code: 200, message: OK}
113version: 1
diff --git a/tests/cassettes/test_suggested_tags.yaml b/tests/cassettes/test_suggested_tags.yaml
new file mode 100644
index 0000000..2a73e03
--- /dev/null
+++ b/tests/cassettes/test_suggested_tags.yaml
@@ -0,0 +1,90 @@
1interactions:
2- request:
3 body: status=cool+free+%23ringtones
4 headers:
5 Accept: ['*/*']
6 Accept-Encoding: ['gzip, deflate']
7 Authorization: [Bearer __MASTODON_PY_TEST_ACCESS_TOKEN]
8 Connection: [keep-alive]
9 Content-Length: ['29']
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":"102951123020457171","created_at":"2019-10-12T19:44:29.956Z","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/102951123020457171","url":"http://localhost/@mastodonpy_test/102951123020457171","replies_count":0,"reblogs_count":0,"favourites_count":0,"favourited":false,"reblogged":false,"muted":false,"pinned":false,"content":"\u003cp\u003ecool
16 free \u003ca href=\"http://localhost/tags/ringtones\" class=\"mention hashtag\"
17 rel=\"tag\"\u003e#\u003cspan\u003eringtones\u003c/span\u003e\u003c/a\u003e\u003c/p\u003e","reblog":null,"application":{"name":"Mastodon.py
18 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-12T19:44:29.976Z","emojis":[],"fields":[]},"media_attachments":[],"mentions":[],"tags":[{"name":"ringtones","url":"http://localhost/tags/ringtones"}],"emojis":[],"card":null,"poll":null}'}
19 headers:
20 Cache-Control: ['no-cache, no-store']
21 Content-Type: [application/json; charset=utf-8]
22 Referrer-Policy: [strict-origin-when-cross-origin]
23 Transfer-Encoding: [chunked]
24 Vary: ['Accept-Encoding, Origin']
25 X-Content-Type-Options: [nosniff]
26 X-Download-Options: [noopen]
27 X-Frame-Options: [SAMEORIGIN]
28 X-Permitted-Cross-Domain-Policies: [none]
29 X-Request-Id: [7c348bde-1d33-451a-8ae0-87cb09d8a1a2]
30 X-Runtime: ['0.222922']
31 X-XSS-Protection: [1; mode=block]
32 content-length: ['1494']
33 status: {code: 200, message: OK}
34- request:
35 body: null
36 headers:
37 Accept: ['*/*']
38 Accept-Encoding: ['gzip, deflate']
39 Authorization: [Bearer __MASTODON_PY_TEST_ACCESS_TOKEN]
40 Connection: [keep-alive]
41 User-Agent: [python-requests/2.18.4]
42 method: GET
43 uri: http://localhost:3000/api/v1/featured_tags/suggestions
44 response:
45 body: {string: '[{"name":"ringtones","url":"http://localhost/tags/ringtones","history":[{"day":"1570838400","uses":"1","accounts":"1"},{"day":"1570752000","uses":"0","accounts":"0"},{"day":"1570665600","uses":"0","accounts":"0"},{"day":"1570579200","uses":"0","accounts":"0"},{"day":"1570492800","uses":"0","accounts":"0"},{"day":"1570406400","uses":"0","accounts":"0"},{"day":"1570320000","uses":"0","accounts":"0"}]}]'}
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: [fc6ec1a8-9c2e-4c08-925d-f80522d5bf65]
57 X-Runtime: ['0.061457']
58 X-XSS-Protection: [1; mode=block]
59 content-length: ['403']
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 Content-Length: ['0']
69 User-Agent: [python-requests/2.18.4]
70 method: DELETE
71 uri: http://localhost:3000/api/v1/statuses/102951123020457171
72 response:
73 body: {string: '{"id":"102951123020457171","created_at":"2019-10-12T19:44:29.956Z","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/102951123020457171","url":"http://localhost/@mastodonpy_test/102951123020457171","replies_count":0,"reblogs_count":0,"favourites_count":0,"favourited":false,"reblogged":false,"muted":false,"pinned":false,"text":"cool
74 free #ringtones","reblog":null,"application":{"name":"Mastodon.py 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-12T19:44:29.976Z","emojis":[],"fields":[]},"media_attachments":[],"mentions":[],"tags":[{"name":"ringtones","url":"http://localhost/tags/ringtones"}],"emojis":[],"card":null,"poll":null}'}
75 headers:
76 Cache-Control: ['no-cache, no-store']
77 Content-Type: [application/json; charset=utf-8]
78 Referrer-Policy: [strict-origin-when-cross-origin]
79 Transfer-Encoding: [chunked]
80 Vary: ['Accept-Encoding, Origin']
81 X-Content-Type-Options: [nosniff]
82 X-Download-Options: [noopen]
83 X-Frame-Options: [SAMEORIGIN]
84 X-Permitted-Cross-Domain-Policies: [none]
85 X-Request-Id: [68301b80-982f-4397-8c6b-d9e7d8cd308b]
86 X-Runtime: ['0.118422']
87 X-XSS-Protection: [1; mode=block]
88 content-length: ['1325']
89 status: {code: 200, message: OK}
90version: 1
diff --git a/tests/test_account.py b/tests/test_account.py
index 55f766f..2e823d1 100644
--- a/tests/test_account.py
+++ b/tests/test_account.py
@@ -1,6 +1,7 @@
1import pytest 1import pytest
2from mastodon.Mastodon import MastodonAPIError, MastodonIllegalArgumentError 2from mastodon.Mastodon import MastodonAPIError, MastodonIllegalArgumentError
3import re 3import re
4import time
4 5
5@pytest.mark.vcr() 6@pytest.mark.vcr()
6def test_account(api): 7def test_account(api):
@@ -215,3 +216,36 @@ def test_account_pin_unpin(api, api2):
215def test_preferences(api): 216def test_preferences(api):
216 prefs = api.preferences() 217 prefs = api.preferences()
217 assert prefs 218 assert prefs
219
220@pytest.mark.vcr()
221def test_suggested_tags(api):
222 try:
223 status = api.status_post("cool free #ringtones")
224 time.sleep(2)
225
226 suggests = api.featured_tag_suggestions()
227 assert suggests
228 assert len(suggests) > 0
229 finally:
230 api.status_delete(status)
231
232@pytest.mark.vcr()
233def test_featured_tags(api):
234 featured_tag = api.featured_tag_create("mastopytesttag")
235 assert featured_tag
236
237 tag_list = api.featured_tags()
238 assert featured_tag.name in list(map(lambda x: x.name, tag_list))
239
240 api.featured_tag_delete(featured_tag)
241 tag_list = api.featured_tags()
242 assert not featured_tag.name in list(map(lambda x: x.name, tag_list))
243
244
245
246
247
248
249
250
251
Powered by cgit v1.2.3 (git 2.41.0)