[no bump] Implement telegra.ph media forward
Publish plugin / publish (push) Successful in 3s

Adds a share action to the Instant View photo viewer that downloads the
media and re-sends it from the user's own account with a text_url caption
linking back to the source article.

- src/main.py: hooks PhotoViewer.onPhotoShow, injects the toolbar item
- src/ivmedia.py: resolves the viewer's page block to a TL media object
- src/picker.py: share sheet + comment field (restores focus after the
  keyboard animation, which PhotoViewer.showShareAlert does itself)
- src/sender.py: FileLoader download, cache reuse, re-send with caption
- src/icons.py: IconManager-aware drawable lookup so icon packs apply
- tools/verify_bundle.py: offline checks for URL acceptance, caption
  entity shape and bundled-module isolation

Drops the sample greeting/textutil modules.
This commit is contained in:
lyuksovannyy committed 2026-09-25 00:49:58 +02:00
1 parent bfd0f80c6f
commit 05c5212a8a
10 files changed
+757 -95

No files matched your search

+92
View File
@@ -0,0 +1,92 @@
"""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("[telegraph_media_forward] focus handoff failed: %s" % exc)