diff --git a/TODO.md b/TODO.md index cf845fc..d1b1e7a 100644 --- a/TODO.md +++ b/TODO.md @@ -65,5 +65,5 @@ General improvements that would be good to do before doing another release: * [ ] Split mastodon.py into parts in some way that makes sense, it's getting very unwieldy * [x] Fix the CI * [ ] Get test coverage like, real high -* [ ] Add all those streaming events?? +* [x] Add all those streaming events?? * [ ] Document return values diff --git a/docs/index.rst b/docs/index.rst index 450300e..3d52b08 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -876,6 +876,19 @@ Admin account dicts 'account': # The user's account, as a standard user dict } +Status edit dicts +~~~~~~~~~~~~~~~~~ +.. _status edit dict: + +.. code-block:: python + + mastodonstatus_history(id)[0] + # Returns the following dictionary + { + TODO + } + + App registration and user authentication ---------------------------------------- Before you can use the Mastodon API, you have to register your @@ -967,6 +980,8 @@ These functions allow you to get information about single statuses. .. automethod:: Mastodon.status_reblogged_by .. automethod:: Mastodon.status_favourited_by .. automethod:: Mastodon.status_card +.. automethod:: Mastodon.status_history +.. automethod:: Mastodon.status_source Reading data: Scheduled statuses ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -1142,6 +1157,8 @@ interact with already posted statuses. .. automethod:: Mastodon.status_bookmark .. automethod:: Mastodon.status_unbookmark .. automethod:: Mastodon.status_delete +.. automethod:: Mastodon.status_update + Writing data: Scheduled statuses ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/mastodon/Mastodon.py b/mastodon/Mastodon.py index 2a70ce7..48b1307 100644 --- a/mastodon/Mastodon.py +++ b/mastodon/Mastodon.py @@ -229,30 +229,25 @@ class Mastodon: __DICT_VERSION_HASHTAG = "2.3.4" __DICT_VERSION_EMOJI = "3.0.0" __DICT_VERSION_RELATIONSHIP = "3.3.0" - __DICT_VERSION_NOTIFICATION = bigger_version(bigger_version( - "1.0.0", __DICT_VERSION_ACCOUNT), __DICT_VERSION_STATUS) + __DICT_VERSION_NOTIFICATION = bigger_version(bigger_version("1.0.0", __DICT_VERSION_ACCOUNT), __DICT_VERSION_STATUS) __DICT_VERSION_CONTEXT = bigger_version("1.0.0", __DICT_VERSION_STATUS) __DICT_VERSION_LIST = "2.1.0" __DICT_VERSION_CARD = "3.2.0" - __DICT_VERSION_SEARCHRESULT = bigger_version(bigger_version(bigger_version( - "1.0.0", __DICT_VERSION_ACCOUNT), __DICT_VERSION_STATUS), __DICT_VERSION_HASHTAG) + __DICT_VERSION_SEARCHRESULT = bigger_version(bigger_version(bigger_version("1.0.0", __DICT_VERSION_ACCOUNT), __DICT_VERSION_STATUS), __DICT_VERSION_HASHTAG) __DICT_VERSION_ACTIVITY = "2.1.2" __DICT_VERSION_REPORT = "2.9.1" __DICT_VERSION_PUSH = "2.4.0" __DICT_VERSION_PUSH_NOTIF = "2.4.0" __DICT_VERSION_FILTER = "2.4.3" - __DICT_VERSION_CONVERSATION = bigger_version(bigger_version( - "2.6.0", __DICT_VERSION_ACCOUNT), __DICT_VERSION_STATUS) - __DICT_VERSION_SCHEDULED_STATUS = bigger_version( - "2.7.0", __DICT_VERSION_STATUS) + __DICT_VERSION_CONVERSATION = bigger_version(bigger_version("2.6.0", __DICT_VERSION_ACCOUNT), __DICT_VERSION_STATUS) + __DICT_VERSION_SCHEDULED_STATUS = bigger_version("2.7.0", __DICT_VERSION_STATUS) __DICT_VERSION_PREFERENCES = "2.8.0" - __DICT_VERSION_ADMIN_ACCOUNT = bigger_version( - "2.9.1", __DICT_VERSION_ACCOUNT) + __DICT_VERSION_ADMIN_ACCOUNT = bigger_version("2.9.1", __DICT_VERSION_ACCOUNT) __DICT_VERSION_FEATURED_TAG = "3.0.0" __DICT_VERSION_MARKER = "3.0.0" __DICT_VERSION_REACTION = "3.1.0" - __DICT_VERSION_ANNOUNCEMENT = bigger_version( - "3.1.0", __DICT_VERSION_REACTION) + __DICT_VERSION_ANNOUNCEMENT = bigger_version("3.1.0", __DICT_VERSION_REACTION) + __DICT_VERSION_STATUS_EDIT = "3.5.0" ### # Registering apps @@ -1797,6 +1792,82 @@ class Mastodon: ### # Writing data: Statuses ### + def __status_internal(self, status, in_reply_to_id=None, media_ids=None, + sensitive=False, visibility=None, spoiler_text=None, + language=None, idempotency_key=None, content_type=None, + scheduled_at=None, poll=None, quote_id=None, edit=False): + if quote_id is not None: + if self.feature_set != "fedibird": + raise MastodonIllegalArgumentError('quote_id is only available with feature set fedibird') + quote_id = self.__unpack_id(quote_id) + + if content_type is not None: + if self.feature_set != "pleroma": + raise MastodonIllegalArgumentError('content_type is only available with feature set pleroma') + # It would be better to read this from nodeinfo and cache, but this is easier + if not content_type in ["text/plain", "text/html", "text/markdown", "text/bbcode"]: + raise MastodonIllegalArgumentError('Invalid content type specified') + + if in_reply_to_id is not None: + in_reply_to_id = self.__unpack_id(in_reply_to_id) + + if scheduled_at is not None: + scheduled_at = self.__consistent_isoformat_utc(scheduled_at) + + params_initial = locals() + + # Validate poll/media exclusivity + if poll is not None: + if media_ids is not None and len(media_ids) != 0: + raise ValueError( + 'Status can have media or poll attached - not both.') + + # Validate visibility parameter + valid_visibilities = ['private', 'public', 'unlisted', 'direct'] + if params_initial['visibility'] is None: + del params_initial['visibility'] + else: + params_initial['visibility'] = params_initial['visibility'].lower() + if params_initial['visibility'] not in valid_visibilities: + raise ValueError('Invalid visibility value! Acceptable values are %s' % valid_visibilities) + + if params_initial['language'] is None: + del params_initial['language'] + + if params_initial['sensitive'] is False: + del [params_initial['sensitive']] + + headers = {} + if idempotency_key is not None: + headers['Idempotency-Key'] = idempotency_key + + if media_ids is not None: + try: + media_ids_proper = [] + if not isinstance(media_ids, (list, tuple)): + media_ids = [media_ids] + for media_id in media_ids: + media_ids_proper.append(self.__unpack_id(media_id)) + except Exception as e: + raise MastodonIllegalArgumentError("Invalid media dict: %s" % e) + + params_initial["media_ids"] = media_ids_proper + + if params_initial['content_type'] is None: + del params_initial['content_type'] + + use_json = False + if poll is not None: + use_json = True + + params = self.__generate_params(params_initial, ['idempotency_key', 'edit']) + if edit is None: + # Post + return self.__api_request('POST', '/api/v1/statuses', params, headers=headers, use_json=use_json) + else: + # Edit + return self.__api_request('PUT', '/api/v1/statuses/{0}'.format(str(self.__unpack_id(edit))), params, headers=headers, use_json=use_json) + @api_version("1.0.0", "2.8.0", __DICT_VERSION_STATUS) def status_post(self, status, in_reply_to_id=None, media_ids=None, sensitive=False, visibility=None, spoiler_text=None, @@ -1857,77 +1928,21 @@ class Mastodon: Returns a `toot dict`_ with the new status. """ - if quote_id is not None: - if self.feature_set != "fedibird": - raise MastodonIllegalArgumentError( - 'quote_id is only available with feature set fedibird') - quote_id = self.__unpack_id(quote_id) - - if content_type is not None: - if self.feature_set != "pleroma": - raise MastodonIllegalArgumentError( - 'content_type is only available with feature set pleroma') - # It would be better to read this from nodeinfo and cache, but this is easier - if not content_type in ["text/plain", "text/html", "text/markdown", "text/bbcode"]: - raise MastodonIllegalArgumentError( - 'Invalid content type specified') - - if in_reply_to_id is not None: - in_reply_to_id = self.__unpack_id(in_reply_to_id) - - if scheduled_at is not None: - scheduled_at = self.__consistent_isoformat_utc(scheduled_at) - - params_initial = locals() - - # Validate poll/media exclusivity - if poll is not None: - if media_ids is not None and len(media_ids) != 0: - raise ValueError( - 'Status can have media or poll attached - not both.') - - # Validate visibility parameter - valid_visibilities = ['private', 'public', 'unlisted', 'direct'] - if params_initial['visibility'] is None: - del params_initial['visibility'] - else: - params_initial['visibility'] = params_initial['visibility'].lower() - if params_initial['visibility'] not in valid_visibilities: - raise ValueError('Invalid visibility value! Acceptable ' - 'values are %s' % valid_visibilities) - - if params_initial['language'] is None: - del params_initial['language'] - - if params_initial['sensitive'] is False: - del [params_initial['sensitive']] - - headers = {} - if idempotency_key is not None: - headers['Idempotency-Key'] = idempotency_key - - if media_ids is not None: - try: - media_ids_proper = [] - if not isinstance(media_ids, (list, tuple)): - media_ids = [media_ids] - for media_id in media_ids: - media_ids_proper.append(self.__unpack_id(media_id)) - except Exception as e: - raise MastodonIllegalArgumentError("Invalid media " - "dict: %s" % e) - - params_initial["media_ids"] = media_ids_proper - - if params_initial['content_type'] is None: - del params_initial['content_type'] - - use_json = False - if poll is not None: - use_json = True - - params = self.__generate_params(params_initial, ['idempotency_key']) - return self.__api_request('POST', '/api/v1/statuses', params, headers=headers, use_json=use_json) + return self.__status_internal( + status, + in_reply_to_id, + media_ids, + sensitive, + visibility, + spoiler_text, + language, + idempotency_key, + content_type, + scheduled_at, + poll, + quote_id, + edit=None + ) @api_version("1.0.0", "2.8.0", __DICT_VERSION_STATUS) def toot(self, status): @@ -1940,6 +1955,45 @@ class Mastodon: """ return self.status_post(status) + @api_version("3.5.0", "3.5.0", __DICT_VERSION_STATUS) + def status_update(self, id, status = None, spoiler_text = None, sensitive = None, media_ids = None, poll = None): + """ + Edit a status. The meanings of the fields are largely the same as in `status_post()`_, + though not every field can be edited. + + Note that editing a poll will reset the votes. + """ + return self.__status_internal( + status = status, + media_ids = media_ids, + sensitive = sensitive, + spoiler_text = spoiler_text, + poll = poll, + edit = id + ) + + @api_version("3.5.0", "3.5.0", __DICT_VERSION_STATUS_EDIT) + def status_history(self, id): + """ + Returns the edit history of a status as a list of `status edit dicts`_, starting + from the original form. Note that this means that a status that has been edited + once will have *two* entries in this list, a status that has been edited twice + will have three, and so on. + """ + id = self.__unpack_id(id) + return self.__api_request('GET', "/api/v1/statuses/{0}/history".format(str(id))) + + def status_source(self, id): + """ + Returns the source of a status for editing. + + Return value is a dictionary containing exactly the parameters you could pass to + `status_update()`_ to change nothing about the status, except `status` is `text` + instead. + """ + id = self.__unpack_id(id) + return self.__api_request('GET', "/api/v1/statuses/{0}/source".format(str(id))) + @api_version("1.0.0", "2.8.0", __DICT_VERSION_STATUS) def status_reply(self, to_status, status, in_reply_to_id=None, media_ids=None, sensitive=False, visibility=None, spoiler_text=None, diff --git a/tests/cassettes/test_status_edit.yaml b/tests/cassettes/test_status_edit.yaml new file mode 100644 index 0000000..1343b28 --- /dev/null +++ b/tests/cassettes/test_status_edit.yaml @@ -0,0 +1,379 @@ +interactions: +- request: + body: status=the+best+editor%3F+why%2C+of+course+it+is+VS+Code + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Authorization: + - Bearer __MASTODON_PY_TEST_ACCESS_TOKEN + Connection: + - keep-alive + Content-Length: + - '56' + Content-Type: + - application/x-www-form-urlencoded + User-Agent: + - tests/v311 + method: POST + uri: http://localhost:3000/api/v1/statuses + response: + body: + string: '{"id":"109384054168698393","created_at":"2022-11-21T22:03:29.362Z","in_reply_to_id":null,"in_reply_to_account_id":null,"sensitive":false,"spoiler_text":"","visibility":"public","language":"ja","uri":"http://localhost:3000/users/mastodonpy_test/statuses/109384054168698393","url":"http://localhost:3000/@mastodonpy_test/109384054168698393","replies_count":0,"reblogs_count":0,"favourites_count":0,"edited_at":null,"favourited":false,"reblogged":false,"muted":false,"bookmarked":false,"pinned":false,"content":"\u003cp\u003ethe + best editor? why, of course it is VS Code\u003c/p\u003e","filtered":[],"reblog":null,"application":{"name":"Mastodon.py + test suite","website":null},"account":{"id":"109383687546708201","username":"mastodonpy_test","acct":"mastodonpy_test","display_name":"","locked":true,"bot":false,"discoverable":null,"group":false,"created_at":"2022-11-21T00:00:00.000Z","note":"","url":"http://localhost:3000/@mastodonpy_test","avatar":"http://localhost:3000/avatars/original/missing.png","avatar_static":"http://localhost:3000/avatars/original/missing.png","header":"http://localhost:3000/headers/original/missing.png","header_static":"http://localhost:3000/headers/original/missing.png","followers_count":0,"following_count":0,"statuses_count":6,"last_status_at":"2022-11-21","noindex":false,"emojis":[],"fields":[]},"media_attachments":[],"mentions":[],"tags":[],"emojis":[],"card":null,"poll":null}' + headers: + Cache-Control: + - no-store + Content-Security-Policy: + - 'base-uri ''none''; default-src ''none''; frame-ancestors ''none''; font-src + ''self'' http://localhost:3000; img-src ''self'' https: data: blob: http://localhost:3000; + style-src ''self'' http://localhost:3000 ''nonce-3UDW6mpgnOTfx5Euaa1LQA==''; + media-src ''self'' https: data: http://localhost:3000; frame-src ''self'' + https:; manifest-src ''self'' http://localhost:3000; connect-src ''self'' + data: blob: http://localhost:3000 http://localhost:3000 ws://localhost:4000 + ws://localhost:3035 http://localhost:3035; script-src ''self'' ''unsafe-inline'' + ''unsafe-eval'' http://localhost:3000; child-src ''self'' blob: http://localhost:3000; + worker-src ''self'' blob: http://localhost:3000' + Content-Type: + - application/json; charset=utf-8 + ETag: + - W/"8a45eb762bdb11cdd6cd63bfebe39f63" + Referrer-Policy: + - strict-origin-when-cross-origin + Transfer-Encoding: + - chunked + Vary: + - Accept, Origin + X-Content-Type-Options: + - nosniff + X-Download-Options: + - noopen + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-RateLimit-Limit: + - '300' + X-RateLimit-Remaining: + - '292' + X-RateLimit-Reset: + - '2022-11-22T00:00:00.383365Z' + X-Request-Id: + - e56f662b-28f8-4ba7-9b38-36e71ac7d73c + X-Runtime: + - '0.033929' + X-XSS-Protection: + - 1; mode=block + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Authorization: + - Bearer __MASTODON_PY_TEST_ACCESS_TOKEN_2 + Connection: + - keep-alive + User-Agent: + - tests/v311 + method: GET + uri: http://localhost:3000/api/v1/statuses/109384054168698393/history + response: + body: + string: '[]' + headers: + Cache-Control: + - no-store + Content-Security-Policy: + - 'base-uri ''none''; default-src ''none''; frame-ancestors ''none''; font-src + ''self'' http://localhost:3000; img-src ''self'' https: data: blob: http://localhost:3000; + style-src ''self'' http://localhost:3000 ''nonce-oKku/pMs24FqYkpW5MkiGQ==''; + media-src ''self'' https: data: http://localhost:3000; frame-src ''self'' + https:; manifest-src ''self'' http://localhost:3000; connect-src ''self'' + data: blob: http://localhost:3000 http://localhost:3000 ws://localhost:4000 + ws://localhost:3035 http://localhost:3035; script-src ''self'' ''unsafe-inline'' + ''unsafe-eval'' http://localhost:3000; child-src ''self'' blob: http://localhost:3000; + worker-src ''self'' blob: http://localhost:3000' + Content-Type: + - application/json; charset=utf-8 + ETag: + - W/"4f53cda18c2baa0c0354bb5f9a3ecbe5" + Referrer-Policy: + - strict-origin-when-cross-origin + Transfer-Encoding: + - chunked + Vary: + - Accept, Origin + X-Content-Type-Options: + - nosniff + X-Download-Options: + - noopen + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Request-Id: + - 4a780264-f947-4745-a5de-7165d8eee3d9 + X-Runtime: + - '0.010494' + X-XSS-Protection: + - 1; mode=block + status: + code: 200 + message: OK +- request: + body: status=the+best+editor%3F+why%2C+of+course+it+is+the+KDE+Advanced+Text+Editor%2C+Kate + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Authorization: + - Bearer __MASTODON_PY_TEST_ACCESS_TOKEN + Connection: + - keep-alive + Content-Length: + - '85' + Content-Type: + - application/x-www-form-urlencoded + User-Agent: + - tests/v311 + method: PUT + uri: http://localhost:3000/api/v1/statuses/109384054168698393 + response: + body: + string: '{"id":"109384054168698393","created_at":"2022-11-21T22:03:29.362Z","in_reply_to_id":null,"in_reply_to_account_id":null,"sensitive":false,"spoiler_text":"","visibility":"public","language":"ja","uri":"http://localhost:3000/users/mastodonpy_test/statuses/109384054168698393","url":"http://localhost:3000/@mastodonpy_test/109384054168698393","replies_count":0,"reblogs_count":0,"favourites_count":0,"edited_at":"2022-11-21T22:03:29.418Z","favourited":false,"reblogged":false,"muted":false,"bookmarked":false,"pinned":false,"content":"\u003cp\u003ethe + best editor? why, of course it is the KDE Advanced Text Editor, Kate\u003c/p\u003e","filtered":[],"reblog":null,"application":{"name":"Mastodon.py + test suite","website":null},"account":{"id":"109383687546708201","username":"mastodonpy_test","acct":"mastodonpy_test","display_name":"","locked":true,"bot":false,"discoverable":null,"group":false,"created_at":"2022-11-21T00:00:00.000Z","note":"","url":"http://localhost:3000/@mastodonpy_test","avatar":"http://localhost:3000/avatars/original/missing.png","avatar_static":"http://localhost:3000/avatars/original/missing.png","header":"http://localhost:3000/headers/original/missing.png","header_static":"http://localhost:3000/headers/original/missing.png","followers_count":0,"following_count":0,"statuses_count":6,"last_status_at":"2022-11-21","noindex":false,"emojis":[],"fields":[]},"media_attachments":[],"mentions":[],"tags":[],"emojis":[],"card":null,"poll":null}' + headers: + Cache-Control: + - no-store + Content-Security-Policy: + - 'base-uri ''none''; default-src ''none''; frame-ancestors ''none''; font-src + ''self'' http://localhost:3000; img-src ''self'' https: data: blob: http://localhost:3000; + style-src ''self'' http://localhost:3000 ''nonce-11jMbHJPLcAv+qMX8EK9Eg==''; + media-src ''self'' https: data: http://localhost:3000; frame-src ''self'' + https:; manifest-src ''self'' http://localhost:3000; connect-src ''self'' + data: blob: http://localhost:3000 http://localhost:3000 ws://localhost:4000 + ws://localhost:3035 http://localhost:3035; script-src ''self'' ''unsafe-inline'' + ''unsafe-eval'' http://localhost:3000; child-src ''self'' blob: http://localhost:3000; + worker-src ''self'' blob: http://localhost:3000' + Content-Type: + - application/json; charset=utf-8 + ETag: + - W/"976b69a807cf9d4601d0c73c8eb249fc" + Referrer-Policy: + - strict-origin-when-cross-origin + Transfer-Encoding: + - chunked + Vary: + - Accept, Origin + X-Content-Type-Options: + - nosniff + X-Download-Options: + - noopen + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-RateLimit-Limit: + - '300' + X-RateLimit-Remaining: + - '291' + X-RateLimit-Reset: + - '2022-11-22T00:00:00.437259Z' + X-Request-Id: + - 1f4c3ffd-1c13-45ee-a9df-8d7fff11a17c + X-Runtime: + - '0.033812' + X-XSS-Protection: + - 1; mode=block + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Authorization: + - Bearer __MASTODON_PY_TEST_ACCESS_TOKEN_2 + Connection: + - keep-alive + User-Agent: + - tests/v311 + method: GET + uri: http://localhost:3000/api/v1/statuses/109384054168698393 + response: + body: + string: '{"id":"109384054168698393","created_at":"2022-11-21T22:03:29.362Z","in_reply_to_id":null,"in_reply_to_account_id":null,"sensitive":false,"spoiler_text":"","visibility":"public","language":"ja","uri":"http://localhost:3000/users/mastodonpy_test/statuses/109384054168698393","url":"http://localhost:3000/@mastodonpy_test/109384054168698393","replies_count":0,"reblogs_count":0,"favourites_count":0,"edited_at":"2022-11-21T22:03:29.418Z","favourited":false,"reblogged":false,"muted":false,"bookmarked":false,"content":"\u003cp\u003ethe + best editor? why, of course it is the KDE Advanced Text Editor, Kate\u003c/p\u003e","filtered":[],"reblog":null,"application":{"name":"Mastodon.py + test suite","website":null},"account":{"id":"109383687546708201","username":"mastodonpy_test","acct":"mastodonpy_test","display_name":"","locked":true,"bot":false,"discoverable":null,"group":false,"created_at":"2022-11-21T00:00:00.000Z","note":"","url":"http://localhost:3000/@mastodonpy_test","avatar":"http://localhost:3000/avatars/original/missing.png","avatar_static":"http://localhost:3000/avatars/original/missing.png","header":"http://localhost:3000/headers/original/missing.png","header_static":"http://localhost:3000/headers/original/missing.png","followers_count":0,"following_count":0,"statuses_count":6,"last_status_at":"2022-11-21","noindex":false,"emojis":[],"fields":[]},"media_attachments":[],"mentions":[],"tags":[],"emojis":[],"card":null,"poll":null}' + headers: + Cache-Control: + - no-store + Content-Security-Policy: + - 'base-uri ''none''; default-src ''none''; frame-ancestors ''none''; font-src + ''self'' http://localhost:3000; img-src ''self'' https: data: blob: http://localhost:3000; + style-src ''self'' http://localhost:3000 ''nonce-/HjTa7cbsYkrasMByN5dRw==''; + media-src ''self'' https: data: http://localhost:3000; frame-src ''self'' + https:; manifest-src ''self'' http://localhost:3000; connect-src ''self'' + data: blob: http://localhost:3000 http://localhost:3000 ws://localhost:4000 + ws://localhost:3035 http://localhost:3035; script-src ''self'' ''unsafe-inline'' + ''unsafe-eval'' http://localhost:3000; child-src ''self'' blob: http://localhost:3000; + worker-src ''self'' blob: http://localhost:3000' + Content-Type: + - application/json; charset=utf-8 + ETag: + - W/"8d8244c105ccd1fff42ddc2e75976f91" + Referrer-Policy: + - strict-origin-when-cross-origin + Transfer-Encoding: + - chunked + Vary: + - Accept, Origin + X-Content-Type-Options: + - nosniff + X-Download-Options: + - noopen + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Request-Id: + - 36c0e7e0-064a-42f1-91e0-82c90ebc4456 + X-Runtime: + - '0.030588' + X-XSS-Protection: + - 1; mode=block + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Authorization: + - Bearer __MASTODON_PY_TEST_ACCESS_TOKEN_2 + Connection: + - keep-alive + User-Agent: + - tests/v311 + method: GET + uri: http://localhost:3000/api/v1/statuses/109384054168698393/history + response: + body: + string: '[{"content":"\u003cp\u003ethe best editor? why, of course it is VS + Code\u003c/p\u003e","spoiler_text":"","sensitive":false,"created_at":"2022-11-21T22:03:29.362Z","account":{"id":"109383687546708201","username":"mastodonpy_test","acct":"mastodonpy_test","display_name":"","locked":true,"bot":false,"discoverable":null,"group":false,"created_at":"2022-11-21T00:00:00.000Z","note":"","url":"http://localhost:3000/@mastodonpy_test","avatar":"http://localhost:3000/avatars/original/missing.png","avatar_static":"http://localhost:3000/avatars/original/missing.png","header":"http://localhost:3000/headers/original/missing.png","header_static":"http://localhost:3000/headers/original/missing.png","followers_count":0,"following_count":0,"statuses_count":6,"last_status_at":"2022-11-21","noindex":false,"emojis":[],"fields":[]},"media_attachments":[],"emojis":[]},{"content":"\u003cp\u003ethe + best editor? why, of course it is the KDE Advanced Text Editor, Kate\u003c/p\u003e","spoiler_text":"","sensitive":false,"created_at":"2022-11-21T22:03:29.418Z","account":{"id":"109383687546708201","username":"mastodonpy_test","acct":"mastodonpy_test","display_name":"","locked":true,"bot":false,"discoverable":null,"group":false,"created_at":"2022-11-21T00:00:00.000Z","note":"","url":"http://localhost:3000/@mastodonpy_test","avatar":"http://localhost:3000/avatars/original/missing.png","avatar_static":"http://localhost:3000/avatars/original/missing.png","header":"http://localhost:3000/headers/original/missing.png","header_static":"http://localhost:3000/headers/original/missing.png","followers_count":0,"following_count":0,"statuses_count":6,"last_status_at":"2022-11-21","noindex":false,"emojis":[],"fields":[]},"media_attachments":[],"emojis":[]}]' + headers: + Cache-Control: + - no-store + Content-Security-Policy: + - 'base-uri ''none''; default-src ''none''; frame-ancestors ''none''; font-src + ''self'' http://localhost:3000; img-src ''self'' https: data: blob: http://localhost:3000; + style-src ''self'' http://localhost:3000 ''nonce-mn4VRGJCjg55CXsiLcW5tA==''; + media-src ''self'' https: data: http://localhost:3000; frame-src ''self'' + https:; manifest-src ''self'' http://localhost:3000; connect-src ''self'' + data: blob: http://localhost:3000 http://localhost:3000 ws://localhost:4000 + ws://localhost:3035 http://localhost:3035; script-src ''self'' ''unsafe-inline'' + ''unsafe-eval'' http://localhost:3000; child-src ''self'' blob: http://localhost:3000; + worker-src ''self'' blob: http://localhost:3000' + Content-Type: + - application/json; charset=utf-8 + ETag: + - W/"e5185ce4a3ebf261210613a8bb85e77e" + Referrer-Policy: + - strict-origin-when-cross-origin + Transfer-Encoding: + - chunked + Vary: + - Accept, Origin + X-Content-Type-Options: + - nosniff + X-Download-Options: + - noopen + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Request-Id: + - 17896b84-96a0-4ed4-b3a0-8fca353a3725 + X-Runtime: + - '0.016628' + X-XSS-Protection: + - 1; mode=block + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Authorization: + - Bearer __MASTODON_PY_TEST_ACCESS_TOKEN_2 + Connection: + - keep-alive + User-Agent: + - tests/v311 + method: GET + uri: http://localhost:3000/api/v1/statuses/109384054168698393/source + response: + body: + string: '{"id":"109384054168698393","text":"the best editor? why, of course + it is the KDE Advanced Text Editor, Kate","spoiler_text":""}' + headers: + Cache-Control: + - no-store + Content-Security-Policy: + - 'base-uri ''none''; default-src ''none''; frame-ancestors ''none''; font-src + ''self'' http://localhost:3000; img-src ''self'' https: data: blob: http://localhost:3000; + style-src ''self'' http://localhost:3000 ''nonce-1CVpCwMC0PSWzPM4QmGGoA==''; + media-src ''self'' https: data: http://localhost:3000; frame-src ''self'' + https:; manifest-src ''self'' http://localhost:3000; connect-src ''self'' + data: blob: http://localhost:3000 http://localhost:3000 ws://localhost:4000 + ws://localhost:3035 http://localhost:3035; script-src ''self'' ''unsafe-inline'' + ''unsafe-eval'' http://localhost:3000; child-src ''self'' blob: http://localhost:3000; + worker-src ''self'' blob: http://localhost:3000' + Content-Type: + - application/json; charset=utf-8 + ETag: + - W/"41ed5fd640708627beb7f31b84cb65d3" + Referrer-Policy: + - strict-origin-when-cross-origin + Transfer-Encoding: + - chunked + Vary: + - Accept, Origin + X-Content-Type-Options: + - nosniff + X-Download-Options: + - noopen + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Request-Id: + - b5919220-e51e-4496-80f5-5a17e908702f + X-Runtime: + - '0.016356' + X-XSS-Protection: + - 1; mode=block + status: + code: 200 + message: OK +version: 1 diff --git a/tests/test_status.py b/tests/test_status.py index 20e32f2..e747571 100644 --- a/tests/test_status.py +++ b/tests/test_status.py @@ -213,3 +213,19 @@ def test_scheduled_status_long_part2(api): if text in status.content: found_status = True assert found_status + +@pytest.mark.vcr() +def test_status_edit(api, api2): + status = api.status_post("the best editor? why, of course it is VS Code") + edit_list_1 = api2.status_history(status) + status_edited = api.status_update(status, "the best editor? why, of course it is the KDE Advanced Text Editor, Kate") + status_result = api2.status(status) + edit_list_2 = api2.status_history(status) + + assert len(edit_list_1) == 0 + assert len(edit_list_2) == 2 + assert "the best editor? why, of course it is the KDE Advanced Text Editor, Kate" in status_result.content + + source = api2.status_source(status) + assert source.text == "the best editor? why, of course it is the KDE Advanced Text Editor, Kate" + \ No newline at end of file