mirror of
				https://github.com/mihonapp/mihon.git
				synced 2025-11-04 16:18:55 +01:00 
			
		
		
		
	MAL API Workaround (#1647)
* Mal API workaround * remove unused import * Reuse existing token preference * Minor code format
This commit is contained in:
		@@ -60,12 +60,11 @@ abstract class TrackService(val id: Int) {
 | 
			
		||||
        get() = !getUsername().isEmpty() &&
 | 
			
		||||
                !getPassword().isEmpty()
 | 
			
		||||
 | 
			
		||||
    fun getUsername() = preferences.trackUsername(this)
 | 
			
		||||
    fun getUsername() = preferences.trackUsername(this)!!
 | 
			
		||||
 | 
			
		||||
    fun getPassword() = preferences.trackPassword(this)
 | 
			
		||||
    fun getPassword() = preferences.trackPassword(this)!!
 | 
			
		||||
 | 
			
		||||
    fun saveCredentials(username: String, password: String) {
 | 
			
		||||
        preferences.setTrackCredentials(this, username, password)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -4,10 +4,12 @@ import android.content.Context
 | 
			
		||||
import android.graphics.Color
 | 
			
		||||
import eu.kanade.tachiyomi.R
 | 
			
		||||
import eu.kanade.tachiyomi.data.database.models.Track
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.model.TrackSearch
 | 
			
		||||
import eu.kanade.tachiyomi.data.preference.getOrDefault
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.TrackService
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.model.TrackSearch
 | 
			
		||||
import rx.Completable
 | 
			
		||||
import rx.Observable
 | 
			
		||||
import java.net.URI
 | 
			
		||||
 | 
			
		||||
class Myanimelist(private val context: Context, id: Int) : TrackService(id) {
 | 
			
		||||
 | 
			
		||||
@@ -21,9 +23,13 @@ class Myanimelist(private val context: Context, id: Int) : TrackService(id) {
 | 
			
		||||
 | 
			
		||||
        const val DEFAULT_STATUS = READING
 | 
			
		||||
        const val DEFAULT_SCORE = 0
 | 
			
		||||
 | 
			
		||||
        const val BASE_URL = "https://myanimelist.net"
 | 
			
		||||
        const val USER_SESSION_COOKIE = "MALSESSIONID"
 | 
			
		||||
        const val LOGGED_IN_COOKIE = "is_logged_in"
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private val api by lazy { MyanimelistApi(client, getUsername(), getPassword()) }
 | 
			
		||||
    private val api by lazy { MyanimelistApi(client) }
 | 
			
		||||
 | 
			
		||||
    override val name: String
 | 
			
		||||
        get() = "MyAnimeList"
 | 
			
		||||
@@ -56,7 +62,7 @@ class Myanimelist(private val context: Context, id: Int) : TrackService(id) {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun add(track: Track): Observable<Track> {
 | 
			
		||||
        return api.addLibManga(track)
 | 
			
		||||
        return api.addLibManga(track, getCSRF())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun update(track: Track): Observable<Track> {
 | 
			
		||||
@@ -64,11 +70,11 @@ class Myanimelist(private val context: Context, id: Int) : TrackService(id) {
 | 
			
		||||
            track.status = COMPLETED
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return api.updateLibManga(track)
 | 
			
		||||
        return api.updateLibManga(track, getCSRF())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun bind(track: Track): Observable<Track> {
 | 
			
		||||
        return api.findLibManga(track, getUsername())
 | 
			
		||||
        return api.findLibManga(track, getCSRF())
 | 
			
		||||
                .flatMap { remoteTrack ->
 | 
			
		||||
                    if (remoteTrack != null) {
 | 
			
		||||
                        track.copyPersonalFrom(remoteTrack)
 | 
			
		||||
@@ -83,11 +89,11 @@ class Myanimelist(private val context: Context, id: Int) : TrackService(id) {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun search(query: String): Observable<List<TrackSearch>> {
 | 
			
		||||
        return api.search(query, getUsername())
 | 
			
		||||
        return api.search(query)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun refresh(track: Track): Observable<Track> {
 | 
			
		||||
        return api.getLibManga(track, getUsername())
 | 
			
		||||
        return api.getLibManga(track, getCSRF())
 | 
			
		||||
                .map { remoteTrack ->
 | 
			
		||||
                    track.copyPersonalFrom(remoteTrack)
 | 
			
		||||
                    track.total_chapters = remoteTrack.total_chapters
 | 
			
		||||
@@ -96,10 +102,40 @@ class Myanimelist(private val context: Context, id: Int) : TrackService(id) {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun login(username: String, password: String): Completable {
 | 
			
		||||
        logout()
 | 
			
		||||
 | 
			
		||||
        return api.login(username, password)
 | 
			
		||||
                .doOnNext { csrf -> saveCSRF(csrf) }
 | 
			
		||||
                .doOnNext { saveCredentials(username, password) }
 | 
			
		||||
                .doOnError { logout() }
 | 
			
		||||
                .toCompletable()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun logout() {
 | 
			
		||||
        super.logout()
 | 
			
		||||
        preferences.trackToken(this).delete()
 | 
			
		||||
        networkService.cookies.remove(URI(BASE_URL))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override val isLogged: Boolean
 | 
			
		||||
        get() = !getUsername().isEmpty() &&
 | 
			
		||||
                !getPassword().isEmpty() &&
 | 
			
		||||
                checkCookies(URI(BASE_URL)) &&
 | 
			
		||||
                !getCSRF().isEmpty()
 | 
			
		||||
 | 
			
		||||
    private fun getCSRF(): String = preferences.trackToken(this).getOrDefault()
 | 
			
		||||
 | 
			
		||||
    private fun saveCSRF(csrf: String) = preferences.trackToken(this).set(csrf)
 | 
			
		||||
 | 
			
		||||
    private fun checkCookies(uri: URI): Boolean {
 | 
			
		||||
        var ckCount = 0
 | 
			
		||||
 | 
			
		||||
        for (ck in networkService.cookies.get(uri)) {
 | 
			
		||||
            if (ck.name() == USER_SESSION_COOKIE || ck.name() == LOGGED_IN_COOKIE)
 | 
			
		||||
                ckCount++
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return ckCount == 2
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,6 @@
 | 
			
		||||
package eu.kanade.tachiyomi.data.track.myanimelist
 | 
			
		||||
 | 
			
		||||
import android.net.Uri
 | 
			
		||||
import android.util.Xml
 | 
			
		||||
import eu.kanade.tachiyomi.data.database.models.Track
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.TrackManager
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.model.TrackSearch
 | 
			
		||||
@@ -12,191 +11,266 @@ import eu.kanade.tachiyomi.network.asObservableSuccess
 | 
			
		||||
import eu.kanade.tachiyomi.util.selectInt
 | 
			
		||||
import eu.kanade.tachiyomi.util.selectText
 | 
			
		||||
import okhttp3.*
 | 
			
		||||
import org.json.JSONObject
 | 
			
		||||
import org.jsoup.Jsoup
 | 
			
		||||
import org.jsoup.nodes.Document
 | 
			
		||||
import org.jsoup.nodes.Element
 | 
			
		||||
import org.jsoup.parser.Parser
 | 
			
		||||
import org.xmlpull.v1.XmlSerializer
 | 
			
		||||
import rx.Observable
 | 
			
		||||
import java.io.StringWriter
 | 
			
		||||
import java.io.BufferedReader
 | 
			
		||||
import java.io.InputStreamReader
 | 
			
		||||
import java.util.zip.GZIPInputStream
 | 
			
		||||
 | 
			
		||||
class MyanimelistApi(private val client: OkHttpClient, username: String, password: String) {
 | 
			
		||||
 | 
			
		||||
    private var headers = createHeaders(username, password)
 | 
			
		||||
class MyanimelistApi(private val client: OkHttpClient) {
 | 
			
		||||
 | 
			
		||||
    fun addLibManga(track: Track): Observable<Track> {
 | 
			
		||||
    fun addLibManga(track: Track, csrf: String): Observable<Track> {
 | 
			
		||||
        return Observable.defer {
 | 
			
		||||
            client.newCall(POST(getAddUrl(track), headers, getMangaPostPayload(track)))
 | 
			
		||||
            client.newCall(POST(url = getAddUrl(), body = getMangaPostPayload(track, csrf)))
 | 
			
		||||
                    .asObservableSuccess()
 | 
			
		||||
                    .map { track }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun updateLibManga(track: Track): Observable<Track> {
 | 
			
		||||
    fun updateLibManga(track: Track, csrf: String): Observable<Track> {
 | 
			
		||||
        return Observable.defer {
 | 
			
		||||
            client.newCall(POST(getUpdateUrl(track), headers, getMangaPostPayload(track)))
 | 
			
		||||
            client.newCall(POST(url = getUpdateUrl(), body = getMangaPostPayload(track, csrf)))
 | 
			
		||||
                    .asObservableSuccess()
 | 
			
		||||
                    .map { track }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun search(query: String, username: String): Observable<List<TrackSearch>> {
 | 
			
		||||
        return if (query.startsWith(PREFIX_MY)) {
 | 
			
		||||
            val realQuery = query.substring(PREFIX_MY.length).toLowerCase().trim()
 | 
			
		||||
            getList(username)
 | 
			
		||||
                    .flatMap { Observable.from(it) }
 | 
			
		||||
                    .filter { realQuery in it.title.toLowerCase() }
 | 
			
		||||
                    .toList()
 | 
			
		||||
        } else {
 | 
			
		||||
            client.newCall(GET(getSearchUrl(query), headers))
 | 
			
		||||
                    .asObservable()
 | 
			
		||||
                    .map { Jsoup.parse(Parser.unescapeEntities(it.body()!!.string(), false), "", Parser.xmlParser()) }
 | 
			
		||||
                    .flatMap { Observable.from(it.select("entry")) }
 | 
			
		||||
                    .filter { it.select("type").text() != "Novel" }
 | 
			
		||||
                    .map {
 | 
			
		||||
                        TrackSearch.create(TrackManager.MYANIMELIST).apply {
 | 
			
		||||
                            title = it.selectText("title")!!
 | 
			
		||||
                            media_id = it.selectInt("id")
 | 
			
		||||
                            total_chapters = it.selectInt("chapters")
 | 
			
		||||
                            summary = it.selectText("synopsis")!!
 | 
			
		||||
                            cover_url = it.selectText("image")!!
 | 
			
		||||
                            tracking_url = MyanimelistApi.mangaUrl(media_id)
 | 
			
		||||
                            publishing_status = it.selectText("status")!!
 | 
			
		||||
                            publishing_type = it.selectText("type")!!
 | 
			
		||||
                            start_date = it.selectText("start_date")!!
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    .toList()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun getList(username: String): Observable<List<TrackSearch>> {
 | 
			
		||||
        return client
 | 
			
		||||
                .newCall(GET(getListUrl(username), headers))
 | 
			
		||||
    fun search(query: String): Observable<List<TrackSearch>> {
 | 
			
		||||
        return client.newCall(GET(getSearchUrl(query)))
 | 
			
		||||
                .asObservable()
 | 
			
		||||
                .map { Jsoup.parse(Parser.unescapeEntities(it.body()!!.string(), false), "", Parser.xmlParser()) }
 | 
			
		||||
                .flatMap { Observable.from(it.select("manga")) }
 | 
			
		||||
                .map {
 | 
			
		||||
                .flatMap { response ->
 | 
			
		||||
                    Observable.from(Jsoup.parse(response.consumeBody())
 | 
			
		||||
                            .select("div.js-categories-seasonal.js-block-list.list")
 | 
			
		||||
                            .select("table").select("tbody")
 | 
			
		||||
                            .select("tr").drop(1))
 | 
			
		||||
                }
 | 
			
		||||
                .filter { row ->
 | 
			
		||||
                    row.select(TD)[2].text() != "Novel"
 | 
			
		||||
                }
 | 
			
		||||
                .map { row ->
 | 
			
		||||
                    TrackSearch.create(TrackManager.MYANIMELIST).apply {
 | 
			
		||||
                        title = it.selectText("series_title")!!
 | 
			
		||||
                        media_id = it.selectInt("series_mangadb_id")
 | 
			
		||||
                        last_chapter_read = it.selectInt("my_read_chapters")
 | 
			
		||||
                        status = it.selectInt("my_status")
 | 
			
		||||
                        score = it.selectInt("my_score").toFloat()
 | 
			
		||||
                        total_chapters = it.selectInt("series_chapters")
 | 
			
		||||
                        cover_url = it.selectText("series_image")!!
 | 
			
		||||
                        tracking_url = MyanimelistApi.mangaUrl(media_id)
 | 
			
		||||
                        title = row.searchTitle()
 | 
			
		||||
                        media_id = row.searchMediaId()
 | 
			
		||||
                        total_chapters = row.searchTotalChapters()
 | 
			
		||||
                        summary = row.searchSummary()
 | 
			
		||||
                        cover_url = row.searchCoverUrl()
 | 
			
		||||
                        tracking_url = mangaUrl(media_id)
 | 
			
		||||
                        publishing_status = row.searchPublishingStatus()
 | 
			
		||||
                        publishing_type = row.searchPublishingType()
 | 
			
		||||
                        start_date = row.searchStartDate()
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                .toList()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun findLibManga(track: Track, username: String): Observable<Track?> {
 | 
			
		||||
        return getList(username)
 | 
			
		||||
    private fun getList(csrf: String): Observable<List<TrackSearch>> {
 | 
			
		||||
        return getListUrl(csrf)
 | 
			
		||||
                .flatMap { url ->
 | 
			
		||||
                    getListXml(url)
 | 
			
		||||
                }
 | 
			
		||||
                .flatMap { doc ->
 | 
			
		||||
                    Observable.from(doc.select("manga"))
 | 
			
		||||
                }
 | 
			
		||||
                .map { it ->
 | 
			
		||||
                    TrackSearch.create(TrackManager.MYANIMELIST).apply {
 | 
			
		||||
                        title = it.selectText("manga_title")!!
 | 
			
		||||
                        media_id = it.selectInt("manga_mangadb_id")
 | 
			
		||||
                        last_chapter_read = it.selectInt("my_read_chapters")
 | 
			
		||||
                        status = getStatus(it.selectText("my_status")!!)
 | 
			
		||||
                        score = it.selectInt("my_score").toFloat()
 | 
			
		||||
                        total_chapters = it.selectInt("manga_chapters")
 | 
			
		||||
                        tracking_url = mangaUrl(media_id)
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                .toList()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun getListXml(url: String): Observable<Document> {
 | 
			
		||||
        return client.newCall(GET(url))
 | 
			
		||||
                .asObservable()
 | 
			
		||||
                .map { response ->
 | 
			
		||||
                    Jsoup.parse(response.consumeXmlBody(), "", Parser.xmlParser())
 | 
			
		||||
                }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun findLibManga(track: Track, csrf: String): Observable<Track?> {
 | 
			
		||||
        return getList(csrf)
 | 
			
		||||
                .map { list -> list.find { it.media_id == track.media_id } }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun getLibManga(track: Track, username: String): Observable<Track> {
 | 
			
		||||
        return findLibManga(track, username)
 | 
			
		||||
    fun getLibManga(track: Track, csrf: String): Observable<Track> {
 | 
			
		||||
        return findLibManga(track, csrf)
 | 
			
		||||
                .map { it ?: throw Exception("Could not find manga") }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun login(username: String, password: String): Observable<Response> {
 | 
			
		||||
        headers = createHeaders(username, password)
 | 
			
		||||
        return client.newCall(GET(getLoginUrl(), headers))
 | 
			
		||||
                .asObservable()
 | 
			
		||||
                .doOnNext { response ->
 | 
			
		||||
                    response.close()
 | 
			
		||||
                    if (response.code() != 200) throw Exception("Login error")
 | 
			
		||||
    fun login(username: String, password: String): Observable<String> {
 | 
			
		||||
        return getSessionInfo()
 | 
			
		||||
                .flatMap { csrf ->
 | 
			
		||||
                    login(username, password, csrf)
 | 
			
		||||
                }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun getMangaPostPayload(track: Track): RequestBody {
 | 
			
		||||
        val data = xml {
 | 
			
		||||
            element(ENTRY_TAG) {
 | 
			
		||||
                if (track.last_chapter_read != 0) {
 | 
			
		||||
                    text(CHAPTER_TAG, track.last_chapter_read.toString())
 | 
			
		||||
    private fun getSessionInfo(): Observable<String> {
 | 
			
		||||
        return client.newCall(GET(getLoginUrl()))
 | 
			
		||||
                .asObservable()
 | 
			
		||||
                .map { response ->
 | 
			
		||||
                    Jsoup.parse(response.consumeBody())
 | 
			
		||||
                            .select("meta[name=csrf_token]")
 | 
			
		||||
                            .attr("content")
 | 
			
		||||
                }
 | 
			
		||||
                text(STATUS_TAG, track.status.toString())
 | 
			
		||||
                text(SCORE_TAG, track.score.toString())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun login(username: String, password: String, csrf: String): Observable<String> {
 | 
			
		||||
        return client.newCall(POST(url = getLoginUrl(), body = getLoginPostBody(username, password, csrf)))
 | 
			
		||||
                .asObservable()
 | 
			
		||||
                .map { response ->
 | 
			
		||||
                    response.use {
 | 
			
		||||
                        if (response.priorResponse()?.code() != 302) throw Exception("Authentication error")
 | 
			
		||||
                    }
 | 
			
		||||
                    csrf
 | 
			
		||||
                }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun getLoginPostBody(username: String, password: String, csrf: String): RequestBody {
 | 
			
		||||
        return FormBody.Builder()
 | 
			
		||||
                .add("user_name", username)
 | 
			
		||||
                .add("password", password)
 | 
			
		||||
                .add("cookie", "1")
 | 
			
		||||
                .add("sublogin", "Login")
 | 
			
		||||
                .add("submit", "1")
 | 
			
		||||
                .add(CSRF, csrf)
 | 
			
		||||
                .build()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun getExportPostBody(csrf: String): RequestBody {
 | 
			
		||||
        return FormBody.Builder()
 | 
			
		||||
                .add("type", "2")
 | 
			
		||||
                .add("subexport", "Export My List")
 | 
			
		||||
                .add(CSRF, csrf)
 | 
			
		||||
                .build()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun getMangaPostPayload(track: Track, csrf: String): RequestBody {
 | 
			
		||||
        val body = JSONObject()
 | 
			
		||||
                .put("manga_id", track.media_id)
 | 
			
		||||
                .put("status", track.status)
 | 
			
		||||
                .put("score", track.score)
 | 
			
		||||
                .put("num_read_chapters", track.last_chapter_read)
 | 
			
		||||
                .put(CSRF, csrf)
 | 
			
		||||
 | 
			
		||||
        return RequestBody.create(MediaType.parse("application/json; charset=utf-8"), body.toString())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun getLoginUrl() = Uri.parse(baseUrl).buildUpon()
 | 
			
		||||
            .appendPath("login.php")
 | 
			
		||||
            .toString()
 | 
			
		||||
 | 
			
		||||
    private fun getSearchUrl(query: String): String {
 | 
			
		||||
        val col = "c[]"
 | 
			
		||||
        return Uri.parse(baseUrl).buildUpon()
 | 
			
		||||
                .appendPath("manga.php")
 | 
			
		||||
                .appendQueryParameter("q", query)
 | 
			
		||||
                .appendQueryParameter(col, "a")
 | 
			
		||||
                .appendQueryParameter(col, "b")
 | 
			
		||||
                .appendQueryParameter(col, "c")
 | 
			
		||||
                .appendQueryParameter(col, "d")
 | 
			
		||||
                .appendQueryParameter(col, "e")
 | 
			
		||||
                .appendQueryParameter(col, "g")
 | 
			
		||||
                .toString()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun getExportListUrl() = Uri.parse(baseUrl).buildUpon()
 | 
			
		||||
            .appendPath("panel.php")
 | 
			
		||||
            .appendQueryParameter("go", "export")
 | 
			
		||||
            .toString()
 | 
			
		||||
 | 
			
		||||
    private fun getListUrl(csrf: String): Observable<String> {
 | 
			
		||||
        return client.newCall(POST(url = getExportListUrl(), body = getExportPostBody(csrf)))
 | 
			
		||||
                .asObservable()
 | 
			
		||||
                .map {response ->
 | 
			
		||||
                    baseUrl + Jsoup.parse(response.consumeBody())
 | 
			
		||||
                            .select("div.goodresult")
 | 
			
		||||
                            .select("a")
 | 
			
		||||
                            .attr("href")
 | 
			
		||||
                }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun getUpdateUrl() = Uri.parse(baseModifyListUrl).buildUpon()
 | 
			
		||||
            .appendPath("edit.json")
 | 
			
		||||
            .toString()
 | 
			
		||||
 | 
			
		||||
    private fun getAddUrl() = Uri.parse(baseModifyListUrl).buildUpon()
 | 
			
		||||
            .appendPath( "add.json")
 | 
			
		||||
            .toString()
 | 
			
		||||
    
 | 
			
		||||
    private fun Response.consumeBody(): String? {
 | 
			
		||||
        use {
 | 
			
		||||
            if (it.code() != 200) throw Exception("Login error")
 | 
			
		||||
            return it.body()?.string()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun Response.consumeXmlBody(): String? {
 | 
			
		||||
        use { res ->
 | 
			
		||||
            if (res.code() != 200) throw Exception("Export list error")
 | 
			
		||||
            BufferedReader(InputStreamReader(GZIPInputStream(res.body()?.source()?.inputStream()))).use { reader ->
 | 
			
		||||
                val sb = StringBuilder()
 | 
			
		||||
                reader.forEachLine { line ->
 | 
			
		||||
                    sb.append(line)
 | 
			
		||||
                }
 | 
			
		||||
                return sb.toString()
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return FormBody.Builder()
 | 
			
		||||
                .add("data", data)
 | 
			
		||||
                .build()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private inline fun xml(block: XmlSerializer.() -> Unit): String {
 | 
			
		||||
        val x = Xml.newSerializer()
 | 
			
		||||
        val writer = StringWriter()
 | 
			
		||||
 | 
			
		||||
        with(x) {
 | 
			
		||||
            setOutput(writer)
 | 
			
		||||
            startDocument("UTF-8", false)
 | 
			
		||||
            block()
 | 
			
		||||
            endDocument()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return writer.toString()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private inline fun XmlSerializer.element(tag: String, block: XmlSerializer.() -> Unit) {
 | 
			
		||||
        startTag("", tag)
 | 
			
		||||
        block()
 | 
			
		||||
        endTag("", tag)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun XmlSerializer.text(tag: String, body: String) {
 | 
			
		||||
        startTag("", tag)
 | 
			
		||||
        text(body)
 | 
			
		||||
        endTag("", tag)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun getLoginUrl() = Uri.parse(baseUrl).buildUpon()
 | 
			
		||||
            .appendEncodedPath("api/account/verify_credentials.xml")
 | 
			
		||||
            .toString()
 | 
			
		||||
 | 
			
		||||
    fun getSearchUrl(query: String) = Uri.parse(baseUrl).buildUpon()
 | 
			
		||||
            .appendEncodedPath("api/manga/search.xml")
 | 
			
		||||
            .appendQueryParameter("q", query)
 | 
			
		||||
            .toString()
 | 
			
		||||
 | 
			
		||||
    fun getListUrl(username: String) = Uri.parse(baseUrl).buildUpon()
 | 
			
		||||
            .appendPath("malappinfo.php")
 | 
			
		||||
            .appendQueryParameter("u", username)
 | 
			
		||||
            .appendQueryParameter("status", "all")
 | 
			
		||||
            .appendQueryParameter("type", "manga")
 | 
			
		||||
            .toString()
 | 
			
		||||
 | 
			
		||||
    fun getUpdateUrl(track: Track) = Uri.parse(baseUrl).buildUpon()
 | 
			
		||||
            .appendEncodedPath("api/mangalist/update")
 | 
			
		||||
            .appendPath("${track.media_id}.xml")
 | 
			
		||||
            .toString()
 | 
			
		||||
 | 
			
		||||
    fun getAddUrl(track: Track) = Uri.parse(baseUrl).buildUpon()
 | 
			
		||||
            .appendEncodedPath("api/mangalist/add")
 | 
			
		||||
            .appendPath("${track.media_id}.xml")
 | 
			
		||||
            .toString()
 | 
			
		||||
 | 
			
		||||
    fun createHeaders(username: String, password: String): Headers {
 | 
			
		||||
        return Headers.Builder()
 | 
			
		||||
                .add("Authorization", Credentials.basic(username, password))
 | 
			
		||||
                .add("User-Agent", "api-indiv-9F93C52A963974CF674325391990191C")
 | 
			
		||||
                .build()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
        const val baseUrl = "https://myanimelist.net"
 | 
			
		||||
        const val baseMangaUrl = baseUrl + "/manga/"
 | 
			
		||||
        private const val baseMangaUrl = "$baseUrl/manga/"
 | 
			
		||||
        private const val baseModifyListUrl = "$baseUrl/ownlist/manga"
 | 
			
		||||
 | 
			
		||||
        fun mangaUrl(remoteId: Int): String {
 | 
			
		||||
            return baseMangaUrl + remoteId
 | 
			
		||||
        }
 | 
			
		||||
        fun mangaUrl(remoteId: Int) = baseMangaUrl + remoteId
 | 
			
		||||
 | 
			
		||||
        private val ENTRY_TAG = "entry"
 | 
			
		||||
        private val CHAPTER_TAG = "chapter"
 | 
			
		||||
        private val SCORE_TAG = "score"
 | 
			
		||||
        private val STATUS_TAG = "status"
 | 
			
		||||
        fun Element.searchTitle() = select("strong").text()!!
 | 
			
		||||
 | 
			
		||||
        const val PREFIX_MY = "my:"
 | 
			
		||||
        fun Element.searchTotalChapters() = if (select(TD)[4].text() == "-") 0 else select(TD)[4].text().toInt()
 | 
			
		||||
 | 
			
		||||
        fun Element.searchCoverUrl() = select("img")
 | 
			
		||||
                .attr("data-src")
 | 
			
		||||
                .split("\\?")[0]
 | 
			
		||||
                .replace("/r/50x70/", "/")
 | 
			
		||||
 | 
			
		||||
        fun Element.searchMediaId() = select("div.picSurround")
 | 
			
		||||
                .select("a").attr("id")
 | 
			
		||||
                .replace("sarea", "")
 | 
			
		||||
                .toInt()
 | 
			
		||||
 | 
			
		||||
        fun Element.searchSummary() = select("div.pt4")
 | 
			
		||||
                .first()
 | 
			
		||||
                .ownText()!!
 | 
			
		||||
 | 
			
		||||
        fun Element.searchPublishingStatus() = if (select(TD).last().text() == "-") PUBLISHING else FINISHED
 | 
			
		||||
 | 
			
		||||
        fun Element.searchPublishingType() = select(TD)[2].text()!!
 | 
			
		||||
 | 
			
		||||
        fun Element.searchStartDate() = select(TD)[6].text()!!
 | 
			
		||||
 | 
			
		||||
        fun getStatus(status: String) = when (status) {
 | 
			
		||||
            "Reading" -> 1
 | 
			
		||||
            "Completed" -> 2
 | 
			
		||||
            "On-Hold" -> 3
 | 
			
		||||
            "Dropped" -> 4
 | 
			
		||||
            "Plan to Read" -> 6
 | 
			
		||||
            else -> 1
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        const val CSRF = "csrf_token"
 | 
			
		||||
        const val TD = "td"
 | 
			
		||||
        private const val FINISHED = "Finished"
 | 
			
		||||
        private const val PUBLISHING = "Publishing"
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -60,6 +60,11 @@ class PersistentCookieStore(context: Context) {
 | 
			
		||||
        cookieMap.clear()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun remove(uri: URI) {
 | 
			
		||||
        prefs.edit().remove(uri.host).apply()
 | 
			
		||||
        cookieMap.remove(uri.host)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun get(url: HttpUrl) = get(url.uri().host)
 | 
			
		||||
 | 
			
		||||
    fun get(uri: URI) = get(uri.host)
 | 
			
		||||
 
 | 
			
		||||
@@ -46,7 +46,6 @@ class TrackLoginDialog(bundle: Bundle? = null) : LoginDialogPreference(bundle) {
 | 
			
		||||
                        login.setText(R.string.unknown_error)
 | 
			
		||||
                        error.message?.let { context.toast(it) }
 | 
			
		||||
                    })
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user