Skip to content

Commit eda503d

Browse files
authored
feat: marketplace (#5226)
Allows subscribing to marketplace items such as themes and scripts which are automatically being installed and updated. Introduces: - `.marketplace list <type> [featured]` - `.marketplace subscribe <id>` - `.marketplace unsubscribe <id>` - `.marketplace update [id]` - `.marketplace revisions list <id>` All item creation and editing commands are currently commented out because it will be easier to maintain them through https://liquidbounce.net/marketplace in the future. This commit also introduces an automatic theme upload workflow to the LiquidBounce Marketplace and improved README for the theme workspace.
1 parent 79c6347 commit eda503d

File tree

52 files changed

+2219
-107
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+2219
-107
lines changed

build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ dependencies {
155155

156156
// JCEF Support
157157
includeModDependency("com.github.CCBlueX:mcef:${project.property("mcef_version")}")
158-
includeDependency("net.ccbluex:netty-httpserver:2.2.1")
158+
includeDependency("net.ccbluex:netty-httpserver:2.2.2")
159159

160160
// Discord RPC Support
161161
includeDependency("com.github.CCBlueX:DiscordIPC:4.0.0")
Lines changed: 36 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,24 @@
11
name: Build
2-
32
on:
43
push:
54
branches:
65
- main
76

7+
permissions:
8+
contents: write
9+
10+
env:
11+
ZIP_NAME: theme # Name for the distribution archive
12+
MARKETPLACE_ITEM_ID: 12345 # Replace with your actual marketplace item ID
13+
814
jobs:
915
build:
1016
runs-on: ubuntu-latest
1117
steps:
1218
- name: Checkout repository
1319
uses: actions/checkout@v4
20+
with:
21+
fetch-depth: 0
1422

1523
- name: Setup Node.js
1624
uses: actions/setup-node@v4
@@ -24,23 +32,23 @@ jobs:
2432
- name: Build project
2533
run: npm run build
2634

27-
- name: Upload dist artifact
35+
- name: Create dist archive
36+
run: zip -r ${{ env.ZIP_NAME }}.zip dist/
37+
38+
- name: Upload build artifact
2839
uses: actions/upload-artifact@v4
2940
with:
30-
name: dist
31-
path: dist/
41+
name: ${{ env.ZIP_NAME }}
42+
path: ${{ env.ZIP_NAME }}.zip
3243

33-
# Uncomment the sections below if you want to create GitHub releases
34-
#
35-
# - name: Create dist archive
36-
# run: zip -r dist.zip dist/
37-
#
38-
# - name: Generate version tag
39-
# id: version_tag
40-
# run: |
41-
# COMMIT_HASH=$(git rev-parse --short HEAD)
42-
# TAG="build-$COMMIT_HASH-$(date +'%Y%m%d%H%M%S')"
43-
# echo "version=$TAG" >> $GITHUB_OUTPUT
44+
- name: Generate version tag
45+
id: version_tag
46+
run: |
47+
COMMIT_HASH=$(git rev-parse --short HEAD)
48+
TAG="build-$COMMIT_HASH-$(date +'%Y%m%d%H%M%S')"
49+
echo "version=$TAG" >> $GITHUB_OUTPUT
50+
51+
# Uncomment the sections below to enable GitHub releases and marketplace publishing
4452
#
4553
# - name: Create and push Git tag
4654
# run: |
@@ -52,8 +60,18 @@ jobs:
5260
# - name: Upload to GitHub Release
5361
# uses: softprops/action-gh-release@v1
5462
# with:
55-
# files: dist.zip
56-
# token: ${{ secrets.GITHUB_TOKEN }}
63+
# files: ${{ env.ZIP_NAME }}.zip
5764
# tag_name: ${{ steps.version_tag.outputs.version }}
58-
# prerelease: false
65+
# prerelease: true
5966
# name: "Build Release ${{ steps.version_tag.outputs.version }}"
67+
#
68+
# - name: Upload to LiquidBounce Marketplace
69+
# run: |
70+
# curl -X POST \
71+
# "https://api.liquidbounce.net/api/v3/marketplace/${{ env.MARKETPLACE_ITEM_ID }}/revisions" \
72+
# -H "Authorization: Bearer ${{ secrets.API_TOKEN }}" \
73+
# -F "file=@${{ env.ZIP_NAME }}.zip" \
74+
# -F "version=${{ steps.version_tag.outputs.version }}" \
75+
# -F "changelog=Automated build from commit ${{ github.sha }}" \
76+
# -F "channel=alpha" \
77+
# --fail-with-body

src-theme/README.md

Lines changed: 67 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,74 @@
11
# LiquidBounce Default Theme
22

3-
This directory contains the source code of LiquidBounce's default theme made with [Svelte](https://svelte.dev/).
3+
This directory contains the source code of LiquidBounce's default theme built with [Svelte](https://svelte.dev/).
44

55
## Development
66

7-
**This development setup is temporary and will be improved in the future!**
7+
### Prerequisites
8+
- [Node.js](https://nodejs.org/en) (latest or stable version)
89

9-
1. Install [Node.js](https://nodejs.org/en) on your computer (either latest or stable should be fine).
10-
2. Install required dependencies using `npm install`.
11-
3. Set `IN_DEV` in [host.ts](https://github.com/CCBlueX/LiquidBounce/blob/nextgen/src-theme/src/integration/host.ts) to `true`.
12-
4. Launch the client.
13-
5. Run `npm run dev` to start a development server.
10+
### Setup
1411

15-
Make sure not to push the changes made to `host.ts` and `NettyServer.kt`!
12+
1. **Install dependencies**
13+
```bash
14+
npm install
15+
```
16+
17+
2. **Configure development mode**
18+
- Set `IN_DEV` to `true` in [host.ts](https://github.com/CCBlueX/LiquidBounce/blob/nextgen/src-theme/src/integration/host.ts)
19+
20+
3. **Launch the client**
21+
22+
4. **Start development server**
23+
```bash
24+
npm run dev
25+
```
26+
27+
5. **Set theme in client**
28+
- `.client theme set http://localhost:5173/`
29+
30+
**Important**: Do not commit changes made to `host.ts` and `NettyServer.kt` when pushing to the repository.
31+
32+
## Building for Production
33+
34+
To build the theme for production use, follow these steps:
35+
36+
1. **Ensure development mode is disabled**
37+
- Set `IN_DEV` to `false` in [host.ts](https://github.com/CCBlueX/LiquidBounce/blob/nextgen/src-theme/src/integration/host.ts)
38+
39+
2. **Build the theme**
40+
```bash
41+
npm run build
42+
```
43+
44+
3. **Locate build output**
45+
- The production build will be generated in the `dist` folder
46+
- This folder contains all the optimized and minified theme files
47+
48+
4. **Deploy to themes directory**
49+
- Copy the entire `dist` folder to your themes directory
50+
- You can open the themes directory by typing `.client theme browse` in the client
51+
- Rename the `dist` folder to your preferred theme name
52+
53+
5. **Apply the theme**
54+
```
55+
.client theme set <your-theme-name>
56+
```
57+
58+
## Marketplace Publishing
59+
60+
1. **Get API token**
61+
- Go to [https://liquidbounce.net/account](https://liquidbounce.net/account)
62+
- Generate your API token
63+
64+
2. **Get marketplace item ID**
65+
- Navigate to [https://liquidbounce.net/marketplace](https://liquidbounce.net/marketplace)
66+
- Go to your item page
67+
- Copy the ID from the "Item ID" field on the right side
68+
69+
3. **Configure repository**
70+
- Add `API_TOKEN` as a repository secret in Settings → Secrets and variables → Actions
71+
- Update `MARKETPLACE_ITEM_ID` and `ZIP_NAME` in the workflow file with your values
72+
73+
4. **Enable publishing**
74+
- Uncomment the GitHub release and marketplace upload sections in `.github/workflows/build.yml`

src/main/kotlin/net/ccbluex/liquidbounce/LiquidBounce.kt

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ import net.ccbluex.liquidbounce.features.cosmetic.ClientAccountManager
4545
import net.ccbluex.liquidbounce.features.cosmetic.CosmeticService
4646
import net.ccbluex.liquidbounce.features.itemgroup.ClientItemGroups
4747
import net.ccbluex.liquidbounce.features.itemgroup.groups.heads
48+
import net.ccbluex.liquidbounce.features.marketplace.MarketplaceManager
4849
import net.ccbluex.liquidbounce.features.misc.AccountManager
4950
import net.ccbluex.liquidbounce.features.misc.FriendManager
5051
import net.ccbluex.liquidbounce.features.misc.proxy.ProxyManager
@@ -230,6 +231,7 @@ object LiquidBounce : EventListener {
230231
ConfigSystem.root(LanguageManager)
231232
ConfigSystem.root(ClientAccountManager)
232233
ConfigSystem.root(SpooferManager)
234+
ConfigSystem.root(MarketplaceManager)
233235
PostRotationExecutor
234236
ServerObserver
235237
ItemImageAtlas
@@ -298,7 +300,7 @@ object LiquidBounce : EventListener {
298300
ClientAccountManager.clientAccount = ClientAccount.EMPTY_ACCOUNT
299301
}.onSuccess {
300302
logger.info("Successfully renewed client account token.")
301-
ConfigSystem.storeConfigurable(ClientAccountManager)
303+
ConfigSystem.store(ClientAccountManager)
302304
}
303305
}
304306
},
@@ -353,6 +355,19 @@ object LiquidBounce : EventListener {
353355
logger.info("Failed to initialize deep learning.", exception)
354356
}
355357
}
358+
359+
launch("Marketplace") { task ->
360+
runCatching {
361+
// Preload marketplace items
362+
ConfigSystem.load(MarketplaceManager)
363+
MarketplaceManager.updateAll(task)
364+
ConfigSystem.store(MarketplaceManager)
365+
}.onFailure { exception ->
366+
logger.error("Failed to update marketplace items.", exception)
367+
}
368+
369+
task.isCompleted = true
370+
}
356371
}
357372

358373
// Prepare glyph manager

src/main/kotlin/net/ccbluex/liquidbounce/api/core/BaseApi.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,8 @@ abstract class BaseApi(protected val baseUrl: String) {
8383

8484
protected suspend inline fun <reified T> delete(
8585
endpoint: String,
86+
body: RequestBody? = null,
8687
crossinline headers: Headers.Builder.() -> Unit = {}
87-
): T = request(endpoint, HttpMethod.DELETE, headers)
88+
): T = request(endpoint, HttpMethod.DELETE, headers, body)
8889

8990
}

src/main/kotlin/net/ccbluex/liquidbounce/api/core/HttpClient.kt

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ import kotlinx.coroutines.*
2323
import net.ccbluex.liquidbounce.LiquidBounce
2424
import net.ccbluex.liquidbounce.config.gson.accessibleInteropGson
2525
import net.ccbluex.liquidbounce.config.gson.util.decode
26+
import net.ccbluex.liquidbounce.mcef.listeners.OkHttpProgressInterceptor
2627
import net.ccbluex.liquidbounce.utils.client.logger
27-
import net.ccbluex.liquidbounce.mcef.utils.FileUtils as McefFileUtils
2828
import net.minecraft.client.texture.NativeImage
2929
import net.minecraft.client.texture.NativeImageBackedTexture
3030
import net.minecraft.util.Util
@@ -38,6 +38,7 @@ import java.io.IOException
3838
import java.io.InputStream
3939
import java.io.Reader
4040
import java.util.concurrent.TimeUnit
41+
import net.ccbluex.liquidbounce.mcef.utils.FileUtils as McefFileUtils
4142

4243
val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
4344

@@ -83,12 +84,14 @@ object HttpClient {
8384
}
8485
.build().also(McefFileUtils::setOkHttpClient)
8586

87+
@Suppress("LongParameterList")
8688
suspend fun request(
8789
url: String,
8890
method: HttpMethod,
8991
agent: String = DEFAULT_AGENT,
9092
headers: Headers.Builder.() -> Unit = {},
91-
body: RequestBody? = null
93+
body: RequestBody? = null,
94+
progressListener: OkHttpProgressInterceptor.ProgressListener? = null
9295
): Response = withContext(Dispatchers.IO) {
9396
val request = Request.Builder()
9497
.url(url)
@@ -97,11 +100,22 @@ object HttpClient {
97100
.header("User-Agent", agent)
98101
.build()
99102

100-
client.newCall(request).execute()
103+
if (progressListener == null) {
104+
client.newCall(request).execute()
105+
} else {
106+
client.newBuilder()
107+
.addNetworkInterceptor(OkHttpProgressInterceptor(progressListener))
108+
.build()
109+
.newCall(request).execute()
110+
}
101111
}
102112

103-
suspend fun download(url: String, file: File, agent: String = DEFAULT_AGENT) =
104-
request(url, HttpMethod.GET, agent).toFile(file)
113+
suspend fun download(
114+
url: String,
115+
file: File,
116+
agent: String = DEFAULT_AGENT,
117+
progressListener: OkHttpProgressInterceptor.ProgressListener? = null
118+
) = request(url, HttpMethod.GET, agent, progressListener = progressListener).toFile(file)
105119

106120
}
107121

src/main/kotlin/net/ccbluex/liquidbounce/api/models/auth/ClientAccount.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ data class ClientAccount(
3939
@Exclude
4040
var cosmetics: Set<Cosmetic>? = null
4141
) {
42-
private suspend fun takeSession(): OAuthSession = session?.takeIf { !it.accessToken.isExpired() } ?: run {
42+
suspend fun takeSession(): OAuthSession = session?.takeIf { !it.accessToken.isExpired() } ?: run {
4343
renew()
4444
session ?: error("No session")
4545
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* This file is part of LiquidBounce (https://github.com/CCBlueX/LiquidBounce)
3+
*
4+
* Copyright (c) 2015 - 2025 CCBlueX
5+
*
6+
* LiquidBounce is free software: you can redistribute it and/or modify
7+
* it under the terms of the GNU General Public License as published by
8+
* the Free Software Foundation, either version 3 of the License, or
9+
* (at your option) any later version.
10+
*
11+
* LiquidBounce is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
* GNU General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU General Public License
17+
* along with LiquidBounce. If not, see <https://www.gnu.org/licenses/>.
18+
*/
19+
package net.ccbluex.liquidbounce.api.models.marketplace
20+
21+
import com.google.gson.annotations.SerializedName
22+
23+
data class MarketplaceItem(
24+
val id: Int,
25+
val uid: String,
26+
val type: MarketplaceItemType,
27+
val name: String,
28+
val branch: String,
29+
val description: String,
30+
@SerializedName("thumbnail_pid")
31+
val thumbnailPid: String?,
32+
val featured: Boolean,
33+
@SerializedName("created_at")
34+
val createdAt: String,
35+
val status: MarketplaceItemStatus
36+
)
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* This file is part of LiquidBounce (https://github.com/CCBlueX/LiquidBounce)
3+
*
4+
* Copyright (c) 2015 - 2025 CCBlueX
5+
*
6+
* LiquidBounce is free software: you can redistribute it and/or modify
7+
* it under the terms of the GNU General Public License as published by
8+
* the Free Software Foundation, either version 3 of the License, or
9+
* (at your option) any later version.
10+
*
11+
* LiquidBounce is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
* GNU General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU General Public License
17+
* along with LiquidBounce. If not, see <https://www.gnu.org/licenses/>.
18+
*/
19+
package net.ccbluex.liquidbounce.api.models.marketplace
20+
21+
import com.google.gson.annotations.SerializedName
22+
23+
data class MarketplaceItemRevision(
24+
val id: Int,
25+
@SerializedName("item_id")
26+
val itemId: Int,
27+
val version: String,
28+
@SerializedName("file_pid")
29+
val filePid: String,
30+
val changelog: String?,
31+
@SerializedName("created_at")
32+
val createdAt: String,
33+
val status: MarketplaceItemStatus
34+
)

0 commit comments

Comments
 (0)