Message Logger: trash icon under the profile menu on logged photos
Publish plugin / publish (push) Successful in 4s

The expanded profile gallery shows a red trash icon under the menu button
while it shows a logged photo the chat no longer has.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
lyuksovannyyandClaude Opus 5.5 committed 2026-10-11 02:12:59 +02:00
1 parent dba0c4f833
commit 8535bde5ae
2 files changed
+118 -1

No files matched your search

+1 -1
View File
@@ -3,7 +3,7 @@
"name": "Message Logger", "name": "Message Logger",
"description": "Shows deleted and edited messages and their edit history from your telegram-panel message logger.", "description": "Shows deleted and edited messages and their edit history from your telegram-panel message logger.",
"author": "@lyuksovannyy", "author": "@lyuksovannyy",
"version": "0.1.4", "version": "0.1.5",
"icon": "exteraGramPluginIcons/4", "icon": "exteraGramPluginIcons/4",
"app_version": ">=12.5.1", "app_version": ">=12.5.1",
"sdk_version": ">=1.4.4.3", "sdk_version": ">=1.4.4.3",
+117
View File
@@ -29,6 +29,9 @@ from views.history import _epoch
CONTROLLER = "org.telegram.messenger.MessagesController" CONTROLLER = "org.telegram.messenger.MessagesController"
THUMBS = "org.telegram.ui.Components.GroupedPhotosListView" THUMBS = "org.telegram.ui.Components.GroupedPhotosListView"
VIEWER = "org.telegram.ui.PhotoViewer" VIEWER = "org.telegram.ui.PhotoViewer"
GALLERY = "org.telegram.ui.Components.ProfileGalleryView"
OVERLAYS = "org.telegram.ui.ProfileActivity$OverlaysView"
_MARK_TAG = "message_logger_trash"
NOTIFICATIONS = "org.telegram.messenger.NotificationCenter" NOTIFICATIONS = "org.telegram.messenger.NotificationCenter"
# A logged photo whose id isn't Telegram's (logged by a bot) is the chat's # A logged photo whose id isn't Telegram's (logged by a bot) is the chat's
# own when one of the chat's photos was set this close to its post. # own when one of the chat's photos was set this close to its post.
@@ -87,6 +90,8 @@ class Avatars:
self._appending = False self._appending = False
self._updates = {} # (account, dialog) -> args of its last dialogPhotosUpdate self._updates = {} # (account, dialog) -> args of its last dialogPhotosUpdate
self._icon = None self._icon = None
self._watched = {} # gallery hash -> its page listener (kept alive)
self._position = None # ProfileGalleryView.getRealPosition
self._shown = None # PhotoViewer.isVisible() self._shown = None # PhotoViewer.isVisible()
def install(self): def install(self):
@@ -99,6 +104,20 @@ class Avatars:
except Exception as exc: except Exception as exc:
diag.error("avatars.photos", exc) diag.error("avatars.photos", exc)
class AfterSetData(MethodHook):
def after_hooked_method(self, param):
try:
avatars.watch_gallery(param.thisObject)
except Exception as exc:
diag.error("avatars.gallery", exc)
class AfterOverlays(MethodHook):
def after_hooked_method(self, param):
try:
avatars.update_profile(field(param.thisObject, "this$0"))
except Exception as exc:
diag.error("avatars.profile", exc)
class AfterThumbs(MethodHook): class AfterThumbs(MethodHook):
def after_hooked_method(self, param): def after_hooked_method(self, param):
try: try:
@@ -115,6 +134,9 @@ class Avatars:
self.handles += hook_methods(self.plugin, CONTROLLER, "getDialogPhotos", AfterPhotos()) self.handles += hook_methods(self.plugin, CONTROLLER, "getDialogPhotos", AfterPhotos())
self.handles += hook_methods(self.plugin, THUMBS, "onDraw", AfterThumbs()) self.handles += hook_methods(self.plugin, THUMBS, "onDraw", AfterThumbs())
self.handles += hook_methods(self.plugin, GALLERY, "setData", AfterSetData())
for name in ("onDraw", "setAlphaValue"):
self.handles += hook_methods(self.plugin, OVERLAYS, name, AfterOverlays())
self.handles += hook_methods( self.handles += hook_methods(
self.plugin, NOTIFICATIONS, "postNotificationName", BeforeNotify() self.plugin, NOTIFICATIONS, "postNotificationName", BeforeNotify()
) )
@@ -344,6 +366,101 @@ class Avatars:
icon.setBounds(right - size, bottom - size, right, bottom) icon.setBounds(right - size, bottom - size, right, bottom)
icon.draw(canvas) icon.draw(canvas)
# the profile's trash icon (under the menu button)
def watch_gallery(self, gallery):
"""Redraws the icon as the profile's gallery changes page."""
key = int(gallery.hashCode())
if key in self._watched:
return
from java import dynamic_proxy, jclass
listener_class = jclass("androidx.viewpager.widget.ViewPager$OnPageChangeListener")
avatars = self
class Pages(dynamic_proxy(listener_class)):
def onPageScrolled(self, position, offset, pixels):
pass
def onPageSelected(self, position):
on_ui(lambda: avatars.update_profile(field(gallery, "this$0")))
def onPageScrollStateChanged(self, state):
pass
listener = Pages()
gallery.addOnPageChangeListener(listener)
self._watched[key] = listener
def _current_photo(self, gallery):
if self._position is None:
method = java_class(GALLERY).getDeclaredMethod("getRealPosition")
method.setAccessible(True)
self._position = method
photos = field(gallery, "photos")
index = int(self._position.invoke(gallery))
if photos is None or not 0 <= index < photos.size():
return None
return photos.get(index)
def update_profile(self, profile):
"""Shows the icon while the expanded gallery shows a logged photo."""
if profile is None or not self._ids:
return
gallery = field(profile, "avatarsViewPager")
if gallery is None:
return
photo = self._current_photo(gallery)
shown = (
photo is not None
and int(photo.id) in self._ids
and bool(field(profile, "isPulledDown"))
)
mark = self._profile_mark(profile, create=shown)
if mark is None:
return
from android.view import View
mark.setVisibility(View.VISIBLE if shown else View.GONE)
mark.setAlpha(float(field(profile, "currentExpandAnimatorValue") or 1.0))
def _profile_mark(self, profile, create):
root = field(profile, "fragmentView")
if root is None or not hasattr(root, "addView"):
return None
for index in range(root.getChildCount()):
child = root.getChildAt(index)
if str(child.getTag()) == _MARK_TAG:
return child
if not create:
return None
from android.view import Gravity
from android.widget import FrameLayout, ImageView
from java import jint
from org.telegram.messenger import AndroidUtilities
from org.telegram.ui.ActionBar import ActionBar, Theme
from android.graphics import PorterDuff, PorterDuffColorFilter
from org.telegram.messenger import R
from hooks.render import _trash_drawable
dp = AndroidUtilities.dp
icon = _trash_drawable(R.drawable.msg_delete)
red = jint(Theme.getColor(Theme.key_text_RedBold))
icon.setColorFilter(PorterDuffColorFilter(red, PorterDuff.Mode.SRC_IN))
mark = ImageView(root.getContext())
mark.setTag(_MARK_TAG)
mark.setImageDrawable(icon)
mark.setPadding(dp(8), dp(8), dp(8), dp(8))
mark.setBackground(Theme.createCircleDrawable(dp(36), jint(0x66000000)))
params = FrameLayout.LayoutParams(dp(36), dp(36))
params.gravity = Gravity.TOP | Gravity.RIGHT
params.topMargin = AndroidUtilities.statusBarHeight + ActionBar.getCurrentActionBarHeight()
params.rightMargin = dp(10)
root.addView(mark, params)
return mark
def _trash(self): def _trash(self):
if self._icon is None: if self._icon is None:
from android.graphics import Paint, PorterDuff, PorterDuffColorFilter from android.graphics import Paint, PorterDuff, PorterDuffColorFilter