Initial commit
This commit is contained in:
commit
21bfbfb139
520 changed files with 47819 additions and 0 deletions
52
app/src/main/java/ani/dantotsu/connections/mal/Login.kt
Normal file
52
app/src/main/java/ani/dantotsu/connections/mal/Login.kt
Normal file
|
@ -0,0 +1,52 @@
|
|||
package ani.dantotsu.connections.mal
|
||||
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import ani.dantotsu.*
|
||||
import ani.dantotsu.connections.mal.MAL.clientId
|
||||
import ani.dantotsu.connections.mal.MAL.saveResponse
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class Login : AppCompatActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
try {
|
||||
val data: Uri = intent?.data
|
||||
?: throw Exception(getString(R.string.mal_login_uri_not_found))
|
||||
val codeChallenge: String = loadData("malCodeChallenge", this)
|
||||
?: throw Exception(getString(R.string.mal_login_code_challenge_not_found))
|
||||
val code = data.getQueryParameter("code")
|
||||
?: throw Exception(getString(R.string.mal_login_code_not_present))
|
||||
|
||||
snackString(getString(R.string.logging_in_mal))
|
||||
lifecycleScope.launch(Dispatchers.IO) {
|
||||
tryWithSuspend(true) {
|
||||
val res = client.post(
|
||||
"https://myanimelist.net/v1/oauth2/token",
|
||||
data = mapOf(
|
||||
"client_id" to clientId,
|
||||
"code" to code,
|
||||
"code_verifier" to codeChallenge,
|
||||
"grant_type" to "authorization_code"
|
||||
)
|
||||
).parsed<MAL.ResponseToken>()
|
||||
saveResponse(res)
|
||||
MAL.token = res.accessToken
|
||||
snackString(getString(R.string.getting_user_data))
|
||||
MAL.query.getUserData()
|
||||
launch(Dispatchers.Main) {
|
||||
startMainActivity(this@Login)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (e:Exception){
|
||||
logError(e,snackbar = false)
|
||||
startMainActivity(this)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
99
app/src/main/java/ani/dantotsu/connections/mal/MAL.kt
Normal file
99
app/src/main/java/ani/dantotsu/connections/mal/MAL.kt
Normal file
|
@ -0,0 +1,99 @@
|
|||
package ani.dantotsu.connections.mal
|
||||
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.util.Base64
|
||||
import androidx.browser.customtabs.CustomTabsIntent
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import ani.dantotsu.*
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import java.io.File
|
||||
import java.security.SecureRandom
|
||||
|
||||
object MAL {
|
||||
val query: MALQueries = MALQueries()
|
||||
const val clientId = "86b35cf02205a0303da3aaea1c9e33f3"
|
||||
var username: String? = null
|
||||
var avatar: String? = null
|
||||
var token: String? = null
|
||||
var userid: Int? = null
|
||||
|
||||
fun loginIntent(context: Context) {
|
||||
val codeVerifierBytes = ByteArray(96)
|
||||
SecureRandom().nextBytes(codeVerifierBytes)
|
||||
val codeChallenge = Base64.encodeToString(codeVerifierBytes, Base64.DEFAULT).trimEnd('=')
|
||||
.replace("+", "-")
|
||||
.replace("/", "_")
|
||||
.replace("\n", "")
|
||||
|
||||
saveData("malCodeChallenge", codeChallenge, context)
|
||||
val request =
|
||||
"https://myanimelist.net/v1/oauth2/authorize?response_type=code&client_id=$clientId&code_challenge=$codeChallenge"
|
||||
try {
|
||||
CustomTabsIntent.Builder().build().launchUrl(
|
||||
context,
|
||||
Uri.parse(request)
|
||||
)
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
openLinkInBrowser(request)
|
||||
}
|
||||
}
|
||||
|
||||
private const val MAL_TOKEN = "malToken"
|
||||
|
||||
private suspend fun refreshToken(): ResponseToken? {
|
||||
return tryWithSuspend {
|
||||
val token = loadData<ResponseToken>(MAL_TOKEN)
|
||||
?: throw Exception(currContext()?.getString(R.string.refresh_token_load_failed))
|
||||
val res = client.post(
|
||||
"https://myanimelist.net/v1/oauth2/token",
|
||||
data = mapOf(
|
||||
"client_id" to clientId,
|
||||
"grant_type" to "refresh_token",
|
||||
"refresh_token" to token.refreshToken
|
||||
)
|
||||
).parsed<ResponseToken>()
|
||||
saveResponse(res)
|
||||
return@tryWithSuspend res
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
suspend fun getSavedToken(context: FragmentActivity): Boolean {
|
||||
return tryWithSuspend(false) {
|
||||
var res: ResponseToken = loadData(MAL_TOKEN, context)
|
||||
?: return@tryWithSuspend false
|
||||
if (System.currentTimeMillis() > res.expiresIn)
|
||||
res = refreshToken()
|
||||
?: throw Exception(currContext()?.getString(R.string.refreshing_token_failed))
|
||||
token = res.accessToken
|
||||
return@tryWithSuspend true
|
||||
} ?: false
|
||||
}
|
||||
|
||||
fun removeSavedToken(context: Context) {
|
||||
token = null
|
||||
username = null
|
||||
userid = null
|
||||
avatar = null
|
||||
if (MAL_TOKEN in context.fileList()) {
|
||||
File(context.filesDir, MAL_TOKEN).delete()
|
||||
}
|
||||
}
|
||||
|
||||
fun saveResponse(res: ResponseToken) {
|
||||
res.expiresIn += System.currentTimeMillis()
|
||||
saveData(MAL_TOKEN, res)
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class ResponseToken(
|
||||
@SerialName("token_type") val tokenType: String,
|
||||
@SerialName("expires_in") var expiresIn: Long,
|
||||
@SerialName("access_token") val accessToken: String,
|
||||
@SerialName("refresh_token") val refreshToken: String,
|
||||
): java.io.Serializable
|
||||
|
||||
}
|
90
app/src/main/java/ani/dantotsu/connections/mal/MALQueries.kt
Normal file
90
app/src/main/java/ani/dantotsu/connections/mal/MALQueries.kt
Normal file
|
@ -0,0 +1,90 @@
|
|||
package ani.dantotsu.connections.mal
|
||||
|
||||
import ani.dantotsu.connections.anilist.api.FuzzyDate
|
||||
import ani.dantotsu.client
|
||||
import ani.dantotsu.tryWithSuspend
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
class MALQueries {
|
||||
private val apiUrl = "https://api.myanimelist.net/v2"
|
||||
private val authHeader: Map<String, String>?
|
||||
get() {
|
||||
return mapOf("Authorization" to "Bearer ${MAL.token ?: return null}")
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class MalUser(
|
||||
val id: Int,
|
||||
val name: String,
|
||||
val picture: String?,
|
||||
)
|
||||
|
||||
suspend fun getUserData(): Boolean {
|
||||
val res = tryWithSuspend {
|
||||
client.get(
|
||||
"$apiUrl/users/@me",
|
||||
authHeader ?: return@tryWithSuspend null
|
||||
).parsed<MalUser>()
|
||||
} ?: return false
|
||||
MAL.userid = res.id
|
||||
MAL.username = res.name
|
||||
MAL.avatar = res.picture
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
suspend fun editList(
|
||||
idMAL: Int?,
|
||||
isAnime: Boolean,
|
||||
progress: Int?,
|
||||
score: Int?,
|
||||
status: String,
|
||||
rewatch: Int? = null,
|
||||
start: FuzzyDate? = null,
|
||||
end: FuzzyDate? = null
|
||||
) {
|
||||
if(idMAL==null) return
|
||||
val data = mutableMapOf("status" to convertStatus(isAnime, status))
|
||||
if (progress != null)
|
||||
data[if (isAnime) "num_watched_episodes" else "num_chapters_read"] = progress.toString()
|
||||
data[if (isAnime) "is_rewatching" else "is_rereading"] = (status == "REPEATING").toString()
|
||||
if (score != null)
|
||||
data["score"] = score.div(10).toString()
|
||||
if(rewatch!=null)
|
||||
data[if(isAnime) "num_times_rewatched" else "num_times_reread"] = rewatch.toString()
|
||||
if(start!=null)
|
||||
data["start_date"] = start.toMALString()
|
||||
if(end!=null)
|
||||
data["finish_date"] = end.toMALString()
|
||||
tryWithSuspend {
|
||||
client.put(
|
||||
"$apiUrl/${if (isAnime) "anime" else "manga"}/$idMAL/my_list_status",
|
||||
authHeader ?: return@tryWithSuspend null,
|
||||
data = data,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun deleteList(isAnime: Boolean, idMAL: Int?){
|
||||
if(idMAL==null) return
|
||||
tryWithSuspend {
|
||||
client.delete(
|
||||
"$apiUrl/${if (isAnime) "anime" else "manga"}/$idMAL/my_list_status",
|
||||
authHeader ?: return@tryWithSuspend null
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun convertStatus(isAnime: Boolean, status: String): String {
|
||||
return when (status) {
|
||||
"PLANNING" -> if (isAnime) "plan_to_watch" else "plan_to_read"
|
||||
"COMPLETED" -> "completed"
|
||||
"PAUSED" -> "on_hold"
|
||||
"DROPPED" -> "dropped"
|
||||
"CURRENT" -> if (isAnime) "watching" else "reading"
|
||||
else -> if (isAnime) "watching" else "reading"
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue