generated from exteraGramPlugins/pluginBase
Message Logger: show logged edits and deletions from telegram-panel [no bump]
Publish plugin / publish (push) Successful in 4s
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:
1 parent
80f1d5e55c
commit
9b2f26a845
26 files changed
+2928
-112
No files matched your search
@@ -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)
|
||||
Reference in new issue
Block a user