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,145 @@
|
||||
"""Keeps messages others delete: the delete updates are dropped (the message
|
||||
stays in the app's cache and chat) and the ids are remembered as deleted.
|
||||
|
||||
Deletions the user makes here go through MessagesController.deleteMessages
|
||||
(both overloads start `(messages, randoms, encryptedChat, dialogId, ...)`);
|
||||
those ids are let through when their echo update arrives.
|
||||
|
||||
TL_updateDeleteMessages (private chats, basic groups) carries no chat: ids
|
||||
are per account, so the dialog is looked up in the app's own database on the
|
||||
storage queue.
|
||||
"""
|
||||
|
||||
from base_plugin import HookResult, HookStrategy, MethodHook
|
||||
|
||||
import diag
|
||||
import peers
|
||||
import store
|
||||
from api import ApiError
|
||||
from config import KEEP_DELETED, REPORT_DELETIONS
|
||||
from jutil import field, hook_methods, in_background, java_ints
|
||||
|
||||
UPDATES = ("TL_updateDeleteMessages", "TL_updateDeleteChannelMessages")
|
||||
|
||||
|
||||
class Deletes:
|
||||
def __init__(self, plugin, api, chats):
|
||||
self.plugin = plugin
|
||||
self.api = api
|
||||
self.chats = chats
|
||||
self.handles = []
|
||||
|
||||
def install(self):
|
||||
for name in UPDATES:
|
||||
self.plugin.add_hook(name)
|
||||
deletes = self
|
||||
|
||||
class BeforeDelete(MethodHook):
|
||||
def before_hooked_method(self, param):
|
||||
try:
|
||||
deletes.on_own_delete(param)
|
||||
except Exception as exc:
|
||||
diag.error("deletes.own", exc)
|
||||
|
||||
self.handles += hook_methods(
|
||||
self.plugin,
|
||||
"org.telegram.messenger.MessagesController",
|
||||
"deleteMessages",
|
||||
BeforeDelete(),
|
||||
)
|
||||
|
||||
def uninstall(self):
|
||||
for name in UPDATES:
|
||||
try:
|
||||
self.plugin.remove_hook(name)
|
||||
except Exception:
|
||||
pass
|
||||
for handle in self.handles:
|
||||
try:
|
||||
self.plugin.unhook_method(handle)
|
||||
except Exception:
|
||||
pass
|
||||
self.handles = []
|
||||
|
||||
def on_own_delete(self, param):
|
||||
args = list(param.args or [])
|
||||
if len(args) < 4:
|
||||
return
|
||||
ids = [i for i in java_ints(args[0]) if i > 0]
|
||||
if not ids:
|
||||
return
|
||||
account = int(field(param.thisObject, "currentAccount") or 0)
|
||||
dialog_id = int(args[3] or 0)
|
||||
store.note_own_deletion(account, ids)
|
||||
store.forget_deleted(account, dialog_id, ids)
|
||||
|
||||
def on_update(self, name, account, update):
|
||||
ids = java_ints(update.messages)
|
||||
others = [i for i in ids if not store.is_own_deletion(account, i)]
|
||||
if not others:
|
||||
return HookResult()
|
||||
if name == "TL_updateDeleteChannelMessages":
|
||||
self._deleted(account, -int(update.channel_id), others)
|
||||
else:
|
||||
self._resolve_dialogs(account, others)
|
||||
if not self.plugin.get_setting(KEEP_DELETED, True):
|
||||
return HookResult()
|
||||
diag.log(f"kept {name} {others}")
|
||||
if len(others) == len(ids):
|
||||
return HookResult(strategy=HookStrategy.CANCEL)
|
||||
from java import jint
|
||||
from java.util import ArrayList
|
||||
|
||||
own = ArrayList()
|
||||
for i in ids:
|
||||
if i not in others:
|
||||
own.add(jint(i)) # a plain int would be boxed as Long
|
||||
update.messages = own
|
||||
return HookResult(strategy=HookStrategy.MODIFY, update=update)
|
||||
|
||||
def _deleted(self, account, dialog_id, ids):
|
||||
store.mark_deleted(account, dialog_id, ids)
|
||||
self.chats.refresh_dialog(account, dialog_id, ids)
|
||||
# Check the chat again once the server had the report.
|
||||
self.chats.changed(account, dialog_id, 3000)
|
||||
if self.plugin.get_setting(REPORT_DELETIONS, True) and self.api.configured:
|
||||
in_background(lambda: self._report(account, dialog_id, ids))
|
||||
|
||||
def _report(self, account, dialog_id, ids):
|
||||
chat_id = peers.marked_chat_id(account, dialog_id)
|
||||
if chat_id is None:
|
||||
return
|
||||
try:
|
||||
self.api.deletions(peers.own_user_id(account), chat_id, ids)
|
||||
except ApiError as exc:
|
||||
diag.log(f"report {chat_id}: {exc}")
|
||||
|
||||
def _resolve_dialogs(self, account, ids):
|
||||
"""Dialogs of per-account message ids, from the app's database."""
|
||||
from java import dynamic_proxy
|
||||
from java.lang import Runnable
|
||||
from org.telegram.messenger import MessagesStorage
|
||||
|
||||
storage = MessagesStorage.getInstance(account)
|
||||
deletes = self
|
||||
|
||||
class Lookup(dynamic_proxy(Runnable)):
|
||||
def run(self):
|
||||
try:
|
||||
found = {}
|
||||
query = "SELECT mid, uid FROM messages_v2 WHERE mid IN (%s)" % (
|
||||
",".join(str(int(i)) for i in ids)
|
||||
)
|
||||
cursor = storage.getDatabase().queryFinalized(query)
|
||||
while cursor.next():
|
||||
uid = int(cursor.longValue(1))
|
||||
if uid < 0 and peers.is_channel_dialog(account, uid):
|
||||
continue # channel ids are numbered per channel
|
||||
found.setdefault(uid, []).append(int(cursor.intValue(0)))
|
||||
cursor.dispose()
|
||||
for dialog_id, mids in found.items():
|
||||
deletes._deleted(account, dialog_id, mids)
|
||||
except Exception as exc:
|
||||
diag.error("deletes.resolve", exc)
|
||||
|
||||
storage.getStorageQueue().postRunnable(Lookup())
|
||||
Reference in new issue
Block a user