Remanga. Fixes and login (#3422)

* Remanga. Fixes and login

* Remanga login fix

* Fix remanga chapter name format

* Fix empty scanlator

* Fix image loading

Co-authored-by: pavkazzz <me@pavkazzz.ru>
This commit is contained in:
Pavka 2020-06-07 19:08:30 +03:00 committed by GitHub
parent e69c49cf97
commit 2f0e88d18c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 185 additions and 20 deletions

View File

@ -5,7 +5,7 @@ ext {
appName = 'Tachiyomi: Remanga'
pkgNameSuffix = 'ru.remanga'
extClass = '.Remanga'
extVersionCode = 2
extVersionCode = 3
libVersion = '1.2'
}

View File

@ -8,10 +8,19 @@ import MangaDetDto
import PageDto
import PageWrapperDto
import SeriesWrapperDto
import UserDto
import android.app.Application
import android.content.SharedPreferences
import android.support.v7.preference.EditTextPreference
import android.support.v7.preference.PreferenceScreen
import android.text.InputType
import android.widget.Toast
import com.github.salomonbrys.kotson.fromJson
import com.google.gson.Gson
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.network.asObservableSuccess
import eu.kanade.tachiyomi.source.ConfigurableSource
import eu.kanade.tachiyomi.source.model.Filter
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.MangasPage
@ -24,28 +33,75 @@ import java.util.Date
import java.util.Locale
import okhttp3.Headers
import okhttp3.HttpUrl
import okhttp3.Interceptor
import okhttp3.MediaType
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody
import okhttp3.Response
import org.json.JSONObject
import org.jsoup.Jsoup
import rx.Observable
class Remanga : HttpSource() {
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
class Remanga : ConfigurableSource, HttpSource() {
override val name = "Remanga"
override val baseUrl = "https://remanga.org"
override val baseUrl = "https://api.remanga.org"
override val lang = "ru"
override val supportsLatest = true
var token: String = ""
override fun headersBuilder() = Headers.Builder().apply {
add("User-Agent", "Tachiyomi")
add("Referer", baseUrl)
}
private val preferences: SharedPreferences by lazy {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
}
private fun authIntercept(chain: Interceptor.Chain): Response {
val request = chain.request()
if (username.isEmpty() or password.isEmpty()) {
return chain.proceed(request)
}
if (token.isEmpty()) {
token = this.login(chain, username, password)
}
val authRequest = request.newBuilder()
.addHeader("Authorization", "bearer $token")
.build()
return chain.proceed(authRequest)
}
override val client: OkHttpClient =
network.client.newBuilder()
.addInterceptor { authIntercept(it) }
.build()
private val count = 30
private var branches = mutableMapOf<String, List<BranchesDto>>()
private fun login(chain: Interceptor.Chain, username: String, password: String): String {
val jsonObject = JSONObject()
jsonObject.put("user", username)
jsonObject.put("password", password)
val body = RequestBody.create(MEDIA_TYPE, jsonObject.toString())
val response = chain.proceed(POST("$baseUrl/api/users/login/", headers, body))
if (response.code() == 400) {
throw Exception("Failed to login")
}
val user = gson.fromJson<SeriesWrapperDto<UserDto>>(response.body()?.charStream()!!)
return user.content.access_token
}
override fun popularMangaRequest(page: Int) = GET("$baseUrl/api/search/catalog/?ordering=rating&count=$count&page=$page", headers)
override fun popularMangaParse(response: Response): MangasPage = searchMangaParse(response)
@ -59,7 +115,7 @@ class Remanga : HttpSource() {
val mangas = page.content.map {
it.toSManga()
}
return MangasPage(mangas, !page.last)
return MangasPage(mangas, page.props.page < page.props.total_pages)
}
private fun LibraryDto.toSManga(): SManga =
@ -166,17 +222,24 @@ class Remanga : HttpSource() {
return series.content.branches
}
private fun selector(b: BranchesDto): Int = b.count_chapters
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
val branch = branches.getOrElse(manga.title) { mangaBranches(manga) }
return if (manga.status != SManga.LICENSED) {
// Use only first branch for all cases
client.newCall(chapterListRequest(branch[0].id))
.asObservableSuccess()
.map { response ->
chapterListParse(response)
}
} else {
Observable.error(Exception("Licensed - No chapters to show"))
return when {
branch.isEmpty() -> {
return Observable.just(listOf())
}
manga.status == SManga.LICENSED -> {
Observable.error(Exception("Licensed - No chapters to show"))
}
else -> {
val branchId = branch.maxBy { selector(it) }!!.id
client.newCall(chapterListRequest(branchId))
.asObservableSuccess()
.map { response ->
chapterListParse(response)
}
}
}
}
@ -186,23 +249,26 @@ class Remanga : HttpSource() {
private fun chapterName(book: BookDto): String {
val chapterId = if (book.chapter % 1 == 0f) book.chapter.toInt() else book.chapter
var chapterName = "${book.tome} - $chapterId"
if (book.name.isNotBlank() && chapterName != chapterName) {
chapterName += "- $chapterName"
var chapterName = "${book.tome}. Глава $chapterId"
if (book.name.isNotBlank()) {
chapterName += " ${book.name.capitalize()}"
}
return chapterName
}
override fun chapterListParse(response: Response): List<SChapter> {
val chapters = gson.fromJson<PageWrapperDto<BookDto>>(response.body()?.charStream()!!)
return chapters.content.filter { !it.is_paid }.map { chapter ->
return chapters.content.filter { !it.is_paid or it.is_bought }.map { chapter ->
SChapter.create().apply {
chapter_number = chapter.chapter
name = chapterName(chapter)
url = "/api/titles/chapters/${chapter.id}"
date_upload = parseDate(chapter.upload_date)
scanlator = if (chapter.publishers.isNotEmpty()) {
chapter.publishers.joinToString { it.name }
} else null
}
}.sortedByDescending { it.chapter_number }
}
}
override fun imageUrlParse(response: Response): String = ""
@ -213,6 +279,15 @@ class Remanga : HttpSource() {
Page(it.page, "", it.link)
}
}
override fun imageRequest(page: Page): Request {
val refererHeaders = Headers.Builder().apply {
add("User-Agent", "Tachiyomi")
add("Referer", "https://img.remanga.org")
}.build()
return GET(page.imageUrl!!, refererHeaders)
}
private class SearchFilter(name: String, val id: String) : Filter.TriState(name)
private class CheckFilter(name: String, val id: String) : Filter.CheckBox(name)
@ -234,11 +309,13 @@ class Remanga : HttpSource() {
private class OrderBy : Filter.Sort("Сортировка",
arrayOf("Новизне", "Последним обновлениям", "Популярности", "Лайкам", "Просмотрам", "Мне повезет"),
Selection(2, false))
private fun getAgeList() = listOf(
CheckFilter("Для всех", "0"),
CheckFilter("16+", "1"),
CheckFilter("18+", "2")
)
private fun getTypeList() = listOf(
SearchFilter("Манга", "0"),
SearchFilter("Манхва", "1"),
@ -255,6 +332,7 @@ class Remanga : HttpSource() {
CheckFilter("Продолжается", "1"),
CheckFilter("Заморожен", "2")
)
private fun getCategoryList() = listOf(
SearchFilter("алхимия", "47"),
SearchFilter("ангелы", "48"),
@ -350,6 +428,7 @@ class Remanga : HttpSource() {
SearchFilter("шантаж", "99"),
SearchFilter("эльфы", "46")
)
private fun getGenreList() = listOf(
SearchFilter("арт", "1"),
SearchFilter("бдсм", "44"),
@ -396,5 +475,75 @@ class Remanga : HttpSource() {
SearchFilter("яой", "43")
)
override fun setupPreferenceScreen(screen: androidx.preference.PreferenceScreen) {
screen.addPreference(screen.editTextPreference(USERNAME_TITLE, USERNAME_DEFAULT, username))
screen.addPreference(screen.editTextPreference(PASSWORD_TITLE, PASSWORD_DEFAULT, password, true))
}
private fun androidx.preference.PreferenceScreen.editTextPreference(title: String, default: String, value: String, isPassword: Boolean = false): androidx.preference.EditTextPreference {
return androidx.preference.EditTextPreference(context).apply {
key = title
this.title = title
summary = value
this.setDefaultValue(default)
dialogTitle = title
if (isPassword) {
setOnBindEditTextListener {
it.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD
}
}
setOnPreferenceChangeListener { _, newValue ->
try {
val res = preferences.edit().putString(title, newValue as String).commit()
Toast.makeText(context, "Restart Tachiyomi to apply new setting.", Toast.LENGTH_LONG).show()
res
} catch (e: Exception) {
e.printStackTrace()
false
}
}
}
}
override fun setupPreferenceScreen(screen: PreferenceScreen) {
screen.addPreference(screen.supportEditTextPreference(USERNAME_TITLE, USERNAME_DEFAULT, username))
screen.addPreference(screen.supportEditTextPreference(PASSWORD_TITLE, PASSWORD_DEFAULT, password))
}
private fun PreferenceScreen.supportEditTextPreference(title: String, default: String, value: String): EditTextPreference {
return EditTextPreference(context).apply {
key = title
this.title = title
summary = value
this.setDefaultValue(default)
dialogTitle = title
setOnPreferenceChangeListener { _, newValue ->
try {
val res = preferences.edit().putString(title, newValue as String).commit()
Toast.makeText(context, "Restart Tachiyomi to apply new setting.", Toast.LENGTH_LONG).show()
res
} catch (e: Exception) {
e.printStackTrace()
false
}
}
}
}
private fun getPrefUsername(): String = preferences.getString(USERNAME_TITLE, USERNAME_DEFAULT)!!
private fun getPrefPassword(): String = preferences.getString(PASSWORD_TITLE, PASSWORD_DEFAULT)!!
private val gson by lazy { Gson() }
private val username by lazy { getPrefUsername() }
private val password by lazy { getPrefPassword() }
companion object {
private val MEDIA_TYPE = MediaType.parse("application/json; charset=utf-8")
private const val USERNAME_TITLE = "Username"
private const val USERNAME_DEFAULT = ""
private const val PASSWORD_TITLE = "Password"
private const val PASSWORD_DEFAULT = ""
}
}

View File

@ -2,9 +2,12 @@ data class GenresDto(
val id: Int,
val name: String
)
data class BranchesDto(
val id: Long
val id: Long,
val count_chapters: Int
)
data class ImgDto(
val high: String,
val mid: String,
@ -39,6 +42,7 @@ data class MangaDetDto(
val branches: List<BranchesDto>,
val status: StatusDto
)
data class PropsDto(
val total_items: Int,
val total_pages: Int,
@ -58,13 +62,20 @@ data class SeriesWrapperDto<T>(
val props: PropsDto
)
data class PublisherDto(
val name: String,
val dir: String
)
data class BookDto(
val id: Long,
val tome: Int,
val chapter: Float,
val name: String,
val upload_date: String,
val is_paid: Boolean
val is_paid: Boolean,
val is_bought: Boolean,
val publishers: List<PublisherDto>
)
data class PagesDto(
@ -73,6 +84,11 @@ data class PagesDto(
val page: Int,
val count_comments: Int
)
data class PageDto(
val pages: List<PagesDto>
)
data class UserDto(
val access_token: String
)