mirror of
				https://github.com/mihonapp/mihon.git
				synced 2025-10-30 22:07:57 +01:00 
			
		
		
		
	Make MAL Tracking Slightly Less Shitty (#2042)
* * fix cookieManager not clearing cookies properly * manually clear tracking prefs when !isLogged (e.g. cookies were cleared) * use full url for removing cookies * add interceptor for all non-login network calls * attempt auto login if cookies are missing * move handling of csrf token to interceptor * * move methods around to improve readability * fix TrackSearchAdapter not updating other fields if cover_url is missing * revert accidental removal of feature in https://github.com/inorichi/tachiyomi/issues/65 * avoid login if credentials are missing * fix eol * *separate login flow from rxjava for reuse in sync * *use less expensive method of finding manga * *move variable declaration * formatting * set total chapters in remote track
This commit is contained in:
		| @@ -10,11 +10,11 @@ import eu.kanade.tachiyomi.data.track.model.TrackSearch | ||||
| import okhttp3.HttpUrl | ||||
| import rx.Completable | ||||
| import rx.Observable | ||||
| import java.lang.Exception | ||||
|  | ||||
| class Myanimelist(private val context: Context, id: Int) : TrackService(id) { | ||||
|  | ||||
|     companion object { | ||||
|  | ||||
|         const val READING = 1 | ||||
|         const val COMPLETED = 2 | ||||
|         const val ON_HOLD = 3 | ||||
| @@ -29,7 +29,8 @@ class Myanimelist(private val context: Context, id: Int) : TrackService(id) { | ||||
|         const val LOGGED_IN_COOKIE = "is_logged_in" | ||||
|     } | ||||
|  | ||||
|     private val api by lazy { MyanimelistApi(client) } | ||||
|     private val interceptor by lazy { MyAnimeListInterceptor(this) } | ||||
|     private val api by lazy { MyanimelistApi(client, interceptor) } | ||||
|  | ||||
|     override val name: String | ||||
|         get() = "MyAnimeList" | ||||
| @@ -62,7 +63,7 @@ class Myanimelist(private val context: Context, id: Int) : TrackService(id) { | ||||
|     } | ||||
|  | ||||
|     override fun add(track: Track): Observable<Track> { | ||||
|         return api.addLibManga(track, getCSRF()) | ||||
|         return api.addLibManga(track) | ||||
|     } | ||||
|  | ||||
|     override fun update(track: Track): Observable<Track> { | ||||
| @@ -70,11 +71,11 @@ class Myanimelist(private val context: Context, id: Int) : TrackService(id) { | ||||
|             track.status = COMPLETED | ||||
|         } | ||||
|  | ||||
|         return api.updateLibManga(track, getCSRF()) | ||||
|         return api.updateLibManga(track) | ||||
|     } | ||||
|  | ||||
|     override fun bind(track: Track): Observable<Track> { | ||||
|         return api.findLibManga(track, getCSRF()) | ||||
|         return api.findLibManga(track) | ||||
|                 .flatMap { remoteTrack -> | ||||
|                     if (remoteTrack != null) { | ||||
|                         track.copyPersonalFrom(remoteTrack) | ||||
| @@ -93,7 +94,7 @@ class Myanimelist(private val context: Context, id: Int) : TrackService(id) { | ||||
|     } | ||||
|  | ||||
|     override fun refresh(track: Track): Observable<Track> { | ||||
|         return api.getLibManga(track, getCSRF()) | ||||
|         return api.getLibManga(track) | ||||
|                 .map { remoteTrack -> | ||||
|                     track.copyPersonalFrom(remoteTrack) | ||||
|                     track.total_chapters = remoteTrack.total_chapters | ||||
| @@ -104,26 +105,44 @@ class Myanimelist(private val context: Context, id: Int) : TrackService(id) { | ||||
|     override fun login(username: String, password: String): Completable { | ||||
|         logout() | ||||
|  | ||||
|         return api.login(username, password) | ||||
|         return Observable.fromCallable { api.login(username, password) } | ||||
|                 .doOnNext { csrf -> saveCSRF(csrf) } | ||||
|                 .doOnNext { saveCredentials(username, password) } | ||||
|                 .doOnError { logout() } | ||||
|                 .toCompletable() | ||||
|     } | ||||
|  | ||||
|     // Attempt to login again if cookies have been cleared but credentials are still filled | ||||
|     fun ensureLoggedIn() { | ||||
|         if (isAuthorized) return | ||||
|         if (!isLogged) throw Exception("MAL Login Credentials not found") | ||||
|  | ||||
|         val username = getUsername() | ||||
|         val password = getPassword() | ||||
|         logout() | ||||
|  | ||||
|         try { | ||||
|             val csrf = api.login(username, password) | ||||
|             saveCSRF(csrf) | ||||
|             saveCredentials(username, password) | ||||
|         } catch (e: Exception) { | ||||
|             logout() | ||||
|             throw e | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     override fun logout() { | ||||
|         super.logout() | ||||
|         preferences.trackToken(this).delete() | ||||
|         networkService.cookieManager.remove(HttpUrl.parse(BASE_URL)!!) | ||||
|     } | ||||
|  | ||||
|     override val isLogged: Boolean | ||||
|         get() = !getUsername().isEmpty() && | ||||
|                 !getPassword().isEmpty() && | ||||
|                 checkCookies() && | ||||
|                 !getCSRF().isEmpty() | ||||
|     val isAuthorized: Boolean | ||||
|         get() = super.isLogged && | ||||
|                 getCSRF().isNotEmpty() && | ||||
|                 checkCookies() | ||||
|  | ||||
|     private fun getCSRF(): String = preferences.trackToken(this).getOrDefault() | ||||
|     fun getCSRF(): String = preferences.trackToken(this).getOrDefault() | ||||
|  | ||||
|     private fun saveCSRF(csrf: String) = preferences.trackToken(this).set(csrf) | ||||
|  | ||||
|   | ||||
| @@ -0,0 +1,49 @@ | ||||
| package eu.kanade.tachiyomi.data.track.myanimelist | ||||
|  | ||||
| import okhttp3.Interceptor | ||||
| import okhttp3.RequestBody | ||||
| import okhttp3.Response | ||||
| import okio.Buffer | ||||
| import org.json.JSONObject | ||||
|  | ||||
| class MyAnimeListInterceptor(private val myanimelist: Myanimelist): Interceptor { | ||||
|  | ||||
|     override fun intercept(chain: Interceptor.Chain): Response { | ||||
|         myanimelist.ensureLoggedIn() | ||||
|  | ||||
|         var request = chain.request() | ||||
|         request.body()?.let { | ||||
|             val contentType = it.contentType().toString() | ||||
|             val updatedBody = when { | ||||
|                 contentType.contains("x-www-form-urlencoded") -> updateFormBody(it) | ||||
|                 contentType.contains("json") -> updateJsonBody(it) | ||||
|                 else -> it | ||||
|             } | ||||
|             request = request.newBuilder().post(updatedBody).build() | ||||
|         } | ||||
|  | ||||
|         return chain.proceed(request) | ||||
|     } | ||||
|  | ||||
|     private fun bodyToString(requestBody: RequestBody): String { | ||||
|         Buffer().use { | ||||
|             requestBody.writeTo(it) | ||||
|             return it.readUtf8() | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private fun updateFormBody(requestBody: RequestBody): RequestBody { | ||||
|         val formString = bodyToString(requestBody) | ||||
|  | ||||
|         return RequestBody.create(requestBody.contentType(), | ||||
|                 "$formString${if (formString.isNotEmpty()) "&" else ""}${MyanimelistApi.CSRF}=${myanimelist.getCSRF()}") | ||||
|     } | ||||
|  | ||||
|     private fun updateJsonBody(requestBody: RequestBody): RequestBody { | ||||
|         val jsonString = bodyToString(requestBody) | ||||
|         val newBody = JSONObject(jsonString) | ||||
|                 .put(MyanimelistApi.CSRF, myanimelist.getCSRF()) | ||||
|  | ||||
|         return RequestBody.create(requestBody.contentType(), newBody.toString()) | ||||
|     } | ||||
| } | ||||
| @@ -22,61 +22,122 @@ import java.io.InputStreamReader | ||||
| import java.util.zip.GZIPInputStream | ||||
|  | ||||
|  | ||||
| class MyanimelistApi(private val client: OkHttpClient) { | ||||
| class MyanimelistApi(private val client: OkHttpClient, interceptor: MyAnimeListInterceptor) { | ||||
|  | ||||
|     fun addLibManga(track: Track, csrf: String): Observable<Track> { | ||||
|         return Observable.defer { | ||||
|             client.newCall(POST(url = getAddUrl(), body = getMangaPostPayload(track, csrf))) | ||||
|                     .asObservableSuccess() | ||||
|                     .map { track } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fun updateLibManga(track: Track, csrf: String): Observable<Track> { | ||||
|         return Observable.defer { | ||||
|             client.newCall(POST(url = getUpdateUrl(), body = getMangaPostPayload(track, csrf))) | ||||
|                     .asObservableSuccess() | ||||
|                     .map { track } | ||||
|         } | ||||
|     } | ||||
|     private val authClient = client.newBuilder().addInterceptor(interceptor).build() | ||||
|  | ||||
|     fun search(query: String): Observable<List<TrackSearch>> { | ||||
|         return client.newCall(GET(getSearchUrl(query))) | ||||
|                 .asObservable() | ||||
|                 .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 = 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() | ||||
|         return if (query.startsWith(PREFIX_MY)) { | ||||
|             val realQuery = query.removePrefix(PREFIX_MY) | ||||
|             getList() | ||||
|                     .flatMap { Observable.from(it) } | ||||
|                     .filter { it.title.contains(realQuery, true) } | ||||
|                     .toList() | ||||
|         } | ||||
|         else { | ||||
|             client.newCall(GET(searchUrl(query))) | ||||
|                     .asObservable() | ||||
|                     .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)) | ||||
|                     } | ||||
|                 } | ||||
|                 .toList() | ||||
|                     .filter { row -> | ||||
|                         row.select(TD)[2].text() != "Novel" | ||||
|                     } | ||||
|                     .map { row -> | ||||
|                         TrackSearch.create(TrackManager.MYANIMELIST).apply { | ||||
|                             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() | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private fun getList(csrf: String): Observable<List<TrackSearch>> { | ||||
|         return getListUrl(csrf) | ||||
|     fun addLibManga(track: Track): Observable<Track> { | ||||
|         return Observable.defer { | ||||
|             authClient.newCall(POST(url = addUrl(), body = mangaPostPayload(track))) | ||||
|                     .asObservableSuccess() | ||||
|                     .map { track } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fun updateLibManga(track: Track): Observable<Track> { | ||||
|         return Observable.defer { | ||||
|             authClient.newCall(POST(url = updateUrl(), body = mangaPostPayload(track))) | ||||
|                     .asObservableSuccess() | ||||
|                     .map { track } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fun findLibManga(track: Track): Observable<Track?> { | ||||
|         return authClient.newCall(GET(url = listEntryUrl(track.media_id))) | ||||
|                 .asObservable() | ||||
|                 .map {response -> | ||||
|                     var libTrack: Track? = null | ||||
|                     response.use { | ||||
|                         if (it.priorResponse()?.isRedirect != true) { | ||||
|                             val trackForm = Jsoup.parse(it.consumeBody()) | ||||
|  | ||||
|                             libTrack = Track.create(TrackManager.MYANIMELIST).apply { | ||||
|                                 last_chapter_read = trackForm.select("#add_manga_num_read_chapters").`val`().toInt() | ||||
|                                 total_chapters = trackForm.select("#totalChap").text().toInt() | ||||
|                                 status = trackForm.select("#add_manga_status > option[selected]").`val`().toInt() | ||||
|                                 score = trackForm.select("#add_manga_score > option[selected]").`val`().toFloatOrNull() ?: 0f | ||||
|                             } | ||||
|                         } | ||||
|                     } | ||||
|                     libTrack | ||||
|                 } | ||||
|     } | ||||
|  | ||||
|     fun getLibManga(track: Track): Observable<Track> { | ||||
|         return findLibManga(track) | ||||
|                 .map { it ?: throw Exception("Could not find manga") } | ||||
|     } | ||||
|  | ||||
|     fun login(username: String, password: String): String { | ||||
|         val csrf = getSessionInfo() | ||||
|  | ||||
|         login(username, password, csrf) | ||||
|  | ||||
|         return csrf | ||||
|     } | ||||
|  | ||||
|     private fun getSessionInfo(): String { | ||||
|         val response = client.newCall(GET(loginUrl())).execute() | ||||
|  | ||||
|         return Jsoup.parse(response.consumeBody()) | ||||
|                 .select("meta[name=csrf_token]") | ||||
|                 .attr("content") | ||||
|     } | ||||
|  | ||||
|     private fun login(username: String, password: String, csrf: String) { | ||||
|         val response = client.newCall(POST(url = loginUrl(), body = loginPostBody(username, password, csrf))).execute() | ||||
|  | ||||
|         response.use { | ||||
|             if (response.priorResponse()?.code() != 302) throw Exception("Authentication error") | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private fun getList(): Observable<List<TrackSearch>> { | ||||
|         return getListUrl() | ||||
|                 .flatMap { url -> | ||||
|                     getListXml(url) | ||||
|                 } | ||||
|                 .flatMap { doc -> | ||||
|                     Observable.from(doc.select("manga")) | ||||
|                 } | ||||
|                 .map { it -> | ||||
|                 .map { | ||||
|                     TrackSearch.create(TrackManager.MYANIMELIST).apply { | ||||
|                         title = it.selectText("manga_title")!! | ||||
|                         media_id = it.selectInt("manga_mangadb_id") | ||||
| @@ -90,107 +151,8 @@ class MyanimelistApi(private val client: OkHttpClient) { | ||||
|                 .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, csrf: String): Observable<Track> { | ||||
|         return findLibManga(track, csrf) | ||||
|                 .map { it ?: throw Exception("Could not find manga") } | ||||
|     } | ||||
|  | ||||
|     fun login(username: String, password: String): Observable<String> { | ||||
|         return getSessionInfo() | ||||
|                 .flatMap { csrf -> | ||||
|                     login(username, password, csrf) | ||||
|                 } | ||||
|     } | ||||
|  | ||||
|     private fun getSessionInfo(): Observable<String> { | ||||
|         return client.newCall(GET(getLoginUrl())) | ||||
|                 .asObservable() | ||||
|                 .map { response -> | ||||
|                     Jsoup.parse(response.consumeBody()) | ||||
|                             .select("meta[name=csrf_token]") | ||||
|                             .attr("content") | ||||
|                 } | ||||
|     } | ||||
|  | ||||
|     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))) | ||||
|     private fun getListUrl(): Observable<String> { | ||||
|         return authClient.newCall(POST(url = exportListUrl(), body = exportPostBody())) | ||||
|                 .asObservable() | ||||
|                 .map {response -> | ||||
|                     baseUrl + Jsoup.parse(response.consumeBody()) | ||||
| @@ -200,17 +162,17 @@ class MyanimelistApi(private val client: OkHttpClient) { | ||||
|                 } | ||||
|     } | ||||
|  | ||||
|     private fun getUpdateUrl() = Uri.parse(baseModifyListUrl).buildUpon() | ||||
|             .appendPath("edit.json") | ||||
|             .toString() | ||||
|     private fun getListXml(url: String): Observable<Document> { | ||||
|         return authClient.newCall(GET(url)) | ||||
|                 .asObservable() | ||||
|                 .map { response -> | ||||
|                     Jsoup.parse(response.consumeXmlBody(), "", Parser.xmlParser()) | ||||
|                 } | ||||
|     } | ||||
|  | ||||
|     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") | ||||
|             if (it.code() != 200) throw Exception("HTTP error ${it.code()}") | ||||
|             return it.body()?.string() | ||||
|         } | ||||
|     } | ||||
| @@ -229,37 +191,105 @@ class MyanimelistApi(private val client: OkHttpClient) { | ||||
|     } | ||||
|  | ||||
|     companion object { | ||||
|         const val baseUrl = "https://myanimelist.net" | ||||
|         const val CSRF = "csrf_token" | ||||
|  | ||||
|         private const val baseUrl = "https://myanimelist.net" | ||||
|         private const val baseMangaUrl = "$baseUrl/manga/" | ||||
|         private const val baseModifyListUrl = "$baseUrl/ownlist/manga" | ||||
|         private const val PREFIX_MY = "my:" | ||||
|         private const val TD = "td" | ||||
|  | ||||
|         fun mangaUrl(remoteId: Int) = baseMangaUrl + remoteId | ||||
|         private fun mangaUrl(remoteId: Int) = baseMangaUrl + remoteId | ||||
|  | ||||
|         fun Element.searchTitle() = select("strong").text()!! | ||||
|         private fun loginUrl() = Uri.parse(baseUrl).buildUpon() | ||||
|                 .appendPath("login.php") | ||||
|                 .toString() | ||||
|  | ||||
|         fun Element.searchTotalChapters() = if (select(TD)[4].text() == "-") 0 else select(TD)[4].text().toInt() | ||||
|         private fun searchUrl(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() | ||||
|         } | ||||
|  | ||||
|         fun Element.searchCoverUrl() = select("img") | ||||
|         private fun exportListUrl() = Uri.parse(baseUrl).buildUpon() | ||||
|                 .appendPath("panel.php") | ||||
|                 .appendQueryParameter("go", "export") | ||||
|                 .toString() | ||||
|  | ||||
|         private fun updateUrl() = Uri.parse(baseModifyListUrl).buildUpon() | ||||
|                 .appendPath("edit.json") | ||||
|                 .toString() | ||||
|  | ||||
|         private fun addUrl() = Uri.parse(baseModifyListUrl).buildUpon() | ||||
|                 .appendPath( "add.json") | ||||
|                 .toString() | ||||
|  | ||||
|         private fun listEntryUrl(mediaId: Int) = Uri.parse(baseModifyListUrl).buildUpon() | ||||
|                 .appendPath(mediaId.toString()) | ||||
|                 .appendPath("edit") | ||||
|                 .toString() | ||||
|  | ||||
|         private fun loginPostBody(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 exportPostBody(): RequestBody { | ||||
|             return FormBody.Builder() | ||||
|                     .add("type", "2") | ||||
|                     .add("subexport", "Export My List") | ||||
|                     .build() | ||||
|         } | ||||
|  | ||||
|         private fun mangaPostPayload(track: Track): 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) | ||||
|  | ||||
|             return RequestBody.create(MediaType.parse("application/json; charset=utf-8"), body.toString()) | ||||
|         } | ||||
|  | ||||
|         private fun Element.searchTitle() = select("strong").text()!! | ||||
|  | ||||
|         private fun Element.searchTotalChapters() = if (select(TD)[4].text() == "-") 0 else select(TD)[4].text().toInt() | ||||
|  | ||||
|         private fun Element.searchCoverUrl() = select("img") | ||||
|                 .attr("data-src") | ||||
|                 .split("\\?")[0] | ||||
|                 .replace("/r/50x70/", "/") | ||||
|  | ||||
|         fun Element.searchMediaId() = select("div.picSurround") | ||||
|         private fun Element.searchMediaId() = select("div.picSurround") | ||||
|                 .select("a").attr("id") | ||||
|                 .replace("sarea", "") | ||||
|                 .toInt() | ||||
|  | ||||
|         fun Element.searchSummary() = select("div.pt4") | ||||
|         private fun Element.searchSummary() = select("div.pt4") | ||||
|                 .first() | ||||
|                 .ownText()!! | ||||
|  | ||||
|         fun Element.searchPublishingStatus() = if (select(TD).last().text() == "-") PUBLISHING else FINISHED | ||||
|         private fun Element.searchPublishingStatus() = if (select(TD).last().text() == "-") "Publishing" else "Finished" | ||||
|  | ||||
|         fun Element.searchPublishingType() = select(TD)[2].text()!! | ||||
|         private fun Element.searchPublishingType() = select(TD)[2].text()!! | ||||
|  | ||||
|         fun Element.searchStartDate() = select(TD)[6].text()!! | ||||
|         private fun Element.searchStartDate() = select(TD)[6].text()!! | ||||
|  | ||||
|         fun getStatus(status: String) = when (status) { | ||||
|         private fun getStatus(status: String) = when (status) { | ||||
|             "Reading" -> 1 | ||||
|             "Completed" -> 2 | ||||
|             "On-Hold" -> 3 | ||||
| @@ -267,10 +297,5 @@ class MyanimelistApi(private val client: OkHttpClient) { | ||||
|             "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" | ||||
|     } | ||||
| } | ||||
| @@ -47,11 +47,12 @@ class AndroidCookieJar(context: Context) : CookieJar { | ||||
|     } | ||||
|  | ||||
|     fun remove(url: HttpUrl) { | ||||
|         val cookies = manager.getCookie(url.toString()) ?: return | ||||
|         val domain = ".${url.host()}" | ||||
|         val urlString = url.toString() | ||||
|         val cookies = manager.getCookie(urlString) ?: return | ||||
|  | ||||
|         cookies.split(";") | ||||
|             .map { it.substringBefore("=") } | ||||
|             .onEach { manager.setCookie(domain, "$it=;Max-Age=-1") } | ||||
|             .onEach { manager.setCookie(urlString, "$it=;Max-Age=-1") } | ||||
|  | ||||
|         if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { | ||||
|             syncManager.sync() | ||||
|   | ||||
| @@ -52,27 +52,27 @@ class TrackSearchAdapter(context: Context) | ||||
|                         .diskCacheStrategy(DiskCacheStrategy.RESOURCE) | ||||
|                         .centerCrop() | ||||
|                         .into(view.track_search_cover) | ||||
|             } | ||||
|  | ||||
|                 if (track.publishing_status.isNullOrBlank()) { | ||||
|                     view.track_search_status.gone() | ||||
|                     view.track_search_status_result.gone() | ||||
|                 } else { | ||||
|                     view.track_search_status_result.text = track.publishing_status.capitalize() | ||||
|                 } | ||||
|             if (track.publishing_status.isNullOrBlank()) { | ||||
|                 view.track_search_status.gone() | ||||
|                 view.track_search_status_result.gone() | ||||
|             } else { | ||||
|                 view.track_search_status_result.text = track.publishing_status.capitalize() | ||||
|             } | ||||
|  | ||||
|                 if (track.publishing_type.isNullOrBlank()) { | ||||
|                     view.track_search_type.gone() | ||||
|                     view.track_search_type_result.gone() | ||||
|                 } else { | ||||
|                     view.track_search_type_result.text = track.publishing_type.capitalize() | ||||
|                 } | ||||
|             if (track.publishing_type.isNullOrBlank()) { | ||||
|                 view.track_search_type.gone() | ||||
|                 view.track_search_type_result.gone() | ||||
|             } else { | ||||
|                 view.track_search_type_result.text = track.publishing_type.capitalize() | ||||
|             } | ||||
|  | ||||
|                 if (track.start_date.isNullOrBlank()) { | ||||
|                     view.track_search_start.gone() | ||||
|                     view.track_search_start_result.gone() | ||||
|                 } else { | ||||
|                     view.track_search_start_result.text = track.start_date | ||||
|                 } | ||||
|             if (track.start_date.isNullOrBlank()) { | ||||
|                 view.track_search_start.gone() | ||||
|                 view.track_search_start_result.gone() | ||||
|             } else { | ||||
|                 view.track_search_start_result.text = track.start_date | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user