diff --git a/src/all/komga/build.gradle b/src/all/komga/build.gradle new file mode 100644 index 000000000..5d412a16a --- /dev/null +++ b/src/all/komga/build.gradle @@ -0,0 +1,19 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' + +ext { + appName = 'Tachiyomi: Komga' + pkgNameSuffix = 'all.komga' + extClass = '.Komga' + extVersionCode = 1 + libVersion = '1.2' +} + +dependencies { + compileOnly project(':preference-stub') + compileOnly 'com.google.code.gson:gson:2.8.5' + compileOnly 'com.github.salomonbrys.kotson:kotson:2.5.0' + compileOnly 'com.github.inorichi.injekt:injekt-core:65b0440' +} + +apply from: "$rootDir/common.gradle" diff --git a/src/all/komga/res/mipmap-hdpi/ic_launcher.png b/src/all/komga/res/mipmap-hdpi/ic_launcher.png new file mode 100755 index 000000000..4a5514218 Binary files /dev/null and b/src/all/komga/res/mipmap-hdpi/ic_launcher.png differ diff --git a/src/all/komga/res/mipmap-mdpi/ic_launcher.png b/src/all/komga/res/mipmap-mdpi/ic_launcher.png new file mode 100755 index 000000000..af95fb6ca Binary files /dev/null and b/src/all/komga/res/mipmap-mdpi/ic_launcher.png differ diff --git a/src/all/komga/res/mipmap-xhdpi/ic_launcher.png b/src/all/komga/res/mipmap-xhdpi/ic_launcher.png new file mode 100755 index 000000000..76c5112ce Binary files /dev/null and b/src/all/komga/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/src/all/komga/res/mipmap-xxhdpi/ic_launcher.png b/src/all/komga/res/mipmap-xxhdpi/ic_launcher.png new file mode 100755 index 000000000..80894b006 Binary files /dev/null and b/src/all/komga/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src/all/komga/res/mipmap-xxxhdpi/ic_launcher.png b/src/all/komga/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100755 index 000000000..798b30128 Binary files /dev/null and b/src/all/komga/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src/all/komga/res/web_hi_res_512.png b/src/all/komga/res/web_hi_res_512.png new file mode 100755 index 000000000..d39f53c61 Binary files /dev/null and b/src/all/komga/res/web_hi_res_512.png differ diff --git a/src/all/komga/src/eu/kanade/tachiyomi/extension/all/komga/Komga.kt b/src/all/komga/src/eu/kanade/tachiyomi/extension/all/komga/Komga.kt new file mode 100644 index 000000000..3af5b3ba4 --- /dev/null +++ b/src/all/komga/src/eu/kanade/tachiyomi/extension/all/komga/Komga.kt @@ -0,0 +1,181 @@ +package eu.kanade.tachiyomi.extension.all.komga + +import android.app.Application +import android.content.SharedPreferences +import android.support.v7.preference.EditTextPreference +import android.support.v7.preference.PreferenceScreen +import android.widget.Toast +import com.github.salomonbrys.kotson.fromJson +import com.google.gson.Gson +import eu.kanade.tachiyomi.extension.all.komga.dto.BookDto +import eu.kanade.tachiyomi.extension.all.komga.dto.PageDto +import eu.kanade.tachiyomi.extension.all.komga.dto.PageWrapperDto +import eu.kanade.tachiyomi.extension.all.komga.dto.SerieDto +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.source.ConfigurableSource +import eu.kanade.tachiyomi.source.model.* +import eu.kanade.tachiyomi.source.online.HttpSource +import okhttp3.* +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get +import java.text.SimpleDateFormat +import java.util.* +import java.util.concurrent.TimeUnit + +open class Komga : ConfigurableSource, HttpSource() { + override fun popularMangaRequest(page: Int): Request = + GET("$baseUrl/api/v1/series?page=${page - 1}", headers) + + override fun popularMangaParse(response: Response): MangasPage = + processSeriePage(response) + + override fun latestUpdatesRequest(page: Int): Request = + GET("$baseUrl/api/v1/series/latest?page=${page - 1}", headers) + + override fun latestUpdatesParse(response: Response): MangasPage = + processSeriePage(response) + + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = + GET("$baseUrl/api/v1/series?search=$query&page=${page - 1}", headers) + + override fun searchMangaParse(response: Response): MangasPage = + processSeriePage(response) + + override fun mangaDetailsRequest(manga: SManga): Request = + GET(baseUrl + manga.url, headers) + + override fun mangaDetailsParse(response: Response): SManga { + val serie = gson.fromJson(response.body()?.charStream()!!) + return serie.toSManga() + } + + override fun chapterListRequest(manga: SManga): Request = + GET("$baseUrl${manga.url}/books?size=1000", headers) + + override fun chapterListParse(response: Response): List { + val page = gson.fromJson>(response.body()?.charStream()!!) + val chapterListUrl = response.request().url().newBuilder() + .removeAllQueryParameters("size").build().toString() + + return page.content.mapIndexed { i, book -> + SChapter.create().apply { + chapter_number = (i + 1).toFloat() + name = book.name + url = "$chapterListUrl/${book.id}" + date_upload = parseDate(book.lastModified) + } + }.sortedByDescending { it.chapter_number } + } + + override fun pageListRequest(chapter: SChapter): Request = + GET("${chapter.url}/pages") + + override fun pageListParse(response: Response): List { + val pages = gson.fromJson>(response.body()?.charStream()!!) + return pages.map { + val url = "${response.request().url()}/${it.number}" + Page( + index = it.number - 1, + imageUrl = url + ) + } + } + + private fun processSeriePage(response: Response): MangasPage { + val page = gson.fromJson>(response.body()?.charStream()!!) + val mangas = page.content.map { + it.toSManga() + } + return MangasPage(mangas, !page.last) + } + + private fun SerieDto.toSManga(): SManga = + SManga.create().apply { + title = this@toSManga.name + url = "/api/v1/series/${this@toSManga.id}" + thumbnail_url = "$baseUrl/api/v1/series/${this@toSManga.id}/thumbnail" + status = SManga.UNKNOWN + } + + private fun parseDate(date: String?): Long = + if (date == null) + Date().time + else { + try { + SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.US).parse(date).time + } catch (ex: Exception) { + try { + SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.S", Locale.US).parse(date).time + } catch (ex: Exception) { + Date().time + } + } + } + + override fun imageUrlParse(response: Response): String = "" + + override val name = "Komga" + override val lang = "en" + + override val supportsLatest = true + override val baseUrl by lazy { getPrefBaseUrl() } + private val username by lazy { getPrefUsername() } + private val password by lazy { getPrefPassword() } + + private val gson by lazy { Gson() } + + override fun headersBuilder(): Headers.Builder = + Headers.Builder() + .add("Authorization", Credentials.basic(username, password)) + + private val preferences: SharedPreferences by lazy { + Injekt.get().getSharedPreferences("source_$id", 0x0000) + } + + private fun clientBuilder(): OkHttpClient = network.client.newBuilder() + .connectTimeout(10, TimeUnit.SECONDS) + .readTimeout(30, TimeUnit.SECONDS) + .cache(null) + .build()!! + + override fun setupPreferenceScreen(screen: PreferenceScreen) { + screen.addPreference(screen.editTextPreference(ADDRESS_TITLE, ADDRESS_DEFAULT, baseUrl)) + screen.addPreference(screen.editTextPreference(USERNAME_TITLE, USERNAME_DEFAULT, username)) + screen.addPreference(screen.editTextPreference(PASSWORD_TITLE, PASSWORD_DEFAULT, password)) + } + + private fun PreferenceScreen.editTextPreference(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 getPrefBaseUrl(): String = preferences.getString(ADDRESS_TITLE, ADDRESS_DEFAULT)!! + private fun getPrefUsername(): String = preferences.getString(USERNAME_TITLE, USERNAME_DEFAULT)!! + private fun getPrefPassword(): String = preferences.getString(PASSWORD_TITLE, PASSWORD_DEFAULT)!! + + companion object { + private const val ADDRESS_TITLE = "Address" + private const val ADDRESS_DEFAULT = "" + private const val USERNAME_TITLE = "Username" + private const val USERNAME_DEFAULT = "" + private const val PASSWORD_TITLE = "Password" + private const val PASSWORD_DEFAULT = "" + } +} diff --git a/src/all/komga/src/eu/kanade/tachiyomi/extension/all/komga/dto/Dto.kt b/src/all/komga/src/eu/kanade/tachiyomi/extension/all/komga/dto/Dto.kt new file mode 100644 index 000000000..144270fa7 --- /dev/null +++ b/src/all/komga/src/eu/kanade/tachiyomi/extension/all/komga/dto/Dto.kt @@ -0,0 +1,24 @@ +package eu.kanade.tachiyomi.extension.all.komga.dto + +data class SerieDto( + val id: Long, + val name: String, + val lastModified: String? +) + +data class BookDto( + val id: Long, + val name: String, + val lastModified: String?, + val metadata: BookMetadataDto +) + +data class BookMetadataDto( + val status: String, + val mediaType: String +) + +data class PageDto( + val number: Int, + val fileName: String +) diff --git a/src/all/komga/src/eu/kanade/tachiyomi/extension/all/komga/dto/PageWrapperDto.kt b/src/all/komga/src/eu/kanade/tachiyomi/extension/all/komga/dto/PageWrapperDto.kt new file mode 100644 index 000000000..2b1683ba8 --- /dev/null +++ b/src/all/komga/src/eu/kanade/tachiyomi/extension/all/komga/dto/PageWrapperDto.kt @@ -0,0 +1,13 @@ +package eu.kanade.tachiyomi.extension.all.komga.dto + +data class PageWrapperDto( + val content: List, + val empty: Boolean, + val first: Boolean, + val last: Boolean, + val number: Long, + val numberOfElements: Long, + val size: Long, + val totalElements: Long, + val totalPages: Long +)