generated from exteraGramPlugins/pluginBase
Publish plugin / publish (push) Successful in 4s
- Delete dialog: "Keep message for myself" checkbox keeps your deletion on this device (marked deleted); unticked, the message is hidden and never restored. Already deleted messages get a plain "Remove from view" dialog (fixes the chat staying dimmed/touch-blocked after it). - "Reset deleted/edited marks" in the chat menu (Plugins) and, for all chats, in the plugin settings. - Fix restored text-only messages showing as empty bubbles (rich text wrappers like links lost their text); missing settings icons. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
153 lines
5.6 KiB
Python
153 lines
5.6 KiB
Python
"""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, dialog=None):
|
|
self.plugin = plugin
|
|
self.api = api
|
|
self.chats = chats
|
|
self.dialog = dialog # hooks/delete_dialog.DeleteDialog
|
|
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)
|
|
kept = self.dialog.apply_choice(account, dialog_id, ids) if self.dialog else []
|
|
if kept:
|
|
self.chats.refresh_dialog(account, dialog_id, kept)
|
|
ids = [i for i in ids if i not in kept]
|
|
if not ids:
|
|
return
|
|
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())
|