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

+176
View File
@@ -0,0 +1,176 @@
"""Chat cell decoration of deleted messages: translucent, with a red trash icon
(from the active icon pack) in the time row like the app's own "edited" pencil.
ChatMessageCell (exteraGram 12.10.6, probed on device): setMessageObject
(5 and 6 args), setAlpha(float), measureTime(MessageObject), and the private
`currentTimeString` (SpannableStringBuilder), `timeTextWidth`, `timeWidth`,
`timeLayout`. The pencil is a ColoredImageSpan on a leading "\\u200d " whose
width (Layout.getDesiredWidth with Theme.chat_timePaint) the time widths
include; the trash icon is added the same way, before the bubble is laid out.
"""
from base_plugin import MethodHook
import diag
import store
from config import ALPHA_CHOICES, DELETED_ALPHA
from jutil import field, hook_methods
CELL = "org.telegram.ui.Cells.ChatMessageCell"
_ICON = "\u200d "
def _dp(value):
from org.telegram.messenger import AndroidUtilities
return AndroidUtilities.dp(value)
def _message(cell):
try:
return cell.getMessageObject()
except Exception:
return None
def _key(message):
"""(account, dialog_id, message_id) of a real message, else None."""
if message is None:
return None
message_id = int(message.getId())
if message_id <= 0:
return None
return int(message.currentAccount), int(message.getDialogId()), message_id
def _deleted(message):
key = _key(message)
return key is not None and store.is_deleted(*key)
def _trash_drawable(res_id):
"""The trash icon of the active icon pack: the activity's resources are
exteraGram's ExteraResources, which apply it (the application context's
plain Resources give the stock icon)."""
from client_utils import get_last_fragment
fragment = get_last_fragment()
activity = fragment.getParentActivity() if fragment is not None else None
if activity is not None:
resources = activity.getResources()
else:
from org.telegram.messenger import ApplicationLoader
resources = ApplicationLoader.applicationContext.getResources()
return resources.getDrawable(res_id, None).mutate()
def _set_int(cell, name, value):
from hook_utils import set_private_field
from java import jint
set_private_field(cell, name, jint(int(value)))
class Render:
def __init__(self, plugin):
self.plugin = plugin
self.handles = []
def _alpha(self):
index = int(self.plugin.get_setting(DELETED_ALPHA, 0) or 0)
return ALPHA_CHOICES[max(0, min(index, len(ALPHA_CHOICES) - 1))][1]
def install(self):
plugin = self.plugin
render = self
class AfterBind(MethodHook):
def after_hooked_method(self, param):
try:
render.apply_alpha(param.thisObject)
except Exception as exc:
diag.error("render.bind", exc)
class BeforeAlpha(MethodHook):
def before_hooked_method(self, param):
try:
render.scale_alpha(param)
except Exception as exc:
diag.error("render.alpha", exc)
class AfterMeasureTime(MethodHook):
def after_hooked_method(self, param):
try:
render.add_trash(param.thisObject, param.args[0])
except Exception as exc:
diag.error("render.time", exc)
self.handles += hook_methods(plugin, CELL, "setMessageObject", AfterBind())
self.handles += hook_methods(plugin, CELL, "setAlpha", BeforeAlpha())
self.handles += hook_methods(plugin, CELL, "measureTime", AfterMeasureTime())
def uninstall(self):
for handle in self.handles:
try:
self.plugin.unhook_method(handle)
except Exception:
pass
self.handles = []
# opacity
def apply_alpha(self, cell):
"""Re-applies the cell's alpha through setAlpha (scaled below)."""
cell.setAlpha(1.0)
def scale_alpha(self, param):
"""Deleted messages stay translucent whatever alpha the list's item
animators set: every setAlpha on their cell is scaled."""
if not _deleted(_message(param.thisObject)):
return
from java import jfloat
param.args[0] = jfloat(float(param.args[0]) * self._alpha())
# trash icon
def add_trash(self, cell, message):
if not _deleted(message):
return
# measureTime builds the string afresh: the icon goes in once, before
# the edited pencil if there is one.
text = field(cell, "currentTimeString")
if text is None:
return
from android.text import Layout, Spanned, StaticLayout
from org.telegram.messenger import R
from org.telegram.ui.ActionBar import Theme
from org.telegram.ui.Components import ColoredImageSpan
paint = Theme.chat_timePaint
before = Layout.getDesiredWidth(text, paint)
from java import jint
span = ColoredImageSpan(_trash_drawable(R.drawable.msg_delete))
span.setSize(_dp(13))
span.setTranslateX(-3.0)
span.setOverrideColor(jint(Theme.getColor(Theme.key_text_RedBold)))
text.insert(0, _ICON)
text.setSpan(span, 0, 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
added = int(Layout.getDesiredWidth(text, paint) - before + 0.999)
_set_int(cell, "timeTextWidth", int(field(cell, "timeTextWidth") or 0) + added)
_set_int(cell, "timeWidth", int(field(cell, "timeWidth") or 0) + added)
if field(cell, "timeLayout") is not None:
from hook_utils import set_private_field
layout = StaticLayout(
text,
paint,
int(field(cell, "timeTextWidth")) + _dp(100),
Layout.Alignment.ALIGN_NORMAL,
1.0,
0.0,
False,
)
set_private_field(cell, "timeLayout", layout)