# Media viewer internals — findings
Notes on how exteraGram opens page-block media, gathered by decompiling the
12.10.6 APK (versionCode 71129) with jadx and testing on a device. Class and
field names are from that build. They are app internals, not SDK API, so check
them again after app updates.
## Two adapters, one PhotoViewer
Page-block media never opens with a `MessageObject` list. PhotoViewer runs on a
`PhotoViewer.PageBlocksAdapter` instead, and `onPhotoShow` fires the same way
for both callers below, so one hook covers them.
| Opened from | Adapter | `getParentObject()` |
|---|---|---|
| Instant View article (`ArticleViewer.openPhoto`) | `ArticleViewer.RealPageBlocksAdapter` | `TLRPC.WebPage` |
| Rich message in a chat (`ChatActivity.openArticlePhoto`) | `ChatActivity.ChatArticlePageBlocksAdapter` | the message's `MessageObject`, or the bare `TL_iv.RichMessage` when constructed without one |
Both adapters expose the same `getMedia(i)` / `getFile(i)` / `isVideo(i)`. Those
methods resolve through `ArticleViewer.WebPageUtils`, which has overloads for
`WebPage`, `TL_iv.Page` and `TL_iv.RichMessage`. That is why `ivmedia.media_at`
works unchanged for both. Only the parent object tells them apart.
A rich message is recognised by `MessageObject.messageOwner.rich_message != null`.
## Carousels (slideshows)
- **Articles:** `WebpageAdapter.addAllMediaFromBlock` flattens every
`pageBlockSlideshow` / `pageBlockCollage` item into `photoBlocks`. Tapping a
slideshow item therefore goes through `RealPageBlocksAdapter` like any other
article photo, and needed no special handling.
- **Rich messages:** `RichMessageLayout.collectMediaBlocks` flattens single
media and slideshow items into one list for `ChatArticlePageBlocksAdapter`.
Swiping in the viewer walks through all of them, and
`updateSlideshowCell` keeps the in-chat carousel in sync.
## telegra.ph cannot produce a carousel
Telegraph has no gallery/slideshow node. The editor has no such block, and
`createPage` keeps only the first `` of a `` holding several, so
consecutive images render as separate `pageBlockPhoto`s. Carousels only reach
the viewer from other Instant View templates (sites other than telegra.ph,
covered by the `any_article` setting) and from rich messages.
`telegra.ph/upload` no longer accepts files ("Unknown error"). Test pages have
to reference external image URLs, e.g. on `upload.wikimedia.org`.
## Rich messages need Premium to send media
The rich composer is the attach menu's "Article" tab (`ChatAttachAlertRichLayout`).
Sending is locked unless `MessagesController.richEditorAllowed()` or
`UserConfig.isPremium()`, whenever the content is "lossy"
(`RichMessageConvert.isLossy`). Lossy means anything a plain message can't
represent, and every media block is lossy. A non-Premium account therefore
cannot send a test rich message with media.
To test without one, build the message locally and open it the same way
`ChatActivity.openArticlePhoto` does:
```python
rm = TL_iv.RichMessage() # fill rm.photos and rm.blocks
msg = TLRPC.TL_message() # peer_id/id/dialog_id of any chat
msg.rich_message = rm
mo = MessageObject(account, msg, False, False)
adapter = ChatActivity.ChatArticlePageBlocksAdapter(rm, flat_blocks, mo)
viewer = PhotoViewer.getInstance()
viewer.setParentActivity(chat_fragment)
viewer.openPhoto(index, adapter, PhotoViewer.EmptyPhotoViewerProvider())
```
Real `TLRPC.Photo` objects with server locations can be borrowed from the
`cached_page.photos` of any loaded article. Pointing `peer_id` at a channel
exercises the message-link caption.
`ArticleViewer.open(TL_webPage, anchor)` re-requests `messages.getWebPage` and
replaces the page with the server copy. Copy the original `hash` onto the
injected `TL_webPage` to keep a locally modified `cached_page`.
## Message links
Only channels and supergroups (`ChatObject.isChannel`) have message links:
`https://t.me//` when public, `https://t.me/c//`
otherwise. On Android the dialog id of a channel is `-chat.id`, with no `-100`
prefix. DMs, basic groups and Saved Messages have no link, so media forwarded
from rich messages there is sent without a caption.
## File references
Pass the adapter's parent object (WebPage or MessageObject) as `parentObject`
to `FileLoader.loadFile`. `FileRefController` uses it to refresh an expired
file reference, and it handles rich messages explicitly.
## Device debugging
Logcat can be completely silent when the device's log buffer is disabled in
developer options, and `scripts/logs.sh` then shows nothing. A plugin can
still write diagnostics to
`ApplicationLoader.applicationContext.getExternalFilesDir(None)`, which maps
to `/sdcard/Android/data/com.exteragram.messenger/files/` and is readable with
`adb shell cat` without `run-as`.