generated from exteraGramPlugins/pluginBase
Publish plugin / publish (push) Successful in 5s
Read receipts, story views, typing and online status are held back per the options; read on interact, scheduled and silent sending as in AyuGram. Every option resolves chat -> account -> global; forum topics follow their group. Settings live in the plugin page, a chat/profile menu popup, and a drawer row for the current account. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
76 lines
4.3 KiB
Markdown
76 lines
4.3 KiB
Markdown
# How Ghost Mode works
|
|
|
|
## What is intercepted
|
|
|
|
Like AyuGram's hook in `ConnectionsManager.sendRequestInternal`, the plugin checks requests
|
|
on their way out (`pre_request_hook`). Each request is checked against the options for the
|
|
account it comes from and, where it has one, the chat it's about:
|
|
|
|
| Option | Requests | What happens |
|
|
| --- | --- | --- |
|
|
| Don't read messages | `messages.readHistory`, `messages.readDiscussion`, `messages.readEncryptedHistory`, `messages.readMessageContents`, `channels.readHistory`, `channels.readMessageContents` | dropped |
|
|
| Don't read stories | `stories.readStories`, `stories.incrementStoryViews` | dropped |
|
|
| Don't send typing | `messages.setTyping`, `messages.setEncryptedTyping` | dropped |
|
|
| Don't send online | `account.updateStatus` | `offline` forced to true |
|
|
| Go offline automatically | `account.updateStatus` | an offline status is sent after each online one |
|
|
| Read on interact | `messages.sendMessage`, `sendMedia`, `sendMultiMedia`, `sendInlineBotResult`, `forwardMessages` | once the send succeeds, the chat is read up to its newest message |
|
|
|
|
Forum topics have no settings of their own. A topic's read receipt (`messages.readDiscussion`)
|
|
and typing status carry the group as their peer, so the group's settings apply to every topic.
|
|
The plugin's own read receipts follow the app's behaviour in forums: *Read until here* and
|
|
*Read on interact* read the message's topic, and *Read all messages now* reads each loaded
|
|
topic up to its newest message.
|
|
|
|
`messages.readMessageContents` carries only message ids. It is sent for the chat on screen,
|
|
so that chat's settings apply. The app still marks messages read locally, so chats look read
|
|
to you and unread to the sender. AyuGram behaves the same way.
|
|
|
|
The plugin's own read receipts (*Read on interact*, *Read all messages now*, *Read until
|
|
here*) are let through by a one-shot allowance per chat that expires after 10 seconds.
|
|
|
|
Scheduled and silent sending change the message before the app builds it:
|
|
`on_send_message_hook` handles text and media, and an Xposed hook on
|
|
`SendMessagesHelper.sendMessage(ArrayList<MessageObject>, …)` handles forwards. Telegram sends
|
|
anything scheduled less than 10 seconds ahead immediately, so the plugin schedules 12 seconds
|
|
ahead, plus 10 seconds for photos and 15 for documents. All parts of an album share one date.
|
|
When a scheduled message lands in the chat, the app would normally jump to the scheduled
|
|
messages screen. For messages the plugin scheduled, it stays in the chat.
|
|
|
|
## How settings resolve
|
|
|
|
```
|
|
chat override → account override → global switch
|
|
```
|
|
|
|
- Global: `g_<option>` (bool), the switches on the main settings page.
|
|
- Account: `a_<user id>_<option>` (0 Global, 1 On, 2 Off), the selectors on an account's
|
|
page. Accounts are keyed by Telegram user id, not by app slot, so overrides follow the
|
|
account when slots shift.
|
|
- Chat: `c_<user id>`, a JSON object `{"<dialog id>": {"<option>": bool}}`. Only chat-level
|
|
options can be stored here. *Don't send online* and *Go offline automatically* apply to
|
|
the whole account.
|
|
|
|
"Ghost mode is on" means all five essentials are on, the same test as AyuGram's
|
|
`isGhostModeActive`. At chat level only the three chat essentials count.
|
|
|
|
## The drawer row
|
|
|
|
exteraGram only shows plugin drawer items where the drawer layout has its *Plugins* entry, and
|
|
that entry is optional. When the layout has no *Plugins* entry, an after-hook on
|
|
`DrawerMenuView.rebuildMenu` appends a `DrawerMenuItemView`, built the way the app builds its
|
|
own rows. With the entry, the app shows the plugin's `DRAWER_MENU` item there instead, so the
|
|
row never shows twice. The row's icon is exteraGram's own `ghost` drawable.
|
|
|
|
## Runtime notes
|
|
|
|
Things the on-device runtime does differently from the SDK docs, all handled in `src/`:
|
|
|
|
- Bundled modules aren't in `sys.modules`, so `dataclasses` can't be used in them.
|
|
- `param.setResult()` in a before-hook doesn't skip the original method. To stop the
|
|
scheduled-screen jump, the chat is marked paused during that one
|
|
`didReceivedNotification` call.
|
|
- `SendMessageParams.notify` clashes with `Object.notify()`, so it is read and written with
|
|
`hook_utils.get_private_field` / `set_private_field`.
|
|
- A Python `int` put into a hooked method's `Object[]` arguments becomes a `Long`, so the
|
|
forward hook writes a boxed `Integer` and `Boolean`.
|