translation support

pull/397/head
halcy 2025-02-15 17:27:04 +02:00
rodzic e5c00a9229
commit 404ff39a2b
9 zmienionych plików z 309 dodań i 4 usunięć

Wyświetl plik

@ -37,6 +37,7 @@ v2.0.0 (IN PROGRESS)
* Add media attachment editing
* Add email domain blocking support (`admin_email_domain_blocks`, `admin_email_domain_block`, `admin_email_domain_block_create`, `admin_email_domain_block_delete`)
* Add `instance_extended_description`
* Add `instance_translation_languages`, translation support (`status_translate`)
v1.8.1
------

Wyświetl plik

@ -85,3 +85,8 @@ Reading
Writing
~~~~~~~
.. automethod:: Mastodon.poll_vote
Translation
-----------
These functions allow you to get machine translations for statuses, if the instance supports it.
.. automethod:: Mastodon.status_translate

Wyświetl plik

@ -59,3 +59,7 @@ These functions allow you to search for users, tags and, when enabled, full text
.. automethod:: Mastodon.search
.. automethod:: Mastodon.search_v2
Translation support
-------------------
.. automethod:: Mastodon.instance_translation_languages

Wyświetl plik

@ -6,7 +6,7 @@ from mastodon.compat import urlparse
from mastodon.internals import Mastodon as Internals
from mastodon.return_types import Instance, InstanceV2, NonPaginatableList, Activity, Nodeinfo, AttribAccessDict, Rule, Announcement, CustomEmoji, Account, IdType, ExtendedDescription
from typing import Union, Optional
from typing import Union, Optional, Dict, List
class Mastodon(Internals):
###
@ -206,3 +206,16 @@ class Mastodon(Internals):
Retrieve the instance's extended description.
"""
return self.__api_request('GET', '/api/v1/instance/extended_description', parse=False).decode("utf-8")
def instance_translation_languages(self) -> Dict[str, List[str]]:
"""
Retrieve the instance's translation languages.
Returns a dict with language pairs, where the key is the language code and the value is a list of language codes that the instance can translate that language to.
"""
ret_value = self.__api_request('GET', '/api/v1/instance/translation_languages')
result_real = AttribAccessDict()
for key, value in ret_value.items():
result_real[key] = NonPaginatableList(value)
return result_real

Wyświetl plik

@ -9,7 +9,7 @@ from mastodon.utility import api_version
from mastodon.internals import Mastodon as Internals
from mastodon.return_types import Status, IdType, ScheduledStatus, PreviewCard, Context, NonPaginatableList, Account,\
MediaAttachment, Poll, StatusSource, StatusEdit, PaginatableList, PathOrFile
MediaAttachment, Poll, StatusSource, StatusEdit, PaginatableList, PathOrFile, Translation
from typing import Union, Optional, List, Dict, Any, Tuple
@ -570,3 +570,18 @@ class Mastodon(Internals):
"""
id = self.__unpack_id(id)
self.__api_request('DELETE', f'/api/v1/scheduled_statuses/{id}')
##
# Translation
##
@api_version("4.0.0", "4.0.0")
def status_translate(self, id: Union[Status, IdType], lang: Optional[str] = None) -> Translation:
"""
Translate the status content into some language.
Raises a MastodonAPIError if the server can't perform the requested translation, for any
reason (doesn't support translation, unsupported language pair, etc.).
"""
id = self.__unpack_id(id)
params = self.__generate_params(locals(), ['id'])
return self.__api_request('POST', f'/api/v1/statuses/{id}/translate', params)

Wyświetl plik

@ -0,0 +1,193 @@
interactions:
- request:
body: status=Toot%21
headers:
Accept:
- '*/*'
Accept-Encoding:
- gzip, deflate
Authorization:
- Bearer __MASTODON_PY_TEST_ACCESS_TOKEN
Connection:
- keep-alive
Content-Length:
- '14'
Content-Type:
- application/x-www-form-urlencoded
User-Agent:
- tests/v311
method: POST
uri: http://localhost:3000/api/v1/statuses
response:
body:
string: '{"id":"114008596311558624","created_at":"2025-02-15T15:25:22.431Z","in_reply_to_id":null,"in_reply_to_account_id":null,"sensitive":false,"spoiler_text":"","visibility":"private","language":"ja","uri":"http://localhost:3000/users/mastodonpy_test/statuses/114008596311558624","url":"http://localhost:3000/@mastodonpy_test/114008596311558624","replies_count":0,"reblogs_count":0,"favourites_count":0,"edited_at":null,"favourited":false,"reblogged":false,"muted":false,"bookmarked":false,"pinned":false,"content":"\u003cp\u003eToot!\u003c/p\u003e","filtered":[],"reblog":null,"application":{"name":"Mastodon.py
test suite","website":null},"account":{"id":"113998801242326861","username":"mastodonpy_test","acct":"mastodonpy_test","display_name":"John
Lennon","locked":true,"bot":false,"discoverable":null,"indexable":false,"group":false,"created_at":"2025-02-13T00:00:00.000Z","note":"\u003cp\u003eI
walk funny\u003c/p\u003e","url":"http://localhost:3000/@mastodonpy_test","uri":"http://localhost:3000/users/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":1,"statuses_count":9,"last_status_at":"2025-02-15","hide_collections":null,"noindex":false,"emojis":[],"roles":[],"fields":[{"name":"bread","value":"toasty.","verified_at":null},{"name":"lasagna","value":"no!!!","verified_at":null}]},"media_attachments":[],"mentions":[],"tags":[],"emojis":[],"card":null,"poll":null}'
headers:
Cache-Control:
- private, no-store
Content-Length:
- '1638'
Content-Security-Policy:
- default-src 'none'; frame-ancestors 'none'; form-action 'none'
Content-Type:
- application/json; charset=utf-8
ETag:
- W/"b7efe40a95ee9fc07d42c7656cbdf8ec"
Referrer-Policy:
- strict-origin-when-cross-origin
Server-Timing:
- cache_read.active_support;dur=0.08, sql.active_record;dur=19.05, cache_generate.active_support;dur=5.82,
cache_write.active_support;dur=0.18, instantiation.active_record;dur=1.14,
start_processing.action_controller;dur=0.00, transaction.active_record;dur=10.37,
render.active_model_serializers;dur=23.35, process_action.action_controller;dur=79.00
X-Content-Type-Options:
- nosniff
X-Frame-Options:
- SAMEORIGIN
X-Permitted-Cross-Domain-Policies:
- none
X-RateLimit-Limit:
- '300'
X-RateLimit-Remaining:
- '299'
X-RateLimit-Reset:
- '2025-02-15T18:00:00.477175Z'
X-Request-Id:
- f44e326a-a840-4685-92b7-008f5552cff8
X-Runtime:
- '0.103655'
X-XSS-Protection:
- '0'
vary:
- Authorization, Origin
status:
code: 200
message: OK
- request:
body: lang=de
headers:
Accept:
- '*/*'
Accept-Encoding:
- gzip, deflate
Authorization:
- Bearer __MASTODON_PY_TEST_ACCESS_TOKEN
Connection:
- keep-alive
Content-Length:
- '7'
Content-Type:
- application/x-www-form-urlencoded
User-Agent:
- tests/v311
method: POST
uri: http://localhost:3000/api/v1/statuses/114008596311558624/translate
response:
body:
string: '{"error":"This action is not allowed"}'
headers:
Cache-Control:
- private, no-store
Content-Length:
- '38'
Content-Security-Policy:
- default-src 'none'; frame-ancestors 'none'; form-action 'none'
Content-Type:
- application/json; charset=utf-8
Referrer-Policy:
- strict-origin-when-cross-origin
Server-Timing:
- cache_read.active_support;dur=0.02, sql.active_record;dur=1.74, cache_generate.active_support;dur=1.03,
cache_write.active_support;dur=0.08, instantiation.active_record;dur=0.58,
start_processing.action_controller;dur=0.01, render.active_model_serializers;dur=0.05,
process_action.action_controller;dur=45.87
X-Content-Type-Options:
- nosniff
X-Frame-Options:
- SAMEORIGIN
X-Permitted-Cross-Domain-Policies:
- none
X-RateLimit-Limit:
- '300'
X-RateLimit-Remaining:
- '299'
X-RateLimit-Reset:
- '2025-02-15T15:30:00.565674Z'
X-Request-Id:
- 3e421f8f-e7eb-4e1c-9d60-6d5d726ab2c1
X-Runtime:
- '0.081421'
X-XSS-Protection:
- '0'
vary:
- Authorization, Origin
status:
code: 403
message: Forbidden
- request:
body: null
headers:
Accept:
- '*/*'
Accept-Encoding:
- gzip, deflate
Authorization:
- Bearer __MASTODON_PY_TEST_ACCESS_TOKEN
Connection:
- keep-alive
Content-Length:
- '0'
User-Agent:
- tests/v311
method: DELETE
uri: http://localhost:3000/api/v1/statuses/114008596311558624
response:
body:
string: '{"id":"114008596311558624","created_at":"2025-02-15T15:25:22.431Z","in_reply_to_id":null,"in_reply_to_account_id":null,"sensitive":false,"spoiler_text":"","visibility":"private","language":"ja","uri":"http://localhost:3000/users/mastodonpy_test/statuses/114008596311558624","url":"http://localhost:3000/@mastodonpy_test/114008596311558624","replies_count":0,"reblogs_count":0,"favourites_count":0,"edited_at":null,"favourited":false,"reblogged":false,"muted":false,"bookmarked":false,"pinned":false,"text":"Toot!","filtered":[],"reblog":null,"application":{"name":"Mastodon.py
test suite","website":null},"account":{"id":"113998801242326861","username":"mastodonpy_test","acct":"mastodonpy_test","display_name":"John
Lennon","locked":true,"bot":false,"discoverable":null,"indexable":false,"group":false,"created_at":"2025-02-13T00:00:00.000Z","note":"\u003cp\u003eI
walk funny\u003c/p\u003e","url":"http://localhost:3000/@mastodonpy_test","uri":"http://localhost:3000/users/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":1,"statuses_count":8,"last_status_at":"2025-02-15","hide_collections":null,"noindex":false,"emojis":[],"roles":[],"fields":[{"name":"bread","value":"toasty.","verified_at":null},{"name":"lasagna","value":"no!!!","verified_at":null}]},"media_attachments":[],"mentions":[],"tags":[],"emojis":[],"card":null,"poll":null}'
headers:
Cache-Control:
- private, no-store
Content-Length:
- '1608'
Content-Security-Policy:
- default-src 'none'; frame-ancestors 'none'; form-action 'none'
Content-Type:
- application/json; charset=utf-8
ETag:
- W/"34838106aadb8da6b97b3fb5ba7dc85e"
Referrer-Policy:
- strict-origin-when-cross-origin
Server-Timing:
- cache_read.active_support;dur=0.05, sql.active_record;dur=7.63, cache_generate.active_support;dur=3.53,
cache_write.active_support;dur=0.16, instantiation.active_record;dur=0.61,
start_processing.action_controller;dur=0.00, transaction.active_record;dur=4.03,
render.active_model_serializers;dur=27.61, process_action.action_controller;dur=57.26
X-Content-Type-Options:
- nosniff
X-Frame-Options:
- SAMEORIGIN
X-Permitted-Cross-Domain-Policies:
- none
X-RateLimit-Limit:
- '30'
X-RateLimit-Remaining:
- '29'
X-RateLimit-Reset:
- '2025-02-15T15:30:00.683901Z'
X-Request-Id:
- a79bf3e1-4091-4332-9c47-45e8766ba9e6
X-Runtime:
- '0.072595'
X-XSS-Protection:
- '0'
vary:
- Authorization, Origin
status:
code: 200
message: OK
version: 1

Wyświetl plik

@ -0,0 +1,63 @@
interactions:
- request:
body: null
headers:
Accept:
- '*/*'
Accept-Encoding:
- gzip, deflate
Authorization:
- Bearer __MASTODON_PY_TEST_ACCESS_TOKEN
Connection:
- keep-alive
User-Agent:
- tests/v311
method: GET
uri: http://localhost:3000/api/v1/instance/translation_languages
response:
body:
string: '{}'
headers:
Cache-Control:
- max-age=300, public, stale-while-revalidate=30, stale-if-error=86400
Content-Length:
- '2'
Content-Security-Policy:
- default-src 'none'; frame-ancestors 'none'; form-action 'none'
Content-Type:
- application/json; charset=utf-8
Date:
- Sat, 15 Feb 2025 15:20:56 GMT
ETag:
- W/"44136fa355b3678a1146ad16f7e8649e"
Referrer-Policy:
- strict-origin-when-cross-origin
Server-Timing:
- cache_read.active_support;dur=0.04, sql.active_record;dur=3.48, cache_generate.active_support;dur=6.47,
cache_write.active_support;dur=0.13, instantiation.active_record;dur=0.38,
start_processing.action_controller;dur=0.01, render.active_model_serializers;dur=0.02,
process_action.action_controller;dur=37.74
X-Content-Type-Options:
- nosniff
X-Frame-Options:
- SAMEORIGIN
X-Permitted-Cross-Domain-Policies:
- none
X-RateLimit-Limit:
- '300'
X-RateLimit-Remaining:
- '299'
X-RateLimit-Reset:
- '2025-02-15T15:25:00.208109Z'
X-Request-Id:
- 915a0f13-8d73-43e0-bcfe-2f43294bdb6d
X-Runtime:
- '0.074836'
X-XSS-Protection:
- '0'
vary:
- Accept, Origin
status:
code: 200
message: OK
version: 1

Wyświetl plik

@ -100,3 +100,7 @@ def test_version_parsing(api):
assert parse_version_string(api._Mastodon__normalize_version_string("3.5.0 (compatible; Pleroma 1.2.3)")) == (3, 5, 0)
assert parse_version_string(api._Mastodon__normalize_version_string("3.2.1rc3 (compatible; Akkoma 3.2.4+shinychariot)")) == (3, 2, 1)
assert parse_version_string(api._Mastodon__normalize_version_string("3.5.3+0.17.3+git-6f4cb2f")) == (3, 5, 3)
@pytest.mark.vcr()
def test_translation_languages(api):
assert api.instance_translation_languages() is not None

Wyświetl plik

@ -289,3 +289,10 @@ def test_status_update_with_media_edit(api2):
assert updated_status['media_attachments'][0]['preview_url'] != status['media_attachments'][0]['preview_url']
finally:
api2.status_delete(status['id'])
@pytest.mark.vcr()
def test_status_translate(api, status):
# our test server does not support translation, so this will raise a MastodonAPIError
with pytest.raises(MastodonAPIError):
translation = api.status_translate(status['id'], 'de')