Repository navigation
Conversation
Match what the LINE Chrome Extension 3.7.2 sends for images, as captured on 2026-10-25 (beeper#90): - MEDIA_CONTENT_INFO carries the image's real width and height, the original file size, and animated true/false for GIFs. - FILE_SIZE is the original size, not the encrypted upload size. - Images no longer send FILE_NAME or a contentType metadata key. - Plain chats send MEDIA_CONTENT_INFO only and upload no thumbnail, which also stops the failing (400) plain thumbnail upload. - Thumbnails are at most 384 px on the longest side, never upscaled, with transparent areas on white instead of black. - Detect animated GIFs by walking the GIF blocks; the old byte scan marked some single-frame GIFs as animated. Advertise fi.mau.gif with LINE's 20 MB GIF limit, convert GIF-picker videos (MP4/WebM) to GIF, and refuse GIFs over 20 MB as too large. Receive LINE images with their real type and size, and mark animated GIFs fi.mau.gif, instead of labelling every image image.jpg. Co-authored-by: formeo14 <maiki2004@gmx.de> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
In chats without end-to-end encryption (for example with a LINE business
account), the message says MEDIA_CONTENT_INFO category "original", but the
image was uploaded without cat=original. LINE iOS then showed the preview
frame and "Unable to load this photo as it's no longer available".
Upload plain images with cat=original and the file name, as the LINE
Chrome Extension does: {"ver":"2.0","name":"<file>","type":"image","cat":"original"}.
Co-authored-by: formeo14 <maiki2004@gmx.de>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With direct media on, generateDirectMedia replaced the type and file name from MEDIA_CONTENT_INFO with image/jpeg and image.jpg. The saved metadata also made the download response say image/jpeg, so GIFs from LINE still arrived as JPEGs on that path. Set the type, file name and (when FILE_SIZE is missing) the size from lineImageInfo on the direct media metadata before the URI is made. Answers the Greptile review comment "Direct GIFs still claim JPEG". Co-authored-by: formeo14 <maiki2004@gmx.de> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Picker videos are converted to GIF with ffprobe and ffmpeg. A native install without them still offered video/mp4 and video/webm for fi.mau.gif, so every picker GIF failed instead of sending. Offer those types only when both programs are on PATH, add "+no_gif_video" to the capability ID when they are not, and list FFmpeg as an optional native prerequisite in the README. Answers the Greptile review comment "Picker support ignores missing programs". Co-authored-by: formeo14 <maiki2004@gmx.de> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
The full test plan with every result, and the field-by-field findings from the LINE Chrome Extension capture. All IDs, keys and tokens are left out. 🇬🇧 More details · 30 min readTest plan and resultsTest plan: send GIFs from Beeper to LINE (#90)Goal: a GIF sent from Beeper (the GIF picker, a ScopeCopy what LINE does, nothing extra. Anything LINE's own clients cannot do is out of scope. Write down that they cannot. Known before testing:
Out of scope, noted on purpose:
Not testable, a known gap:
Platforms
Test chats
Every message is visible to all members. Before A: AirDrop Test dataEvery dummy string used in the tests.
Run orderLINE allows one Chrome Extension session, so the capture logs the bridge out.
Settings record: before A, write down the LINE iOS values under Settings → Photos and videos (recorded 2026-10-25: Photo quality Relay: keep the bot's relay off except in sections B, C and H, so other sends do not get doubled. Pacing: wait about 10 s between steps (about 15 s after a relay). Write what happened in the Result column: ✅ worked, ❌ rejected (copy the error text), A. Beeper today: sendingCurrent code. Check each one on LINE iOS with Autoplay GIFs on.
B. Beeper today: LINE settings, through the relayThe bot's relayed copy arrives as a received message, so the LINE iOS settings apply to it. Send from Beeper iOS in
C. The same GIFs sent from LINE iOS (the reference)Same files and settings as A and B, sent from LINE iOS. Also check how each one arrives in Beeper iOS and Desktop (today's receive path).
D. Sending from Chrome
E. Size limit and other edge casesSend each from Chrome in
F. GIFs from the phone during the captureKeep the extension open on
G. Beeper after deploy: sendingRepeat A on the new build. Each row must match LINE's own behaviour from C, D and E.
H. Beeper after deploy: LINE settings, through the relayRepeat B on the new build. Each row must match its C row (LINE's own reference).
Observations during the run
Evidence note: phase 2 (D, E, F) has the full network capture but no Chrome extension screenshots. Nothing looked unusual in Chrome during the run. Phases 1 and 3 have LINE iOS and Beeper screenshots.
Findingsthe Findings section below (written by the agent in the analyze phase, after FindingsFindings: sending GIFs (#90)Capture: 2026-10-25, LINE Chrome Extension 3.7.2, What the Chrome extension sends for a GIFEnd-to-end encrypted chat (
|
| Input | Sent as |
|---|---|
12-animated.apng (D5) |
the extension called it unsupported and offered to send it as an archive (user chose "always"); sent as a file, contentType: 14, SID: emf, FILE_SIZE 130 bytes larger than the file (archive wrapper) |
11-animated.webp (D5) |
sent as a file without a prompt (after "always"), contentType: 14, FILE_SIZE equal to the file |
24-animated-27mb.gif (E4) |
sent as a file without a prompt, contentType: 14, FILE_SIZE equal to the file. Possibly caused by the "always send as archive" choice in D5; whether a GIF over 20 MB becomes a file by default is still open (Settings: "Photos larger than 20 MB can't be sent in their original resolution") |
13-h264.mp4 (E8) |
video, contentType: 2, DURATION: 3000 |
GIFs of 4.7, 10.2 and 19.7 MB (E1–E3) were sent as animated images.
GIFs from LINE iOS (F1–F3)
MEDIA_CONTENT_INFO with width, height, animated, extension, category, fileSize and attribute: {}, plus FILE_SIZE, with no thumbnail info.
Photo quality Standard and High give the same result for a GIF (F1, F2). LINE iOS has no GIF search: F3 pasted a GIF copied with Raycast's iOS keyboard (3.1 MB, 480×400, animated: true), and it was sent as a normal animated image.
Bridge today vs the extension
| Field / behaviour | Chrome extension | Bridge today | Change |
|---|---|---|---|
MEDIA_CONTENT_INFO.width / height |
real pixel size | missing | add |
fileSize / FILE_SIZE (encrypted path) |
plaintext size | encrypted size (+32 bytes) | fix |
FILE_NAME |
not sent | sent | drop for images |
contentType key in metadata |
not sent | sent | drop |
Thumbnail size (MEDIA_THUMB_INFO) |
longest side 384, never upscaled | up to 480 | match 384 |
| Thumbnail transparency | (not checked) | black background while stopped | keep alpha or match Chrome |
| Plain path: thumbnail | none | uploaded, fails with 400 | don't upload one |
| Plain path: upload parameters | cat: original + file name |
no cat, timestamp name |
send cat: original (found in G10) |
| Plain path: metadata | MEDIA_CONTENT_INFO only |
adds FILE_NAME, FILE_SIZE, MEDIA_THUMB_INFO, contentType |
match |
| GIF over 20 MB | sent as a file (maybe only after "always send as archive") | sent as an image (bridge advertises 100 MB) | confirm the default, then match |
| APNG / WebP | "unsupported": offered as an archive, sent as files | (to check) | send as files |
| Single-frame GIF | animated: false |
animated from frame count |
already matches |
Autoplay on LINE iOS
A GIF sent from another device of the same account needs a tap on LINE iOS, whether it came from the Chrome extension (D2) or the bridge (A3–A7). Only GIFs sent from the phone itself autoplay there (C1, C3–C6). In the Chrome extension every GIF autoplays (D10). No bridge bug here.
Receiving in Beeper (separate from sending)
- GIFs from LINE arrive in Beeper as still images with no GIF badge (C1, C6). The incoming
MEDIA_CONTENT_INFOhasanimated: true, so the bridge can mark them as GIFs. - Images from business accounts are dropped:
public OBS resource must be an absolute path(B2). Separate issue.
Beeper side
- Beeper iOS can send GIFs (picker and files). The picker sends real GIFs (
extension: gif,animated: true). They are small in pixels (124×126) and large in bytes (1.9 MB). - Beeper Desktop: no GIF picker in LINE chats (A8), and sending a
.giffile fails withThis chat doesn't support sending filesbefore the bridge sees it (A9). Check the room's advertised features.
Found during the after phase (2026-10-25)
Plain image uploads need cat=original (fixed)
In plain chats the bridge's GIF reached LINE, but LINE iOS showed only the preview frame and
Unable to load this photo as it's no longer available (G10). The Chrome extension's plain upload (D7):
POST r/talk/m/<messageId>
x-obs-params: {"ver":"2.0","name":"10-animated.gif","type":"image","cat":"original"}
The bridge sent {"ver":"2.0","name":"<timestamp>","type":"image"} with no cat, while the message says
category: original. Fixed by uploading plain images with cat=original and the file name. G15–G21 load on LINE iOS.
Plain receives race the sender's upload (not a regression, separate issue)
In plain chats LINE delivers the message before the sender's upload finishes. The bridge downloads at once from
r/talk/m/<messageId> (tid original), gets LINE OBS object not found 100–200 ms after the message, and posts
the bridge's Image unavailable notice. Right after, LINE sends an op 43 for the same
image with "onObs":true in MEDIA_CONTENT_INFO, which the bridge ignores.
| Build | Plain receives from LINE iOS | Failed |
|---|---|---|
main (c5a432d) |
6 | 4 |
| GIF branch | 4 | 3 |
The download code is not changed by this PR. Next step: capture the Chrome extension receiving a plain image to see
whether it waits for op 43 / onObs, retries, or both.
Other results on the new build
- Beeper Desktop's GIF picker is enabled, and
.giffiles send (A9 fixed by advertisingfi.mau.gif). - Thumbnails match Chrome exactly: 384×384, 384×144, 144×384.
- GIFs over 20 MB: Beeper Desktop refuses them ("file too large"). Beeper iOS shrank a 27 MB GIF to 16.7 MB, 640×640 itself before sending.
- A single-frame GIF is sent with
animated: false. The old byte-scan check marked some such GIFs as animated. The new check walks the GIF blocks and matches Chrome on every sample.
Open
- Neither the extension nor LINE iOS has a GIF search. A GIF pasted from the clipboard (Raycast GIF search, D6: 8 MB, 480×480) is sent as a normal animated image.
- Whether a GIF over 20 MB becomes a file by default (E4 possibly reflects the D5 "always send as archive" choice). Retest with that choice reset.
- A second person receiving a bridge-sent GIF: untested.
🤖 Generated with Claude Code using clins:github
highesttt
left a comment
There was a problem hiding this comment.
looks ok, I dont see anything wrong with it
GIFs sent from Beeper now reach LINE exactly as the LINE Chrome Extension sends them, and GIFs from LINE arrive in Beeper as GIFs. Review the image send path in
pkg/connector/send_message.goand the newpkg/connector/gif.go.thumb_width=384 thumb_height=384).image/gifwithfi.mau.gifand animate (checked in Beeper iOS and Desktop).cat=original, like Chrome. LINE iOS loads and plays it.🇬🇧 More details · 5 min read
Summary
Closes #90. This takes the GIF part of #246 by formeo14 (co-author on every commit) and matches it field by field to a network capture of the LINE Chrome Extension 3.7.2.
Why
The bridge sent GIFs that differed from what LINE's own clients send, and labelled every received image as
image.jpg:MEDIA_CONTENT_INFOhad nowidthorheight, andfileSizeandFILE_SIZEheld the encrypted size (the file plus 32 bytes).FILE_NAMEand acontentTypekey, which the extension never sends.cat=original, and LINE iOS then failed to load it..giffiles (This chat doesn't support sending files), because the bridge did not advertisefi.mau.gif.0x2C, which also appears in compressed pixel data. Some single-frame GIFs were marked animated.Changes
pkg/connector/gif.go:originalImageInfoandimageMediaMetadatabuild the image metadata the extension sends.convertVideoToGIFturns GIF-picker MP4 and WebM into GIF (from GIF, sticker and emoji support with media #246).pkg/connector/send_message.go:width/height, the original size, andanimatedfor GIFs, with noFILE_NAMEorcontentTypekey.MEDIA_CONTENT_INFOonly and upload no thumbnail.fi.mau.gifvideos are converted to GIF and sent as images.ErrMediaTooLarge.pkg/connector/media.go:isAnimatedGifwalks the GIF block structure.pkg/connector/userinfo.go: advertisesfi.mau.gifwith a 20 MBMaxSize, and bumps the capabilities ID. It offersimage/gif, plusvideo/mp4andvideo/webm(converted) only whenffmpegandffprobeare installed.README.md: lists FFmpeg as optional for native installs.pkg/line/client.go:UploadOBSPlainOriginalImageuploads plain images withcat=originaland the file name.pkg/connector/handlers/image.go: received images keep their real MIME type, size and dimensions, also with direct media, and animated GIFs getfi.mau.gif.ENC_KMis left as it is. The extension never puts it in the metadata, but changing it affects every media type, so it belongs in its own PR.How it was tested
We wrote a 77-step test plan with
line-test-plan, then ran it in three phases. The skills used here are in clins1994/beeper-line-skills. The full plan with every result, and the field-by-field findings, are in the first comment.line-captureand parsed and redacted byline-analyze.Each row compares the bridge on this branch with the capture:
width/heightfileSize/FILE_SIZEanimated: falseanimated: falseMEDIA_CONTENT_INFOonly, upload withcat: originalChecks:
go build ./...go test $(go list ./... | grep -v /ltsm)go vet $(go list ./... | grep -v /ltsm)staticcheck $(go list ./... | grep -v /ltsm)goimports -local github.com/beeper/line -l pkgNew tests:
pkg/connector/gif_test.goandpkg/connector/handlers/image_info_test.go.Not tested (and why)
Found while testing, not changed here
LINE OBS object not found, then show "Image unavailable". This happens onmaintoo: 4 of 6 failed there. See bug: 🐛 images in plain chats show Image unavailable because the download races the upload #283.public OBS resource must be an absolute path. See bug: 🐛 images from LINE business accounts are dropped #282.Screenshots
The images at the top are LINE iOS and Beeper iOS screenshots from the "before" phase. The "after" side comes from the bridge's debug log on this branch.
Related
🤖 Generated with Claude Code using clins:create-pr