Skip to main content

Marketplace

The LiquidBounce Marketplace allows users to subscribe to and install community-created content including scripts, themes, and configurations. The MarketplaceManager handles all marketplace operations.

Overview

The Marketplace system provides:

Script Library

Download and install custom scripts to extend functionality

Theme Collection

Install custom themes to personalize your UI

Auto Updates

Automatically check for and install updates to subscribed items

Version Control

Manage different revisions of installed items

Item Types

Marketplace items are categorized by type in MarketplaceItemType:
enum class MarketplaceItemType(
    override val tag: String,
    val isListable: Boolean,      // Can be browsed in marketplace
    val isSubscribable: Boolean    // Can be subscribed/installed
) : Tagged {
    CONFIG("Config", false, false),
    SCRIPT("Script", true, true),
    THEME("Theme", true, true),
    OTHER("Other", false, false)
}
Only Script and Theme types are currently subscribable and can be installed through the marketplace.

Subscribing to Items

Subscribe to a marketplace item:
suspend fun subscribeToItem(item: MarketplaceItem) {
    MarketplaceManager.subscribe(item)
}
The subscription process:
  1. Checks if already subscribed via isSubscribed(item.id)
  2. Creates a SubscribedItem wrapper
  3. Adds to subscribedItems list
  4. Installs the newest revision
  5. Stores configuration

Subscribed Items

Subscribed items are tracked with metadata:
data class SubscribedItem(
    val name: String,
    val id: Int,
    val type: MarketplaceItemType,
    var installedRevisionId: Int?
)

Storage Structure

Items are stored in the marketplace directory:
.liquidbounce/
└── marketplace/
    └── items/
        └── {item_id}/
            └── {revision_id}/
                └── [extracted files]
The installation folder is determined dynamically:
fun getInstallationFolder(): File? {
    val revisionDir = itemDir.resolve(installedRevisionId.toString())
    
    // Returns the folder containing files
    // Could be revision root or a subdirectory like /dist
    return findFolderWithFiles(revisionDir)
}

Installing and Updating

Install Process

suspend fun install(revisionId: Int, subTask: ResourceTask? = null) {
    // Download revision archive
    val revisionUrl = MarketplaceApi.downloadRevision(id, revisionId)
    download(revisionUrl, revisionArchiveFile, progressListener)
    
    // Extract to revision directory
    extractZip(revisionArchiveFile, revisionDir)
    
    // Update installed revision ID
    installedRevisionId = revisionId
    ConfigSystem.store(MarketplaceManager)
    
    // Clean up old revision
    previousRevisionDir?.deleteRecursively()
    
    // Reload the item type's manager
    type.reload()
}
The installation process includes cleanup of previous revisions to save disk space.

Update Items

Update all subscribed items:
suspend fun updateAll(task: Task? = null, command: Command? = null) {
    subscribedItems.toList().forEach { item ->
        update(item, task, command)
    }
}
Update a specific item:
suspend fun update(item: SubscribedItem) {
    // Check if update is available
    val updateRevisionId = item.checkUpdate() ?: return
    
    // Install the new revision
    item.install(updateRevisionId)
}

Check for Updates

suspend fun checkUpdate(): Int? {
    val newestRevisionId = getNewestRevisionId()
    
    if (installedRevisionId == newestRevisionId) {
        return null  // Already up to date
    }
    
    return newestRevisionId
}
The newest revision is fetched from the API:
suspend fun getNewestRevisionId(): Int? {
    val item = MarketplaceApi.getMarketplaceItem(id)
    
    // Only update if item is still active
    if (item.status != MarketplaceItemStatus.ACTIVE) {
        return null
    }
    
    // Get first (newest) revision
    val revisions = MarketplaceApi.getMarketplaceItemRevisions(id, 1, 1)
    return revisions.items.firstOrNull()?.id
}

Unsubscribing

Remove a subscribed item:
suspend fun unsubscribe(itemId: Int) {
    val item = subscribedItems.find { it.id == itemId }
        ?: error("Item $itemId not found")
    
    // Delete all item files
    check(item.itemDir.deleteRecursively()) {
        "Failed to delete item directory"
    }
    
    // Remove from subscriptions
    subscribedItems.remove(item)
    ConfigSystem.store(this)
    
    // Reload the item type's manager
    item.type.reload()
}
Unsubscribing permanently deletes all downloaded files for that item.

Querying Subscriptions

Get subscribed items by type:
val scripts = MarketplaceManager.getSubscribedItemsOfType(
    MarketplaceItemType.SCRIPT
)

val themes = MarketplaceManager.getSubscribedItemsOfType(
    MarketplaceItemType.THEME
)
Check subscription status:
if (MarketplaceManager.isSubscribed(itemId)) {
    // Already subscribed
}
Get specific item:
val item = MarketplaceManager.getItem(itemId)

Progress Tracking

Download progress can be tracked using ResourceTask:
val progressListener = OkHttpProgressInterceptor.ProgressListener { 
    bytesRead, contentLength, done ->
    subTask.update(bytesRead, contentLength)
}

download(url, file, progressListener = progressListener)

Type-Specific Reloading

When items are installed, updated, or removed, their managers are reloaded:
suspend fun reload() = when (this) {
    THEME -> ThemeManager.load()
    SCRIPT -> ScriptManager.reload()
    else -> { }
}
This ensures:
  • New scripts are compiled and loaded
  • New themes are recognized and available
  • Removed items are unloaded from memory

Marketplace Commands

The marketplace can be controlled via commands:
// Subscribe to an item
MarketplaceSubscribeCommand.execute(itemId)

Error Handling

The marketplace system uses runCatching for robust error handling:
runCatching {
    item.install(updateRevisionId, subTask)
}.onFailure { e ->
    logger.error("Failed to update item ${item.id}", e)
    command?.chat(markAsError(
        translation("liquidbounce.command.marketplace.error.updateFailed",
            item.id, e.message ?: "Unknown error")
    ))
}

Code References

MarketplaceManager.kt

Core marketplace logic - line 42
features/marketplace/MarketplaceManager.kt

SubscribedItem.kt

Subscribed item handling - line 36
features/marketplace/SubscribedItem.kt

MarketplaceItemType.kt

Item type definitions - line 26
api/models/marketplace/MarketplaceItemType.kt

Marketplace Commands

Command implementations
features/command/commands/client/marketplace/

Best Practices

1

Check Before Subscribe

Always verify an item isn’t already subscribed before subscribing
2

Handle Updates Gracefully

Use update tasks to provide progress feedback to users
3

Verify Installation

Check getInstallationFolder() returns a valid path before using installed items
4

Clean Up

Old revisions are automatically deleted, but ensure proper error handling

Build docs developers (and LLMs) love