Skip to main content
The Marketplace system allows users to subscribe to community-created scripts, modules, and other content, with automatic updates and dependency management.

Overview

The MarketplaceManager (features/marketplace/MarketplaceManager.kt:42) handles:
  • Browsing marketplace items
  • Subscribing to items
  • Automatic updates
  • Version management
  • Local file storage

Marketplace Items

Item Types

Supported via MarketplaceItemType:
  • Scripts - Custom JavaScript/Kotlin scripts
  • Modules - Pre-built module packages
  • Other content types (extensible)
Each type has its own reload mechanism (MarketplaceManager.kt:122).

Subscribed Items

Subscribed items are tracked with:
data class SubscribedItem(
    val id: Int,
    val type: MarketplaceItemType,
    // ... additional metadata
)
Stored in config:
val subscribedItems by list("subscribed", mutableListOf<SubscribedItem>(), ValueType.SUBSCRIBED_ITEM)

File Storage

Marketplace content is stored in a dedicated directory:
val marketplaceRoot = File(ConfigSystem.rootFolder, "marketplace").apply {
    mkdirs()
}
Each item gets its own subdirectory in marketplaceRoot.

Subscribing to Items

1

Browse Marketplace

Browse available items from the marketplace API
2

Subscribe

MarketplaceManager.subscribe(item)
Checks if already subscribed before adding (MarketplaceManager.kt:102-111)
3

Download

Automatically downloads the newest revision of the item
4

Install

Item is installed to the marketplace directory
5

Save Config

Subscription is saved to config for persistence

Subscribe Implementation

suspend fun subscribe(item: MarketplaceItem) {
    if (isSubscribed(item.id)) {
        return
    }
    
    val item = SubscribedItem(item)
    subscribedItems.add(item)
    item.install(item.getNewestRevisionId() ?: return)
    ConfigSystem.store(this)
}

Unsubscribing

Remove items and clean up files:
suspend fun unsubscribe(itemId: Int)
1

Find Item

Locates the subscribed item by ID
2

Delete Files

Recursively deletes the item’s directory (MarketplaceManager.kt:116)
3

Remove from List

Removes from subscribed items list
4

Reload Type Manager

Reloads the appropriate manager (scripts, modules, etc.)
5

Save Config

Persists changes to config
check(!item.itemDir.exists() || item.itemDir.deleteRecursively()) { 
    "Failed to delete item directory" 
}

Update System

Checking for Updates

val updateRevisionId = item.checkUpdate()
if (updateRevisionId != null) {
    // Update available
}

Manual Update

Update a specific item:
suspend fun update(
    item: SubscribedItem, 
    task: Task? = null, 
    command: Command? = null
)
The function (MarketplaceManager.kt:64-100):
  1. Checks for updates
  2. Downloads new revision if available
  3. Installs the update
  4. Reports progress via task or command
  5. Handles errors gracefully

Update All Items

Update all subscribed items:
suspend fun updateAll(task: Task? = null, command: Command? = null) {
    subscribedItems.toList().forEach { item ->
        update(item, task, command)
    }
}
The .toList() creates a copy to avoid concurrent modification issues during updates.

Progress Tracking

Task Integration

Updates can be tracked with tasks:
val subTask = task?.getOrCreateFileTask(item.id.toString())
item.install(updateRevisionId, subTask)
subTask?.isCompleted = true
This provides:
  • Progress indicators
  • File download tracking
  • Completion status

Command Feedback

Updates provide feedback through commands:
command?.run { 
    chat(regular(command.result("updating", variable(item.id.toString()))))
}

// On success
command?.run {
    chat(regular(
        command.result("success", 
            variable(item.id.toString()),
            variable(updateRevisionId.toString())
        )
    ))
}

Error Handling

Comprehensive error handling for reliability:
.onFailure { e ->
    logger.error("Failed to update item ${item.id}", e)
    if (command != null) {
        chat(markAsError(
            translation(
                "liquidbounce.command.marketplace.error.updateFailed",
                item.id,
                e.message ?: "Unknown error"
            )
        ))
    }
}
Errors are:
  • Logged for debugging
  • Displayed to user via chat
  • Don’t prevent other items from updating

Querying Items

By Type

val scripts = MarketplaceManager.getSubscribedItemsOfType(MarketplaceItemType.SCRIPT)

By ID

val item = MarketplaceManager.getItem(itemId)

Check Subscription

if (MarketplaceManager.isSubscribed(itemId)) {
    // Already subscribed
}

Configuration

Subscriptions are persisted in the config system:
object MarketplaceManager : Config("marketplace"), EventListener
This ensures:
  • Subscriptions survive client restarts
  • Automatic update tracking
  • Version history

Revision Management

Each item tracks its installed revision:
// Get newest available revision
val revisionId = item.getNewestRevisionId()

// Check if update available
val updateRevision = item.checkUpdate()

// Install specific revision
item.install(revisionId, task)
This allows:
  • Version control
  • Rollback capability (if implemented)
  • Update detection

Best Practices

Call updateAll() periodically to keep marketplace content current. Consider:
  • On client startup
  • Daily automatic checks
  • Manual update command
Always provide user feedback on errors. The update system is designed to be resilient:
  • Failed updates don’t affect other items
  • Errors are logged for debugging
  • Users receive actionable error messages
Use tasks for large batches:
val task = Task("Updating Marketplace")
MarketplaceManager.updateAll(task)
After subscribe/unsubscribe, the appropriate type manager is automatically reloaded. Don’t manually reload unless necessary.

Usage Example

// Subscribe to a script
val scriptItem = // ... from marketplace API
MarketplaceManager.subscribe(scriptItem)

// Check for updates
MarketplaceManager.subscribedItems.forEach { item ->
    val updateId = item.checkUpdate()
    if (updateId != null) {
        logger.info("Update available for ${item.id}: revision $updateId")
    }
}

// Update all
MarketplaceManager.updateAll(
    task = updateTask,
    command = marketplaceCommand
)

// Unsubscribe
MarketplaceManager.unsubscribe(itemId)

// Get all subscribed scripts
val scripts = MarketplaceManager.getSubscribedItemsOfType(MarketplaceItemType.SCRIPT)

Build docs developers (and LLMs) love