diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt
index b775fd1e8..f65ac8099 100755
--- a/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt
@@ -50,7 +50,6 @@ import nl.adaptivity.xmlutil.serialization.XML
 import okhttp3.Response
 import tachiyomi.core.metadata.comicinfo.COMIC_INFO_FILE
 import tachiyomi.core.metadata.comicinfo.ComicInfo
-import tachiyomi.core.util.lang.awaitSingle
 import tachiyomi.core.util.lang.launchIO
 import tachiyomi.core.util.lang.launchNow
 import tachiyomi.core.util.lang.withIOContext
@@ -380,7 +379,7 @@ class Downloader(
                         if (page.imageUrl.isNullOrEmpty()) {
                             page.status = Page.State.LOAD_PAGE
                             try {
-                                page.imageUrl = download.source.fetchImageUrl(page).awaitSingle()
+                                page.imageUrl = download.source.getImageUrl(page)
                             } catch (e: Throwable) {
                                 page.status = Page.State.ERROR
                             }
diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/online/all/EHentai.kt b/app/src/main/java/eu/kanade/tachiyomi/source/online/all/EHentai.kt
index 6baabe493..c31c7401d 100755
--- a/app/src/main/java/eu/kanade/tachiyomi/source/online/all/EHentai.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/source/online/all/EHentai.kt
@@ -54,7 +54,7 @@ import exh.util.nullIfBlank
 import exh.util.trimAll
 import exh.util.trimOrNull
 import exh.util.urlImportFetchSearchManga
-import kotlinx.serialization.decodeFromString
+import exh.util.urlImportFetchSearchMangaSuspend
 import kotlinx.serialization.json.Json
 import kotlinx.serialization.json.JsonObject
 import kotlinx.serialization.json.add
@@ -456,27 +456,44 @@ class EHentai(
     }
 
     private fun <T : MangasPage> Observable<T>.checkValid(): Observable<MangasPage> = map {
-        if (exh && it.mangas.isEmpty() && preferences.igneousVal().get().equals("mystery", true)) {
-            throw Exception("Invalid igneous cookie, try re-logging or finding a correct one to input in the login menu")
-        } else {
-            it
-        }
+        it.checkValid()
     }
 
+    private fun <T : MangasPage> T.checkValid(): MangasPage =
+        if (exh && mangas.isEmpty() && preferences.igneousVal().get().equals("mystery", true)) {
+            throw Exception("Invalid igneous cookie, try re-logging or finding a correct one to input in the login menu")
+        } else {
+            this
+        }
+
     override fun fetchLatestUpdates(page: Int): Observable<MangasPage> {
-        return super.fetchLatestUpdates(page).checkValid()
+        return super<HttpSource>.fetchLatestUpdates(page).checkValid()
+    }
+
+    override suspend fun getLatestUpdates(page: Int): MangasPage {
+        return super<HttpSource>.getLatestUpdates(page).checkValid()
     }
 
     override fun fetchPopularManga(page: Int): Observable<MangasPage> {
-        return super.fetchPopularManga(page).checkValid()
+        return super<HttpSource>.fetchPopularManga(page).checkValid()
+    }
+
+    override suspend fun getPopularManga(page: Int): MangasPage {
+        return super<HttpSource>.getPopularManga(page).checkValid()
     }
 
     // Support direct URL importing
     override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> =
         urlImportFetchSearchManga(context, query) {
-            super.fetchSearchManga(page, query, filters).checkValid()
+            super<HttpSource>.fetchSearchManga(page, query, filters).checkValid()
         }
 
+    override suspend fun getSearchManga(page: Int, query: String, filters: FilterList): MangasPage {
+        return urlImportFetchSearchMangaSuspend(context, query) {
+            super<HttpSource>.getSearchManga(page, query, filters).checkValid()
+        }
+    }
+
     override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
         val toplist = ToplistOption.values()[filters.firstNotNullOfOrNull { (it as? ToplistOptions)?.state } ?: 0]
         if (toplist != ToplistOption.NONE) {
diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/online/all/MangaDex.kt b/app/src/main/java/eu/kanade/tachiyomi/source/online/all/MangaDex.kt
index b8988f648..92a3d1ca3 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/source/online/all/MangaDex.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/source/online/all/MangaDex.kt
@@ -159,6 +159,7 @@ class MangaDex(delegate: HttpSource, val context: Context) :
         return mangaHandler.getMangaFromChapterId(id)?.let { MdUtil.buildMangaUrl(it) }
     }
 
+    @Deprecated("Use the non-RxJava API instead", replaceWith = ReplaceWith("getLatestUpdates"))
     override fun fetchLatestUpdates(page: Int): Observable<MangasPage> {
         val request = delegate.latestUpdatesRequest(page)
         val url = request.url.newBuilder()
@@ -171,6 +172,16 @@ class MangaDex(delegate: HttpSource, val context: Context) :
             }
     }
 
+    override suspend fun getLatestUpdates(page: Int): MangasPage {
+        val request = delegate.latestUpdatesRequest(page)
+        val url = request.url.newBuilder()
+            .removeAllQueryParameters("includeFutureUpdates")
+            .build()
+
+        val response = client.newCall(request.newBuilder().url(url).build()).awaitSuccess()
+        return delegate.latestUpdatesParse(response)
+    }
+
     @Deprecated("Use the 1.x API instead", replaceWith = ReplaceWith("getMangaDetails"))
     override fun fetchMangaDetails(manga: SManga): Observable<SManga> {
         return mangaHandler.fetchMangaDetailsObservable(manga, id)
@@ -198,22 +209,25 @@ class MangaDex(delegate: HttpSource, val context: Context) :
         return pageHandler.fetchPageList(chapter, usePort443Only(), dataSaver(), delegate)
     }
 
-    override fun fetchImage(page: Page): Observable<Response> {
-        val call = pageHandler.getImageCall(page)
-        return call?.asObservableSuccess() ?: super.fetchImage(page)
-    }
-
     override suspend fun getImage(page: Page): Response {
         val call = pageHandler.getImageCall(page)
         return call?.awaitSuccess() ?: super.getImage(page)
     }
 
+    @Deprecated("Use the non-RxJava API instead", replaceWith = ReplaceWith("getImageUrl"))
     override fun fetchImageUrl(page: Page): Observable<String> {
         return pageHandler.fetchImageUrl(page) {
+            @Suppress("DEPRECATION")
             super.fetchImageUrl(it)
         }
     }
 
+    override suspend fun getImageUrl(page: Page): String {
+        return pageHandler.getImageUrl(page) {
+            super.getImageUrl(page)
+        }
+    }
+
     // MetadataSource methods
     override val metaClass: KClass<MangaDexSearchMetadata> = MangaDexSearchMetadata::class
 
diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/online/all/MergedSource.kt b/app/src/main/java/eu/kanade/tachiyomi/source/online/all/MergedSource.kt
index b197ecfd9..b2d073cc4 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/source/online/all/MergedSource.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/source/online/all/MergedSource.kt
@@ -61,15 +61,20 @@ class MergedSource : HttpSource() {
     @Deprecated("Use the 1.x API instead", replaceWith = ReplaceWith("getChapterList"))
     override fun fetchChapterList(manga: SManga) = throw UnsupportedOperationException()
     override suspend fun getChapterList(manga: SManga) = throw UnsupportedOperationException()
-    override fun fetchImage(page: Page) = throw UnsupportedOperationException()
     override suspend fun getImage(page: Page): Response = throw UnsupportedOperationException()
+    @Deprecated("Use the non-RxJava API instead", replaceWith = ReplaceWith("getImageUrl"))
     override fun fetchImageUrl(page: Page) = throw UnsupportedOperationException()
+    override suspend fun getImageUrl(page: Page) = throw UnsupportedOperationException()
 
     @Deprecated("Use the 1.x API instead", replaceWith = ReplaceWith("getPageList"))
     override fun fetchPageList(chapter: SChapter) = throw UnsupportedOperationException()
     override suspend fun getPageList(chapter: SChapter) = throw UnsupportedOperationException()
+    @Deprecated("Use the non-RxJava API instead", replaceWith = ReplaceWith("getLatestUpdates"))
     override fun fetchLatestUpdates(page: Int) = throw UnsupportedOperationException()
+    override suspend fun getLatestUpdates(page: Int) = throw UnsupportedOperationException()
+    @Deprecated("Use the non-RxJava API instead", replaceWith = ReplaceWith("getPopularManga"))
     override fun fetchPopularManga(page: Int) = throw UnsupportedOperationException()
+    override suspend fun getPopularManga(page: Int) = throw UnsupportedOperationException()
 
     override suspend fun getMangaDetails(manga: SManga): SManga {
         return withIOContext {
diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/online/all/NHentai.kt b/app/src/main/java/eu/kanade/tachiyomi/source/online/all/NHentai.kt
index fa4633445..cbe4a7f97 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/source/online/all/NHentai.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/source/online/all/NHentai.kt
@@ -10,6 +10,7 @@ import eu.kanade.tachiyomi.source.PagePreviewInfo
 import eu.kanade.tachiyomi.source.PagePreviewPage
 import eu.kanade.tachiyomi.source.PagePreviewSource
 import eu.kanade.tachiyomi.source.model.FilterList
+import eu.kanade.tachiyomi.source.model.MangasPage
 import eu.kanade.tachiyomi.source.model.SChapter
 import eu.kanade.tachiyomi.source.model.SManga
 import eu.kanade.tachiyomi.source.online.HttpSource
@@ -22,9 +23,9 @@ import exh.metadata.metadata.base.RaisedTag
 import exh.source.DelegatedHttpSource
 import exh.util.trimOrNull
 import exh.util.urlImportFetchSearchManga
+import exh.util.urlImportFetchSearchMangaSuspend
 import kotlinx.serialization.SerialName
 import kotlinx.serialization.Serializable
-import kotlinx.serialization.decodeFromString
 import kotlinx.serialization.json.Json
 import okhttp3.CacheControl
 import okhttp3.Response
@@ -52,9 +53,15 @@ class NHentai(delegate: HttpSource, val context: Context) :
     // Support direct URL importing
     override fun fetchSearchManga(page: Int, query: String, filters: FilterList) =
         urlImportFetchSearchManga(context, query) {
-            super.fetchSearchManga(page, query, filters)
+            super<DelegatedHttpSource>.fetchSearchManga(page, query, filters)
         }
 
+    override suspend fun getSearchManga(page: Int, query: String, filters: FilterList): MangasPage {
+        return urlImportFetchSearchMangaSuspend(context, query) {
+            super<DelegatedHttpSource>.getSearchManga(page, query, filters,)
+        }
+    }
+
     override suspend fun getMangaDetails(manga: SManga): SManga {
         val response = client.newCall(mangaDetailsRequest(manga)).awaitSuccess()
         return parseToManga(manga, response)
diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/online/english/EightMuses.kt b/app/src/main/java/eu/kanade/tachiyomi/source/online/english/EightMuses.kt
index 5c00c4443..98853c6f4 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/source/online/english/EightMuses.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/source/online/english/EightMuses.kt
@@ -5,6 +5,7 @@ import android.net.Uri
 import androidx.core.net.toUri
 import eu.kanade.tachiyomi.network.awaitSuccess
 import eu.kanade.tachiyomi.source.model.FilterList
+import eu.kanade.tachiyomi.source.model.MangasPage
 import eu.kanade.tachiyomi.source.model.SManga
 import eu.kanade.tachiyomi.source.online.HttpSource
 import eu.kanade.tachiyomi.source.online.MetadataSource
@@ -15,6 +16,7 @@ import exh.metadata.metadata.EightMusesSearchMetadata
 import exh.metadata.metadata.base.RaisedTag
 import exh.source.DelegatedHttpSource
 import exh.util.urlImportFetchSearchManga
+import exh.util.urlImportFetchSearchMangaSuspend
 import org.jsoup.nodes.Document
 import org.jsoup.nodes.Element
 
@@ -28,11 +30,18 @@ class EightMuses(delegate: HttpSource, val context: Context) :
     override val lang = "en"
 
     // Support direct URL importing
+    @Deprecated("Use the non-RxJava API instead", replaceWith = ReplaceWith("getSearchManga"))
     override fun fetchSearchManga(page: Int, query: String, filters: FilterList) =
         urlImportFetchSearchManga(context, query) {
-            super.fetchSearchManga(page, query, filters)
+            super<DelegatedHttpSource>.fetchSearchManga(page, query, filters)
         }
 
+    override suspend fun getSearchManga(page: Int, query: String, filters: FilterList): MangasPage {
+        return urlImportFetchSearchMangaSuspend(context, query) {
+            super<DelegatedHttpSource>.getSearchManga(page, query, filters,)
+        }
+    }
+
     override suspend fun getMangaDetails(manga: SManga): SManga {
         val response = client.newCall(mangaDetailsRequest(manga)).awaitSuccess()
         return parseToManga(manga, response.asJsoup())
diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/online/english/HBrowse.kt b/app/src/main/java/eu/kanade/tachiyomi/source/online/english/HBrowse.kt
index 2de327ecc..ce63b5fbf 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/source/online/english/HBrowse.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/source/online/english/HBrowse.kt
@@ -4,6 +4,7 @@ import android.content.Context
 import android.net.Uri
 import eu.kanade.tachiyomi.network.awaitSuccess
 import eu.kanade.tachiyomi.source.model.FilterList
+import eu.kanade.tachiyomi.source.model.MangasPage
 import eu.kanade.tachiyomi.source.model.SManga
 import eu.kanade.tachiyomi.source.online.HttpSource
 import eu.kanade.tachiyomi.source.online.MetadataSource
@@ -14,6 +15,7 @@ import exh.metadata.metadata.HBrowseSearchMetadata
 import exh.metadata.metadata.base.RaisedTag
 import exh.source.DelegatedHttpSource
 import exh.util.urlImportFetchSearchManga
+import exh.util.urlImportFetchSearchMangaSuspend
 import org.jsoup.nodes.Document
 import org.jsoup.nodes.Element
 
@@ -27,11 +29,18 @@ class HBrowse(delegate: HttpSource, val context: Context) :
     override val lang = "en"
 
     // Support direct URL importing
+    @Deprecated("Use the non-RxJava API instead", replaceWith = ReplaceWith("getSearchManga"))
     override fun fetchSearchManga(page: Int, query: String, filters: FilterList) =
         urlImportFetchSearchManga(context, query) {
-            super.fetchSearchManga(page, query, filters)
+            super<DelegatedHttpSource>.fetchSearchManga(page, query, filters)
         }
 
+    override suspend fun getSearchManga(page: Int, query: String, filters: FilterList): MangasPage {
+        return urlImportFetchSearchMangaSuspend(context, query) {
+            super<DelegatedHttpSource>.getSearchManga(page, query, filters,)
+        }
+    }
+
     override suspend fun getMangaDetails(manga: SManga): SManga {
         val response = client.newCall(mangaDetailsRequest(manga)).awaitSuccess()
         return parseToManga(manga, response.asJsoup())
diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/online/english/Pururin.kt b/app/src/main/java/eu/kanade/tachiyomi/source/online/english/Pururin.kt
index dc14a971b..f5b1eb48f 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/source/online/english/Pururin.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/source/online/english/Pururin.kt
@@ -19,6 +19,7 @@ import exh.source.DelegatedHttpSource
 import exh.util.dropBlank
 import exh.util.trimAll
 import exh.util.urlImportFetchSearchManga
+import exh.util.urlImportFetchSearchMangaSuspend
 import org.jsoup.nodes.Document
 import rx.Observable
 
@@ -39,6 +40,7 @@ class Pururin(delegate: HttpSource, val context: Context) :
     override fun newMetaInstance() = PururinSearchMetadata()
 
     // Support direct URL importing
+    @Deprecated("Use the non-RxJava API instead", replaceWith = ReplaceWith("getSearchManga"))
     override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
         val trimmedIdQuery = query.trim().removePrefix("id:")
         val newQuery = if ((trimmedIdQuery.toIntOrNull() ?: -1) >= 0) {
@@ -48,7 +50,19 @@ class Pururin(delegate: HttpSource, val context: Context) :
         }
 
         return urlImportFetchSearchManga(context, newQuery) {
-            super.fetchSearchManga(page, query, filters)
+            super<DelegatedHttpSource>.fetchSearchManga(page, query, filters)
+        }
+    }
+
+    override suspend fun getSearchManga(page: Int, query: String, filters: FilterList): MangasPage {
+        val trimmedIdQuery = query.trim().removePrefix("id:")
+        val newQuery = if ((trimmedIdQuery.toIntOrNull() ?: -1) >= 0) {
+            "$baseUrl/gallery/$trimmedIdQuery/-"
+        } else {
+            query
+        }
+        return urlImportFetchSearchMangaSuspend(context, newQuery) {
+            super<DelegatedHttpSource>.getSearchManga(page, query, filters,)
         }
     }
 
diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/online/english/Tsumino.kt b/app/src/main/java/eu/kanade/tachiyomi/source/online/english/Tsumino.kt
index 63c1f25bb..0babef369 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/source/online/english/Tsumino.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/source/online/english/Tsumino.kt
@@ -19,6 +19,7 @@ import exh.source.DelegatedHttpSource
 import exh.util.dropBlank
 import exh.util.trimAll
 import exh.util.urlImportFetchSearchManga
+import exh.util.urlImportFetchSearchMangaSuspend
 import org.jsoup.nodes.Document
 import rx.Observable
 import java.text.SimpleDateFormat
@@ -34,11 +35,18 @@ class Tsumino(delegate: HttpSource, val context: Context) :
     override val lang = "en"
 
     // Support direct URL importing
+    @Deprecated("Use the non-RxJava API instead", replaceWith = ReplaceWith("getSearchManga"))
     override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> =
         urlImportFetchSearchManga(context, query) {
-            super.fetchSearchManga(page, query, filters)
+            super<DelegatedHttpSource>.fetchSearchManga(page, query, filters)
         }
 
+    override suspend fun getSearchManga(page: Int, query: String, filters: FilterList): MangasPage {
+        return urlImportFetchSearchMangaSuspend(context, query) {
+            super<DelegatedHttpSource>.getSearchManga(page, query, filters,)
+        }
+    }
+
     override suspend fun mapUrlToMangaUrl(uri: Uri): String? {
         val lcFirstPathSegment = uri.pathSegments.firstOrNull()?.lowercase(Locale.ROOT) ?: return null
         if (lcFirstPathSegment != "read" && lcFirstPathSegment != "book" && lcFirstPathSegment != "entry") {
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/SearchScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/SearchScreenModel.kt
index 898377f0e..66736f390 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/SearchScreenModel.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/SearchScreenModel.kt
@@ -19,7 +19,6 @@ import kotlinx.coroutines.flow.update
 import kotlinx.coroutines.isActive
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.withContext
-import tachiyomi.core.util.lang.awaitSingle
 import tachiyomi.domain.manga.interactor.GetManga
 import tachiyomi.domain.manga.interactor.NetworkToLocalManga
 import tachiyomi.domain.manga.model.Manga
@@ -143,7 +142,7 @@ abstract class SearchScreenModel(
 
                     try {
                         val page = withContext(coroutineDispatcher) {
-                            source.fetchSearchManga(1, query, source.getFilterList()).awaitSingle()
+                            source.getSearchManga(1, query, source.getFilterList())
                         }
 
                         val titles = page.mangas.map {
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/HttpPageLoader.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/HttpPageLoader.kt
index 54e359ba7..53a3bbb0e 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/HttpPageLoader.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/HttpPageLoader.kt
@@ -20,7 +20,6 @@ import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.flow
 import kotlinx.coroutines.runInterruptible
 import kotlinx.coroutines.suspendCancellableCoroutine
-import tachiyomi.core.util.lang.awaitSingle
 import tachiyomi.core.util.lang.launchIO
 import tachiyomi.core.util.lang.withIOContext
 import uy.kohesive.injekt.Injekt
@@ -211,7 +210,7 @@ internal class HttpPageLoader(
         try {
             if (page.imageUrl.isNullOrEmpty()) {
                 page.status = Page.State.LOAD_PAGE
-                page.imageUrl = source.fetchImageUrl(page).awaitSingle()
+                page.imageUrl = source.getImageUrl(page)
             }
             val imageUrl = page.imageUrl!!
 
diff --git a/app/src/main/java/exh/md/handlers/BilibiliHandler.kt b/app/src/main/java/exh/md/handlers/BilibiliHandler.kt
index 080d52d7b..86e245ef7 100644
--- a/app/src/main/java/exh/md/handlers/BilibiliHandler.kt
+++ b/app/src/main/java/exh/md/handlers/BilibiliHandler.kt
@@ -1,7 +1,6 @@
 package exh.md.handlers
 
 import eu.kanade.tachiyomi.network.POST
-import eu.kanade.tachiyomi.network.asObservableSuccess
 import eu.kanade.tachiyomi.network.awaitSuccess
 import eu.kanade.tachiyomi.network.interceptor.rateLimit
 import eu.kanade.tachiyomi.network.parseAs
@@ -22,6 +21,7 @@ import okhttp3.Request
 import okhttp3.RequestBody.Companion.toRequestBody
 import okhttp3.Response
 import rx.Observable
+import tachiyomi.core.util.lang.runAsObservable
 import uy.kohesive.injekt.injectLazy
 import java.util.concurrent.TimeUnit
 
@@ -154,12 +154,13 @@ class BilibiliHandler(currentClient: OkHttpClient) {
             .mapIndexed { i, page -> Page(i, page.path, "") }
     }
 
+    suspend fun getImageUrl(page: Page): String {
+        val response = client.newCall(imageUrlRequest(page)).awaitSuccess()
+        return imageUrlParse(response)
+    }
+
     fun fetchImageUrl(page: Page): Observable<String> {
-        return client.newCall(imageUrlRequest(page))
-            .asObservableSuccess()
-            .map {
-                imageUrlParse(it)
-            }
+        return runAsObservable { getImageUrl(page) }
     }
 
     private fun imageUrlRequest(page: Page): Request {
diff --git a/app/src/main/java/exh/md/handlers/PageHandler.kt b/app/src/main/java/exh/md/handlers/PageHandler.kt
index cf5ddd5a0..14f06c412 100644
--- a/app/src/main/java/exh/md/handlers/PageHandler.kt
+++ b/app/src/main/java/exh/md/handlers/PageHandler.kt
@@ -134,4 +134,13 @@ class PageHandler(
             else -> superMethod(page)
         }
     }
+
+    suspend fun getImageUrl(page: Page, superMethod: suspend (Page) -> String): String {
+        return when {
+            page.url.contains("/bfs/comic/") -> {
+                bilibiliHandler.getImageUrl(page)
+            }
+            else -> superMethod(page)
+        }
+    }
 }
diff --git a/app/src/main/java/exh/util/DataSaver.kt b/app/src/main/java/exh/util/DataSaver.kt
index e84626337..a9f4ab2c1 100644
--- a/app/src/main/java/exh/util/DataSaver.kt
+++ b/app/src/main/java/exh/util/DataSaver.kt
@@ -8,7 +8,6 @@ import eu.kanade.tachiyomi.source.Source
 import eu.kanade.tachiyomi.source.model.Page
 import eu.kanade.tachiyomi.source.online.HttpSource
 import okhttp3.Response
-import rx.Observable
 import tachiyomi.core.preference.Preference
 
 interface DataSaver {
@@ -22,15 +21,6 @@ interface DataSaver {
             }
         }
 
-        fun HttpSource.fetchImage(page: Page, dataSaver: DataSaver): Observable<Response> {
-            val imageUrl = page.imageUrl ?: return fetchImage(page)
-            page.imageUrl = dataSaver.compress(imageUrl)
-            return fetchImage(page)
-                .doOnNext {
-                    page.imageUrl = imageUrl
-                }
-        }
-
         suspend fun HttpSource.getImage(page: Page, dataSaver: DataSaver): Response {
             val imageUrl = page.imageUrl ?: return getImage(page)
             page.imageUrl = dataSaver.compress(imageUrl)
diff --git a/app/src/main/java/exh/util/SearchOverride.kt b/app/src/main/java/exh/util/SearchOverride.kt
index d1647ba16..7a68f3751 100644
--- a/app/src/main/java/exh/util/SearchOverride.kt
+++ b/app/src/main/java/exh/util/SearchOverride.kt
@@ -35,3 +35,29 @@ fun UrlImportableSource.urlImportFetchSearchManga(context: Context, query: Strin
         }
         else -> fail()
     }
+
+
+/**
+ * A version of fetchSearchManga that supports URL importing
+ */
+suspend fun UrlImportableSource.urlImportFetchSearchMangaSuspend(context: Context, query: String, fail: suspend () -> MangasPage): MangasPage =
+    when {
+        query.startsWith("http://") || query.startsWith("https://") -> {
+            val res = galleryAdder.addGallery(
+                context = context,
+                url = query,
+                fav = false,
+                forceSource = this
+            )
+
+            MangasPage(
+                if (res is GalleryAddEvent.Success) {
+                    listOf(res.manga.toSManga())
+                } else {
+                    emptyList()
+                },
+                false,
+            )
+        }
+        else -> fail()
+    }
diff --git a/core/src/main/java/eu/kanade/tachiyomi/network/OkHttpExtensions.kt b/core/src/main/java/eu/kanade/tachiyomi/network/OkHttpExtensions.kt
index 4cbb34812..e6eec02f4 100755
--- a/core/src/main/java/eu/kanade/tachiyomi/network/OkHttpExtensions.kt
+++ b/core/src/main/java/eu/kanade/tachiyomi/network/OkHttpExtensions.kt
@@ -58,6 +58,15 @@ fun Call.asObservable(): Observable<Response> {
     }
 }
 
+fun Call.asObservableSuccess(): Observable<Response> {
+    return asObservable().doOnNext { response ->
+        if (!response.isSuccessful) {
+            response.close()
+            throw HttpException(response.code)
+        }
+    }
+}
+
 // Based on https://github.com/gildor/kotlin-coroutines-okhttp
 @OptIn(ExperimentalCoroutinesApi::class)
 private suspend fun Call.await(callStack: Array<StackTraceElement>): Response {
@@ -95,6 +104,9 @@ suspend fun Call.await(): Response {
     return await(callStack)
 }
 
+/**
+ * @since extensions-lib 1.5
+ */
 suspend fun Call.awaitSuccess(): Response {
     val callStack = Exception().stackTrace.run { copyOfRange(1, size) }
     val response = await(callStack)
@@ -105,15 +117,6 @@ suspend fun Call.awaitSuccess(): Response {
     return response
 }
 
-fun Call.asObservableSuccess(): Observable<Response> {
-    return asObservable().doOnNext { response ->
-        if (!response.isSuccessful) {
-            response.close()
-            throw HttpException(response.code)
-        }
-    }
-}
-
 fun OkHttpClient.newCachelessCallWithProgress(request: Request, listener: ProgressListener): Call {
     val progressClient = newBuilder()
         .cache(null)
diff --git a/data/src/main/java/tachiyomi/data/source/SourcePagingSource.kt b/data/src/main/java/tachiyomi/data/source/SourcePagingSource.kt
index 9b9c7d9a0..998c75f33 100644
--- a/data/src/main/java/tachiyomi/data/source/SourcePagingSource.kt
+++ b/data/src/main/java/tachiyomi/data/source/SourcePagingSource.kt
@@ -7,7 +7,6 @@ import eu.kanade.tachiyomi.source.model.MangasPage
 import eu.kanade.tachiyomi.source.model.MetadataMangasPage
 import eu.kanade.tachiyomi.source.model.SManga
 import exh.metadata.metadata.RaisedSearchMetadata
-import tachiyomi.core.util.lang.awaitSingle
 import tachiyomi.core.util.lang.withIOContext
 import tachiyomi.domain.source.repository.SourcePagingSourceType
 
@@ -15,19 +14,19 @@ class SourceSearchPagingSource(source: CatalogueSource, val query: String, val f
     source,
 ) {
     override suspend fun requestNextPage(currentPage: Int): MangasPage {
-        return source.fetchSearchManga(currentPage, query, filters).awaitSingle()
+        return source.getSearchManga(currentPage, query, filters)
     }
 }
 
 class SourcePopularPagingSource(source: CatalogueSource) : SourcePagingSource(source) {
     override suspend fun requestNextPage(currentPage: Int): MangasPage {
-        return source.fetchPopularManga(currentPage).awaitSingle()
+        return source.getPopularManga(currentPage)
     }
 }
 
 class SourceLatestPagingSource(source: CatalogueSource) : SourcePagingSource(source) {
     override suspend fun requestNextPage(currentPage: Int): MangasPage {
-        return source.fetchLatestUpdates(currentPage).awaitSingle()
+        return source.getLatestUpdates(currentPage)
     }
 }
 
diff --git a/domain/src/main/java/tachiyomi/domain/source/model/StubSource.kt b/domain/src/main/java/tachiyomi/domain/source/model/StubSource.kt
index 38d38ef57..8b5f67ef5 100644
--- a/domain/src/main/java/tachiyomi/domain/source/model/StubSource.kt
+++ b/domain/src/main/java/tachiyomi/domain/source/model/StubSource.kt
@@ -4,7 +4,6 @@ import eu.kanade.tachiyomi.source.Source
 import eu.kanade.tachiyomi.source.model.Page
 import eu.kanade.tachiyomi.source.model.SChapter
 import eu.kanade.tachiyomi.source.model.SManga
-import rx.Observable
 
 class StubSource(
     override val id: Long,
@@ -14,36 +13,16 @@ class StubSource(
 
     private val isInvalid: Boolean = name.isBlank() || lang.isBlank()
 
-    override suspend fun getMangaDetails(manga: SManga): SManga {
+    override suspend fun getMangaDetails(manga: SManga): SManga =
         throw SourceNotInstalledException()
-    }
 
-    @Deprecated("Use the non-RxJava API instead", replaceWith = ReplaceWith("getMangaDetails"))
-    override fun fetchMangaDetails(manga: SManga): Observable<SManga> {
-        return Observable.error(SourceNotInstalledException())
-    }
-
-    override suspend fun getChapterList(manga: SManga): List<SChapter> {
+    override suspend fun getChapterList(manga: SManga): List<SChapter> =
         throw SourceNotInstalledException()
-    }
-
-    @Deprecated("Use the non-RxJava API instead", replaceWith = ReplaceWith("getChapterList"))
-    override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
-        return Observable.error(SourceNotInstalledException())
-    }
-
-    override suspend fun getPageList(chapter: SChapter): List<Page> {
+    override suspend fun getPageList(chapter: SChapter): List<Page> =
         throw SourceNotInstalledException()
-    }
 
-    @Deprecated("Use the non-RxJava API instead", replaceWith = ReplaceWith("getPageList"))
-    override fun fetchPageList(chapter: SChapter): Observable<List<Page>> {
-        return Observable.error(SourceNotInstalledException())
-    }
-
-    override fun toString(): String {
-        return if (isInvalid.not()) "$name (${lang.uppercase()})" else id.toString()
-    }
+    override fun toString(): String =
+        if (isInvalid.not()) "$name (${lang.uppercase()})" else id.toString()
 }
 
 class SourceNotInstalledException : Exception()
diff --git a/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/CatalogueSource.kt b/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/CatalogueSource.kt
index f9e416def..937fa28a9 100755
--- a/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/CatalogueSource.kt
+++ b/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/CatalogueSource.kt
@@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.source
 import eu.kanade.tachiyomi.source.model.FilterList
 import eu.kanade.tachiyomi.source.model.MangasPage
 import rx.Observable
+import tachiyomi.core.util.lang.awaitSingle
 
 interface CatalogueSource : Source {
 
@@ -17,30 +18,63 @@ interface CatalogueSource : Source {
     val supportsLatest: Boolean
 
     /**
-     * Returns an observable containing a page with a list of manga.
+     * Get a page with a list of manga.
      *
+     * @since extensions-lib 1.5
      * @param page the page number to retrieve.
      */
-    fun fetchPopularManga(page: Int): Observable<MangasPage>
+    @Suppress("DEPRECATION")
+    suspend fun getPopularManga(page: Int): MangasPage {
+        return fetchPopularManga(page).awaitSingle()
+    }
 
     /**
-     * Returns an observable containing a page with a list of manga.
+     * Get a page with a list of manga.
      *
+     * @since extensions-lib 1.5
      * @param page the page number to retrieve.
      * @param query the search query.
      * @param filters the list of filters to apply.
      */
-    fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage>
+    @Suppress("DEPRECATION")
+    suspend fun getSearchManga(page: Int, query: String, filters: FilterList): MangasPage {
+        return fetchSearchManga(page, query, filters).awaitSingle()
+    }
 
     /**
-     * Returns an observable containing a page with a list of latest manga updates.
+     * Get a page with a list of latest manga updates.
      *
+     * @since extensions-lib 1.5
      * @param page the page number to retrieve.
      */
-    fun fetchLatestUpdates(page: Int): Observable<MangasPage>
+    @Suppress("DEPRECATION")
+    suspend fun getLatestUpdates(page: Int): MangasPage {
+        return fetchLatestUpdates(page).awaitSingle()
+    }
 
     /**
      * Returns the list of filters for the source.
      */
     fun getFilterList(): FilterList
+
+    @Deprecated(
+        "Use the non-RxJava API instead",
+        ReplaceWith("getPopularManga"),
+    )
+    fun fetchPopularManga(page: Int): Observable<MangasPage> =
+        throw IllegalStateException("Not used")
+
+    @Deprecated(
+        "Use the non-RxJava API instead",
+        ReplaceWith("getSearchManga"),
+    )
+    fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> =
+        throw IllegalStateException("Not used")
+
+    @Deprecated(
+        "Use the non-RxJava API instead",
+        ReplaceWith("getLatestUpdates"),
+    )
+    fun fetchLatestUpdates(page: Int): Observable<MangasPage> =
+        throw IllegalStateException("Not used")
 }
diff --git a/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/ConfigurableSource.kt b/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/ConfigurableSource.kt
index d42885f80..e6a019ecc 100644
--- a/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/ConfigurableSource.kt
+++ b/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/ConfigurableSource.kt
@@ -1,6 +1,19 @@
 package eu.kanade.tachiyomi.source
 
+import android.app.Application
+import android.content.SharedPreferences
+import uy.kohesive.injekt.Injekt
+import uy.kohesive.injekt.api.get
+
 interface ConfigurableSource : Source {
 
+    /**
+     * Gets instance of [SharedPreferences] scoped to the specific source.
+     *
+     * @since extensions-lib 1.5
+     */
+    fun getPreferences(): SharedPreferences =
+        Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
+
     fun setupPreferenceScreen(screen: PreferenceScreen)
 }
diff --git a/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/Source.kt b/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/Source.kt
index f140eda74..83fcd7962 100755
--- a/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/Source.kt
+++ b/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/Source.kt
@@ -27,7 +27,7 @@ interface Source {
     /**
      * Get the updated details for a manga.
      *
-     * @since extensions-lib 1.4
+     * @since extensions-lib 1.5
      * @param manga the manga to update.
      * @return the updated manga.
      */
@@ -39,7 +39,7 @@ interface Source {
     /**
      * Get all the available chapters for a manga.
      *
-     * @since extensions-lib 1.4
+     * @since extensions-lib 1.5
      * @param manga the manga to update.
      * @return the chapters for the manga.
      */
@@ -52,7 +52,7 @@ interface Source {
      * Get the list of pages a chapter has. Pages should be returned
      * in the expected order; the index is ignored.
      *
-     * @since extensions-lib 1.4
+     * @since extensions-lib 1.5
      * @param chapter the chapter.
      * @return the pages for the chapter.
      */
@@ -61,41 +61,24 @@ interface Source {
         return fetchPageList(chapter).awaitSingle()
     }
 
-    /**
-     * Returns an observable with the updated details for a manga.
-     *
-     * @param manga the manga to update.
-     */
     @Deprecated(
         "Use the non-RxJava API instead",
         ReplaceWith("getMangaDetails"),
     )
-    fun fetchMangaDetails(manga: SManga): Observable<SManga> = throw IllegalStateException(
-        "Not used",
-    )
+    fun fetchMangaDetails(manga: SManga): Observable<SManga> =
+        throw IllegalStateException("Not used")
 
-    /**
-     * Returns an observable with all the available chapters for a manga.
-     *
-     * @param manga the manga to update.
-     */
     @Deprecated(
         "Use the non-RxJava API instead",
         ReplaceWith("getChapterList"),
     )
-    fun fetchChapterList(manga: SManga): Observable<List<SChapter>> = throw IllegalStateException(
-        "Not used",
-    )
+    fun fetchChapterList(manga: SManga): Observable<List<SChapter>> =
+        throw IllegalStateException("Not used")
 
-    /**
-     * Returns an observable with the list of pages a chapter has. Pages should be returned
-     * in the expected order; the index is ignored.
-     *
-     * @param chapter the chapter.
-     */
     @Deprecated(
         "Use the non-RxJava API instead",
         ReplaceWith("getPageList"),
     )
-    fun fetchPageList(chapter: SChapter): Observable<List<Page>> = Observable.empty()
+    fun fetchPageList(chapter: SChapter): Observable<List<Page>> =
+        throw IllegalStateException("Not used")
 }
diff --git a/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/online/HttpSource.kt b/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/online/HttpSource.kt
index 10b741132..677516cb3 100755
--- a/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/online/HttpSource.kt
+++ b/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/online/HttpSource.kt
@@ -21,6 +21,7 @@ import okhttp3.OkHttpClient
 import okhttp3.Request
 import okhttp3.Response
 import rx.Observable
+import tachiyomi.core.util.lang.awaitSingle
 import uy.kohesive.injekt.Injekt
 import uy.kohesive.injekt.api.get
 import java.net.URI
@@ -30,6 +31,7 @@ import java.security.MessageDigest
 /**
  * A simple implementation for sources from a website.
  */
+@Suppress("unused")
 abstract class HttpSource : CatalogueSource {
 
     /**
@@ -108,6 +110,7 @@ abstract class HttpSource : CatalogueSource {
      * @param versionId [Int] the version ID of the source
      * @return a unique ID for the source
      */
+    @Suppress("MemberVisibilityCanBePrivate")
     protected fun generateId(name: String, lang: String, versionId: Int): Long {
         val key = "${name.lowercase()}/$lang/$versionId"
         val bytes = MessageDigest.getInstance("MD5").digest(key.toByteArray())
@@ -231,11 +234,18 @@ abstract class HttpSource : CatalogueSource {
     abstract fun latestUpdatesParse(response: Response): MangasPage
 
     /**
-     * Returns an observable with the updated details for a manga. Normally it's not needed to
-     * override this method.
+     * Get the updated details for a manga.
+     * Normally it's not needed to override this method.
      *
-     * @param manga the manga to be updated.
+     * @param manga the manga to update.
+     * @return the updated manga.
      */
+    @Suppress("DEPRECATION")
+    override suspend fun getMangaDetails(manga: SManga): SManga {
+        return fetchMangaDetails(manga).awaitSingle()
+    }
+
+    @Deprecated("Use the non-RxJava API instead", replaceWith = ReplaceWith("getMangaDetails"))
     override fun fetchMangaDetails(manga: SManga): Observable<SManga> {
         return client.newCall(mangaDetailsRequest(manga))
             .asObservableSuccess()
@@ -262,11 +272,23 @@ abstract class HttpSource : CatalogueSource {
     protected abstract fun mangaDetailsParse(response: Response): SManga
 
     /**
-     * Returns an observable with the updated chapter list for a manga. Normally it's not needed to
-     * override this method.  If a manga is licensed an empty chapter list observable is returned
+     * Get all the available chapters for a manga.
+     * Normally it's not needed to override this method.
      *
-     * @param manga the manga to look for chapters.
+     * @param manga the manga to update.
+     * @return the chapters for the manga.
+     * @throws LicensedMangaChaptersException if a manga is licensed and therefore no chapters are available.
      */
+    @Suppress("DEPRECATION")
+    override suspend fun getChapterList(manga: SManga): List<SChapter> {
+        if (manga.status == SManga.LICENSED) {
+            throw LicensedMangaChaptersException()
+        }
+
+        return fetchChapterList(manga).awaitSingle()
+    }
+
+    @Deprecated("Use the non-RxJava API instead", replaceWith = ReplaceWith("getChapterList"))
     override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
         return if (manga.status != SManga.LICENSED) {
             client.newCall(chapterListRequest(manga))
@@ -297,10 +319,18 @@ abstract class HttpSource : CatalogueSource {
     protected abstract fun chapterListParse(response: Response): List<SChapter>
 
     /**
-     * Returns an observable with the page list for a chapter.
+     * Get the list of pages a chapter has. Pages should be returned
+     * in the expected order; the index is ignored.
      *
-     * @param chapter the chapter whose page list has to be fetched.
+     * @param chapter the chapter.
+     * @return the pages for the chapter.
      */
+    @Suppress("DEPRECATION")
+    override suspend fun getPageList(chapter: SChapter): List<Page> {
+        return fetchPageList(chapter).awaitSingle()
+    }
+
+    @Deprecated("Use the non-RxJava API instead", replaceWith = ReplaceWith("getPageList"))
     override fun fetchPageList(chapter: SChapter): Observable<List<Page>> {
         return client.newCall(pageListRequest(chapter))
             .asObservableSuccess()
@@ -330,8 +360,15 @@ abstract class HttpSource : CatalogueSource {
      * Returns an observable with the page containing the source url of the image. If there's any
      * error, it will return null instead of throwing an exception.
      *
+     * @since extensions-lib 1.5
      * @param page the page whose source image has to be fetched.
      */
+    @Suppress("DEPRECATION")
+    open suspend fun getImageUrl(page: Page): String {
+        return fetchImageUrl(page).awaitSingle()
+    }
+
+    @Deprecated("Use the non-RxJava API instead", replaceWith = ReplaceWith("getImageUrl"))
     open fun fetchImageUrl(page: Page): Observable<String> {
         return client.newCall(imageUrlRequest(page))
             .asObservableSuccess()
@@ -355,26 +392,14 @@ abstract class HttpSource : CatalogueSource {
      */
     protected abstract fun imageUrlParse(response: Response): String
 
-    /**
-     * Returns an observable with the response of the source image.
-     *
-     * @param page the page whose source image has to be downloaded.
-     */
-    /* SY --> */
-    open /* SY <-- */ fun fetchImage(page: Page): Observable<Response> {
-        // images will be cached or saved manually, so don't take up network cache
-        return client.newCachelessCallWithProgress(imageRequest(page), page)
-            .asObservableSuccess()
-    }
-
     /**
      * Returns the response of the source image.
+     * Typically does not need to be overridden.
      *
+     * @since extensions-lib 1.5
      * @param page the page whose source image has to be downloaded.
      */
-    /* SY --> */
-    open /* SY <-- */ suspend fun getImage(page: Page): Response {
-        // images will be cached or saved manually, so don't take up network cache
+    open suspend fun getImage(page: Page): Response {
         return client.newCachelessCallWithProgress(imageRequest(page), page)
             .awaitSuccess()
     }
diff --git a/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/online/HttpSourceFetcher.kt b/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/online/HttpSourceFetcher.kt
deleted file mode 100755
index 76c68e882..000000000
--- a/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/online/HttpSourceFetcher.kt
+++ /dev/null
@@ -1,25 +0,0 @@
-package eu.kanade.tachiyomi.source.online
-
-import eu.kanade.tachiyomi.source.model.Page
-import rx.Observable
-
-fun HttpSource.fetchAllImageUrlsFromPageList(pages: List<Page>): Observable<Page> {
-    return Observable.from(pages)
-        .filter { !it.imageUrl.isNullOrEmpty() }
-        .mergeWith(fetchRemainingImageUrlsFromPageList(pages))
-}
-
-private fun HttpSource.fetchRemainingImageUrlsFromPageList(pages: List<Page>): Observable<Page> {
-    return Observable.from(pages)
-        .filter { it.imageUrl.isNullOrEmpty() }
-        .concatMap { getImageUrl(it) }
-}
-
-private fun HttpSource.getImageUrl(page: Page): Observable<Page> {
-    page.status = Page.State.LOAD_PAGE
-    return fetchImageUrl(page)
-        .doOnError { page.status = Page.State.ERROR }
-        .onErrorReturn { null }
-        .doOnNext { page.imageUrl = it }
-        .map { page }
-}
diff --git a/source-api/src/commonMain/kotlin/exh/source/DelegatedHttpSource.kt b/source-api/src/commonMain/kotlin/exh/source/DelegatedHttpSource.kt
index ad105d248..1de09d68d 100644
--- a/source-api/src/commonMain/kotlin/exh/source/DelegatedHttpSource.kt
+++ b/source-api/src/commonMain/kotlin/exh/source/DelegatedHttpSource.kt
@@ -146,11 +146,17 @@ abstract class DelegatedHttpSource(val delegate: HttpSource) : HttpSource() {
      *
      * @param page the page number to retrieve.
      */
+    @Deprecated("Use the non-RxJava API instead", replaceWith = ReplaceWith("getPopularManga"))
     override fun fetchPopularManga(page: Int): Observable<MangasPage> {
         ensureDelegateCompatible()
         return delegate.fetchPopularManga(page)
     }
 
+    override suspend fun getPopularManga(page: Int): MangasPage {
+        ensureDelegateCompatible()
+        return delegate.getPopularManga(page)
+    }
+
     /**
      * Returns an observable containing a page with a list of manga. Normally it's not needed to
      * override this method.
@@ -159,21 +165,33 @@ abstract class DelegatedHttpSource(val delegate: HttpSource) : HttpSource() {
      * @param query the search query.
      * @param filters the list of filters to apply.
      */
+    @Deprecated("Use the non-RxJava API instead", replaceWith = ReplaceWith("getSearchManga"))
     override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
         ensureDelegateCompatible()
         return delegate.fetchSearchManga(page, query, filters)
     }
 
+    override suspend fun getSearchManga(page: Int, query: String, filters: FilterList): MangasPage {
+        ensureDelegateCompatible()
+        return delegate.getSearchManga(page, query, filters)
+    }
+
     /**
      * Returns an observable containing a page with a list of latest manga updates.
      *
      * @param page the page number to retrieve.
      */
+    @Deprecated("Use the non-RxJava API instead", replaceWith = ReplaceWith("getLatestUpdates"))
     override fun fetchLatestUpdates(page: Int): Observable<MangasPage> {
         ensureDelegateCompatible()
         return delegate.fetchLatestUpdates(page)
     }
 
+    override suspend fun getLatestUpdates(page: Int): MangasPage {
+        ensureDelegateCompatible()
+        return delegate.getLatestUpdates(page)
+    }
+
     /**
      * Returns an observable with the updated details for a manga. Normally it's not needed to
      * override this method.
@@ -250,19 +268,15 @@ abstract class DelegatedHttpSource(val delegate: HttpSource) : HttpSource() {
      *
      * @param page the page whose source image has to be fetched.
      */
+    @Deprecated("Use the non-RxJava API instead", replaceWith = ReplaceWith("getImageUrl"))
     override fun fetchImageUrl(page: Page): Observable<String> {
         ensureDelegateCompatible()
         return delegate.fetchImageUrl(page)
     }
 
-    /**
-     * Returns an observable with the response of the source image.
-     *
-     * @param page the page whose source image has to be downloaded.
-     */
-    override fun fetchImage(page: Page): Observable<Response> {
+    override suspend fun getImageUrl(page: Page): String {
         ensureDelegateCompatible()
-        return delegate.fetchImage(page)
+        return delegate.getImageUrl(page)
     }
 
     /**
diff --git a/source-api/src/commonMain/kotlin/exh/source/EnhancedHttpSource.kt b/source-api/src/commonMain/kotlin/exh/source/EnhancedHttpSource.kt
index 43dc38dcf..5c8675a3d 100644
--- a/source-api/src/commonMain/kotlin/exh/source/EnhancedHttpSource.kt
+++ b/source-api/src/commonMain/kotlin/exh/source/EnhancedHttpSource.kt
@@ -149,8 +149,11 @@ class EnhancedHttpSource(
      *
      * @param page the page number to retrieve.
      */
+    @Deprecated("Use the non-RxJava API instead", replaceWith = ReplaceWith("getPopularManga"))
     override fun fetchPopularManga(page: Int) = source().fetchPopularManga(page)
 
+    override suspend fun getPopularManga(page: Int) = source().getPopularManga(page)
+
     /**
      * Returns an observable containing a page with a list of manga. Normally it's not needed to
      * override this method.
@@ -159,16 +162,23 @@ class EnhancedHttpSource(
      * @param query the search query.
      * @param filters the list of filters to apply.
      */
+    @Deprecated("Use the non-RxJava API instead", replaceWith = ReplaceWith("getSearchManga"))
     override fun fetchSearchManga(page: Int, query: String, filters: FilterList) =
         source().fetchSearchManga(page, query, filters)
 
+    override suspend fun getSearchManga(page: Int, query: String, filters: FilterList) =
+        source().getSearchManga(page, query, filters)
+
     /**
      * Returns an observable containing a page with a list of latest manga updates.
      *
      * @param page the page number to retrieve.
      */
+    @Deprecated("Use the non-RxJava API instead", replaceWith = ReplaceWith("getLatestUpdates"))
     override fun fetchLatestUpdates(page: Int) = source().fetchLatestUpdates(page)
 
+    override suspend fun getLatestUpdates(page: Int) = source().getLatestUpdates(page)
+
     /**
      * Returns an observable with the updated details for a manga. Normally it's not needed to
      * override this method.
@@ -224,14 +234,10 @@ class EnhancedHttpSource(
      *
      * @param page the page whose source image has to be fetched.
      */
+    @Deprecated("Use the non-RxJava API instead", replaceWith = ReplaceWith("getImageUrl"))
     override fun fetchImageUrl(page: Page) = source().fetchImageUrl(page)
 
-    /**
-     * Returns an observable with the response of the source image.
-     *
-     * @param page the page whose source image has to be downloaded.
-     */
-    override fun fetchImage(page: Page) = source().fetchImage(page)
+    override suspend fun getImageUrl(page: Page) = source().getImageUrl(page)
 
     /**
      * Returns the response of the source image.
diff --git a/source-local/src/androidMain/kotlin/tachiyomi/source/local/LocalSource.kt b/source-local/src/androidMain/kotlin/tachiyomi/source/local/LocalSource.kt
index e1d9fbf8e..8dd747227 100755
--- a/source-local/src/androidMain/kotlin/tachiyomi/source/local/LocalSource.kt
+++ b/source-local/src/androidMain/kotlin/tachiyomi/source/local/LocalSource.kt
@@ -20,7 +20,6 @@ import net.lingala.zip4j.ZipFile
 import net.lingala.zip4j.model.ZipParameters
 import nl.adaptivity.xmlutil.AndroidXmlReader
 import nl.adaptivity.xmlutil.serialization.XML
-import rx.Observable
 import tachiyomi.core.metadata.comicinfo.COMIC_INFO_FILE
 import tachiyomi.core.metadata.comicinfo.ComicInfo
 import tachiyomi.core.metadata.comicinfo.copyFromComicInfo
@@ -72,11 +71,11 @@ actual class LocalSource(
     override val supportsLatest: Boolean = true
 
     // Browse related
-    override fun fetchPopularManga(page: Int) = fetchSearchManga(page, "", POPULAR_FILTERS)
+    override suspend fun getPopularManga(page: Int) = getSearchManga(page, "", POPULAR_FILTERS)
 
-    override fun fetchLatestUpdates(page: Int) = fetchSearchManga(page, "", LATEST_FILTERS)
+    override suspend fun getLatestUpdates(page: Int) = getSearchManga(page, "", LATEST_FILTERS)
 
-    override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
+    override suspend fun getSearchManga(page: Int, query: String, filters: FilterList): MangasPage {
         val baseDirsFiles = fileSystem.getFilesInBaseDirectories()
         val lastModifiedLimit by lazy { if (filters === LATEST_FILTERS) System.currentTimeMillis() - LATEST_THRESHOLD else 0L }
         // SY -->
@@ -153,7 +152,7 @@ actual class LocalSource(
             }
         }
 
-        return Observable.just(MangasPage(mangas.toList(), false))
+        return MangasPage(mangas.toList(), false)
     }
 
     // SY -->