zFImE^5i+CAcO}72&c7f&Hk+iDxpc4VpR1X=Hp(S-l^`nlX2Zd}>-QIJsICp7V=s*P
zvAr8PC0669^#lx}%?`yMy%F*Z_lKgoW@L6{3{y`=jZkmH~^uy7_;9$t&O%Qd~29M-<@mZaf+I$t_ulG
zi^h$z*E(?%@nQ?Ou6p8{soMr(txxHQB#U{yn_kcd9PqgTPWceSE?Ec;7=lx#_>eUMeqEw%;gQ-jHgEy?ql?7(F@?%-ay>nX
z;1;uC+4&m$0GFS&32>l~7d|
zA6k!uuxW2bF8l>k$5U;?+)lCJTTUxxnXyyVc=%b+bsV!{mi2#O3`^8c3}jux@mmTM
z#4$|kg$O|gXhq~uAh~~c-0HHBBXZkJpTg_|AsM_ALO@y5@t}-9=%3krxRl>ZA2Uj(
zEOxs!?1JVxWat%v(sN#E9tlY$wVOIDT0B<`nOFr&geATdq%d9p)k4z@C~kzk0Wg)^
z>is@9S^`nfyLpX3J#^wD_19zl%^a&^m*paWnJ+Z97!R%*%~6>WETHM>_D0pfW;k1r
z++J~`q=GoPyKx`I(QTWipLYEvsR|Iibw&An(`V}1i3m6Hr%%_oGUabp5&OT(Zu{6u
zji1)Duca&kB2#bQ_1)5m#11x+GI0|_@t!&&;u#(l+5|Z`h_SQ+7Cgy->R|dhG1{Q+
zIg#!jJg5lSo@0RtfuPI^@cX>|H!uac7S>b=}_cuUU}Z}_lWag$h9=7-)0wJ}o*C)!6$+ve;%v!{AAsKqY_
z-i*mzEDoHdWO2>)EY07P+Ia3Q1+VnQOz
zUEyDIJY&3A#O#W8NBjzH2>GAReCE;*06(`>RaF&M$WOUWle$EPoeZ)PYh2Nyjd`Xm
z>^KgLhG;W;?&+@}1?x_+zKdJSThbG5*JW!vj+n6ePHt4nCc^+}pI_Ew#A(`>2M5ZG
z|JzwIREeDOVz5r9UA)JUcu}qr$FGiET6b&d>-8f3S_J+db`ZM}&d33ho-=ZAMo@M6
zR*`5&$2HOo?Jw$?7a`-;b{CM`PfN(3X!ZO?K5q=Ix)c@{y;
zq1>C>?l@s7A{PE^0{1(Z1}w+l
zLjTrxXe`FULho+rYpU%C|
zw8Jemxj55zd0vkM)dZ?avH5)BRfahdon(Ep>&qR(^F6oOj1EPblh7o_iNr_E5^~g0
z6Wg|5;rm+a1(<->@&u&}F02^+4MHWj%zTQzsGg7hq*ohYki_s?Y_4bdXN6`te)E7Y
zblGFEY|0-x#;lmtcTzV;{Z{b}(UPTK`DPrWGde;hG-U}&vK2MEdeu4DP^Kb0%P%z<
z%IK@T_)i|g^_}p3Sm;5B$ojJ$Qj^53S9}18?A&zxJP~uE$^V&J(9l17eO`|yVGR4N
zV-r>UR*Wma#z?%@f1GFu78uFrypGUPW6`^&ePBI27KUw(MvJeMIO5#Vl@NQQ(Tz}D
z8#(vB&_J7!^~IH0?w|FpaIl(HcD*ASVDRPeKUz6b9fi4X*65V0^DP_`K&$!-TZw9NMRQJAfuT&OEx+>J*v0@0v6`si5u4Fs|;m{B)O<77`;fmi{agW
z#ZkpgD8!Bi>^2L9K9uQqA_XfspU+gWIve93y71F`jNl20>sUJRMPw&=_JKN3>2**l
zPdCqXMf`6Q=SLbi!pI_WY<&^hw`BU5`8jxd*FJ9WqjiG4UUq{h5dNcKk%%Gb754V#
z;!0tr3TZmo;7to+aF)qN@&3>jAurjXBjznttW4tbpV3mGI&JqL%lh+FgEx+d1!W*d
z>tdWhN
z49#{mMwVgLlA{4dV-eLZRX1NxP8|;ym6^}2#@xvOKejwKXR~u~U3dABd5j5jgVi|V
z-|B%_DNOOC>Hi&JgHarJRR7OyfGTV~Vu_fsm}J|o=-dTNi4r#5uChApFzEuE;Ay97
zvs#(twm=p_YcKV_Th$?|5gsev)`z4Yr1T$3W0|Z1bGbZg76lZX%`XkSfw_U4;abhA
z`{-XB!7{Rox|n+W+5gb1ny(rjzHdH#dM*+hV<^No<3w8$jRutr3$Kp+XFP^2OPtZP
ztq=|nQqg%vekBN8@xV>g)1Ru_#Vk#$Ds_|p6oyiKsMf(F%cL{v2x7xQ#lnF$BY
zHMKsN$CPY?)|g|fOHKX+KyE|oJz=gTV~DvEKC0E&UPZSr^#81_wxqjR`oBONu6B{S
zAPus{rl=w;EA}hKgq9%+wFp&3qd;+f2#JvGx2{&Y^k!s_I8k=qww~qF?@#*^aE(ND
zdzkuRqWUN!oSzwf*Enpo)@O_P{Cw4{QM08jcy1*KjJ*UrmYn;9zv7TLmy!P5_z_WE
zTy@jCp^<@H($!Q^aU5njm~_MgKrdm)9b5eg9F*J^zuTn3%JwqXN{bbLr(4%;#}s1t
z`};Sr8Pq!jyaI6=`a+pB3NjX^r`^q3YzGBjIm(%z#`MV0!>;lUj*r{_nYNn`R2I!s
zkEy~FQ9FLiCD0%7n00?+xz+aQ*Eq(K&DWf~xV$WiA{C%G{rz9e5*O|>!3)unL^~2d
z_f@SS5|lJgE?CB~VcCP&&C_!{kH-w>s6dQ;I
z63~UkspTqB0jdrpZ0#?-hzFZr?2YSSlk&4R+3*{AHaz|n!3sr{>e-Sybx+Y>j7O+a
zYGnoue}5pV1}mw>z&c3P&j`gO=`#ngC#&=21n`sTFnuk_XY+7#+IM+zk%5X%t5Q>H
zf%i!COM|A%BY(cuHbEcJcreeRQ{K|88bJP}`hpJmjQO)2-4!fd50kHpY9Kcl@*jg@
z$|o~V&+9OC+#77kjSqe|1&fJV{^49R9_ydCP1*wosz3L@PtL3A;c&J>+c@T>fO!)B{K?V5MYHfjXepb9O^1T)zKY
z@`m4GZm7JxoIFu|vD>&~fut|V=JWy4(U5&`cxbRNJKNyZ_4Or|HlpIY+QH5-Q2xp!
zRFtt@>}f6Yp%)dJihJk>sN@6!AUSSv0GXua>qlbD?hT!yztr`}Zol&gbQ4p9iMFNt
zZ$SW~*z-XpL*|W7h*%Y1x`TJ;5c{+vYxL#?a=}?T!1)f>po6ddK%ql3fB+yt7FrtD
z7I(Z2N)+eC;O{A6Ys7eAL@_O~W>WUBfMf%7lPO##w^HO5H3~(J1Dve@W<}sdbj6!1
zO;jLCOx4
z&JRS(6(RFVn)fuFDpJk?i)KuPD&l@d%@)4%;(rZT(>gyWOmgvW#Ha138Nc>2SRuN{
O068gT$#RJ4m;V7zFBrQ3
literal 0
HcmV?d00001
diff --git a/src/en/koharu/src/eu/kanade/tachiyomi/extension/en/koharu/Koharu.kt b/src/en/koharu/src/eu/kanade/tachiyomi/extension/en/koharu/Koharu.kt
new file mode 100644
index 000000000..1698cd469
--- /dev/null
+++ b/src/en/koharu/src/eu/kanade/tachiyomi/extension/en/koharu/Koharu.kt
@@ -0,0 +1,305 @@
+package eu.kanade.tachiyomi.extension.en.koharu
+
+import android.app.Application
+import android.content.SharedPreferences
+import androidx.preference.ListPreference
+import androidx.preference.PreferenceScreen
+import eu.kanade.tachiyomi.network.GET
+import eu.kanade.tachiyomi.network.POST
+import eu.kanade.tachiyomi.network.interceptor.rateLimit
+import eu.kanade.tachiyomi.source.ConfigurableSource
+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.model.UpdateStrategy
+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 rx.Observable
+import uy.kohesive.injekt.Injekt
+import uy.kohesive.injekt.api.get
+import uy.kohesive.injekt.injectLazy
+import java.text.SimpleDateFormat
+import java.util.Locale
+
+class Koharu : HttpSource(), ConfigurableSource {
+ override val name = "Koharu"
+
+ override val baseUrl = "https://koharu.to"
+
+ private val apiUrl = baseUrl.replace("://", "://api.")
+
+ private val apiBooksUrl = "$apiUrl/books"
+
+ override val lang = "en"
+
+ override val supportsLatest = true
+
+ override val client: OkHttpClient = network.cloudflareClient.newBuilder()
+ .rateLimit(1)
+ .build()
+
+ private val json: Json by injectLazy()
+
+ private val preferences: SharedPreferences by lazy {
+ Injekt.get().getSharedPreferences("source_$id", 0x0000)
+ }
+
+ private fun quality() = preferences.getString(PREF_IMAGERES, "1280")!!
+
+ override fun headersBuilder() = super.headersBuilder()
+ .add("Referer", "$baseUrl/")
+ .add("Origin", baseUrl)
+
+ private fun getManga(book: Entry) = SManga.create().apply {
+ setUrlWithoutDomain("${book.id}/${book.public_key}")
+ title = book.title
+ thumbnail_url = book.thumbnail.path
+ }
+
+ private fun getImagesByMangaEntry(entry: MangaEntry): ImagesInfo {
+ val data = entry.data
+ val dataKey = when (quality()) {
+ "1600" -> data.`1600` ?: data.`1280` ?: data.`0`
+ "1280" -> data.`1280` ?: data.`1600` ?: data.`0`
+ "980" -> data.`980` ?: data.`1280` ?: data.`0`
+ "780" -> data.`780` ?: data.`980` ?: data.`0`
+ else -> data.`0`
+ }
+
+ val imagesResponse = client.newCall(POST("$apiBooksUrl/data/${entry.id}/${entry.public_key}/${dataKey.id}/${dataKey.public_key}", headers)).execute()
+ val images = imagesResponse.parseAs()
+ return images
+ }
+
+ // Latest
+
+ override fun latestUpdatesRequest(page: Int) = GET("$apiBooksUrl?page=$page", headers)
+ override fun latestUpdatesParse(response: Response) = popularMangaParse(response)
+
+ // Popular
+
+ override fun popularMangaRequest(page: Int) = GET("$apiBooksUrl?sort=6&page=$page", headers)
+ override fun popularMangaParse(response: Response): MangasPage {
+ val data = response.parseAs()
+
+ return MangasPage(data.entries.map(::getManga), data.page * data.limit < data.total)
+ }
+
+ // Search
+
+ override fun getFilterList(): FilterList = getFilters()
+
+ override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable {
+ return when {
+ query.startsWith(PREFIX_ID_KEY_SEARCH) -> {
+ val ipk = query.removePrefix(PREFIX_ID_KEY_SEARCH)
+ val response = client.newCall(GET("$apiBooksUrl/detail/$ipk", headers)).execute()
+ Observable.just(searchMangaParse2(response))
+ }
+ else -> super.fetchSearchManga(page, query, filters)
+ }
+ }
+
+ override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
+ val url = apiBooksUrl.toHttpUrl().newBuilder().apply {
+ val terms = mutableListOf(query.trim())
+
+ filters.forEach { filter ->
+ when (filter) {
+ is SortFilter -> addQueryParameter("sort", filter.getValue())
+
+ is CategoryFilter -> {
+ val activeFilter = filter.state.filter { it.state }
+ if (activeFilter.isNotEmpty()) {
+ addQueryParameter("cat", activeFilter.sumOf { it.value }.toString())
+ }
+ }
+
+ is TextFilter -> {
+ if (filter.state.isNotEmpty()) {
+ terms += filter.state.split(",").filter(String::isNotBlank).map { tag ->
+ val trimmed = tag.trim()
+ buildString {
+ if (trimmed.startsWith('-')) {
+ append("-")
+ }
+ append(filter.type)
+ append("!:")
+ append("\"")
+ append(trimmed.lowercase().removePrefix("-"))
+ append("\"")
+ }
+ }
+ }
+ }
+ else -> {}
+ }
+ }
+ if (query.isNotEmpty()) terms.add("title:\"$query\"")
+ if (terms.isNotEmpty()) addQueryParameter("s", terms.joinToString(" "))
+ addQueryParameter("page", page.toString())
+ }.build()
+
+ return GET(url, headers)
+ }
+
+ override fun searchMangaParse(response: Response) = popularMangaParse(response)
+
+ private fun searchMangaParse2(response: Response): MangasPage {
+ val entry = response.parseAs()
+
+ return MangasPage(
+ listOf(
+ SManga.create().apply {
+ setUrlWithoutDomain("${entry.id}/${entry.public_key}")
+ title = entry.title
+ thumbnail_url = entry.thumbnails.base + entry.thumbnails.main.path
+ },
+ ),
+ false,
+ )
+ }
+ // Details
+
+ override fun mangaDetailsRequest(manga: SManga): Request {
+ return GET("$apiBooksUrl/detail/${manga.url}", headers)
+ }
+
+ override fun mangaDetailsParse(response: Response): SManga {
+ return response.parseAs().toSManga()
+ }
+
+ private val dateReformat = SimpleDateFormat("EEEE, d MMM yyyy HH:mm (z)", Locale.ENGLISH)
+ private fun MangaEntry.toSManga() = SManga.create().apply {
+ val artists = mutableListOf()
+ val circles = mutableListOf()
+ val parodies = mutableListOf()
+ val magazines = mutableListOf()
+ val characters = mutableListOf()
+ val cosplayers = mutableListOf()
+ val females = mutableListOf()
+ val males = mutableListOf()
+ val mixed = mutableListOf()
+ val other = mutableListOf()
+ val uploaders = mutableListOf()
+ val tags = mutableListOf()
+ for (tag in this@toSManga.tags) {
+ when (tag.namespace) {
+ 1 -> artists.add(tag.name)
+ 2 -> circles.add(tag.name)
+ 3 -> parodies.add(tag.name)
+ 4 -> magazines.add(tag.name)
+ 5 -> characters.add(tag.name)
+ 6 -> cosplayers.add(tag.name)
+ 7 -> uploaders.add(tag.name)
+ 8 -> males.add(tag.name + " ♂")
+ 9 -> females.add(tag.name + " ♀")
+ 10 -> mixed.add(tag.name)
+ 12 -> other.add(tag.name)
+ else -> tags.add(tag.name)
+ }
+ }
+ author = (circles.emptyToNull() ?: artists).joinToString()
+ artist = artists.joinToString()
+ genre = (tags + males + females + mixed).joinToString()
+ description = buildString {
+ circles.emptyToNull()?.joinToString()?.let {
+ append("Circles: ", it, "\n")
+ }
+ uploaders.emptyToNull()?.joinToString()?.let {
+ append("Uploaders: ", it, "\n")
+ }
+ magazines.emptyToNull()?.joinToString()?.let {
+ append("Magazines: ", it, "\n")
+ }
+ cosplayers.emptyToNull()?.joinToString()?.let {
+ append("Cosplayers: ", it, "\n")
+ }
+ parodies.emptyToNull()?.joinToString()?.let {
+ append("Parodies: ", it, "\n")
+ }
+ characters.emptyToNull()?.joinToString()?.let {
+ append("Characters: ", it, "\n")
+ }
+ append("Pages: ", thumbnails.entries.size, "\n\n")
+
+ try {
+ append("Added: ", dateReformat.format(((updated_at ?: created_at))), "\n")
+ } catch (_: Exception) {}
+ }
+ status = SManga.COMPLETED
+ update_strategy = UpdateStrategy.ONLY_FETCH_ONCE
+ initialized = true
+ }
+
+ private fun Collection.emptyToNull(): Collection? {
+ return this.ifEmpty { null }
+ }
+
+ override fun getMangaUrl(manga: SManga) = "$baseUrl/g/${manga.url}"
+
+ // Chapter
+
+ override fun chapterListRequest(manga: SManga): Request {
+ return GET("$apiBooksUrl/detail/${manga.url}", headers)
+ }
+
+ override fun chapterListParse(response: Response): List {
+ val manga = response.parseAs()
+ return listOf(
+ SChapter.create().apply {
+ name = "Chapter"
+ url = "${manga.id}/${manga.public_key}"
+ date_upload = (manga.updated_at ?: manga.created_at)
+ },
+ )
+ }
+
+ override fun getChapterUrl(chapter: SChapter) = "$baseUrl/g/${chapter.url}"
+
+ // Page List
+
+ override fun pageListRequest(chapter: SChapter): Request {
+ return GET("$apiBooksUrl/detail/${chapter.url}", headers)
+ }
+
+ override fun pageListParse(response: Response): List {
+ val mangaEntry = response.parseAs()
+ val imagesInfo = getImagesByMangaEntry(mangaEntry)
+
+ return imagesInfo.entries.mapIndexed { index, image ->
+ Page(index, imageUrl = "${imagesInfo.base}/${image.path}")
+ }
+ }
+
+ override fun imageUrlParse(response: Response) = throw UnsupportedOperationException()
+
+ // Settings
+
+ override fun setupPreferenceScreen(screen: PreferenceScreen) {
+ ListPreference(screen.context).apply {
+ key = PREF_IMAGERES
+ title = "Image Resolution"
+ entries = arrayOf("780x", "980x", "1280x", "1600x", "Original")
+ entryValues = arrayOf("780", "980", "1280", "1600", "0")
+ summary = "%s"
+ setDefaultValue("1280")
+ }.also(screen::addPreference)
+ }
+
+ private inline fun Response.parseAs(): T {
+ return json.decodeFromString(body.string())
+ }
+
+ companion object {
+ const val PREFIX_ID_KEY_SEARCH = "id:"
+ private const val PREF_IMAGERES = "pref_image_quality"
+ }
+}
diff --git a/src/en/koharu/src/eu/kanade/tachiyomi/extension/en/koharu/KoharuDto.kt b/src/en/koharu/src/eu/kanade/tachiyomi/extension/en/koharu/KoharuDto.kt
new file mode 100644
index 000000000..b3cd70062
--- /dev/null
+++ b/src/en/koharu/src/eu/kanade/tachiyomi/extension/en/koharu/KoharuDto.kt
@@ -0,0 +1,75 @@
+package eu.kanade.tachiyomi.extension.en.koharu
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+class Tag(
+ var name: String,
+ var namespace: Int = 0,
+)
+
+@Serializable
+class Books(
+ val entries: List = emptyList(),
+ val total: Int = 0,
+ val limit: Int = 0,
+ val page: Int,
+)
+
+@Serializable
+class Entry(
+ val id: Int,
+ val public_key: String,
+ val title: String,
+ val thumbnail: Thumbnail,
+)
+
+@Serializable
+class MangaEntry(
+ val id: Int,
+ val title: String,
+ val public_key: String,
+ val created_at: Long = 0L,
+ val updated_at: Long?,
+ val thumbnails: Thumbnails,
+ val tags: List = emptyList(),
+ val data: Data,
+)
+
+@Serializable
+class Thumbnails(
+ val base: String,
+ val main: Thumbnail,
+ val entries: List,
+)
+
+@Serializable
+class Thumbnail(
+ val path: String,
+)
+
+@Serializable
+class Data(
+ val `0`: DataKey,
+ val `780`: DataKey? = null,
+ val `980`: DataKey? = null,
+ val `1280`: DataKey? = null,
+ val `1600`: DataKey? = null,
+)
+
+@Serializable
+class DataKey(
+ val id: Int,
+ val public_key: String,
+)
+
+@Serializable
+class ImagesInfo(
+ val base: String,
+ val entries: List,
+)
+
+@Serializable
+class ImagePath(
+ val path: String,
+)
diff --git a/src/en/koharu/src/eu/kanade/tachiyomi/extension/en/koharu/KoharuFilters.kt b/src/en/koharu/src/eu/kanade/tachiyomi/extension/en/koharu/KoharuFilters.kt
new file mode 100644
index 000000000..603de00fc
--- /dev/null
+++ b/src/en/koharu/src/eu/kanade/tachiyomi/extension/en/koharu/KoharuFilters.kt
@@ -0,0 +1,51 @@
+package eu.kanade.tachiyomi.extension.en.koharu
+
+import eu.kanade.tachiyomi.source.model.Filter
+import eu.kanade.tachiyomi.source.model.FilterList
+
+fun getFilters(): FilterList {
+ return FilterList(
+ SortFilter("Sort by", getSortsList),
+ CategoryFilter("Category"),
+ Filter.Separator(),
+ Filter.Header("Separate tags with commas (,)"),
+ Filter.Header("Prepend with dash (-) to exclude"),
+ TextFilter("Artists", "artist"),
+ TextFilter("Magazines", "magazine"),
+ TextFilter("Publishers", "publisher"),
+ TextFilter("Characters", "character"),
+ TextFilter("Cosplayers", "cosplayer"),
+ TextFilter("Parodies", "parody"),
+ TextFilter("Circles", "circle"),
+ TextFilter("Male Tags", "male"),
+ TextFilter("Female Tags", "female"),
+ TextFilter("Tags ( Universal )", "tag"),
+ Filter.Header("Filter by pages, for example: (>20)"),
+ TextFilter("Pages", "pages"),
+ )
+}
+
+internal open class TextFilter(name: String, val type: String) : Filter.Text(name)
+internal open class SortFilter(name: String, private val vals: List>, state: Int = 0) :
+ Filter.Select(name, vals.map { it.first }.toTypedArray(), state) {
+ fun getValue() = vals[state].second
+}
+
+internal class CategoryFilter(name: String) :
+ Filter.Group(
+ name,
+ listOf(
+ Pair("Manga", 2),
+ Pair("Doujinshi", 4),
+ Pair("Illustration", 8),
+ ).map { CheckBoxFilter(it.first, it.second, true) },
+ )
+internal open class CheckBoxFilter(name: String, val value: Int, state: Boolean) : Filter.CheckBox(name, state)
+
+private val getSortsList: List> = listOf(
+ Pair("Title", "1"),
+ Pair("Pages", "2"),
+ Pair("Recently Posted", ""),
+ Pair("Most Viewed", "6"),
+ Pair("Most Favorited", "8"),
+)
diff --git a/src/en/koharu/src/eu/kanade/tachiyomi/extension/en/koharu/KoharuUrlActivity.kt b/src/en/koharu/src/eu/kanade/tachiyomi/extension/en/koharu/KoharuUrlActivity.kt
new file mode 100644
index 000000000..56799263d
--- /dev/null
+++ b/src/en/koharu/src/eu/kanade/tachiyomi/extension/en/koharu/KoharuUrlActivity.kt
@@ -0,0 +1,34 @@
+package eu.kanade.tachiyomi.extension.en.koharu
+
+import android.app.Activity
+import android.content.ActivityNotFoundException
+import android.content.Intent
+import android.os.Bundle
+import android.util.Log
+import kotlin.system.exitProcess
+
+class KoharuUrlActivity : Activity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ val pathSegments = intent?.data?.pathSegments
+ if (pathSegments != null && pathSegments.size > 2) {
+ val id = "${pathSegments[1]}/${pathSegments[2]}"
+ val mainIntent = Intent().apply {
+ action = "eu.kanade.tachiyomi.SEARCH"
+ putExtra("query", "${Koharu.PREFIX_ID_KEY_SEARCH}$id")
+ putExtra("filter", packageName)
+ }
+
+ try {
+ startActivity(mainIntent)
+ } catch (e: ActivityNotFoundException) {
+ Log.e("KoharuUrlActivity", "Could not start activity", e)
+ }
+ } else {
+ Log.e("KoharuUrlActivity", "Could not parse URI from intent $intent")
+ }
+
+ finish()
+ exitProcess(0)
+ }
+}