Files
extendedMediaForwards/src/picker.py
T
lyuksovannyyandClaude Opus 5.5 46503f24a0
Publish plugin / publish (push) Successful in 5s
Rename to Extended Media Forwards and forward rich message media
Rename the plugin (id extended_media_forwards) and add the Forward button
to media opened from rich messages, both single media and carousels.

Rich message media opens PhotoViewer through ChatActivity's
ChatArticlePageBlocksAdapter, whose parent object is the MessageObject
rather than a WebPage, so the article-only check hid the button. The
re-sent media links back to the message when it lives in a channel or
supergroup; elsewhere it is sent without a caption. FileLoader now gets
the parent object so expired file references can be refreshed.

Instant View carousels already worked: slideshow items are flattened
into the article's photo list. Findings are in docs/media-viewer-notes.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 00:51:58 +02:00

93 lines
3.3 KiB
Python

"""Opens the client's own "Share / Forward to" sheet and reports the chosen chats.
`ShareAlert` is the sheet the app itself uses for forwarding, so reusing it keeps
the picker pixel-identical to the stock one. It is constructed with no payload
(`sendingMessageObjects`, `sendingText` and `sendingFile` all null), which makes
its send path a no-op: it selects chats, fires `ShareAlertDelegate.didShare()`
and dismisses. The selection is read back off the (protected) `selectedDialogs`
field and the comment off `commentTextView`.
"""
from android_utils import log, run_on_ui_thread
from hook_utils import get_private_field
_delegate_class = None
# ShareAlert's own constant for "keep the keyboard out of the way".
_SOFT_INPUT_ADJUST_NOTHING = 48
# PhotoViewer hands focus over after AdjustPanLayoutHelper.keyboardDuration,
# which is 250ms in the app's animation constants.
_FOCUS_DELAY_MS = 250
def _make_delegate_class():
"""Builds the ShareAlertDelegate proxy lazily: the Java interface can only
be resolved once the app classes are loadable."""
from java import dynamic_proxy
from org.telegram.ui.Components import ShareAlert
class Delegate(dynamic_proxy(ShareAlert.ShareAlertDelegate)):
def __init__(self, alert, on_picked):
super().__init__()
self.alert = alert
self.on_picked = on_picked
def didCopy(self):
return False
def didShare(self):
self.on_picked(_selected_peers(self.alert), _comment(self.alert))
return Delegate
def _selected_peers(alert):
dialogs = get_private_field(alert, "selectedDialogs")
if dialogs is None:
return []
return [dialogs.keyAt(i) for i in range(dialogs.size())]
def _comment(alert):
field = get_private_field(alert, "commentTextView")
if field is None:
return ""
text = field.getText()
return str(text).strip() if text is not None else ""
def pick_chats(context, on_picked, resources_provider=None):
"""Shows the stock share sheet. `on_picked(dialog_ids, comment)` runs on the
UI thread once the user confirms."""
global _delegate_class
from org.telegram.ui.Components import ShareAlert
alert = ShareAlert(context, None, None, False, None, False, resources_provider)
if _delegate_class is None:
_delegate_class = _make_delegate_class()
# Keep a reference on the module so the proxy is not collected early.
delegate = _delegate_class(alert, on_picked)
alert.setDelegate(delegate)
pick_chats._alive = delegate
# ShareAlert opens over PhotoViewer's window. Taking focus immediately
# fights the viewer's own window animation, so the stock code in
# PhotoViewer.showShareAlert() starts unfocused and hands focus over once
# the keyboard-transition window has passed; without that second step the
# comment field can be tapped but never receives key events.
alert.setFocusable(False)
alert.getWindow().setSoftInputMode(_SOFT_INPUT_ADJUST_NOTHING)
run_on_ui_thread(lambda: _enable_focus(alert), _FOCUS_DELAY_MS)
alert.show()
return alert
def _enable_focus(alert):
try:
if alert.getWindow() is not None:
alert.setFocusable(True)
except Exception as exc:
log("[extended_media_forwards] focus handoff failed: %s" % exc)