NEW - TaddyINK extension (#18699)
* sss feed parsing complete * removed old references * Updates based on comments * remove unneeded image * using SwitchPreferenceCompat * misc changes * Update src/all/taddyink/src/eu/kanade/tachiyomi/extension/all/taddyink/TaddyUtils.kt Co-authored-by: Alessandro Jean <14254807+alessandrojean@users.noreply.github.com> * Update src/all/taddyink/src/eu/kanade/tachiyomi/extension/all/taddyink/TaddyUtils.kt Co-authored-by: Alessandro Jean <14254807+alessandrojean@users.noreply.github.com> * Update src/all/taddyink/src/eu/kanade/tachiyomi/extension/all/taddyink/TaddyInkUrlActivity.kt Co-authored-by: Alessandro Jean <14254807+alessandrojean@users.noreply.github.com> * Update src/all/taddyink/src/eu/kanade/tachiyomi/extension/all/taddyink/TaddyInkUrlActivity.kt Co-authored-by: Alessandro Jean <14254807+alessandrojean@users.noreply.github.com> * Update src/all/taddyink/src/eu/kanade/tachiyomi/extension/all/taddyink/TaddyInk.kt Co-authored-by: Alessandro Jean <14254807+alessandrojean@users.noreply.github.com> * Update src/all/taddyink/src/eu/kanade/tachiyomi/extension/all/taddyink/TaddyInk.kt Co-authored-by: Alessandro Jean <14254807+alessandrojean@users.noreply.github.com> * Update src/all/taddyink/src/eu/kanade/tachiyomi/extension/all/taddyink/TaddyInk.kt Co-authored-by: Alessandro Jean <14254807+alessandrojean@users.noreply.github.com> * Update src/all/taddyink/src/eu/kanade/tachiyomi/extension/all/taddyink/TaddyInk.kt Co-authored-by: Alessandro Jean <14254807+alessandrojean@users.noreply.github.com> * Update src/all/taddyink/src/eu/kanade/tachiyomi/extension/all/taddyink/TaddyInk.kt Co-authored-by: Alessandro Jean <14254807+alessandrojean@users.noreply.github.com> * Update src/all/taddyink/src/eu/kanade/tachiyomi/extension/all/taddyink/TaddyInk.kt Co-authored-by: Alessandro Jean <14254807+alessandrojean@users.noreply.github.com> * Update src/all/taddyink/src/eu/kanade/tachiyomi/extension/all/taddyink/TaddyInk.kt Co-authored-by: Alessandro Jean <14254807+alessandrojean@users.noreply.github.com> * Update src/all/taddyink/src/eu/kanade/tachiyomi/extension/all/taddyink/TaddyInk.kt Co-authored-by: Alessandro Jean <14254807+alessandrojean@users.noreply.github.com> * changes as per review * Update src/all/taddyink/build.gradle Co-authored-by: Alessandro Jean <14254807+alessandrojean@users.noreply.github.com> * Update src/all/taddyink/src/eu/kanade/tachiyomi/extension/all/taddyink/TaddyUtils.kt Co-authored-by: Alessandro Jean <14254807+alessandrojean@users.noreply.github.com> * Update src/all/taddyink/src/eu/kanade/tachiyomi/extension/all/taddyink/TaddyUtils.kt Co-authored-by: Alessandro Jean <14254807+alessandrojean@users.noreply.github.com> * small fix --------- Co-authored-by: Daniel Mathews <dmathewwws@Daniels-Air.pnwlumber.com> Co-authored-by: Alessandro Jean <14254807+alessandrojean@users.noreply.github.com>
This commit is contained in:
parent
c6f5a54c96
commit
308d945eb7
|
@ -0,0 +1,21 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<application>
|
||||
<activity
|
||||
android:name=".all.taddyink.TaddyInkUrlActivity"
|
||||
android:excludeFromRecents="true"
|
||||
android:exported="true"
|
||||
android:theme="@android:style/Theme.NoDisplay">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data
|
||||
android:host="taddy.org"
|
||||
android:scheme="https" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
</manifest>
|
|
@ -0,0 +1,12 @@
|
|||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'kotlinx-serialization'
|
||||
|
||||
ext {
|
||||
extName = 'Taddy INK (Webtoons)'
|
||||
pkgNameSuffix = 'all.taddyink'
|
||||
extClass = '.TaddyInkFactory'
|
||||
extVersionCode = 1
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
Binary file not shown.
After Width: | Height: | Size: 4.4 KiB |
Binary file not shown.
After Width: | Height: | Size: 2.4 KiB |
Binary file not shown.
After Width: | Height: | Size: 6.3 KiB |
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
Binary file not shown.
After Width: | Height: | Size: 17 KiB |
Binary file not shown.
After Width: | Height: | Size: 88 KiB |
|
@ -0,0 +1,188 @@
|
|||
package eu.kanade.tachiyomi.extension.all.taddyink
|
||||
|
||||
import androidx.preference.PreferenceScreen
|
||||
import androidx.preference.SwitchPreferenceCompat
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.interceptor.rateLimit
|
||||
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
|
||||
import eu.kanade.tachiyomi.source.model.Page
|
||||
import eu.kanade.tachiyomi.source.model.SChapter
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import kotlinx.serialization.json.Json
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
|
||||
open class TaddyInk(
|
||||
override val lang: String,
|
||||
private val taddyLang: String,
|
||||
) : ConfigurableSource, HttpSource() {
|
||||
|
||||
final override val baseUrl = "https://taddy.org"
|
||||
override val name = "Taddy INK (Webtoons)"
|
||||
override val supportsLatest = false
|
||||
|
||||
override val client: OkHttpClient by lazy {
|
||||
network.cloudflareClient.newBuilder()
|
||||
.rateLimit(4)
|
||||
.build()
|
||||
}
|
||||
|
||||
private val json: Json by injectLazy()
|
||||
|
||||
override fun setupPreferenceScreen(screen: PreferenceScreen) {
|
||||
SwitchPreferenceCompat(screen.context).apply {
|
||||
key = TITLE_PREF_KEY
|
||||
title = TITLE_PREF
|
||||
summaryOn = "Full Title"
|
||||
summaryOff = "Short Title"
|
||||
setDefaultValue(true)
|
||||
}.also(screen::addPreference)
|
||||
}
|
||||
|
||||
override fun latestUpdatesRequest(page: Int): Request = throw UnsupportedOperationException("Not used!")
|
||||
|
||||
override fun latestUpdatesParse(response: Response): MangasPage = throw UnsupportedOperationException("Not used!")
|
||||
|
||||
override fun popularMangaRequest(page: Int): Request {
|
||||
val url = "$baseUrl/feeds/directory/list".toHttpUrl().newBuilder()
|
||||
.addQueryParameter("lang", taddyLang)
|
||||
.addQueryParameter("taddyType", "comicseries")
|
||||
.addQueryParameter("ua", "tc")
|
||||
.addQueryParameter("page", page.toString())
|
||||
.addQueryParameter("limit", POPULAR_MANGA_LIMIT.toString())
|
||||
return GET(url.build(), headers)
|
||||
}
|
||||
|
||||
override fun popularMangaParse(response: Response) = parseManga(response)
|
||||
|
||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||
val filterList = if (filters.isEmpty()) getFilterList() else filters
|
||||
val shouldFilterByGenre = filterList.findInstance<GenreFilter>()?.state != 0
|
||||
val shouldFilterByCreator = filterList.findInstance<CreatorFilter>()?.state?.isNotBlank() ?: false
|
||||
val shouldFilterForTags = filterList.findInstance<TagFilter>()?.state?.isNotBlank() ?: false
|
||||
|
||||
val url = "$baseUrl/feeds/directory/search".toHttpUrl().newBuilder()
|
||||
.addQueryParameter("q", query)
|
||||
.addQueryParameter("lang", taddyLang)
|
||||
.addQueryParameter("taddyType", "comicseries")
|
||||
.addQueryParameter("ua", "tc")
|
||||
.addQueryParameter("page", page.toString())
|
||||
.addQueryParameter("limit", SEARCH_MANGA_LIMIT.toString())
|
||||
|
||||
if (shouldFilterByGenre) {
|
||||
filterList.findInstance<GenreFilter>()?.let { f ->
|
||||
url.addQueryParameter("genre", f.toUriPart())
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldFilterByCreator) {
|
||||
filterList.findInstance<CreatorFilter>()?.let { name ->
|
||||
url.addQueryParameter("creator", name.state)
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldFilterForTags) {
|
||||
filterList.findInstance<TagFilter>()?.let { tags ->
|
||||
url.addQueryParameter("tags", tags.state)
|
||||
}
|
||||
}
|
||||
|
||||
return GET(url.build(), headers)
|
||||
}
|
||||
|
||||
override fun searchMangaParse(response: Response) = parseManga(response)
|
||||
|
||||
private fun parseManga(response: Response): MangasPage {
|
||||
val comicSeries = json.decodeFromString<ComicResults>(response.body.string())
|
||||
val mangas = comicSeries.comicseries.map { TaddyUtils.getManga(it) }
|
||||
val hasNextPage = comicSeries.comicseries.size == POPULAR_MANGA_LIMIT
|
||||
return MangasPage(mangas, hasNextPage)
|
||||
}
|
||||
|
||||
override fun mangaDetailsRequest(manga: SManga): Request {
|
||||
return GET(manga.url, headers)
|
||||
}
|
||||
|
||||
override fun mangaDetailsParse(response: Response): SManga {
|
||||
val comicObj = json.decodeFromString<Comic>(response.body.string())
|
||||
return TaddyUtils.getManga(comicObj)
|
||||
}
|
||||
|
||||
override fun chapterListRequest(manga: SManga): Request {
|
||||
return GET(manga.url, headers)
|
||||
}
|
||||
|
||||
override fun chapterListParse(response: Response): List<SChapter> {
|
||||
val comic = json.decodeFromString<Comic>(response.body.string())
|
||||
val sssUrl = comic.url
|
||||
|
||||
val chapters = comic.issues.orEmpty().mapIndexed { i, chapter ->
|
||||
SChapter.create().apply {
|
||||
url = "$sssUrl#${chapter.identifier}"
|
||||
name = chapter.name
|
||||
date_upload = TaddyUtils.getTime(chapter.datePublished)
|
||||
chapter_number = (comic.issues.orEmpty().size - i).toFloat()
|
||||
}
|
||||
}
|
||||
|
||||
return chapters.reversed()
|
||||
}
|
||||
|
||||
override fun pageListRequest(chapter: SChapter): Request {
|
||||
return GET(chapter.url, headers)
|
||||
}
|
||||
|
||||
override fun pageListParse(response: Response): List<Page> {
|
||||
val requestUrl = response.request.url.toString()
|
||||
val issueUuid = requestUrl.substringAfterLast("#")
|
||||
val comic = json.decodeFromString<Comic>(response.body.string())
|
||||
|
||||
val issue = comic.issues.orEmpty().firstOrNull { it.identifier == issueUuid }
|
||||
|
||||
return issue?.stories.orEmpty().mapIndexed { index, storyObj ->
|
||||
Page(index, "", "${storyObj.storyImage?.base_url}${storyObj.storyImage?.story}")
|
||||
}
|
||||
}
|
||||
|
||||
override fun imageUrlParse(response: Response) = ""
|
||||
|
||||
override fun getFilterList(): FilterList = FilterList(
|
||||
GenreFilter(),
|
||||
Filter.Separator(),
|
||||
Filter.Header("Filter by the creator or tags:"),
|
||||
CreatorFilter(),
|
||||
TagFilter(),
|
||||
)
|
||||
|
||||
class CreatorFilter : AdvSearchEntryFilter("Creator")
|
||||
class TagFilter : AdvSearchEntryFilter("Tags")
|
||||
open class AdvSearchEntryFilter(name: String) : Filter.Text(name)
|
||||
|
||||
private class GenreFilter : UriPartFilter(
|
||||
"Filter By Genre",
|
||||
TaddyUtils.genrePairs,
|
||||
)
|
||||
|
||||
private open class UriPartFilter(displayName: String, val vals: List<Pair<String, String>>) :
|
||||
Filter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) {
|
||||
fun toUriPart() = vals[state].second
|
||||
}
|
||||
|
||||
private inline fun <reified T> Iterable<*>.findInstance() = find { it is T } as? T
|
||||
|
||||
companion object {
|
||||
private const val TITLE_PREF_KEY = "display_full_title"
|
||||
private const val TITLE_PREF = "Display manga title as"
|
||||
|
||||
private const val POPULAR_MANGA_LIMIT = 25
|
||||
private const val SEARCH_MANGA_LIMIT = 25
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package eu.kanade.tachiyomi.extension.all.taddyink
|
||||
|
||||
import eu.kanade.tachiyomi.source.Source
|
||||
import eu.kanade.tachiyomi.source.SourceFactory
|
||||
|
||||
class TaddyInkFactory : SourceFactory {
|
||||
override fun createSources(): List<Source> = listOf(
|
||||
TaddyInk("all", ""),
|
||||
)
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
package eu.kanade.tachiyomi.extension.all.taddyink
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
/**
|
||||
* Springboard that accepts https://nhentai.net/g/xxxxxx intents and redirects them to
|
||||
* the main Tachiyomi process.
|
||||
*/
|
||||
class TaddyInkUrlActivity : Activity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
val pathSegments = intent?.data?.pathSegments
|
||||
if (pathSegments != null && pathSegments.size > 1) {
|
||||
val id = pathSegments[1]
|
||||
val mainIntent = Intent().apply {
|
||||
action = "eu.kanade.tachiyomi.SEARCH"
|
||||
putExtra("query", "id")
|
||||
putExtra("filter", packageName)
|
||||
}
|
||||
|
||||
try {
|
||||
startActivity(mainIntent)
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
Log.e("TaddyInkUrlActivity", e.toString())
|
||||
}
|
||||
} else {
|
||||
Log.e("TaddyInkUrlActivity", "Could not parse URI from intent $intent")
|
||||
}
|
||||
|
||||
finish()
|
||||
exitProcess(0)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,144 @@
|
|||
package eu.kanade.tachiyomi.extension.all.taddyink
|
||||
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import kotlinx.serialization.Serializable
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Locale
|
||||
|
||||
object TaddyUtils {
|
||||
private val formatter = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US)
|
||||
|
||||
fun getManga(comicObj: Comic): SManga {
|
||||
val name = comicObj.name
|
||||
val sssUrl = comicObj.url
|
||||
val sssDescription = comicObj.description
|
||||
val genres = comicObj.genres.orEmpty()
|
||||
.mapNotNull { genreMap[it] }
|
||||
.joinToString()
|
||||
|
||||
val creators = comicObj.creators
|
||||
?.mapNotNull { it.name }
|
||||
?.joinToString()
|
||||
|
||||
val thumbnailBaseUrl = comicObj.coverImage?.base_url ?: ""
|
||||
val thumbnail = comicObj.coverImage?.cover_sm ?: ""
|
||||
val thumbnailUrl = if (thumbnailBaseUrl.isNotEmpty() && thumbnail.isNotEmpty()) "$thumbnailBaseUrl$thumbnail" else ""
|
||||
|
||||
return SManga.create().apply {
|
||||
url = sssUrl
|
||||
title = name
|
||||
creators?.takeIf { it.isNotBlank() }?.let { author = it }
|
||||
description = sssDescription
|
||||
thumbnail_url = thumbnailUrl
|
||||
status = SManga.ONGOING
|
||||
genre = genres
|
||||
initialized = true
|
||||
}
|
||||
}
|
||||
|
||||
fun getTime(timeString: String): Long {
|
||||
return runCatching { formatter.parse(timeString)?.time }
|
||||
.getOrNull() ?: 0L
|
||||
}
|
||||
|
||||
val genrePairs: List<Pair<String, String>> = listOf(
|
||||
Pair("", ""),
|
||||
Pair("Action", "COMICSERIES_ACTION"),
|
||||
Pair("Comedy", "COMICSERIES_COMEDY"),
|
||||
Pair("Drama", "COMICSERIES_DRAMA"),
|
||||
Pair("Educational", "COMICSERIES_EDUCATIONAL"),
|
||||
Pair("Fantasy", "COMICSERIES_FANTASY"),
|
||||
Pair("Historical", "COMICSERIES_HISTORICAL"),
|
||||
Pair("Horror", "COMICSERIES_HORROR"),
|
||||
Pair("Inspirational", "COMICSERIES_INSPIRATIONAL"),
|
||||
Pair("Mystery", "COMICSERIES_MYSTERY"),
|
||||
Pair("Romance", "COMICSERIES_ROMANCE"),
|
||||
Pair("Sci-Fi", "COMICSERIES_SCI_FI"),
|
||||
Pair("Slice Of Life", "COMICSERIES_SLICE_OF_LIFE"),
|
||||
Pair("Superhero", "COMICSERIES_SUPERHERO"),
|
||||
Pair("Supernatural", "COMICSERIES_SUPERNATURAL"),
|
||||
Pair("Wholesome", "COMICSERIES_WHOLESOME"),
|
||||
Pair("BL (Boy Love)", "COMICSERIES_BL"),
|
||||
Pair("GL (Girl Love)", "COMICSERIES_GL"),
|
||||
Pair("LGBTQ+", "COMICSERIES_LGBTQ"),
|
||||
Pair("Thriller", "COMICSERIES_THRILLER"),
|
||||
Pair("Zombies", "COMICSERIES_ZOMBIES"),
|
||||
Pair("Post Apocalyptic", "COMICSERIES_POST_APOCALYPTIC"),
|
||||
Pair("School", "COMICSERIES_SCHOOL"),
|
||||
Pair("Sports", "COMICSERIES_SPORTS"),
|
||||
Pair("Animals", "COMICSERIES_ANIMALS"),
|
||||
Pair("Gaming", "COMICSERIES_GAMING"),
|
||||
)
|
||||
|
||||
val genreMap: Map<String, String> = genrePairs.associateBy({ it.second }, { it.first })
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class ComicResults(
|
||||
val status: String,
|
||||
val comicseries: List<Comic> = emptyList(),
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class Comic(
|
||||
val identifier: String? = null,
|
||||
val name: String = "Unknown",
|
||||
val url: String,
|
||||
val description: String? = null,
|
||||
val genres: List<String>? = emptyList(),
|
||||
val creators: List<Creator>? = emptyList(),
|
||||
val coverImage: CoverImage? = null,
|
||||
val bannerImage: BannerImage? = null,
|
||||
val thumbnailImage: ThumbnailImage? = null,
|
||||
val contentRating: String? = null,
|
||||
val inLanguage: String? = null,
|
||||
val seriesType: String? = null,
|
||||
val issues: List<Chapter>? = emptyList(),
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class CoverImage(
|
||||
val base_url: String?,
|
||||
val cover_sm: String?,
|
||||
val cover_md: String?,
|
||||
val cover_lg: String?,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class BannerImage(
|
||||
val base_url: String?,
|
||||
val banner_sm: String?,
|
||||
val banner_md: String?,
|
||||
val banner_lg: String?,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class ThumbnailImage(
|
||||
val base_url: String?,
|
||||
val thumbnail: String?,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class Creator(
|
||||
val identifier: String? = null,
|
||||
val name: String? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class Chapter(
|
||||
val identifier: String,
|
||||
val name: String,
|
||||
val datePublished: String,
|
||||
val stories: List<Story>? = emptyList(),
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class Story(
|
||||
val storyImage: StoryImage?,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class StoryImage(
|
||||
val base_url: String?,
|
||||
val story: String?,
|
||||
)
|
Loading…
Reference in New Issue