This repository was archived by the owner on Dec 12, 2025. It is now read-only.
forked from kami-blue/cape-api
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathAbstractUUIDManager.kt
More file actions
161 lines (141 loc) · 5.51 KB
/
AbstractUUIDManager.kt
File metadata and controls
161 lines (141 loc) · 5.51 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
package com.lambda.capeapi
import com.google.gson.GsonBuilder
import com.google.gson.JsonParser
import com.google.gson.reflect.TypeToken
import com.lambda.commons.extension.synchronized
import com.lambda.commons.utils.ConnectionUtils
import org.apache.logging.log4j.Logger
import java.io.File
import java.io.FileWriter
import java.util.*
abstract class AbstractUUIDManager(
filePath: String,
private val logger: Logger,
private val maxCacheSize: Int = 500
) {
private val file = File(filePath)
@Suppress("DEPRECATION")
private val parser = JsonParser()
private val gson = GsonBuilder().setPrettyPrinting().create()
private val type = TypeToken.getArray(PlayerProfile::class.java).type
private val nameProfileMap = LinkedHashMap<String, PlayerProfile>().synchronized()
private val uuidNameMap = LinkedHashMap<UUID, PlayerProfile>().synchronized()
fun getByString(stringIn: String?) = stringIn?.let { string ->
UUIDUtils.fixUUID(string)?.let { getByUUID(it) } ?: getByName(string)
}
fun getByUUID(uuid: UUID?) = uuid?.let {
uuidNameMap.getOrPut(uuid) {
getOrRequest(uuid.toString())?.also { profile ->
// If UUID already present in nameUuidMap but not in uuidNameMap (user changed name)
nameProfileMap[profile.name]?.let { uuidNameMap.remove(it.uuid) }
nameProfileMap[profile.name] = profile
} ?: return null
}.also {
trimMaps()
}
}
fun getByName(name: String?) = name?.let {
nameProfileMap.getOrPut(name.lowercase()) {
getOrRequest(name)?.also { profile ->
// If UUID already present in uuidNameMap but not in nameUuidMap (user changed name)
uuidNameMap[profile.uuid]?.let { nameProfileMap.remove(it.name) }
uuidNameMap[profile.uuid] = profile
} ?: return null
}.also {
trimMaps()
}
}
private fun trimMaps() {
while (nameProfileMap.size > maxCacheSize) {
nameProfileMap.remove(nameProfileMap.keys.first())?.also {
uuidNameMap.remove(it.uuid)
}
}
}
/**
* Overwrites this if you want to get UUID from other source
* eg. online player in game client
*/
protected open fun getOrRequest(nameOrUUID: String): PlayerProfile? {
return requestProfile(nameOrUUID)
}
private fun requestProfile(nameOrUUID: String): PlayerProfile? {
val isUUID = UUIDUtils.isUUID(nameOrUUID)
val response = if (isUUID) requestProfileFromUUID(nameOrUUID) else requestProfileFromName(nameOrUUID)
return if (response.isNullOrBlank()) {
logger.error("Response is null or blank, internet might be down")
null
} else {
try {
@Suppress("DEPRECATION") val jsonElement = parser.parse(response)
if (isUUID) {
val name = jsonElement.asJsonArray.last().asJsonObject["name"].asString
PlayerProfile(UUID.fromString(nameOrUUID), name)
} else {
val id = jsonElement.asJsonObject["id"].asString
val name = jsonElement.asJsonObject["name"].asString
PlayerProfile(UUIDUtils.fixUUID(id)!!, name) // let it throw a NPE if failed to parse the string to UUID
}
} catch (e: Exception) {
logger.error("Failed parsing profile", e)
null
}
}
}
private fun requestProfileFromUUID(uuid: String): String? {
return request("https://api.mojang.com/user/profiles/${UUIDUtils.removeDashes(uuid)}/names")
}
private fun requestProfileFromName(name: String): String? {
return request("https://api.mojang.com/users/profiles/minecraft/$name")
}
private fun request(url: String): String? {
return ConnectionUtils.requestRawJsonFrom(url) {
logger.error("Failed requesting from Mojang API", it)
}
}
fun load(): Boolean {
fixEmptyJson(file)
return try {
val cacheList = file.bufferedReader().use {
gson.fromJson<Array<PlayerProfile>>(it, type)
}
uuidNameMap.clear()
nameProfileMap.clear()
uuidNameMap.putAll(cacheList.associateBy { it.uuid })
nameProfileMap.putAll(cacheList.associateBy { it.name.lowercase() })
logger.info("UUID cache loaded")
true
} catch (e: Exception) {
logger.warn("Failed loading UUID cache", e)
false
}
}
fun save(): Boolean {
return try {
val cacheList = uuidNameMap.values.sortedBy { it.name }
file.bufferedWriter().use {
gson.toJson(cacheList, it)
}
logger.info("UUID cache saved")
true
} catch (e: Exception) {
logger.warn("Failed saving UUID cache", e)
false
}
}
private fun fixEmptyJson(file: File) {
if (!file.exists()) file.createNewFile()
var notEmpty = false
file.forEachLine { notEmpty = notEmpty || it.trim().isNotBlank() || it == "[]" || it == "{}" }
if (!notEmpty) {
val fileWriter = FileWriter(file)
try {
fileWriter.write("[]")
} catch (e: Exception) {
logger.error("Failed to fix empty json", e)
} finally {
fileWriter.close()
}
}
}
}