generated from exteraGramPlugins/pluginBase
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>
177 lines
5.9 KiB
Python
177 lines
5.9 KiB
Python
"""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)
|