?) {
64 | prefs.edit {
65 | putStringSet(key, values)
66 | }
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | ApkBridge
6 |
7 |
8 |
9 | [](https://github.com/kodjodevf/mangayomi/releases)
10 | 
11 | [](https://discord.com/invite/EjfBuYahsP)
12 |
13 |
14 |
15 | An Android Proxy Server for invoking APK stub functions.
16 |
17 |
18 | ## Installing
19 |
20 | Download [ApkBridge.apk](https://github.com/Schnitzel5/ApkBridge/releases/latest) and press on "Start server" after the installation.
21 |
22 |
23 |
24 | ## Build from souce
25 |
26 | ```
27 | git pull https://github.com/Schnitzel5/ApkBridge.git
28 | cd ApkBridge
29 | chmod +x ./gradlew
30 | ./gradlew build
31 | ./gradlew assembleRelease
32 | ```
33 |
34 | Make sure to provide the signing keys in a new file "local.properties":
35 |
36 | ```
37 | storePassword=
38 | keyPassword=
39 | keyAlias=
40 | storeFile=
41 | ```
42 |
43 | ## License
44 |
45 | ```
46 | Copyright 2025 Schnitzel5
47 |
48 | Licensed under the Apache License, Version 2.0 (the "License");
49 | you may not use this file except in compliance with the License.
50 | You may obtain a copy of the License at
51 |
52 | http://www.apache.org/licenses/LICENSE-2.0
53 |
54 | Unless required by applicable law or agreed to in writing, software
55 | distributed under the License is distributed on an "AS IS" BASIS,
56 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
57 | See the License for the specific language governing permissions and
58 | limitations under the License.
59 | ```
60 |
61 | ## Disclaimer
62 |
63 | The developer(s) of this application does not have any affiliation with the content providers that are freely available in the internet.
64 |
--------------------------------------------------------------------------------
/app/src/main/java/eu/kanade/tachiyomi/network/Requests.kt:
--------------------------------------------------------------------------------
1 | package eu.kanade.tachiyomi.network
2 |
3 | import okhttp3.CacheControl
4 | import okhttp3.FormBody
5 | import okhttp3.Headers
6 | import okhttp3.HttpUrl
7 | import okhttp3.HttpUrl.Companion.toHttpUrl
8 | import okhttp3.Request
9 | import okhttp3.RequestBody
10 | import java.util.concurrent.TimeUnit.MINUTES
11 |
12 | private val DEFAULT_CACHE_CONTROL = CacheControl.Builder().maxAge(10, MINUTES).build()
13 | private val DEFAULT_HEADERS = Headers.Builder().build()
14 | private val DEFAULT_BODY: RequestBody = FormBody.Builder().build()
15 |
16 | fun GET(
17 | url: String,
18 | headers: Headers = DEFAULT_HEADERS,
19 | cache: CacheControl = DEFAULT_CACHE_CONTROL,
20 | ): Request {
21 | val nUrl = url.toHttpUrl()
22 | return GET(nUrl, headers, cache)
23 | }
24 |
25 | /**
26 | * @since extensions-lib 1.4
27 | */
28 | fun GET(
29 | url: HttpUrl,
30 | headers: Headers = DEFAULT_HEADERS,
31 | cache: CacheControl = DEFAULT_CACHE_CONTROL,
32 | ): Request {
33 | return Request.Builder()
34 | .url(url)
35 | .headers(headers)
36 | .cacheControl(cache)
37 | .build()
38 | }
39 |
40 | fun POST(
41 | url: String,
42 | headers: Headers = DEFAULT_HEADERS,
43 | body: RequestBody = DEFAULT_BODY,
44 | cache: CacheControl = DEFAULT_CACHE_CONTROL,
45 | ): Request {
46 | return Request.Builder()
47 | .url(url)
48 | .post(body)
49 | .headers(headers)
50 | .cacheControl(cache)
51 | .build()
52 | }
53 |
54 | fun PUT(
55 | url: String,
56 | headers: Headers = DEFAULT_HEADERS,
57 | body: RequestBody = DEFAULT_BODY,
58 | cache: CacheControl = DEFAULT_CACHE_CONTROL,
59 | ): Request {
60 | return Request.Builder()
61 | .url(url)
62 | .put(body)
63 | .headers(headers)
64 | .cacheControl(cache)
65 | .build()
66 | }
67 |
68 | fun DELETE(
69 | url: String,
70 | headers: Headers = DEFAULT_HEADERS,
71 | body: RequestBody = DEFAULT_BODY,
72 | cache: CacheControl = DEFAULT_CACHE_CONTROL,
73 | ): Request {
74 | return Request.Builder()
75 | .url(url)
76 | .delete(body)
77 | .headers(headers)
78 | .cacheControl(cache)
79 | .build()
80 | }
81 |
--------------------------------------------------------------------------------
/app/src/main/java/eu/kanade/tachiyomi/util/lang/RxCoroutineBridge.kt:
--------------------------------------------------------------------------------
1 | package eu.kanade.tachiyomi.util.lang
2 |
3 | import kotlinx.coroutines.CancellableContinuation
4 | import kotlinx.coroutines.InternalCoroutinesApi
5 | import kotlinx.coroutines.suspendCancellableCoroutine
6 | import rx.Observable
7 | import rx.Subscriber
8 | import rx.Subscription
9 | import kotlin.coroutines.resume
10 | import kotlin.coroutines.resumeWithException
11 |
12 | /*
13 | * Util functions for bridging RxJava and coroutines. Taken from TachiyomiEH/SY.
14 | */
15 |
16 | suspend fun Observable.awaitSingle(): T = single().awaitOne()
17 |
18 | @OptIn(InternalCoroutinesApi::class)
19 | private suspend fun Observable.awaitOne(): T = suspendCancellableCoroutine { cont ->
20 | cont.unsubscribeOnCancellation(
21 | subscribe(
22 | object : Subscriber() {
23 | override fun onStart() {
24 | request(1)
25 | }
26 |
27 | override fun onNext(t: T) {
28 | cont.resume(t)
29 | }
30 |
31 | override fun onCompleted() {
32 | if (cont.isActive) {
33 | cont.resumeWithException(
34 | IllegalStateException(
35 | "Should have invoked onNext",
36 | ),
37 | )
38 | }
39 | }
40 |
41 | override fun onError(e: Throwable) {
42 | /*
43 | * Rx1 observable throws NoSuchElementException if cancellation happened before
44 | * element emission. To mitigate this we try to atomically resume continuation with exception:
45 | * if resume failed, then we know that continuation successfully cancelled itself
46 | */
47 | val token = cont.tryResumeWithException(e)
48 | if (token != null) {
49 | cont.completeResume(token)
50 | }
51 | }
52 | },
53 | ),
54 | )
55 | }
56 |
57 | private fun CancellableContinuation.unsubscribeOnCancellation(sub: Subscription) =
58 | invokeOnCancellation { sub.unsubscribe() }
59 |
--------------------------------------------------------------------------------
/app/src/main/java/eu/kanade/tachiyomi/source/model/SManga.kt:
--------------------------------------------------------------------------------
1 | package eu.kanade.tachiyomi.source.model
2 |
3 | import java.io.Serializable
4 |
5 | interface SManga : Serializable {
6 |
7 | var url: String
8 |
9 | var title: String
10 |
11 | var artist: String?
12 |
13 | var author: String?
14 |
15 | var description: String?
16 |
17 | var genre: String?
18 |
19 | var status: Int
20 |
21 | var thumbnail_url: String?
22 |
23 | var update_strategy: UpdateStrategy
24 |
25 | var initialized: Boolean
26 |
27 | fun getGenres(): List? {
28 | if (genre.isNullOrBlank()) return null
29 | return genre?.split(", ")?.map { it.trim() }?.filterNot { it.isBlank() }?.distinct()
30 | }
31 |
32 | fun copyFrom(other: SManga) {
33 | if (other.author != null) {
34 | author = other.author
35 | }
36 |
37 | if (other.artist != null) {
38 | artist = other.artist
39 | }
40 |
41 | if (other.description != null) {
42 | description = other.description
43 | }
44 |
45 | if (other.genre != null) {
46 | genre = other.genre
47 | }
48 |
49 | if (other.thumbnail_url != null) {
50 | thumbnail_url = other.thumbnail_url
51 | }
52 |
53 | status = other.status
54 |
55 | update_strategy = other.update_strategy
56 |
57 | if (!initialized) {
58 | initialized = other.initialized
59 | }
60 | }
61 |
62 | fun copy() = create().also {
63 | it.url = url
64 | it.title = title
65 | it.artist = artist
66 | it.author = author
67 | it.description = description
68 | it.genre = genre
69 | it.status = status
70 | it.thumbnail_url = thumbnail_url
71 | it.update_strategy = update_strategy
72 | it.initialized = initialized
73 | }
74 |
75 | companion object {
76 | const val UNKNOWN = 0
77 | const val ONGOING = 1
78 | const val COMPLETED = 2
79 | const val LICENSED = 3
80 | const val PUBLISHING_FINISHED = 4
81 | const val CANCELLED = 5
82 | const val ON_HIATUS = 6
83 |
84 | fun create(): SManga {
85 | return SMangaImpl()
86 | }
87 |
88 | private const val serialVersionUID = 1L
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/app/src/main/java/me/schnitzel/apkbridge/ApkBridge.kt:
--------------------------------------------------------------------------------
1 | package me.schnitzel.apkbridge
2 |
3 | import android.content.Context
4 | import androidx.annotation.StringRes
5 | import androidx.compose.foundation.layout.fillMaxSize
6 | import androidx.compose.foundation.layout.padding
7 | import androidx.compose.material3.Scaffold
8 | import androidx.compose.material3.Text
9 | import androidx.compose.runtime.Composable
10 | import androidx.compose.ui.Modifier
11 | import androidx.compose.ui.draw.alpha
12 | import androidx.compose.ui.unit.dp
13 | import androidx.compose.ui.unit.sp
14 | import androidx.navigation.NavHostController
15 | import androidx.navigation.compose.NavHost
16 | import androidx.navigation.compose.composable
17 | import androidx.navigation.compose.rememberNavController
18 | import kotlinx.coroutines.Job
19 |
20 | enum class AppRoutes(@StringRes val title: Int) {
21 | Start(title = R.string.app_name), AppLogs(title = R.string.app_logs)
22 | }
23 |
24 | @Composable
25 | fun ApkBridge(
26 | context: Context,
27 | currentVersion: String?,
28 | model: AppViewModel,
29 | requestDownload: () -> Job,
30 | navController: NavHostController = rememberNavController()
31 | ) {
32 | Scaffold(modifier = Modifier.fillMaxSize(), topBar = {
33 | Text(
34 | text = "Current version: v$currentVersion",
35 | modifier = Modifier
36 | .padding(24.dp)
37 | .alpha(0.5f),
38 | fontSize = 12.sp
39 | )
40 | }) { innerPadding ->
41 | NavHost(
42 | navController = navController,
43 | startDestination = AppRoutes.Start.name,
44 | modifier = Modifier.padding(innerPadding)
45 | ) {
46 | composable(route = AppRoutes.Start.name) {
47 | MainScreen(
48 | navController = navController,
49 | context = context,
50 | modifier = Modifier.padding(innerPadding),
51 | model = model,
52 | requestDownload = requestDownload
53 | )
54 | }
55 | composable(route = AppRoutes.AppLogs.name) {
56 | LogScreen(
57 | navController = navController,
58 | context = context,
59 | model = model
60 | )
61 | }
62 | }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
10 |
11 |
12 |
13 |
23 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
41 |
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/app/src/main/java/eu/kanade/tachiyomi/animesource/model/SAnime.kt:
--------------------------------------------------------------------------------
1 | package eu.kanade.tachiyomi.animesource.model
2 |
3 | import eu.kanade.tachiyomi.source.model.UpdateStrategy
4 | import java.io.Serializable
5 |
6 | interface SAnime : Serializable {
7 |
8 | var url: String
9 |
10 | var title: String
11 |
12 | var artist: String?
13 |
14 | var author: String?
15 |
16 | var description: String?
17 |
18 | var genre: String?
19 |
20 | var status: Int
21 |
22 | var thumbnail_url: String?
23 |
24 | var update_strategy: UpdateStrategy
25 |
26 | var initialized: Boolean
27 |
28 | fun getGenres(): List? {
29 | if (genre.isNullOrBlank()) return null
30 | return genre?.split(", ")?.map { it.trim() }?.filterNot { it.isBlank() }?.distinct()
31 | }
32 |
33 | fun copyFrom(other: SAnime) {
34 | if (other.author != null) {
35 | author = other.author
36 | }
37 |
38 | if (other.artist != null) {
39 | artist = other.artist
40 | }
41 |
42 | if (other.description != null) {
43 | description = other.description
44 | }
45 |
46 | if (other.genre != null) {
47 | genre = other.genre
48 | }
49 |
50 | if (other.thumbnail_url != null) {
51 | thumbnail_url = other.thumbnail_url
52 | }
53 |
54 | status = other.status
55 |
56 | update_strategy = other.update_strategy
57 |
58 | if (!initialized) {
59 | initialized = other.initialized
60 | }
61 | }
62 |
63 | fun copy() = create().also {
64 | it.url = url
65 | it.title = title
66 | it.artist = artist
67 | it.author = author
68 | it.description = description
69 | it.genre = genre
70 | it.status = status
71 | it.thumbnail_url = thumbnail_url
72 | it.update_strategy = update_strategy
73 | it.initialized = initialized
74 | }
75 |
76 | companion object {
77 | const val UNKNOWN = 0
78 | const val ONGOING = 1
79 | const val COMPLETED = 2
80 | const val LICENSED = 3
81 | const val PUBLISHING_FINISHED = 4
82 | const val CANCELLED = 5
83 | const val ON_HIATUS = 6
84 |
85 | fun create(): SAnime {
86 | return SAnimeImpl()
87 | }
88 |
89 | private const val serialVersionUID = 1L
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/app/src/main/java/tachiyomi/core/util/lang/CoroutinesExtensions.kt:
--------------------------------------------------------------------------------
1 | package tachiyomi.core.util.lang
2 |
3 | import kotlinx.coroutines.CoroutineScope
4 | import kotlinx.coroutines.CoroutineStart
5 | import kotlinx.coroutines.DelicateCoroutinesApi
6 | import kotlinx.coroutines.Dispatchers
7 | import kotlinx.coroutines.GlobalScope
8 | import kotlinx.coroutines.Job
9 | import kotlinx.coroutines.NonCancellable
10 | import kotlinx.coroutines.launch
11 | import kotlinx.coroutines.withContext
12 |
13 | /**
14 | * Think twice before using this. This is a delicate API. It is easy to accidentally create resource or memory leaks when GlobalScope is used.
15 | *
16 | * **Possible replacements**
17 | * - suspend function
18 | * - custom scope like view or presenter scope
19 | */
20 | @DelicateCoroutinesApi
21 | fun launchUI(block: suspend CoroutineScope.() -> Unit): Job =
22 | GlobalScope.launch(Dispatchers.Main, CoroutineStart.DEFAULT, block)
23 |
24 | /**
25 | * Think twice before using this. This is a delicate API. It is easy to accidentally create resource or memory leaks when GlobalScope is used.
26 | *
27 | * **Possible replacements**
28 | * - suspend function
29 | * - custom scope like view or presenter scope
30 | */
31 | @DelicateCoroutinesApi
32 | fun launchIO(block: suspend CoroutineScope.() -> Unit): Job =
33 | GlobalScope.launch(Dispatchers.IO, CoroutineStart.DEFAULT, block)
34 |
35 | /**
36 | * Think twice before using this. This is a delicate API. It is easy to accidentally create resource or memory leaks when GlobalScope is used.
37 | *
38 | * **Possible replacements**
39 | * - suspend function
40 | * - custom scope like view or presenter scope
41 | */
42 | @DelicateCoroutinesApi
43 | fun launchNow(block: suspend CoroutineScope.() -> Unit): Job =
44 | GlobalScope.launch(Dispatchers.Main, CoroutineStart.UNDISPATCHED, block)
45 |
46 | fun CoroutineScope.launchUI(block: suspend CoroutineScope.() -> Unit): Job =
47 | launch(Dispatchers.Main, block = block)
48 |
49 | fun CoroutineScope.launchIO(block: suspend CoroutineScope.() -> Unit): Job =
50 | launch(Dispatchers.IO, block = block)
51 |
52 | fun CoroutineScope.launchNonCancellable(block: suspend CoroutineScope.() -> Unit): Job =
53 | launchIO { withContext(NonCancellable, block) }
54 |
55 | suspend fun withUIContext(block: suspend CoroutineScope.() -> T) =
56 | withContext(Dispatchers.Main, block)
57 |
58 | suspend fun withIOContext(block: suspend CoroutineScope.() -> T) =
59 | withContext(Dispatchers.IO, block)
60 |
61 | suspend fun withNonCancellableContext(block: suspend CoroutineScope.() -> T) =
62 | withContext(NonCancellable, block)
63 |
--------------------------------------------------------------------------------
/app/src/main/java/eu/kanade/tachiyomi/source/CatalogueSource.kt:
--------------------------------------------------------------------------------
1 | package eu.kanade.tachiyomi.source
2 |
3 | import eu.kanade.tachiyomi.source.model.FilterList
4 | import eu.kanade.tachiyomi.source.model.MangasPage
5 | import eu.kanade.tachiyomi.util.lang.awaitSingle
6 | import rx.Observable
7 |
8 | interface CatalogueSource : MangaSource {
9 |
10 | /**
11 | * An ISO 639-1 compliant language code (two letters in lower case).
12 | */
13 | override val lang: String
14 |
15 | /**
16 | * Whether the source has support for latest updates.
17 | */
18 | val supportsLatest: Boolean
19 |
20 | /**
21 | * Get a page with a list of manga.
22 | *
23 | * @since extensions-lib 1.5
24 | * @param page the page number to retrieve.
25 | */
26 | @Suppress("DEPRECATION")
27 | suspend fun getPopularManga(page: Int): MangasPage {
28 | return fetchPopularManga(page).awaitSingle()
29 | }
30 |
31 | /**
32 | * Get a page with a list of manga.
33 | *
34 | * @since extensions-lib 1.5
35 | * @param page the page number to retrieve.
36 | * @param query the search query.
37 | * @param filters the list of filters to apply.
38 | */
39 | @Suppress("DEPRECATION")
40 | suspend fun getSearchManga(page: Int, query: String, filters: FilterList): MangasPage {
41 | return fetchSearchManga(page, query, filters).awaitSingle()
42 | }
43 |
44 | /**
45 | * Get a page with a list of latest manga updates.
46 | *
47 | * @since extensions-lib 1.5
48 | * @param page the page number to retrieve.
49 | */
50 | @Suppress("DEPRECATION")
51 | suspend fun getLatestUpdates(page: Int): MangasPage {
52 | return fetchLatestUpdates(page).awaitSingle()
53 | }
54 |
55 | /**
56 | * Returns the list of filters for the source.
57 | */
58 | fun getFilterList(): FilterList
59 |
60 | @Deprecated(
61 | "Use the non-RxJava API instead",
62 | ReplaceWith("getPopularManga"),
63 | )
64 | fun fetchPopularManga(page: Int): Observable =
65 | throw IllegalStateException("Not used")
66 |
67 | @Deprecated(
68 | "Use the non-RxJava API instead",
69 | ReplaceWith("getSearchManga"),
70 | )
71 | fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable =
72 | throw IllegalStateException("Not used")
73 |
74 | @Deprecated(
75 | "Use the non-RxJava API instead",
76 | ReplaceWith("getLatestUpdates"),
77 | )
78 | fun fetchLatestUpdates(page: Int): Observable =
79 | throw IllegalStateException("Not used")
80 | }
--------------------------------------------------------------------------------
/app/src/main/java/eu/kanade/tachiyomi/source/MangaSource.kt:
--------------------------------------------------------------------------------
1 | package eu.kanade.tachiyomi.source
2 |
3 | import eu.kanade.tachiyomi.source.model.Page
4 | import eu.kanade.tachiyomi.source.model.SChapter
5 | import eu.kanade.tachiyomi.source.model.SManga
6 | import eu.kanade.tachiyomi.util.lang.awaitSingle
7 | import rx.Observable
8 |
9 | /**
10 | * A basic interface for creating a source. It could be an online source, a local source, etc.
11 | */
12 | interface MangaSource {
13 |
14 | /**
15 | * ID for the source. Must be unique.
16 | */
17 | val id: Long
18 |
19 | /**
20 | * Name of the source.
21 | */
22 | val name: String
23 |
24 | val lang: String
25 | get() = ""
26 |
27 | /**
28 | * Get the updated details for a manga.
29 | *
30 | * @since extensions-lib 1.5
31 | * @param manga the manga to update.
32 | * @return the updated manga.
33 | */
34 | @Suppress("DEPRECATION")
35 | suspend fun getMangaDetails(manga: SManga): SManga {
36 | return fetchMangaDetails(manga).awaitSingle()
37 | }
38 |
39 | /**
40 | * Get all the available chapters for a manga.
41 | *
42 | * @since extensions-lib 1.5
43 | * @param manga the manga to update.
44 | * @return the chapters for the manga.
45 | */
46 | @Suppress("DEPRECATION")
47 | suspend fun getChapterList(manga: SManga): List {
48 | return fetchChapterList(manga).awaitSingle()
49 | }
50 |
51 | /**
52 | * Get the list of pages a chapter has. Pages should be returned
53 | * in the expected order; the index is ignored.
54 | *
55 | * @since extensions-lib 1.5
56 | * @param chapter the chapter.
57 | * @return the pages for the chapter.
58 | */
59 | @Suppress("DEPRECATION")
60 | suspend fun getPageList(chapter: SChapter): List {
61 | return fetchPageList(chapter).awaitSingle()
62 | }
63 |
64 | @Deprecated(
65 | "Use the non-RxJava API instead",
66 | ReplaceWith("getMangaDetails"),
67 | )
68 | fun fetchMangaDetails(manga: SManga): Observable =
69 | throw IllegalStateException("Not used")
70 |
71 | @Deprecated(
72 | "Use the non-RxJava API instead",
73 | ReplaceWith("getChapterList"),
74 | )
75 | fun fetchChapterList(manga: SManga): Observable> =
76 | throw IllegalStateException("Not used")
77 |
78 | @Deprecated(
79 | "Use the non-RxJava API instead",
80 | ReplaceWith("getPageList"),
81 | )
82 | fun fetchPageList(chapter: SChapter): Observable> =
83 | throw IllegalStateException("Not used")
84 | }
--------------------------------------------------------------------------------
/app/src/main/java/tachiyomi/source/local/entries/manga/LocalMangaSource.kt:
--------------------------------------------------------------------------------
1 | package tachiyomi.source.local.entries.manga
2 |
3 | import android.content.Context
4 | import eu.kanade.tachiyomi.source.CatalogueSource
5 | import eu.kanade.tachiyomi.source.MangaSource
6 | import eu.kanade.tachiyomi.source.UnmeteredSource
7 | import eu.kanade.tachiyomi.source.model.FilterList
8 | import eu.kanade.tachiyomi.source.model.MangasPage
9 | import eu.kanade.tachiyomi.source.model.SChapter
10 | import eu.kanade.tachiyomi.source.model.SManga
11 | import rx.Observable
12 | import tachiyomi.core.util.lang.withIOContext
13 | import tachiyomi.domain.entries.manga.model.Manga
14 | import tachiyomi.source.local.filter.manga.MangaOrderBy
15 | import java.util.concurrent.TimeUnit
16 |
17 | class LocalMangaSource(
18 | private val context: Context,
19 | ) : CatalogueSource, UnmeteredSource {
20 |
21 |
22 | private val POPULAR_FILTERS = FilterList(MangaOrderBy.Popular())
23 | private val LATEST_FILTERS = FilterList(MangaOrderBy.Latest())
24 |
25 | override val name: String = "Local manga source"
26 |
27 | override val id: Long = ID
28 |
29 | override val lang: String = "other"
30 |
31 | override fun toString() = name
32 |
33 | override val supportsLatest: Boolean = true
34 |
35 | // Browse related
36 | override fun fetchPopularManga(page: Int) = fetchSearchManga(page, "", POPULAR_FILTERS)
37 |
38 | override fun fetchLatestUpdates(page: Int) = fetchSearchManga(page, "", LATEST_FILTERS)
39 |
40 | override fun fetchSearchManga(
41 | page: Int,
42 | query: String,
43 | filters: FilterList
44 | ): Observable {
45 | return Observable.just(MangasPage(emptyList(), false))
46 | }
47 |
48 | // Manga details related
49 | override suspend fun getMangaDetails(manga: SManga): SManga = withIOContext {
50 | manga
51 | }
52 |
53 | // Chapters
54 | override suspend fun getChapterList(manga: SManga): List {
55 | return emptyList()
56 | }
57 |
58 | // Filters
59 | override fun getFilterList() = FilterList(MangaOrderBy.Popular())
60 |
61 | // Unused stuff
62 | override suspend fun getPageList(chapter: SChapter) =
63 | throw UnsupportedOperationException("Unused")
64 |
65 | companion object {
66 | const val ID = 0L
67 | const val HELP_URL = "https://aniyomi.org/help/guides/local-manga/"
68 |
69 | private val LATEST_THRESHOLD = TimeUnit.MILLISECONDS.convert(7, TimeUnit.DAYS)
70 | }
71 | }
72 |
73 | fun Manga.isLocal(): Boolean = source == LocalMangaSource.ID
74 |
75 | fun MangaSource.isLocal(): Boolean = id == LocalMangaSource.ID
76 |
--------------------------------------------------------------------------------
/app/src/main/java/eu/kanade/tachiyomi/animesource/model/Hoster.kt:
--------------------------------------------------------------------------------
1 | package eu.kanade.tachiyomi.animesource.model
2 |
3 | import eu.kanade.tachiyomi.animesource.model.SerializableVideo.Companion.serialize
4 | import eu.kanade.tachiyomi.animesource.model.SerializableVideo.Companion.toVideoList
5 | import kotlinx.serialization.Serializable
6 | import kotlinx.serialization.encodeToString
7 | import kotlinx.serialization.json.Json
8 |
9 | open class Hoster(
10 | val hosterUrl: String = "",
11 | val hosterName: String = "",
12 | val videoList: List