Skip to content

About

RuneLite plugin: sends your bank, equipment and inventory to an HTTP endpoint you configure

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Item Sync

Sends your bank, worn equipment and inventory to an HTTP endpoint you configure, so your own tools can answer questions like "do I have the materials for this?" without you checking manually.

No public API exposes item contents, so if you want your bank readable by something outside the game — a spreadsheet, a Discord bot, an assistant — the data has to come from the client.

Not on the Plugin Hub

This plugin will not be distributed through the RuneLite Plugin Hub. Their rejected features list disallows "plugins which expose player information over HTTP", which is precisely what this does, so the submission was declined. That is RuneLite's distribution policy rather than a Jagex rule, but it means the only way to run this is to load it yourself.

Two options, both of which need developer mode:

# Build and run a development client with the plugin loaded
./gradlew run

# Or sideload the jar into a client you launch yourself
./gradlew build
mkdir -p ~/.runelite/sideloaded-plugins
cp build/libs/*.jar ~/.runelite/sideloaded-plugins/
java -ea -jar RuneLite.jar --developer-mode

Developer mode requires the --developer-mode flag, assertions (-ea), and a client not started by an official launcher — any launcher sets runelite.launcher.version, which disables developer mode and with it sideloading. RUNELITE_HOME=/path/containing/.runelite ./gradlew run points the development client at an existing RuneLite directory so it keeps your installed plugins and settings.

What it sends, and where

Nothing is sent until you configure an endpoint URL and a token. With both blank the plugin does nothing.

It POSTs JSON to the single URL you set, with your token in an X-Sync-Token header:

{
  "username": "YourName",
  "timestamp": "2026-07-27T14:32:05Z",
  "containers": {
    "bank":      [ { "id": 1613, "name": "Red topaz", "quantity": 14 } ],
    "inventory": [ { "id": 995,  "name": "Coins", "quantity": 1842305 } ],
    "equipment": [ { "id": 4151, "name": "Abyssal whip", "quantity": 1, "slot": "weapon" } ]
  },
  "inventory_slots_used": 24,
  "inventory_slots_total": 28,
  "pouch_contents": [ { "id": 563, "name": "Law rune", "quantity": 12 } ],
  "quest_journal": {
    "quest": "Dragon Slayer II",
    "progress_var": 42,
    "lines": [ "<str>Retrieve the pieces of the Dragonkin Cog.", "Speak to Fossegrimen." ]
  },
  "quests_in_progress": [ { "quest": "Dragon Slayer II", "progress_var": 42 } ]
}

The container lists merge stacks by item id, so their length counts item types rather than slots: four karambwans fill four inventory slots but arrive as one entry. inventory_slots_used and inventory_slots_total are therefore counted on the raw slot array before merging, and the total comes from the container itself rather than assuming 28.

pouch_contents is present only while a rune pouch is actually in the inventory. The inventory reports a pouch as one opaque item, so its runes are read from the same varbits RuneLite's own runepouch overlay uses (RUNE_POUCH_TYPE_1..6 / RUNE_POUCH_QUANTITY_1..6, resolved through enum RUNEPOUCH_RUNE). The varbits keep their last values after a pouch is banked, so the field is omitted rather than reporting runes you are not carrying.

quest_journal is sent only when you open a quest's page in your in-game quest journal, and only if that quest is not yet finished — a completed quest's journal adds nothing, since completion already comes from Quest.getState, and clicking through old quests happens often enough that storing them would spend write budget for nothing. lines is the journal's raw text, markup and all: a <str> tag marks a step as done, opening at the start of the line and never closed — the game does not emit a matching </str>. progress_var is a per-quest varbit or varp value used purely as a fingerprint — its meaning differs quest to quest and it is never decoded into a game state itself, only compared against a later reading of the same var to tell whether the captured text is still current. The field is omitted entirely, not sent as null, for the handful of quests with no mapped var — Gson drops a null field rather than serialising it, and the receiving schema treats it as optional accordingly. The table mapping each quest to its var is ported from Quest Helper (BSD 2-Clause, © Zoinkwiz), which maintains the same mapping for its own step tracking.

quests_in_progress rides along on every push, not only journal opens, and is the plugin's full current list of in-progress quests with their vars — it lets a receiver notice that a quest has moved on since a journal was captured, even though the journal itself is not re-sent every time.

That is the whole network surface. It contacts no other host, and it reads no chat, no location, and no account credentials.

What triggers a sync

Container Trigger
Bank bank contents change
Equipment worn gear changes, and once on every login
Inventory never on its own — included alongside a bank or equipment sync
Quest journal you open a quest's page in the in-game quest journal

The first three are container-change events; the quest journal is different — it fires on a widget opening, not on any container changing.

Equipment also syncs once per login because container events only fire on change: a player who never swaps gear would otherwise never report any, and the first snapshot would wait indefinitely on an unrelated event.

Inventory does not trigger uploads because it changes every few seconds while playing, which would mean constant traffic for no benefit. Including it with bank and equipment syncs is enough to answer "do I own this".

Uploads are debounced for 3 seconds, so opening the bank sends one request rather than one per tick.

Details worth knowing

  • Placeholders are skipped. A bank placeholder is a reserved empty slot; reporting it would claim you own an item you have actually spent.
  • Noted and un-noted stacks are merged. Item ids are canonicalised via ItemManager, so 100 noted sharks and 5 loose ones report as 105 sharks under the wiki's name for the item.
  • Container contents are captured when the change event fires, not when the debounced upload runs — otherwise closing the bank within those 3 seconds drops it from the payload.
  • Non-standard worlds are skipped: tournament, beta, deadman, PvP Arena, seasonal, fresh start, Last Man Standing and quest speedrunning. Items there are not your real account's.
  • Failures are silent. Network errors are written to the RuneLite log and never interrupt gameplay.

Configuration

Setting Meaning
Endpoint URL Where to POST. Blank disables the plugin.
Sync token Sent as X-Sync-Token. Blank disables the plugin.
Username override Defaults to your logged-in display name.
Sync bank Upload the bank when it changes.
Sync equipment Upload worn gear when it changes.
Include inventory Include the inventory with those uploads.
Sync quest journal Send a quest's journal text when you open its page in the quest journal. Finished quests are skipped.

Receiving the data

Any HTTP server that accepts a JSON POST works. Your endpoint should check the X-Sync-Token header against a value only you know, since that header is the only thing protecting it from anyone who learns the URL.

A bank of ~670 distinct items serialises to roughly 40 KB, so a receiver that stores snapshots should compare a hash of the contents before rewriting anything — most syncs carry data identical to the previous one.

Building

./gradlew build     # jar in build/libs/
./gradlew test
./gradlew run       # developer-mode client with the plugin loaded

Licence

BSD 2-Clause. See LICENSE.

About

RuneLite plugin: sends your bank, equipment and inventory to an HTTP endpoint you configure

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages