# 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`.