Files
messageLogger/src/hooks/menu.py
T
lyuksovannyyandClaude Opus 5.5 362b358d8d
Publish plugin / publish (push) Successful in 6s
Message Logger: hidden messages on the server, removed chats, old chat photos
- Hidden deleted messages are marked on the server (local marks migrated);
  "Reveal hidden messages" in the chat menu, Unhide in the message menu and
  the selection bar.
- Chats an account lost are reported, listed among its chats again and
  opened read-only with their logged messages, all deleted.
- Logged chat photos: a removed chat's avatar, and in a chat's photos the
  ones it no longer has, translucent with a trash icon in the viewer's
  previews.
- Fix: the deleted-post comments thread is answered locally again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 23:12:33 +02:00

144 lines
5.2 KiB
Python

""""Edit history" (and "Unhide" for a revealed hidden message, see
hooks/hidden.py) in the message menu itself, right above Forward.
Plugin menu items (MenuItemData) only go into the menu's Plugins folder, and
exteraGram drops option ids it doesn't know from fillMessageMenu's lists, so
the row is added to the built popup instead: after ChatActivity.createMenu,
an ActionBarMenuSubItem styled like the Forward row (found by its localized
text) is inserted above it, or first in the list when Forward is an icon of
the bottom action bar (groups). ChatActivity.selectedObject is the message.
"""
from base_plugin import MethodHook
import diag
import store
from hooks.hidden import is_revealed_hidden
from jutil import field, hook_methods, java_class
CHAT = "org.telegram.ui.ChatActivity"
_HISTORY = "Edit history"
_UNHIDE = "Unhide"
def _worth_it(message):
"""Messages that may have a logged history: edited ones (the app's own
flag or the server's state), which logger posts are once their header
was refreshed."""
if message is None or int(message.getId()) <= 0:
return False
if int(getattr(message.messageOwner, "edit_date", 0) or 0):
return True
return store.is_edited(
int(message.currentAccount), int(message.getDialogId()), int(message.getId())
)
def _first_item(view):
"""The first ActionBarMenuSubItem under `view` (the main page comes
before the swipe-back pages), else None."""
if view.getClass().getSimpleName() == "ActionBarMenuSubItem":
return view
if hasattr(view, "getChildCount"):
for index in range(view.getChildCount()):
found = _first_item(view.getChildAt(index))
if found is not None:
return found
return None
def _find_item(view, text):
"""The ActionBarMenuSubItem showing `text` under `view`, else None."""
if view.getClass().getSimpleName() == "ActionBarMenuSubItem":
label = view.getTextView()
return view if label is not None and str(label.getText()) == text else None
if hasattr(view, "getChildCount"):
for index in range(view.getChildCount()):
found = _find_item(view.getChildAt(index), text)
if found is not None:
return found
return None
class Menu:
def __init__(self, plugin, open_history, unhide):
self.plugin = plugin
self.open_history = open_history
self.unhide = unhide # hooks/hidden.Hidden.unhide
self.handles = []
self._close = None
def install(self):
menu = self
class AfterCreate(MethodHook):
def after_hooked_method(self, param):
try:
menu.add_item(param.thisObject)
except Exception as exc:
diag.error("menu.add", exc)
self.handles += hook_methods(self.plugin, CHAT, "createMenu", AfterCreate())
def uninstall(self):
for handle in self.handles:
try:
self.plugin.unhook_method(handle)
except Exception:
pass
self.handles = []
def add_item(self, chat):
message = field(chat, "selectedObject")
popup = field(chat, "scrimPopupWindow")
if popup is None or message is None:
return
from org.telegram.messenger import R
rows = []
if _worth_it(message):
rows.append((_HISTORY, R.drawable.msg_edit, lambda: self.open_history(message)))
if is_revealed_hidden(message):
rows.append((_UNHIDE, R.drawable.msg_views, lambda: self.unhide([message])))
# The createMenu overloads call one another: add the rows once.
content = popup.getContentView()
if not rows or _find_item(content, rows[0][0]) is not None:
return
from org.telegram.messenger import LocaleController
# Forward is a row in some chats and an icon in the bottom action bar
# in others (groups): then the rows go first in the list.
anchor = _find_item(
content, str(LocaleController.getString(R.string.Forward))
) or _first_item(content)
if anchor is None:
diag.log("menu: no row to place the rows by")
return
for label, icon, action in rows:
self._add_row(chat, anchor, label, icon, action)
def _add_row(self, chat, anchor, label, icon, action):
from android_utils import OnClickListener
from org.telegram.ui.ActionBar import ActionBarMenuSubItem
parent = anchor.getParent()
item = ActionBarMenuSubItem(anchor.getContext(), False, False)
item.setTextAndIcon(label, icon)
color = anchor.getTextView().getCurrentTextColor()
item.setColors(color, color)
item.setMinimumWidth(anchor.getMinimumWidth())
def chosen(_view):
self._close_menu(chat)
action()
item.setOnClickListener(OnClickListener(chosen))
parent.addView(item, parent.indexOfChild(anchor), anchor.getLayoutParams())
def _close_menu(self, chat):
if self._close is None:
method = java_class(CHAT).getDeclaredMethod("closeMenu")
method.setAccessible(True)
self._close = method
self._close.invoke(chat)