Message Logger: show logged edits and deletions from telegram-panel [no bump]
Publish plugin / publish (push) Successful in 4s

Keeps deleted messages (75% opacity, red icon-pack trash icon), restores
logged messages deleted while away, reports deletions, and shows edit
history as a mock chat (Edit history in the message menu, Open topic).
Message links and history open as any signed-in account with access.
Per-master API tokens; chat state synced per checked range with an hour's
validity, cached in SQLite with a configurable lifetime.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
lyuksovannyyandClaude Opus 5.5 committed 2026-10-08 23:55:23 +02:00
1 parent 80f1d5e55c
commit 9b2f26a845
26 files changed
+2928 -112

No files matched your search

+129
View File
@@ -0,0 +1,129 @@
""""Edit history" 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 jutil import field, hook_methods, java_class
CHAT = "org.telegram.ui.ChatActivity"
_LABEL = "Edit history"
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):
self.plugin = plugin
self.open_history = open_history
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 not _worth_it(message):
return
# The createMenu overloads call one another: add the row once.
if _find_item(popup.getContentView(), _LABEL) is not None:
return
from org.telegram.messenger import LocaleController, R
content = popup.getContentView()
# Forward is a row in some chats and an icon in the bottom action bar
# in others (groups): then the row goes 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 Edit history by")
return
forward = anchor
from android_utils import OnClickListener
from org.telegram.ui.ActionBar import ActionBarMenuSubItem
parent = forward.getParent()
item = ActionBarMenuSubItem(forward.getContext(), False, False)
item.setTextAndIcon(_LABEL, R.drawable.msg_edit)
color = forward.getTextView().getCurrentTextColor()
item.setColors(color, color)
item.setMinimumWidth(forward.getMinimumWidth())
def chosen(_view):
self._close_menu(chat)
self.open_history(message)
item.setOnClickListener(OnClickListener(chosen))
parent.addView(item, parent.indexOfChild(forward), forward.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)