kopia lustrzana https://github.com/ihabunek/toot
Added translate status feature (requires server side support)
rodzic
f9fef1927c
commit
be5948bac8
|
@ -211,6 +211,10 @@ def unbookmark(app, user, status_id):
|
||||||
return _status_action(app, user, status_id, 'unbookmark')
|
return _status_action(app, user, status_id, 'unbookmark')
|
||||||
|
|
||||||
|
|
||||||
|
def translate(app, user, status_id):
|
||||||
|
return _status_action(app, user, status_id, 'translate')
|
||||||
|
|
||||||
|
|
||||||
def context(app, user, status_id):
|
def context(app, user, status_id):
|
||||||
url = '/api/v1/statuses/{}/context'.format(status_id)
|
url = '/api/v1/statuses/{}/context'.format(status_id)
|
||||||
|
|
||||||
|
|
|
@ -106,6 +106,7 @@ class TUI(urwid.Frame):
|
||||||
self.timeline = None
|
self.timeline = None
|
||||||
self.overlay = None
|
self.overlay = None
|
||||||
self.exception = None
|
self.exception = None
|
||||||
|
self.can_translate = False
|
||||||
|
|
||||||
super().__init__(self.body, header=self.header, footer=self.footer)
|
super().__init__(self.body, header=self.header, footer=self.footer)
|
||||||
|
|
||||||
|
@ -206,6 +207,7 @@ class TUI(urwid.Frame):
|
||||||
urwid.connect_signal(timeline, "source", _source)
|
urwid.connect_signal(timeline, "source", _source)
|
||||||
urwid.connect_signal(timeline, "links", _links)
|
urwid.connect_signal(timeline, "links", _links)
|
||||||
urwid.connect_signal(timeline, "zoom", _zoom)
|
urwid.connect_signal(timeline, "zoom", _zoom)
|
||||||
|
urwid.connect_signal(timeline, "translate", self.async_translate)
|
||||||
|
|
||||||
def build_timeline(self, name, statuses, local):
|
def build_timeline(self, name, statuses, local):
|
||||||
def _close(*args):
|
def _close(*args):
|
||||||
|
@ -232,7 +234,7 @@ class TUI(urwid.Frame):
|
||||||
self.loop.set_alarm_in(5, lambda *args: self.footer.clear_message())
|
self.loop.set_alarm_in(5, lambda *args: self.footer.clear_message())
|
||||||
config.save_config(self.config)
|
config.save_config(self.config)
|
||||||
|
|
||||||
timeline = Timeline(name, statuses)
|
timeline = Timeline(name, statuses, self.can_translate)
|
||||||
|
|
||||||
self.connect_default_timeline_signals(timeline)
|
self.connect_default_timeline_signals(timeline)
|
||||||
urwid.connect_signal(timeline, "next", _next)
|
urwid.connect_signal(timeline, "next", _next)
|
||||||
|
@ -261,8 +263,8 @@ class TUI(urwid.Frame):
|
||||||
statuses = ancestors + [status] + descendants
|
statuses = ancestors + [status] + descendants
|
||||||
focus = len(ancestors)
|
focus = len(ancestors)
|
||||||
|
|
||||||
timeline = Timeline("thread", statuses, focus, is_thread=True)
|
timeline = Timeline("thread", statuses, self.can_translate, focus,
|
||||||
|
is_thread=True)
|
||||||
self.connect_default_timeline_signals(timeline)
|
self.connect_default_timeline_signals(timeline)
|
||||||
urwid.connect_signal(timeline, "close", _close)
|
urwid.connect_signal(timeline, "close", _close)
|
||||||
|
|
||||||
|
@ -303,6 +305,11 @@ class TUI(urwid.Frame):
|
||||||
Attempt to update max_toot_chars from instance data.
|
Attempt to update max_toot_chars from instance data.
|
||||||
Does not work on vanilla Mastodon, works on Pleroma.
|
Does not work on vanilla Mastodon, works on Pleroma.
|
||||||
See: https://github.com/tootsuite/mastodon/issues/4915
|
See: https://github.com/tootsuite/mastodon/issues/4915
|
||||||
|
|
||||||
|
Also attempt to update translation flag from instance
|
||||||
|
data. Translation is only present on Mastodon 4+ servers
|
||||||
|
where the administrator has enabled this feature.
|
||||||
|
See: https://github.com/mastodon/mastodon/issues/19328
|
||||||
"""
|
"""
|
||||||
def _load_instance():
|
def _load_instance():
|
||||||
return api.get_instance(self.app.instance)
|
return api.get_instance(self.app.instance)
|
||||||
|
@ -310,6 +317,17 @@ class TUI(urwid.Frame):
|
||||||
def _done(instance):
|
def _done(instance):
|
||||||
if "max_toot_chars" in instance:
|
if "max_toot_chars" in instance:
|
||||||
self.max_toot_chars = instance["max_toot_chars"]
|
self.max_toot_chars = instance["max_toot_chars"]
|
||||||
|
if "translation" in instance:
|
||||||
|
# instance is advertising translation service
|
||||||
|
self.can_translate = instance["translation"]["enabled"]
|
||||||
|
else:
|
||||||
|
if "version" in instance:
|
||||||
|
# fallback check:
|
||||||
|
# get the major version number of the server
|
||||||
|
# this works for Mastodon and Pleroma version strings
|
||||||
|
# Mastodon versions < 4 do not have translation service
|
||||||
|
# Revisit this logic if Pleroma implements translation
|
||||||
|
self.can_translate = int(instance["version"][0]) > 3
|
||||||
|
|
||||||
return self.run_in_thread(_load_instance, done_callback=_done)
|
return self.run_in_thread(_load_instance, done_callback=_done)
|
||||||
|
|
||||||
|
@ -484,6 +502,37 @@ class TUI(urwid.Frame):
|
||||||
done_callback=_done
|
done_callback=_done
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def async_translate(self, timeline, status):
|
||||||
|
def _translate():
|
||||||
|
logger.info("Translating {}".format(status))
|
||||||
|
self.footer.set_message("Translating status {}".format(status.id))
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = api.translate(self.app, self.user, status.id)
|
||||||
|
except:
|
||||||
|
response = None
|
||||||
|
finally:
|
||||||
|
self.footer.clear_message()
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
def _done(response):
|
||||||
|
if response is not None:
|
||||||
|
# Create a new Status that is translated
|
||||||
|
new_data = status.data
|
||||||
|
new_data["content"] = response["content"]
|
||||||
|
new_data["detected_source_language"] = response["detected_source_language"]
|
||||||
|
new_status = self.make_status(new_data)
|
||||||
|
|
||||||
|
timeline.update_status(new_status)
|
||||||
|
self.footer.set_message(f"Translated status {status.id} from {response['detected_source_language']}")
|
||||||
|
else:
|
||||||
|
self.footer.set_error_message("Translate server error")
|
||||||
|
|
||||||
|
self.loop.set_alarm_in(5, lambda *args: self.footer.clear_message())
|
||||||
|
|
||||||
|
self.run_in_thread(_translate, done_callback=_done )
|
||||||
|
|
||||||
def async_delete_status(self, timeline, status):
|
def async_delete_status(self, timeline, status):
|
||||||
def _delete():
|
def _delete():
|
||||||
api.delete_status(self.app, self.user, status.id)
|
api.delete_status(self.app, self.user, status.id)
|
||||||
|
|
|
@ -164,6 +164,7 @@ class Help(urwid.Padding):
|
||||||
yield urwid.Text(h(" [B] - Boost/unboost status"))
|
yield urwid.Text(h(" [B] - Boost/unboost status"))
|
||||||
yield urwid.Text(h(" [C] - Compose new status"))
|
yield urwid.Text(h(" [C] - Compose new status"))
|
||||||
yield urwid.Text(h(" [F] - Favourite/unfavourite status"))
|
yield urwid.Text(h(" [F] - Favourite/unfavourite status"))
|
||||||
|
yield urwid.Text(h(" [N] - Translate status, if possible"))
|
||||||
yield urwid.Text(h(" [R] - Reply to current status"))
|
yield urwid.Text(h(" [R] - Reply to current status"))
|
||||||
yield urwid.Text(h(" [S] - Show text marked as sensitive"))
|
yield urwid.Text(h(" [S] - Show text marked as sensitive"))
|
||||||
yield urwid.Text(h(" [T] - Show status thread (replies)"))
|
yield urwid.Text(h(" [T] - Show status thread (replies)"))
|
||||||
|
|
|
@ -28,19 +28,21 @@ class Timeline(urwid.Columns):
|
||||||
"source", # Show status source
|
"source", # Show status source
|
||||||
"links", # Show status links
|
"links", # Show status links
|
||||||
"thread", # Show thread for status
|
"thread", # Show thread for status
|
||||||
|
"translate", # Translate status
|
||||||
"save", # Save current timeline
|
"save", # Save current timeline
|
||||||
"zoom", # Open status in scrollable popup window
|
"zoom", # Open status in scrollable popup window
|
||||||
]
|
]
|
||||||
|
|
||||||
def __init__(self, name, statuses, focus=0, is_thread=False):
|
def __init__(self, name, statuses, can_translate, focus=0, is_thread=False):
|
||||||
self.name = name
|
self.name = name
|
||||||
self.is_thread = is_thread
|
self.is_thread = is_thread
|
||||||
self.statuses = statuses
|
self.statuses = statuses
|
||||||
|
self.can_translate = can_translate
|
||||||
self.status_list = self.build_status_list(statuses, focus=focus)
|
self.status_list = self.build_status_list(statuses, focus=focus)
|
||||||
try:
|
try:
|
||||||
self.status_details = StatusDetails(statuses[focus], is_thread)
|
self.status_details = StatusDetails(statuses[focus], is_thread, can_translate)
|
||||||
except IndexError:
|
except IndexError:
|
||||||
self.status_details = StatusDetails(None, is_thread)
|
self.status_details = StatusDetails(None, is_thread, can_translate)
|
||||||
|
|
||||||
super().__init__([
|
super().__init__([
|
||||||
("weight", 40, self.status_list),
|
("weight", 40, self.status_list),
|
||||||
|
@ -97,7 +99,7 @@ class Timeline(urwid.Columns):
|
||||||
self.draw_status_details(status)
|
self.draw_status_details(status)
|
||||||
|
|
||||||
def draw_status_details(self, status):
|
def draw_status_details(self, status):
|
||||||
self.status_details = StatusDetails(status, self.is_thread)
|
self.status_details = StatusDetails(status, self.is_thread, self.can_translate)
|
||||||
self.contents[2] = urwid.Padding(self.status_details, left=1), ("weight", 60, False)
|
self.contents[2] = urwid.Padding(self.status_details, left=1), ("weight", 60, False)
|
||||||
|
|
||||||
def keypress(self, size, key):
|
def keypress(self, size, key):
|
||||||
|
@ -157,6 +159,11 @@ class Timeline(urwid.Columns):
|
||||||
self._emit("links", status)
|
self._emit("links", status)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if key in ("n", "N"):
|
||||||
|
if self.can_translate:
|
||||||
|
self._emit("translate", status)
|
||||||
|
return
|
||||||
|
|
||||||
if key in ("t", "T"):
|
if key in ("t", "T"):
|
||||||
self._emit("thread", status)
|
self._emit("thread", status)
|
||||||
return
|
return
|
||||||
|
@ -228,7 +235,7 @@ class Timeline(urwid.Columns):
|
||||||
|
|
||||||
|
|
||||||
class StatusDetails(urwid.Pile):
|
class StatusDetails(urwid.Pile):
|
||||||
def __init__(self, status, in_thread):
|
def __init__(self, status, in_thread, can_translate=False):
|
||||||
"""
|
"""
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
|
@ -239,6 +246,7 @@ class StatusDetails(urwid.Pile):
|
||||||
Whether the status is rendered from a thread status list.
|
Whether the status is rendered from a thread status list.
|
||||||
"""
|
"""
|
||||||
self.in_thread = in_thread
|
self.in_thread = in_thread
|
||||||
|
self.can_translate = can_translate
|
||||||
reblogged_by = status.author if status and status.reblog else None
|
reblogged_by = status.author if status and status.reblog else None
|
||||||
widget_list = list(self.content_generator(status.original, reblogged_by)
|
widget_list = list(self.content_generator(status.original, reblogged_by)
|
||||||
if status else ())
|
if status else ())
|
||||||
|
@ -290,10 +298,14 @@ class StatusDetails(urwid.Pile):
|
||||||
application = application.get("name")
|
application = application.get("name")
|
||||||
|
|
||||||
yield ("pack", urwid.AttrWrap(urwid.Divider("-"), "gray"))
|
yield ("pack", urwid.AttrWrap(urwid.Divider("-"), "gray"))
|
||||||
|
|
||||||
|
translated = status.data.get("detected_source_language")
|
||||||
|
|
||||||
yield ("pack", urwid.Text([
|
yield ("pack", urwid.Text([
|
||||||
("gray", "⤶ {} ".format(status.data["replies_count"])),
|
("gray", "⤶ {} ".format(status.data["replies_count"])),
|
||||||
("yellow" if status.reblogged else "gray", "♺ {} ".format(status.data["reblogs_count"])),
|
("yellow" if status.reblogged else "gray", "♺ {} ".format(status.data["reblogs_count"])),
|
||||||
("yellow" if status.favourited else "gray", "★ {}".format(status.data["favourites_count"])),
|
("yellow" if status.favourited else "gray", "★ {}".format(status.data["favourites_count"])),
|
||||||
|
("yellow" if translated else "gray", " · Translated from {} ".format(translated) if translated else ""),
|
||||||
("gray", " · {}".format(application) if application else ""),
|
("gray", " · {}".format(application) if application else ""),
|
||||||
]))
|
]))
|
||||||
|
|
||||||
|
@ -310,6 +322,7 @@ class StatusDetails(urwid.Pile):
|
||||||
"[R]eply",
|
"[R]eply",
|
||||||
"So[u]rce",
|
"So[u]rce",
|
||||||
"[Z]oom",
|
"[Z]oom",
|
||||||
|
"Tra[n]slate" if self.can_translate else "",
|
||||||
"[H]elp",
|
"[H]elp",
|
||||||
]
|
]
|
||||||
options = " ".join(o for o in options if o)
|
options = " ".join(o for o in options if o)
|
||||||
|
|
Ładowanie…
Reference in New Issue