289 lines
13 KiB
Markdown
289 lines
13 KiB
Markdown
# PhotoSync
|
||
|
||
> [!NOTIFY]
|
||
> This mod is mostly generated by Claude. Bug feedbacks are welcomed and they will get
|
||
> fixed and replied by human.
|
||
|
||
A Fabric client mod that sends your Minecraft screenshots to [Immich](https://immich.app)
|
||
or [Google Photos](https://photos.google.com) and lets you browse the album back
|
||
inside the game. This mod also supports periodic screenshooting.
|
||
|
||
Take a screenshot with F2 and it uploads in the background. Press F6 to see what
|
||
is uploading, scroll through the album's timeline, or change how any of it
|
||
behaves. Nothing about the vanilla screenshot flow changes — F2 still saves a PNG
|
||
to `screenshots/` and still works with the mod misconfigured or offline.
|
||
|
||
Supports **Minecraft 1.20 through 26.2**, client side only. Requires
|
||
[Fabric API](https://modrinth.com/mod/fabric-api). Sinytra connector should work.
|
||
|
||
| Photo Browser | Settings |
|
||
| - | - |
|
||
|  |  |
|
||
|
||
---
|
||
|
||
## Install
|
||
|
||
1. Install Fabric Loader 0.19.3 or newer and Fabric API for your Minecraft
|
||
version.
|
||
2. Drop the jar matching your version into `mods/`:
|
||
|
||
| Your Minecraft version | Jar |
|
||
| --- | --- |
|
||
| 1.20 – 1.20.1 | `photosync-1.20.1-x.y.z.jar` |
|
||
| 1.20.2 – 1.20.4 | `photosync-1.20.4-x.y.z.jar` |
|
||
| 1.20.5 – 1.20.6 | `photosync-1.20.6-x.y.z.jar` |
|
||
| 1.21 – 1.21.1 | `photosync-1.21.1-x.y.z.jar` |
|
||
| 1.21.2 – 1.21.4 | `photosync-1.21.4-x.y.z.jar` |
|
||
| 1.21.5 | `photosync-1.21.5-x.y.z.jar` |
|
||
| 1.21.6 – 1.21.10 | `photosync-1.21.8-x.y.z.jar` |
|
||
| 1.21.11 – 1.21.x | `photosync-1.21.11-x.y.z.jar` |
|
||
| 26.1 – 26.2 | `photosync-26.2-x.y.z.jar` |
|
||
|
||
Each jar declares its own supported range, so Fabric will refuse to load the
|
||
wrong one rather than crashing later.
|
||
|
||
3. Launch the game. PhotoSync does nothing until you connect it to a server.
|
||
|
||
The mod is client-side. Servers neither know nor care that you are running it,
|
||
and it works in single player.
|
||
|
||
## Connect it to Immich
|
||
|
||
Press **F6** (rebindable in Options → Controls → PhotoSync), open **Settings**,
|
||
and fill in two fields:
|
||
|
||
- **Server URL** — `https://photos.example.com`. Typing just `photos.example.com`
|
||
works too; so does pasting a URL that already ends in `/api`.
|
||
- **API key** — Immich's web UI, under Account Settings → API Keys. The field is
|
||
masked, and the key is never written to the log or copied to the clipboard.
|
||
|
||
Press **Test**. It reports the account name and server version on success, and
|
||
the actual failure on the way there — a wrong key and an unreachable host say
|
||
different things.
|
||
|
||
Then pick an **Album**. "Whole library" uploads without adding to any album; the
|
||
picker also creates one if you type a name and press Create. The chosen album is
|
||
both where uploads go and what the Browse tab shows.
|
||
|
||
Every upload carries a one-line description: the window title (which a modpack
|
||
replaces with its own name) and the server you were on, or "Singleplayer". It is
|
||
captured the moment the screenshot is taken, so an album mixed from several
|
||
clients or worlds stays tellable apart.
|
||
|
||
Settings save when you close the screen. **Revert** undoes everything since you
|
||
opened it.
|
||
|
||
## Connect it to Google Photos
|
||
|
||
Press **F6** → **Settings** and switch **Provider** to Google Photos. The two
|
||
fields are an **OAuth client ID** and an **OAuth client secret**, and you have to
|
||
make them yourself — there is no key to paste from a Google Photos settings page,
|
||
and this mod cannot ship one for you. Google classes the Photos scopes as
|
||
restricted, so a shipped credential would need Google's verification, would name
|
||
this mod as the data controller for everyone's photos, and would break for
|
||
everybody at once the day it was revoked.
|
||
|
||
Making one is a five-minute detour through the
|
||
[Google Cloud console](https://console.cloud.google.com), once:
|
||
|
||
1. Create a project (any name).
|
||
2. **APIs & Services → Library**, find **Photos Library API**, press Enable.
|
||
3. **APIs & Services → OAuth consent screen**: pick External, fill in the three
|
||
required fields, and add your own Google address under **Test users**. The app
|
||
can stay unpublished — a test user is exactly what you are.
|
||
4. **APIs & Services → Credentials → Create credentials → OAuth client ID**,
|
||
application type **Desktop app**. Copy the client ID and client secret.
|
||
|
||
Paste both into Settings and press **Test**. Your browser opens Google's consent
|
||
page, you allow it, and the tab tells you to go back to the game; the settings
|
||
screen then shows the account you signed in as. Nothing is typed into Minecraft
|
||
except the two client fields, and the sign-in that comes back is kept in
|
||
`google-photos.json` next to your settings, readable only by your user account.
|
||
|
||
Three things are genuinely different from Immich, and all three are Google's
|
||
rules rather than choices made here:
|
||
|
||
- **Browse only shows what PhotoSync uploaded.** Google withdrew the broad
|
||
library scopes in March 2025. No application can read your Google Photos
|
||
account any more, so the Browse tab is a view of this mod's own screenshots —
|
||
not of your holiday photos.
|
||
- **Albums have to be ones PhotoSync made.** The album picker lists those only,
|
||
because an upload into any other album is refused. Type a name and press Create
|
||
and it will be there.
|
||
- **Retries can leave a duplicate.** The Library API has no checksum, no
|
||
duplicate response, and no way to ask whether something is already stored. If
|
||
the game dies between an upload finishing and its confirmation arriving, the
|
||
retry stores a second copy. Immich does not have this problem; Google's API
|
||
gives nothing to solve it with.
|
||
|
||
## The screens
|
||
|
||
**Uploads** is the queue: everything still in flight plus the last hundred that
|
||
finished, newest first, each with its state — queued, uploading, retrying, done,
|
||
failed — and a preview of the selected one. Failed uploads get a **Retry**;
|
||
anything at all gets **Show in folder**, which opens `screenshots/` in your file
|
||
manager. **Remove** takes an entry out of the list without touching the file, and
|
||
**Clear finished** empties the completed ones in one go.
|
||
|
||
Uploads that fail retry on their own — five attempts by default, with the wait
|
||
doubling each time, so the last one comes 80 seconds after the fourth. Only the
|
||
final failure raises a corner message; the retries in between are visible here
|
||
for anyone who wants to watch them.
|
||
|
||
**Browse** is the album, newest first, with a heading per day. It loads the way a
|
||
photo website does: the per-day counts arrive before any image does, so the
|
||
scrollbar is honest about the album's real size from the first frame, and the
|
||
thumbnails for a day are fetched only when you scroll near them. Click a photo to
|
||
open it, Escape to go back. It is read-only — PhotoSync will not delete, move or
|
||
favourite anything on your server.
|
||
|
||
An opened photo is fetched at the largest size your server will serve — the
|
||
original if it has full-size renditions enabled, its 1440px preview otherwise.
|
||
The blur you see for the moment before it arrives is the thumbhash the timeline
|
||
already carried, and it says so while it is standing in, so a blur is never
|
||
mistaken for the photo.
|
||
|
||
Videos in the album show their preview frame with a video badge in the corner.
|
||
There is no playback; this is a screenshot mod.
|
||
|
||
**Settings** covers everything below.
|
||
|
||
## Automatic screenshots
|
||
|
||
Off by default, and it stays off until you turn it on. Once enabled, PhotoSync
|
||
takes a screenshot every five minutes (adjustable from 5 seconds to an hour) and
|
||
puts it through the same queue as a manual one.
|
||
|
||
- **Name suffix** — appended before `.png`, `_auto` by default, so an automatic
|
||
capture lands as `2026-08-07_14.31.02_auto.png` and is easy to spot or filter.
|
||
Leave it blank for none.
|
||
- **Only while in a world** — on by default. No point photographing the main menu.
|
||
- **Skip while a screen is open** — on by default. Your inventory is not a
|
||
landscape.
|
||
- **Skip if you have not moved** — on by default. A cycle is skipped when the
|
||
camera has been in the same place, looking the same way, for a whole interval:
|
||
that shot would be the previous one again. It resumes on its own the moment you
|
||
move or look somewhere else, so an afk stretch costs you one photo rather than
|
||
a hundred identical ones. The view is judged from the camera, so following
|
||
someone in spectator mode counts as moving.
|
||
|
||
Automatic captures are recorded separately from manual ones, so the two are
|
||
distinguishable everywhere they appear.
|
||
|
||
## Messages in the corner
|
||
|
||
One line, bottom left, gone in three seconds. Separately switchable for
|
||
screenshot taken, upload finished, and upload failed, with an adjustable
|
||
duration — the setting that suits an automatic capture every thirty seconds is
|
||
not the one that suits two screenshots an hour.
|
||
|
||
These draw on the in-world HUD, so they do not appear on the title screen or on
|
||
top of an open screen. An upload that finishes while you are looking at the
|
||
Uploads tab updates the list instead, which is better feedback than a toast.
|
||
|
||
## Quitting with uploads still running
|
||
|
||
If you quit while something is uploading, PhotoSync stops the shutdown and shows
|
||
a progress dialog: how many are left, and two buttons. **Keep playing** returns
|
||
you to the game. **Quit anyway** quits immediately.
|
||
|
||
Either way nothing is lost. The queue is on disk and is written through on every
|
||
state change, so unfinished uploads resume the next time you play — the dialog
|
||
exists to save you the wait, not to prevent a loss. An upload that was mid-flight
|
||
when the game died is simply tried again, and Immich recognises the retry as a
|
||
duplicate rather than storing a second copy. (Google Photos cannot; see above.)
|
||
|
||
You can turn the dialog off under Settings → Uploads → *Ask before quitting
|
||
mid-upload*, in which case the game gets a three-second grace period to finish
|
||
whatever is nearly done and then quits regardless.
|
||
|
||
## Everything else in Settings
|
||
|
||
| Setting | Default | |
|
||
| --- | --- | --- |
|
||
| Upload screenshots automatically | on | Off makes the mod a browser and a manual queue |
|
||
| Uploads at once | 2 | Up to 4 |
|
||
| Attempts before giving up | 5 | Up to 20 |
|
||
| Wait between attempts | 5s | Doubles each attempt: 5s, 10s, 20s, 40s |
|
||
| Delete the local file after upload | off | Only ever after the server confirms it |
|
||
| Thumbnail size | 96px | 48–192 |
|
||
| Thumbnails kept in memory | 256 | The mod's largest memory knob |
|
||
| Mark videos in the grid | on | |
|
||
|
||
## Files it writes
|
||
|
||
By default everything lives in `config/photosync/` in your game directory:
|
||
|
||
- `photosync.json` — settings, including your API key. Back it up accordingly.
|
||
- `uploads.json` — the upload queue, so it survives a crash or a quit.
|
||
|
||
Turn on *Store settings outside the game folder* in Settings → Storage and
|
||
`photosync.json` moves to `~/.config/photosyncmod/` on Linux and macOS, or
|
||
`%APPDATA%\photosyncmod\` on Windows, shared across your Minecraft installs.
|
||
The game folder keeps `uploads.json` and a small `location.json` pointer that
|
||
says where the settings file went.
|
||
|
||
Screenshots themselves go where they always did, in `screenshots/`. PhotoSync
|
||
never moves them, and only deletes one if you asked it to and the upload was
|
||
confirmed.
|
||
|
||
## Troubleshooting
|
||
|
||
**"Not connected" on the Browse tab** — the server URL or API key is missing or
|
||
wrong. Settings → Test says which.
|
||
|
||
**Screenshots are taken but nothing queues** — either *Upload screenshots
|
||
automatically* is off, or there are no credentials yet. PhotoSync deliberately
|
||
does not queue against a server it cannot reach, because the result is a list of
|
||
failures you cannot act on.
|
||
|
||
**Thumbnails are blank in Browse** — Minecraft's image decoder reads PNG and JPEG
|
||
only, and Immich's default thumbnail format is WebP. PhotoSync notices on the
|
||
first tile and asks for JPEG previews instead for the rest of the session, so
|
||
this self-corrects on its own.
|
||
|
||
**An opened photo says it could not load** — close it and open it again, which
|
||
retries. If it fails every time, the log line says why; the usual cause is
|
||
Administration → Settings → Image → Preview format set to WebP on the server,
|
||
which nothing on this side can decode. Set it to JPEG.
|
||
|
||
**A jar refuses to load** — check the table above. Fabric enforces each jar's
|
||
declared version range, so this means the jar is for a different bucket.
|
||
|
||
## Building from source
|
||
|
||
```sh
|
||
./gradlew buildAllPlatforms
|
||
```
|
||
|
||
Jars land in `platform/<version>/build/libs/`. Nothing is shaded or bundled —
|
||
each jar is around 270 KB and contains only this mod's classes. To run one
|
||
version in a dev client:
|
||
|
||
```sh
|
||
./gradlew :platform:1.21.11:runClient
|
||
```
|
||
|
||
Releases are built and published by `.github/workflows/release.yml`: push a
|
||
`v<version>` tag (which must match `mod_version` in `gradle.properties`), or run
|
||
the workflow by hand and it builds the current commit and releases it under the
|
||
`mod_version` tag.
|
||
|
||
The source tree is one shared implementation plus nine thin per-version
|
||
adapters. If you are adding a Minecraft version or wondering why the layout is
|
||
the way it is, [`docs/PORTING.md`](docs/PORTING.md) is the document for that: it
|
||
covers the bucket scheme, the measured API breakpoints between every supported
|
||
version, and the procedure for a new release.
|
||
|
||
Adding a third photo service means implementing `PhotoProvider` and
|
||
`ProviderFactory` in `shared/core` and registering it with `ProviderCatalog`. No
|
||
UI or platform code needs to change — the settings screen builds its connection
|
||
fields from what the provider declares. `provider/immich` is the simpler of the
|
||
two worked examples; `provider/google` is the one to read if the service needs
|
||
OAuth rather than a key.
|
||
|
||
## Licence
|
||
|
||
MIT.
|