generated from exteraGramPlugins/pluginBase
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5e4c2f2a6f | ||
|
|
8535bde5ae |
No files matched your search
+1
-1
@@ -3,7 +3,7 @@
|
||||
"name": "Message Logger",
|
||||
"description": "Shows deleted and edited messages and their edit history from your telegram-panel message logger.",
|
||||
"author": "@lyuksovannyy",
|
||||
"version": "0.1.4",
|
||||
"version": "0.1.6",
|
||||
"icon": "exteraGramPluginIcons/4",
|
||||
"app_version": ">=12.5.1",
|
||||
"sdk_version": ">=1.4.4.3",
|
||||
|
||||
+214
-2
@@ -29,6 +29,9 @@ from views.history import _epoch
|
||||
CONTROLLER = "org.telegram.messenger.MessagesController"
|
||||
THUMBS = "org.telegram.ui.Components.GroupedPhotosListView"
|
||||
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"
|
||||
# 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.
|
||||
@@ -87,6 +90,9 @@ class Avatars:
|
||||
self._appending = False
|
||||
self._updates = {} # (account, dialog) -> args of its last dialogPhotosUpdate
|
||||
self._icon = None
|
||||
self._dates = {} # logged photo id -> (set at, deleted at), ISO strings
|
||||
self._watched = {} # gallery hash -> its page listener (kept alive)
|
||||
self._position = None # ProfileGalleryView.getRealPosition
|
||||
self._shown = None # PhotoViewer.isVisible()
|
||||
|
||||
def install(self):
|
||||
@@ -99,6 +105,35 @@ class Avatars:
|
||||
except Exception as 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 AfterSwitch(MethodHook):
|
||||
def after_hooked_method(self, param):
|
||||
try:
|
||||
avatars.viewer_dates(param.thisObject, int(param.args[0]))
|
||||
except Exception as exc:
|
||||
diag.error("avatars.dates", exc)
|
||||
|
||||
class BeforeOpen(MethodHook):
|
||||
def before_hooked_method(self, param):
|
||||
try:
|
||||
if avatars.open_without_photo(param.thisObject):
|
||||
param.setResult(None)
|
||||
except Exception as exc:
|
||||
diag.error("avatars.open", exc)
|
||||
|
||||
class AfterThumbs(MethodHook):
|
||||
def after_hooked_method(self, param):
|
||||
try:
|
||||
@@ -115,6 +150,15 @@ class Avatars:
|
||||
|
||||
self.handles += hook_methods(self.plugin, CONTROLLER, "getDialogPhotos", AfterPhotos())
|
||||
self.handles += hook_methods(self.plugin, THUMBS, "onDraw", AfterThumbs())
|
||||
self.handles += hook_methods(self.plugin, GALLERY, "setData", AfterSetData())
|
||||
self.handles += hook_methods(
|
||||
self.plugin, VIEWER, "setIsAboutToSwitchToIndex", AfterSwitch()
|
||||
)
|
||||
self.handles += hook_methods(
|
||||
self.plugin, "org.telegram.ui.ProfileActivity", "openAvatar", BeforeOpen()
|
||||
)
|
||||
for name in ("onDraw", "setAlphaValue"):
|
||||
self.handles += hook_methods(self.plugin, OVERLAYS, name, AfterOverlays())
|
||||
self.handles += hook_methods(
|
||||
self.plugin, NOTIFICATIONS, "postNotificationName", BeforeNotify()
|
||||
)
|
||||
@@ -171,6 +215,7 @@ class Avatars:
|
||||
recorded.append(
|
||||
(photo, int(original) if original else None, _epoch(avatar.get("date")))
|
||||
)
|
||||
self._dates[int(photo.id)] = (avatar.get("date"), avatar.get("deleted_at"))
|
||||
with self._lock:
|
||||
self._recorded[(account, dialog_id)] = recorded
|
||||
self._loaded_at[(account, dialog_id)] = time.monotonic()
|
||||
@@ -227,14 +272,14 @@ class Avatars:
|
||||
field(viewer, "avatarsDialogId") or 0
|
||||
) == int(dialog_id)
|
||||
|
||||
def _append(self, account, dialog_id):
|
||||
def _append(self, account, dialog_id, force=False):
|
||||
"""Puts the logged photos into the chat's photos and tells the
|
||||
gallery, at most every few seconds (it reads the photos again)."""
|
||||
from org.telegram.messenger import MessagesController, NotificationCenter
|
||||
|
||||
if not self.handles:
|
||||
return # unloaded
|
||||
if self._viewing(dialog_id):
|
||||
if not force and self._viewing(dialog_id):
|
||||
on_ui(lambda: self._append(account, dialog_id), 1000) # once it closed
|
||||
return
|
||||
self._appending = True # getDialogPhotos calls back into photos_wanted
|
||||
@@ -344,6 +389,173 @@ class Avatars:
|
||||
icon.setBounds(right - size, bottom - size, right, bottom)
|
||||
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
|
||||
|
||||
# dates in the photo viewer
|
||||
|
||||
def viewer_dates(self, viewer, index):
|
||||
"""A logged photo's subtitle: when it was deleted and set (instead of
|
||||
its upload to the log group)."""
|
||||
photos = field(viewer, "avatarsArr")
|
||||
if photos is None or not 0 <= index < photos.size():
|
||||
return
|
||||
photo = photos.get(index)
|
||||
dates = self._dates.get(int(photo.id)) if photo is not None else None
|
||||
container = field(viewer, "actionBarContainer")
|
||||
if dates is None or container is None:
|
||||
return
|
||||
from views.history import _when
|
||||
|
||||
set_at, deleted_at = dates
|
||||
parts = [f"deleted {_when(deleted_at)}" if deleted_at else "deleted"]
|
||||
if set_at:
|
||||
parts.append(f"set {_when(set_at)}")
|
||||
text = " · ".join(parts)
|
||||
container.setSubtitle(text[:1].upper() + text[1:])
|
||||
|
||||
# a chat without a photo
|
||||
|
||||
def open_without_photo(self, profile):
|
||||
"""openAvatar of a chat that has no photo but logged ones: the photo
|
||||
viewer opens on the newest logged photo (with the rest)."""
|
||||
from org.telegram.messenger import ImageLocation, MessagesController
|
||||
from org.telegram.tgnet import TLRPC
|
||||
from org.telegram.ui import PhotoViewer
|
||||
|
||||
account = int(profile.getCurrentAccount())
|
||||
controller = MessagesController.getInstance(account)
|
||||
user_id, chat_id = int(field(profile, "userId") or 0), int(field(profile, "chatId") or 0)
|
||||
if user_id:
|
||||
peer, dialog_id = controller.getUser(user_id), user_id
|
||||
empty = TLRPC.TL_userProfilePhotoEmpty
|
||||
else:
|
||||
peer, dialog_id = controller.getChat(chat_id), -chat_id
|
||||
empty = TLRPC.TL_chatPhotoEmpty
|
||||
photo = getattr(peer, "photo", None) if peer is not None else None
|
||||
if photo is not None and not isinstance(photo, empty):
|
||||
return False # the app opens it
|
||||
recorded = self._recorded.get((account, dialog_id))
|
||||
if not recorded:
|
||||
controller.getDialogPhotos(dialog_id) # loads the logged ones
|
||||
return False
|
||||
# The newest of the chat's photos (its own, then the logged ones).
|
||||
holder = controller.getDialogPhotos(dialog_id)
|
||||
if holder is not None:
|
||||
self._add_to(account, dialog_id, holder)
|
||||
listed = java_list(field(holder, "photos")) if holder is not None else []
|
||||
first = next((p for p in listed if p is not None), recorded[0][0])
|
||||
size = media_share.photo_size(first, 800)
|
||||
if size is None:
|
||||
return False
|
||||
viewer = PhotoViewer.getInstance()
|
||||
viewer.setParentActivity(profile)
|
||||
viewer.openPhoto(
|
||||
size.location, ImageLocation.getForPhoto(size, first), field(profile, "provider")
|
||||
)
|
||||
# The profile names no chat for a photo it doesn't show itself: the
|
||||
# viewer is told, and gets the chat's photos (the logged ones).
|
||||
from hook_utils import set_private_field
|
||||
from java import jlong
|
||||
|
||||
set_private_field(viewer, "avatarsDialogId", jlong(dialog_id))
|
||||
self._told.pop((account, dialog_id), None)
|
||||
self._append(account, dialog_id, force=True)
|
||||
|
||||
return True
|
||||
|
||||
def _trash(self):
|
||||
if self._icon is None:
|
||||
from android.graphics import Paint, PorterDuff, PorterDuffColorFilter
|
||||
|
||||
Reference in new issue
Block a user