""""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)