generated from exteraGramPlugins/pluginBase
Message Logger: hidden messages on the server, removed chats, old chat photos
Publish plugin / publish (push) Successful in 6s
Publish plugin / publish (push) Successful in 6s
- Hidden deleted messages are marked on the server (local marks migrated); "Reveal hidden messages" in the chat menu, Unhide in the message menu and the selection bar. - Chats an account lost are reported, listed among its chats again and opened read-only with their logged messages, all deleted. - Logged chat photos: a removed chat's avatar, and in a chat's photos the ones it no longer has, translucent with a trash icon in the viewer's previews. - Fix: the deleted-post comments thread is answered locally again. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
0176f7099d
commit
362b358d8d
15 files changed
+1821
-125
No files matched your search
@@ -0,0 +1,202 @@
|
||||
"""Deleted messages removed from view ("hidden"), marked on the server so
|
||||
every client skips them; a chat can reveal its hidden messages again.
|
||||
|
||||
- `flush` sends the marks waiting in the store to the server;
|
||||
- the chat menu toggles revealing (per chat, until the app restarts): the
|
||||
hidden deleted messages come back from the logger, then go again;
|
||||
- a revealed hidden message has "Unhide" in its menu (hooks/menu.py), and
|
||||
while revealing, the selection bar gets an Unhide button for those
|
||||
selected (ChatActivity.createActionMode adds the bar's items,
|
||||
updateActionModeTitle runs on every selection change; probed on 12.10.6).
|
||||
"""
|
||||
|
||||
import threading
|
||||
|
||||
from base_plugin import MethodHook
|
||||
|
||||
import diag
|
||||
import peers
|
||||
import store
|
||||
from api import ApiError
|
||||
from jutil import field, hook_methods, in_background, on_ui
|
||||
|
||||
CHAT = "org.telegram.ui.ChatActivity"
|
||||
_UNHIDE_ID = 0x4D4C01 # the selection bar item
|
||||
|
||||
|
||||
def _bulletin(text, error=False):
|
||||
def show():
|
||||
from ui.bulletin import BulletinHelper
|
||||
|
||||
(BulletinHelper.show_error if error else BulletinHelper.show_info)(text)
|
||||
|
||||
on_ui(show)
|
||||
|
||||
|
||||
def _selected(chat):
|
||||
"""MessageObjects selected in the chat (both halves of a merged chat)."""
|
||||
out = []
|
||||
for array in field(chat, "selectedMessagesIds") or []:
|
||||
if array is None:
|
||||
continue
|
||||
for index in range(array.size()):
|
||||
message = array.valueAt(index)
|
||||
if message is not None:
|
||||
out.append(message)
|
||||
return out
|
||||
|
||||
|
||||
def is_revealed_hidden(message):
|
||||
"""A hidden deleted message shown because its chat reveals them."""
|
||||
if message is None or int(message.getId()) <= 0:
|
||||
return False
|
||||
account, dialog_id = int(message.currentAccount), int(message.getDialogId())
|
||||
message_id = int(message.getId())
|
||||
return (
|
||||
store.is_revealed(account, dialog_id)
|
||||
and store.is_deleted(account, dialog_id, message_id)
|
||||
and store.is_hidden(account, dialog_id, message_id)
|
||||
)
|
||||
|
||||
|
||||
class Hidden:
|
||||
def __init__(self, plugin, api, chats):
|
||||
self.plugin = plugin
|
||||
self.api = api
|
||||
self.chats = chats # hooks/chat.ChatSync
|
||||
self.handles = []
|
||||
self._flushing = threading.Lock()
|
||||
self._items = {} # ChatActivity classGuid -> the bar's Unhide item
|
||||
|
||||
def install(self):
|
||||
hidden = self
|
||||
|
||||
class AfterActionMode(MethodHook):
|
||||
def after_hooked_method(self, param):
|
||||
try:
|
||||
hidden.add_bar_item(param.thisObject)
|
||||
except Exception as exc:
|
||||
diag.error("hidden.bar", exc)
|
||||
|
||||
class AfterTitle(MethodHook):
|
||||
def after_hooked_method(self, param):
|
||||
try:
|
||||
hidden.update_bar_item(param.thisObject)
|
||||
except Exception as exc:
|
||||
diag.error("hidden.title", exc)
|
||||
|
||||
self.handles += hook_methods(self.plugin, CHAT, "createActionMode", AfterActionMode())
|
||||
self.handles += hook_methods(self.plugin, CHAT, "updateActionModeTitle", AfterTitle())
|
||||
in_background(self.flush)
|
||||
|
||||
def uninstall(self):
|
||||
for handle in self.handles:
|
||||
try:
|
||||
self.plugin.unhook_method(handle)
|
||||
except Exception:
|
||||
pass
|
||||
self.handles = []
|
||||
|
||||
# the server
|
||||
|
||||
def flush(self):
|
||||
"""Sends hidden marks waiting in the store (background)."""
|
||||
if not self.api.configured or not self._flushing.acquire(blocking=False):
|
||||
return
|
||||
try:
|
||||
for (account, dialog_id, hidden), ids in store.pending_hidden().items():
|
||||
me = peers.own_user_id(account)
|
||||
chat_id = peers.marked_chat_id(account, dialog_id)
|
||||
if not me or chat_id is None:
|
||||
continue
|
||||
try:
|
||||
self.api.hidden(me, chat_id, ids, hidden)
|
||||
except ApiError as exc:
|
||||
diag.log(f"hidden {chat_id}: {exc}")
|
||||
continue
|
||||
store.hidden_sent(account, dialog_id, ids, hidden)
|
||||
diag.log(f"{'hid' if hidden else 'unhid'} {chat_id}: {ids}")
|
||||
finally:
|
||||
self._flushing.release()
|
||||
|
||||
def hide(self, account, dialog_id, ids, hidden=True):
|
||||
store.set_hidden(account, dialog_id, ids, hidden)
|
||||
in_background(self.flush)
|
||||
|
||||
# revealing (chat menu)
|
||||
|
||||
def toggle_reveal(self, fragment):
|
||||
account = int(fragment.getCurrentAccount())
|
||||
dialog_id = int(fragment.getDialogId())
|
||||
on = not store.is_revealed(account, dialog_id)
|
||||
store.reveal(account, dialog_id, on)
|
||||
if on:
|
||||
count = self.chats.restore_hidden(fragment)
|
||||
# And the server's answer, for those not cached.
|
||||
store.expire_coverage(account, dialog_id)
|
||||
self.chats.schedule(fragment)
|
||||
_bulletin(
|
||||
f"Message Logger: revealing hidden deleted messages ({count} loaded)"
|
||||
if count
|
||||
else "Message Logger: revealing hidden deleted messages"
|
||||
)
|
||||
else:
|
||||
ids = store.hidden_deleted(account, dialog_id)
|
||||
self.chats.drop_from_view(account, dialog_id, ids)
|
||||
_bulletin("Message Logger: hidden deleted messages are hidden again")
|
||||
|
||||
# unhiding
|
||||
|
||||
def unhide(self, messages):
|
||||
by_chat = {}
|
||||
for message in messages:
|
||||
if is_revealed_hidden(message):
|
||||
key = (int(message.currentAccount), int(message.getDialogId()))
|
||||
by_chat.setdefault(key, []).append(int(message.getId()))
|
||||
for (account, dialog_id), ids in by_chat.items():
|
||||
self.hide(account, dialog_id, ids, hidden=False)
|
||||
self.chats.refresh_dialog(account, dialog_id, ids)
|
||||
count = sum(len(ids) for ids in by_chat.values())
|
||||
if count:
|
||||
_bulletin(f"Message Logger: {count} message{'s' if count > 1 else ''} unhidden")
|
||||
|
||||
# the selection bar
|
||||
|
||||
def add_bar_item(self, chat):
|
||||
"""The bar's Unhide item (also for a chat opened before the plugin
|
||||
loaded: its bar exists already)."""
|
||||
bar = chat.getActionBar()
|
||||
menu = bar.createActionMode() if bar is not None else None
|
||||
if menu is None:
|
||||
return None
|
||||
from android.view import View
|
||||
from android_utils import OnClickListener
|
||||
from org.telegram.messenger import AndroidUtilities, R
|
||||
|
||||
item = menu.getItem(_UNHIDE_ID)
|
||||
if item is None:
|
||||
item = menu.addItemWithWidth(
|
||||
_UNHIDE_ID, R.drawable.msg_views, AndroidUtilities.dp(54)
|
||||
)
|
||||
item.setContentDescription("Unhide")
|
||||
item.setVisibility(View.GONE)
|
||||
|
||||
def chosen(_view):
|
||||
self.unhide(_selected(chat))
|
||||
chat.clearSelectionMode()
|
||||
|
||||
item.setOnClickListener(OnClickListener(chosen))
|
||||
self._items[int(chat.getClassGuid())] = item
|
||||
return item
|
||||
|
||||
def update_bar_item(self, chat):
|
||||
item = self._items.get(int(chat.getClassGuid())) or self.add_bar_item(chat)
|
||||
if item is None:
|
||||
return
|
||||
from android.view import View
|
||||
|
||||
account, dialog_id = int(chat.getCurrentAccount()), int(chat.getDialogId())
|
||||
show = store.is_revealed(account, dialog_id) and any(
|
||||
is_revealed_hidden(m) for m in _selected(chat)
|
||||
)
|
||||
item.setVisibility(View.VISIBLE if show else View.GONE)
|
||||
Reference in new issue
Block a user