aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/at/hannibal2/skyhanni/data/ApiDataLoader.kt
blob: 17cb04d35608c0a0fe563bab6359fe56af9c8b7a (plain)
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
package at.hannibal2.skyhanni.data

import at.hannibal2.skyhanni.SkyHanniMod
import at.hannibal2.skyhanni.events.LorenzChatEvent
import at.hannibal2.skyhanni.events.ProfileApiDataLoadedEvent
import at.hannibal2.skyhanni.events.ProfileJoinEvent
import at.hannibal2.skyhanni.utils.APIUtil
import at.hannibal2.skyhanni.utils.LorenzUtils
import at.hannibal2.skyhanni.utils.StringUtils.toDashlessUUID
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import net.minecraft.client.Minecraft
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
import net.minecraftforge.fml.common.gameevent.TickEvent
import java.io.File
import java.util.*

class ApiDataLoader {

    private var currentProfileName = ""

    private var nextApiCallTime = -1L
    private var currentProfileId = ""

    @SubscribeEvent
    fun onTick(event: TickEvent.ClientTickEvent) {
        val thePlayer = Minecraft.getMinecraft().thePlayer ?: return
        thePlayer.worldObj ?: return

        if (nextApiCallTime != -1L && System.currentTimeMillis() > nextApiCallTime) {
            nextApiCallTime = System.currentTimeMillis() + 60_000 * 5
            SkyHanniMod.coroutineScope.launch {
                val apiKey = SkyHanniMod.feature.hidden.apiKey
                val uuid = Minecraft.getMinecraft().thePlayer.uniqueID.toDashlessUUID()
                loadProfileData(apiKey, uuid, currentProfileId)
            }
        }
    }

    @SubscribeEvent
    fun onStatusBar(event: LorenzChatEvent) {
        val message = event.message
        if (message.startsWith("§aYour new API key is §r§b")) {
            SkyHanniMod.feature.hidden.apiKey = message.substring(26)
            LorenzUtils.chat("§b[SkyHanni] A new API Key has been detected and installed")

            if (currentProfileName != "") {
                updateApiData()
            }
        }
    }

    @SubscribeEvent
    fun onProfileJoin(event: ProfileJoinEvent) {
        currentProfileName = event.name
        updateApiData()
    }

    private suspend fun tryUpdateProfileDataAndVerifyKey(apiKey: String): Boolean {
        val uuid = Minecraft.getMinecraft().thePlayer.uniqueID.toDashlessUUID()
        val url = "https://api.hypixel.net/player?key=$apiKey&uuid=$uuid"
        val jsonObject = withContext(Dispatchers.IO) { APIUtil.getJSONResponse(url) }

        if (jsonObject["success"]?.asBoolean == false) {
            if (jsonObject["throttle"]?.asBoolean == true) return true // 429 Too Many Requests does not make an invalid key.
            val cause = jsonObject["cause"].asString
            if (cause == "Invalid API key") {
                return false
            } else {
                throw RuntimeException("API error for url '$url': $cause")
            }
        }
        val player = jsonObject["player"].asJsonObject
        val stats = player["stats"].asJsonObject
        val skyBlock = stats["SkyBlock"].asJsonObject
        val profiles = skyBlock["profiles"].asJsonObject
        for (entry in profiles.entrySet()) {
            val asJsonObject = entry.value.asJsonObject
            val name = asJsonObject["cute_name"].asString
            if (currentProfileName == name.lowercase()) {
                currentProfileId = asJsonObject["profile_id"].asString
                loadProfileData(apiKey, uuid, currentProfileId)
            }
        }
        return true
    }

    private fun updateApiData() {
        nextApiCallTime = -1
        SkyHanniMod.coroutineScope.launch {
            val oldApiKey = SkyHanniMod.feature.hidden.apiKey
            if (oldApiKey.isNotEmpty() && tryUpdateProfileDataAndVerifyKey(oldApiKey)) {
                return@launch
            }
            findApiCandidatesFromOtherMods().forEach { (modName, newApiKey) ->
                if (tryUpdateProfileDataAndVerifyKey(newApiKey)) {
                    SkyHanniMod.feature.hidden.apiKey = newApiKey
                    LorenzUtils.chat("§e[SkyHanni] Imported valid new API key from $modName.")
                    return@launch
                } else {
                    LorenzUtils.error("§c[SkyHanni] Invalid API key from $modName")
                }
            }
            LorenzUtils.error("§c[SkyHanni] SkyHanni has no API key set. Please run /api new")
        }
    }

    private fun findApiCandidatesFromOtherMods(): Map<String, String> {
        LorenzUtils.consoleLog("Trying to find the API Key from the config of other mods..")
        val candidates = mutableMapOf<String, String>()
        for (mod in OtherMod.values()) {
            val modName = mod.modName
            val file = File(mod.configPath)
            if (file.exists()) {
                val reader = APIUtil.readFile(file)
                try {
                    val key = mod.readKey(reader).replace("\n", "").replace(" ", "")
                    UUID.fromString(key)
                    candidates[modName] = key
                } catch (e: Throwable) {
                    LorenzUtils.consoleLog("- $modName: wrong config format! (" + e.message + ")")
                    continue
                }
            } else {
                LorenzUtils.consoleLog("- $modName: no config found!")
            }
        }
        return candidates
    }

    private suspend fun loadProfileData(apiKey: String, playerUuid: String, profileId: String) {
        val url = "https://api.hypixel.net/skyblock/profile?key=$apiKey&profile=$profileId"

        val jsonObject = withContext(Dispatchers.IO) { APIUtil.getJSONResponse(url) }
        val profile = jsonObject["profile"]?.asJsonObject ?: return
        val members = profile["members"]?.asJsonObject ?: return
        for (entry in members.entrySet()) {
            if (entry.key == playerUuid) {
                val profileData = entry.value.asJsonObject
                ProfileApiDataLoadedEvent(profileData).postAndCatch()
                nextApiCallTime = System.currentTimeMillis() + 60_000 * 3
            }
        }
    }
}