A Minecraft Fabric mod that connects the game with ATProtocol ⛏️
1package com.jollywhoppers.atproto.examples
2
3import com.jollywhoppers.atproto.server.AppViewService
4import com.jollywhoppers.atproto.server.AppViewHttpServer
5import kotlinx.serialization.json.*
6import java.util.*
7
8/**
9 * Example demonstrating how to use the AppView service for displaying Minecraft data.
10 *
11 * An AppView is a custom service that indexes published AT Protocol records
12 * and provides rich display and query capabilities. This example shows:
13 *
14 * 1. Creating an AppView service instance
15 * 2. Indexing player data as records are published
16 * 3. Querying the indexed data for display
17 * 4. Starting an HTTP server to serve the data to clients
18 */
19class AppViewExample(
20 private val appViewService: AppViewService
21) {
22 private val json = Json {
23 prettyPrint = true
24 ignoreUnknownKeys = true
25 }
26
27 /**
28 * Example 1: Index a player profile when published to AT Protocol
29 */
30 fun exampleIndexPlayerProfile() {
31 // In a real AppView, this would be called by a subscription handler
32 // when a record is published to AT Protocol
33
34 val playerUuid = UUID.randomUUID().toString()
35 val profileRecord = json.parseToJsonElement("""
36 {
37 "${'$'}type": "com.jollywhoppers.minecraft.player.profile",
38 "player": {
39 "uuid": "$playerUuid",
40 "username": "AlicePlayer"
41 },
42 "displayName": "Alice",
43 "bio": "Minecraft enthusiast and builder",
44 "createdAt": "2026-04-20T10:30:00Z",
45 "updatedAt": null,
46 "publicStats": true,
47 "publicSessions": true
48 }
49 """)
50
51 val uri = "at://did:plc:alice123/com.jollywhoppers.minecraft.player.profile/self"
52
53 appViewService.indexPlayerProfile(uri, profileRecord)
54 .onSuccess {
55 println("✓ Indexed player profile for AlicePlayer")
56 }
57 .onFailure { e ->
58 println("✗ Failed to index profile: ${e.message}")
59 }
60 }
61
62 /**
63 * Example 2: Index player stats when synced to AT Protocol
64 */
65 fun exampleIndexPlayerStats() {
66 val playerUuid = UUID.randomUUID().toString()
67 val statsRecord = json.parseToJsonElement("""
68 {
69 "${'$'}type": "com.jollywhoppers.minecraft.player.stats",
70 "player": {
71 "uuid": "$playerUuid",
72 "username": "AlicePlayer"
73 },
74 "server": {
75 "serverId": "server-123",
76 "serverName": "Main SMP"
77 },
78 "statistics": [
79 {"key": "minecraft.mined.oak_log", "value": 1250, "category": "blocks_mined"},
80 {"key": "minecraft.killed.zombie", "value": 425, "category": "mobs_killed"},
81 {"key": "minecraft.custom.play_one_minute", "value": 432000, "category": "playtime"}
82 ],
83 "playtimeMinutes": 7200,
84 "level": 34,
85 "gamemode": "survival",
86 "dimension": "minecraft:overworld",
87 "syncedAt": "2026-04-25T14:22:00Z"
88 }
89 """)
90
91 val uri = "at://did:plc:alice123/com.jollywhoppers.minecraft.player.stats/8l6rvp4j6d3e2c4b9"
92
93 appViewService.indexPlayerStats(uri, statsRecord)
94 .onSuccess {
95 println("✓ Indexed player stats for AlicePlayer")
96 }
97 .onFailure { e ->
98 println("✗ Failed to index stats: ${e.message}")
99 }
100 }
101
102 /**
103 * Example 3: Index achievements as they're earned
104 */
105 fun exampleIndexAchievement() {
106 val playerUuid = UUID.randomUUID().toString()
107 val achievementRecord = json.parseToJsonElement("""
108 {
109 "${'$'}type": "com.jollywhoppers.minecraft.achievement",
110 "player": {
111 "uuid": "$playerUuid",
112 "username": "AlicePlayer"
113 },
114 "server": {
115 "serverId": "server-123",
116 "serverName": "Main SMP"
117 },
118 "achievementId": "minecraft:adventure/kill_a_mob",
119 "achievementName": "Monster Hunter",
120 "achievementDescription": "Kill any type of monster",
121 "achievedAt": "2026-04-24T15:45:00Z",
122 "category": "adventure",
123 "isChallenge": false
124 }
125 """)
126
127 val uri = "at://did:plc:alice123/com.jollywhoppers.minecraft.achievement/8l6rvp4j6d3e2c5a7"
128
129 appViewService.indexAchievement(uri, achievementRecord)
130 .onSuccess {
131 println("✓ Indexed achievement for AlicePlayer")
132 }
133 .onFailure { e ->
134 println("✗ Failed to index achievement: ${e.message}")
135 }
136 }
137
138 /**
139 * Example 4: Query player profile with stats
140 */
141 fun exampleQueryPlayerProfile(playerUuid: String) {
142 appViewService.getPlayerProfile(playerUuid)
143 .onSuccess { profileWithStats ->
144 if (profileWithStats != null) {
145 println("\n━━━ Player Profile ━━━")
146 println("Username: ${profileWithStats.profile.username}")
147 println("Display Name: ${profileWithStats.profile.displayName}")
148 println("Bio: ${profileWithStats.profile.bio}")
149 println("Stats Count: ${profileWithStats.statsCount}")
150 println("Achievements: ${profileWithStats.achievementCount}")
151
152 if (profileWithStats.latestStats != null) {
153 val stats = profileWithStats.latestStats
154 println("\nLatest Stats:")
155 println(" Level: ${stats.level}")
156 println(" Playtime: ${stats.playtimeMinutes} minutes")
157 println(" Gamemode: ${stats.gamemode}")
158 }
159 } else {
160 println("Player not found")
161 }
162 }
163 }
164
165 /**
166 * Example 5: Query leaderboards
167 */
168 fun exampleQueryLeaderboard() {
169 println("\n━━━ Top Players by Blocks Mined ━━━")
170 appViewService.getLeaderboard("minecraft.mined.oak_log", limit = 10)
171 .onSuccess { leaders ->
172 leaders.forEachIndexed { index, entry ->
173 println("${index + 1}. ${entry.username} - ${entry.value} blocks")
174 }
175 }
176 }
177
178 /**
179 * Example 6: Search for players
180 */
181 fun exampleSearchPlayers(query: String) {
182 println("\n━━━ Search Results for '$query' ━━━")
183 appViewService.searchPlayers(query)
184 .onSuccess { players ->
185 if (players.isEmpty()) {
186 println("No players found")
187 } else {
188 players.forEach { player ->
189 println("• ${player.username} (${player.displayName ?: "no display name"})")
190 }
191 }
192 }
193 }
194
195 /**
196 * Example 7: Get trending achievements
197 */
198 fun exampleTrendingAchievements() {
199 println("\n━━━ Trending Achievements ━━━")
200 appViewService.getTrendingAchievements(limit = 5)
201 .onSuccess { trending ->
202 trending.forEachIndexed { index, achievement ->
203 println("${index + 1}. ${achievement.achievementName}")
204 println(" Earned by ${achievement.timesEarned} players")
205 println(" Category: ${achievement.category}")
206 }
207 }
208 }
209
210 /**
211 * Example 8: Get player stats summary
212 */
213 fun examplePlayerStatsSummary(playerUuid: String) {
214 println("\n━━━ Player Stats Summary ━━━")
215 appViewService.getPlayerStatsSummary(playerUuid)
216 .onSuccess { summary ->
217 if (summary != null) {
218 println("Player: ${summary.username}")
219 println("Level: ${summary.level}")
220 println("Playtime: ${summary.playtimeMinutes} minutes")
221 println("Gamemode: ${summary.gamemode}")
222 println("\nTop Statistics:")
223 summary.topStatistics.forEachIndexed { index, stat ->
224 println(" ${index + 1}. ${stat.key}: ${stat.value}")
225 }
226 }
227 }
228 }
229
230 /**
231 * Example 9: Start the AppView HTTP server
232 */
233 fun exampleStartAppViewServer() {
234 val server = AppViewHttpServer(appViewService, port = 8080)
235
236 println("\n━━━ Starting AppView Server ━━━")
237 server.start()
238
239 println("\nAvailable Endpoints:")
240 println(" GET /health")
241 println(" Check server health")
242 println()
243 println(" GET /player/{uuid}")
244 println(" Get player profile with stats summary")
245 println()
246 println(" GET /player/{uuid}/stats?limit=10&offset=0")
247 println(" Get player stats history (paginated)")
248 println()
249 println(" GET /player/{uuid}/achievements?limit=25&offset=0")
250 println(" Get player achievement history (paginated)")
251 println()
252 println(" GET /leaderboard/{statistic}?limit=20")
253 println(" Get leaderboard for a specific statistic")
254 println()
255 println(" GET /search?q={query}")
256 println(" Search for players by username or display name")
257 println()
258 println(" GET /trending/achievements?limit=10")
259 println(" Get trending achievements")
260 println()
261 println(" GET /stats/summary/{uuid}")
262 println(" Get quick summary of player stats")
263 }
264
265 /**
266 * Example 10: Complete workflow
267 */
268 fun exampleCompleteWorkflow() {
269 println("\n┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓")
270 println("┃ AppView Integration Complete Workflow ┃")
271 println("┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛")
272
273 // 1. Set up sample data
274 println("\n[1] Indexing sample player data...")
275 exampleIndexPlayerProfile()
276 exampleIndexPlayerStats()
277 exampleIndexAchievement()
278
279 // 2. Query the data
280 println("\n[2] Querying indexed data...")
281 val sampleUuid = "550e8400-e29b-41d4-a716-446655440000"
282 exampleQueryPlayerProfile(sampleUuid)
283 exampleQueryLeaderboard()
284 exampleSearchPlayers("Alice")
285 exampleTrendingAchievements()
286 examplePlayerStatsSummary(sampleUuid)
287
288 // 3. Start server
289 println("\n[3] Starting HTTP server...")
290 exampleStartAppViewServer()
291
292 println("\n✓ AppView integration example complete!")
293 }
294}
295
296/**
297 * Quick start: Run this to see the AppView in action
298 */
299fun main() {
300 // This is a demonstration - in practice, the AppView would:
301 // 1. Subscribe to AT Protocol repository events
302 // 2. Index records as they're published
303 // 3. Serve queries via HTTP endpoints
304 // 4. Maintain a database of indexed records
305
306 println("""
307 ╔════════════════════════════════════════════╗
308 ║ AT Protocol Minecraft AppView ║
309 ║ Display and Query Synced Minecraft Data ║
310 ╚════════════════════════════════════════════╝
311 """.trimIndent())
312
313 // In a real implementation, you would:
314 // 1. Create the AppViewService with a real database backend
315 // 2. Subscribe to Firehose events or use WebSocket subscriptions
316 // 3. Deploy the HTTP server to a public URL
317 // 4. Register your AppView with the AT Protocol application registry
318
319 println("\nTo implement a full AppView:")
320 println("1. Use a framework like Ktor or Spring Boot for HTTP server")
321 println("2. Subscribe to AT Protocol Firehose for real-time updates")
322 println("3. Use a database (PostgreSQL, MongoDB) for indexing")
323 println("4. Implement pagination, filtering, and search")
324 println("5. Add caching layers (Redis) for performance")
325 println("6. Deploy to a public URL accessible from AT Protocol clients")
326 println("7. Register your AppView in the AT Protocol registry")
327}