Update Mangadex Similar to GoldBattles latest version

This commit is contained in:
Jobobby04 2021-05-12 23:29:14 -04:00
parent e500d0bebf
commit 08f1eff450
25 changed files with 70 additions and 853 deletions

View File

@ -189,10 +189,6 @@
android:exported="false" /> android:exported="false" />
<!-- EH --> <!-- EH -->
<service
android:name="exh.md.similar.SimilarUpdateService"
android:exported="false" />
<service <service
android:name="exh.eh.EHentaiUpdateWorker" android:name="exh.eh.EHentaiUpdateWorker"
android:permission="android.permission.BIND_JOB_SERVICE" android:permission="android.permission.BIND_JOB_SERVICE"

View File

@ -21,9 +21,6 @@ import eu.kanade.tachiyomi.data.database.queries.HistoryQueries
import eu.kanade.tachiyomi.data.database.queries.MangaCategoryQueries import eu.kanade.tachiyomi.data.database.queries.MangaCategoryQueries
import eu.kanade.tachiyomi.data.database.queries.MangaQueries import eu.kanade.tachiyomi.data.database.queries.MangaQueries
import eu.kanade.tachiyomi.data.database.queries.TrackQueries import eu.kanade.tachiyomi.data.database.queries.TrackQueries
import exh.md.similar.sql.mappers.SimilarTypeMapping
import exh.md.similar.sql.models.MangaSimilar
import exh.md.similar.sql.queries.SimilarQueries
import exh.merged.sql.mappers.MergedMangaTypeMapping import exh.merged.sql.mappers.MergedMangaTypeMapping
import exh.merged.sql.models.MergedMangaReference import exh.merged.sql.models.MergedMangaReference
import exh.merged.sql.queries.MergedQueries import exh.merged.sql.queries.MergedQueries
@ -42,7 +39,7 @@ import io.requery.android.database.sqlite.RequerySQLiteOpenHelperFactory
* This class provides operations to manage the database through its interfaces. * This class provides operations to manage the database through its interfaces.
*/ */
open class DatabaseHelper(context: Context) : open class DatabaseHelper(context: Context) :
MangaQueries, ChapterQueries, TrackQueries, CategoryQueries, MangaCategoryQueries, HistoryQueries /* SY --> */, SearchMetadataQueries, SearchTagQueries, SearchTitleQueries, MergedQueries, SimilarQueries /* SY <-- */ { MangaQueries, ChapterQueries, TrackQueries, CategoryQueries, MangaCategoryQueries, HistoryQueries /* SY --> */, SearchMetadataQueries, SearchTagQueries, SearchTitleQueries, MergedQueries /* SY <-- */ {
private val configuration = SupportSQLiteOpenHelper.Configuration.builder(context) private val configuration = SupportSQLiteOpenHelper.Configuration.builder(context)
.name(DbOpenCallback.DATABASE_NAME) .name(DbOpenCallback.DATABASE_NAME)
@ -62,7 +59,6 @@ open class DatabaseHelper(context: Context) :
.addTypeMapping(SearchTag::class.java, SearchTagTypeMapping()) .addTypeMapping(SearchTag::class.java, SearchTagTypeMapping())
.addTypeMapping(SearchTitle::class.java, SearchTitleTypeMapping()) .addTypeMapping(SearchTitle::class.java, SearchTitleTypeMapping())
.addTypeMapping(MergedMangaReference::class.java, MergedMangaTypeMapping()) .addTypeMapping(MergedMangaReference::class.java, MergedMangaTypeMapping())
.addTypeMapping(MangaSimilar::class.java, SimilarTypeMapping())
// SY <-- // SY <--
.build() .build()

View File

@ -8,7 +8,6 @@ import eu.kanade.tachiyomi.data.database.tables.HistoryTable
import eu.kanade.tachiyomi.data.database.tables.MangaCategoryTable import eu.kanade.tachiyomi.data.database.tables.MangaCategoryTable
import eu.kanade.tachiyomi.data.database.tables.MangaTable import eu.kanade.tachiyomi.data.database.tables.MangaTable
import eu.kanade.tachiyomi.data.database.tables.TrackTable import eu.kanade.tachiyomi.data.database.tables.TrackTable
import exh.md.similar.sql.tables.SimilarTable
import exh.merged.sql.tables.MergedTable import exh.merged.sql.tables.MergedTable
import exh.metadata.sql.tables.SearchMetadataTable import exh.metadata.sql.tables.SearchMetadataTable
import exh.metadata.sql.tables.SearchTagTable import exh.metadata.sql.tables.SearchTagTable
@ -25,7 +24,7 @@ class DbOpenCallback : SupportSQLiteOpenHelper.Callback(DATABASE_VERSION) {
/** /**
* Version of the database. * Version of the database.
*/ */
const val DATABASE_VERSION = /* SY --> */ 6 /* SY <-- */ const val DATABASE_VERSION = /* SY --> */ 7 /* SY <-- */
} }
override fun onCreate(db: SupportSQLiteDatabase) = with(db) { override fun onCreate(db: SupportSQLiteDatabase) = with(db) {
@ -40,7 +39,6 @@ class DbOpenCallback : SupportSQLiteOpenHelper.Callback(DATABASE_VERSION) {
execSQL(SearchTagTable.createTableQuery) execSQL(SearchTagTable.createTableQuery)
execSQL(SearchTitleTable.createTableQuery) execSQL(SearchTitleTable.createTableQuery)
execSQL(MergedTable.createTableQuery) execSQL(MergedTable.createTableQuery)
execSQL(SimilarTable.createTableQuery)
// SY <-- // SY <--
// DB indexes // DB indexes
@ -57,7 +55,6 @@ class DbOpenCallback : SupportSQLiteOpenHelper.Callback(DATABASE_VERSION) {
execSQL(SearchTitleTable.createMangaIdIndexQuery) execSQL(SearchTitleTable.createMangaIdIndexQuery)
execSQL(SearchTitleTable.createTitleIndexQuery) execSQL(SearchTitleTable.createTitleIndexQuery)
execSQL(MergedTable.createIndexQuery) execSQL(MergedTable.createIndexQuery)
execSQL(SimilarTable.createMangaIdIndexQuery)
// SY <-- // SY <--
} }
@ -74,13 +71,16 @@ class DbOpenCallback : SupportSQLiteOpenHelper.Callback(DATABASE_VERSION) {
db.execSQL(MergedTable.createTableQuery) db.execSQL(MergedTable.createTableQuery)
db.execSQL(MergedTable.createIndexQuery) db.execSQL(MergedTable.createIndexQuery)
} }
if (oldVersion < 5) { /*if (oldVersion < 5) {
db.execSQL(SimilarTable.createTableQuery) db.execSQL(SimilarTable.createTableQuery)
db.execSQL(SimilarTable.createMangaIdIndexQuery) db.execSQL(SimilarTable.createMangaIdIndexQuery)
} }*/
if (oldVersion < 6) { if (oldVersion < 6) {
db.execSQL(MangaTable.addFilteredScanlators) db.execSQL(MangaTable.addFilteredScanlators)
} }
if (oldVersion < 7) {
db.execSQL("DROP TABLE IF EXISTS manga_related")
}
} }
override fun onConfigure(db: SupportSQLiteDatabase) { override fun onConfigure(db: SupportSQLiteDatabase) {

View File

@ -26,7 +26,6 @@ import eu.kanade.tachiyomi.util.storage.DiskUtil
import eu.kanade.tachiyomi.util.storage.getUriCompat import eu.kanade.tachiyomi.util.storage.getUriCompat
import eu.kanade.tachiyomi.util.system.notificationManager import eu.kanade.tachiyomi.util.system.notificationManager
import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.system.toast
import exh.md.similar.SimilarUpdateService
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
@ -111,9 +110,6 @@ class NotificationReceiver : BroadcastReceiver() {
"text/plain", "text/plain",
intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1) intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1)
) )
// SY -->
ACTION_CANCEL_SIMILAR_UPDATE -> cancelSimilarUpdate(context)
// SY <--
} }
} }
@ -255,18 +251,6 @@ class NotificationReceiver : BroadcastReceiver() {
} }
} }
// SY -->
/**
* Method called when user wants to stop a similar manga update
*
* @param context context of application
*/
private fun cancelSimilarUpdate(context: Context) {
SimilarUpdateService.stop(context)
Handler().post { dismissNotification(context, Notifications.ID_SIMILAR_PROGRESS) }
}
// SY <--
companion object { companion object {
private const val NAME = "NotificationReceiver" private const val NAME = "NotificationReceiver"
@ -299,11 +283,6 @@ class NotificationReceiver : BroadcastReceiver() {
private const val EXTRA_CHAPTER_URL = "$ID.$NAME.EXTRA_CHAPTER_URL" private const val EXTRA_CHAPTER_URL = "$ID.$NAME.EXTRA_CHAPTER_URL"
private const val EXTRA_IS_LEGACY_BACKUP = "$ID.$NAME.EXTRA_IS_LEGACY_BACKUP" private const val EXTRA_IS_LEGACY_BACKUP = "$ID.$NAME.EXTRA_IS_LEGACY_BACKUP"
// Sy -->
// Called to cancel similar manga update.
private const val ACTION_CANCEL_SIMILAR_UPDATE = "$ID.$NAME.CANCEL_SIMILAR_UPDATE"
// SY <--
/** /**
* Returns a [PendingIntent] that resumes the download of a chapter * Returns a [PendingIntent] that resumes the download of a chapter
* *
@ -572,20 +551,5 @@ class NotificationReceiver : BroadcastReceiver() {
} }
return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT) return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
} }
// SY -->
/**
* Returns [PendingIntent] that starts a service which stops the similar update
*
* @param context context of application
* @return [PendingIntent]
*/
internal fun cancelSimilarUpdatePendingBroadcast(context: Context): PendingIntent {
val intent = Intent(context, NotificationReceiver::class.java).apply {
action = ACTION_CANCEL_SIMILAR_UPDATE
}
return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
}
// SY <--
} }
} }

View File

@ -74,15 +74,6 @@ object Notifications {
const val CHANNEL_INCOGNITO_MODE = "incognito_mode_channel" const val CHANNEL_INCOGNITO_MODE = "incognito_mode_channel"
const val ID_INCOGNITO_MODE = -701 const val ID_INCOGNITO_MODE = -701
// SY -->
/**
* Notification channel and ids used for backup and restore.
*/
const val CHANNEL_SIMILAR = "similar_channel"
const val ID_SIMILAR_PROGRESS = -901
const val ID_SIMILAR_COMPLETE = -902
// SY <--
private val deprecatedChannels = listOf( private val deprecatedChannels = listOf(
"downloader_channel", "downloader_channel",
"backup_restore_complete_channel" "backup_restore_complete_channel"
@ -174,14 +165,7 @@ object Notifications {
CHANNEL_INCOGNITO_MODE, CHANNEL_INCOGNITO_MODE,
context.getString(R.string.pref_incognito_mode), context.getString(R.string.pref_incognito_mode),
NotificationManager.IMPORTANCE_LOW NotificationManager.IMPORTANCE_LOW
), )
NotificationChannel(
CHANNEL_SIMILAR,
context.getString(R.string.similar_manga),
NotificationManager.IMPORTANCE_LOW
).apply {
setShowBadge(false)
}
).forEach(context.notificationManager::createNotificationChannel) ).forEach(context.notificationManager::createNotificationChannel)
// Delete old notification channels // Delete old notification channels

View File

@ -327,12 +327,6 @@ object PreferenceKeys {
const val mangaDexForceLatestCovers = "manga_dex_force_latest_covers" const val mangaDexForceLatestCovers = "manga_dex_force_latest_covers"
const val mangadexSimilarEnabled = "pref_related_show_tab_key"
const val mangadexSimilarUpdateInterval = "related_update_interval"
const val mangadexSimilarOnlyOverWifi = "pref_simular_only_over_wifi_key"
const val mangadexSyncToLibraryIndexes = "pref_mangadex_sync_to_library_indexes" const val mangadexSyncToLibraryIndexes = "pref_mangadex_sync_to_library_indexes"
const val preferredMangaDexId = "preferred_mangaDex_id" const val preferredMangaDexId = "preferred_mangaDex_id"

View File

@ -446,16 +446,8 @@ class PreferencesHelper(val context: Context) {
fun preferredMangaDexId() = flowPrefs.getString(Keys.preferredMangaDexId, "0") fun preferredMangaDexId() = flowPrefs.getString(Keys.preferredMangaDexId, "0")
fun mangadexSimilarEnabled() = flowPrefs.getBoolean(Keys.mangadexSimilarEnabled, false)
fun shownMangaDexSimilarAskDialog() = flowPrefs.getBoolean("shown_similar_ask_dialog", false)
fun mangadexSimilarOnlyOverWifi() = flowPrefs.getBoolean(Keys.mangadexSimilarOnlyOverWifi, true)
fun mangadexSyncToLibraryIndexes() = flowPrefs.getStringSet(Keys.mangadexSyncToLibraryIndexes, emptySet()) fun mangadexSyncToLibraryIndexes() = flowPrefs.getStringSet(Keys.mangadexSyncToLibraryIndexes, emptySet())
fun mangadexSimilarUpdateInterval() = flowPrefs.getInt(Keys.mangadexSimilarUpdateInterval, 2)
fun dataSaver() = flowPrefs.getBoolean(Keys.dataSaver, false) fun dataSaver() = flowPrefs.getBoolean(Keys.dataSaver, false)
fun ignoreJpeg() = flowPrefs.getBoolean(Keys.ignoreJpeg, false) fun ignoreJpeg() = flowPrefs.getBoolean(Keys.ignoreJpeg, false)

View File

@ -1,7 +1,6 @@
package eu.kanade.tachiyomi.source.online.all package eu.kanade.tachiyomi.source.online.all
import android.content.Context import android.content.Context
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.models.Track import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.track.TrackManager import eu.kanade.tachiyomi.data.track.TrackManager
@ -242,8 +241,8 @@ class MangaDex(delegate: HttpSource, val context: Context) :
return MangaHandler(baseHttpClient, headers, mdLang.lang).fetchRandomMangaId() return MangaHandler(baseHttpClient, headers, mdLang.lang).fetchRandomMangaId()
} }
suspend fun fetchMangaSimilar(manga: Manga): MangasPage { suspend fun getMangaSimilar(manga: MangaInfo): MangasPage {
return SimilarHandler(preferences, useLowQualityThumbnail()).fetchSimilar(manga) return SimilarHandler(baseHttpClient, mdLang.lang, preferences, useLowQualityThumbnail()).getSimilar(manga)
} }
/*private fun importIdToMdId(query: String, fail: () -> Observable<MangasPage>): Observable<MangasPage> = /*private fun importIdToMdId(query: String, fail: () -> Observable<MangasPage>): Observable<MangasPage> =

View File

@ -35,7 +35,6 @@ import eu.kanade.tachiyomi.source.LocalSource
import eu.kanade.tachiyomi.source.model.FilterList import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.source.online.LoginSource import eu.kanade.tachiyomi.source.online.LoginSource
import eu.kanade.tachiyomi.source.online.all.MangaDex
import eu.kanade.tachiyomi.ui.base.controller.FabController import eu.kanade.tachiyomi.ui.base.controller.FabController
import eu.kanade.tachiyomi.ui.base.controller.SearchableNucleusController import eu.kanade.tachiyomi.ui.base.controller.SearchableNucleusController
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
@ -57,7 +56,6 @@ import eu.kanade.tachiyomi.util.view.snack
import eu.kanade.tachiyomi.widget.AutofitRecyclerView import eu.kanade.tachiyomi.widget.AutofitRecyclerView
import eu.kanade.tachiyomi.widget.EmptyView import eu.kanade.tachiyomi.widget.EmptyView
import exh.log.xLogW import exh.log.xLogW
import exh.md.similar.ui.EnableMangaDexSimilarDialogController
import exh.savedsearches.EXHSavedSearch import exh.savedsearches.EXHSavedSearch
import exh.source.getMainSource import exh.source.getMainSource
import exh.source.isEhBasedSource import exh.source.isEhBasedSource
@ -172,11 +170,6 @@ open class BrowseSourceController(bundle: Bundle) :
// SY --> // SY -->
val mainSource = presenter.source.getMainSource() val mainSource = presenter.source.getMainSource()
if (mainSource is MangaDex && !preferences.mangadexSimilarEnabled().get() && !preferences.shownMangaDexSimilarAskDialog().get()) {
EnableMangaDexSimilarDialogController().showDialog(router)
preferences.shownMangaDexSimilarAskDialog().set(true)
}
if (mainSource is LoginSource && mainSource.requiresLogin && !mainSource.isLogged()) { if (mainSource is LoginSource && mainSource.requiresLogin && !mainSource.isLogged()) {
val dialog = MangadexLoginDialog(mainSource) val dialog = MangadexLoginDialog(mainSource)
dialog.showDialog(router) dialog.showDialog(router)

View File

@ -102,7 +102,7 @@ import eu.kanade.tachiyomi.util.view.getCoordinates
import eu.kanade.tachiyomi.util.view.shrinkOnScroll import eu.kanade.tachiyomi.util.view.shrinkOnScroll
import eu.kanade.tachiyomi.util.view.snack import eu.kanade.tachiyomi.util.view.snack
import exh.log.xLogD import exh.log.xLogD
import exh.md.similar.ui.MangaDexSimilarController import exh.md.similar.MangaDexSimilarController
import exh.metadata.metadata.base.FlatMetadata import exh.metadata.metadata.base.FlatMetadata
import exh.recs.RecommendsController import exh.recs.RecommendsController
import exh.source.MERGED_SOURCE_ID import exh.source.MERGED_SOURCE_ID
@ -767,7 +767,7 @@ class MangaController :
// AZ --> // AZ -->
fun openRecommends() { fun openRecommends() {
val source = presenter.source.getMainSource() val source = presenter.source.getMainSource()
if (source is MangaDex && preferences.mangadexSimilarEnabled().get()) { if (source is MangaDex) {
MaterialDialog(activity!!) MaterialDialog(activity!!)
.title(R.string.az_recommends) .title(R.string.az_recommends)
.listItemsSingleChoice( .listItemsSingleChoice(

View File

@ -6,27 +6,16 @@ import com.afollestad.materialdialogs.list.listItemsMultiChoice
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.library.LibraryUpdateService import eu.kanade.tachiyomi.data.library.LibraryUpdateService
import eu.kanade.tachiyomi.data.preference.PreferenceKeys import eu.kanade.tachiyomi.data.preference.PreferenceKeys
import eu.kanade.tachiyomi.data.preference.asImmediateFlow
import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.ui.base.controller.openInBrowser
import eu.kanade.tachiyomi.util.preference.defaultValue
import eu.kanade.tachiyomi.util.preference.entriesRes
import eu.kanade.tachiyomi.util.preference.intListPreference
import eu.kanade.tachiyomi.util.preference.listPreference import eu.kanade.tachiyomi.util.preference.listPreference
import eu.kanade.tachiyomi.util.preference.onClick import eu.kanade.tachiyomi.util.preference.onClick
import eu.kanade.tachiyomi.util.preference.preference import eu.kanade.tachiyomi.util.preference.preference
import eu.kanade.tachiyomi.util.preference.preferenceCategory
import eu.kanade.tachiyomi.util.preference.summaryRes import eu.kanade.tachiyomi.util.preference.summaryRes
import eu.kanade.tachiyomi.util.preference.switchPreference
import eu.kanade.tachiyomi.util.preference.titleRes import eu.kanade.tachiyomi.util.preference.titleRes
import eu.kanade.tachiyomi.util.system.toast
import exh.md.similar.SimilarUpdateJob
import exh.md.utils.MdUtil import exh.md.utils.MdUtil
import exh.widget.preference.MangaDexLoginPreference import exh.widget.preference.MangaDexLoginPreference
import exh.widget.preference.MangadexLoginDialog import exh.widget.preference.MangadexLoginDialog
import exh.widget.preference.MangadexLogoutDialog import exh.widget.preference.MangadexLogoutDialog
import kotlinx.coroutines.flow.drop
import kotlinx.coroutines.flow.launchIn
class SettingsMangaDexController : class SettingsMangaDexController :
SettingsController(), SettingsController(),
@ -108,76 +97,6 @@ class SettingsMangaDexController :
) )
} }
} }
preferenceCategory {
titleRes = R.string.similar_settings
preference {
key = "pref_similar_screen"
summaryRes = R.string.similar_screen_summary_message
isIconSpaceReserved = true
}
switchPreference {
key = PreferenceKeys.mangadexSimilarEnabled
titleRes = R.string.similar_screen
defaultValue = false
onClick {
SimilarUpdateJob.setupTask(context)
}
}
switchPreference {
key = PreferenceKeys.mangadexSimilarOnlyOverWifi
titleRes = R.string.pref_download_only_over_wifi
defaultValue = true
onClick {
SimilarUpdateJob.setupTask(context, true)
}
}
preference {
key = "pref_similar_manually_update"
titleRes = R.string.similar_manually_update
summaryRes = R.string.similar_manually_update_message
onClick {
SimilarUpdateJob.doWorkNow(context)
context.toast(R.string.similar_manually_toast)
}
}
intListPreference {
key = PreferenceKeys.mangadexSimilarUpdateInterval
titleRes = R.string.similar_update_fequency
entriesRes = arrayOf(
R.string.update_never,
R.string.update_24hour,
R.string.update_48hour,
R.string.update_weekly,
R.string.update_monthly
)
entryValues = arrayOf("0", "1", "2", "7", "30")
defaultValue = "2"
preferences.mangadexSimilarUpdateInterval()
.asImmediateFlow {
SimilarUpdateJob.setupTask(context, true)
}
.drop(1)
.launchIn(viewScope)
}
preference {
key = "similar_credits"
titleRes = R.string.similar_credit
val url = "https://github.com/goldbattle/MangadexRecomendations"
summary = context.getString(R.string.similar_credit_message, url)
onClick {
openInBrowser(url)
}
isIconSpaceReserved = true
}
}
} }
override fun siteLoginDialogClosed(source: Source) { override fun siteLoginDialogClosed(source: Source) {

View File

@ -1,36 +1,40 @@
package exh.md.handlers package exh.md.handlers
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.await
import eu.kanade.tachiyomi.network.parseAs
import eu.kanade.tachiyomi.source.model.MangasPage import eu.kanade.tachiyomi.source.model.MangasPage
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import exh.md.similar.sql.models.MangaSimilarImpl import exh.md.handlers.serializers.SimilarMangaResponse
import exh.md.utils.MdUtil import exh.md.utils.MdUtil
import exh.util.executeOnIO import okhttp3.CacheControl
import uy.kohesive.injekt.Injekt import okhttp3.Headers
import uy.kohesive.injekt.api.get import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import tachiyomi.source.model.MangaInfo
class SimilarHandler(val preferences: PreferencesHelper, private val useLowQualityCovers: Boolean) { class SimilarHandler(val client: OkHttpClient, val lang: String, val preferences: PreferencesHelper, private val useLowQualityCovers: Boolean) {
/** suspend fun getSimilar(manga: MangaInfo): MangasPage {
* fetch our similar mangas val response = client.newCall(similarMangaRequest(manga)).await()
*/ return similarMangaParse(response)
suspend fun fetchSimilar(manga: Manga): MangasPage { }
// Parse the Mangadex id from the URL
val mangaId = MdUtil.getMangaId(manga.url).toLong() private fun similarMangaRequest(manga: MangaInfo): Request {
val similarMangaDb = Injekt.get<DatabaseHelper>().getSimilar(mangaId).executeOnIO() val tempUrl = MdUtil.similarBaseApi + MdUtil.getMangaId(manga.key) + ".json"
return if (similarMangaDb != null) { return GET(tempUrl, Headers.Builder().build(), CacheControl.FORCE_NETWORK)
val similarMangaTitles = similarMangaDb.matched_titles.split(MangaSimilarImpl.DELIMITER) }
val similarMangaIds = similarMangaDb.matched_ids.split(MangaSimilarImpl.DELIMITER)
val similarMangas = similarMangaIds.mapIndexed { index, similarId -> private fun similarMangaParse(response: Response): MangasPage {
SManga.create().apply { val mangaList = response.parseAs<SimilarMangaResponse>().matches.map {
title = similarMangaTitles[index] SManga.create().apply {
url = "/manga/$similarId/" url = "/manga/" + it.id
thumbnail_url = MdUtil.formThumbUrl(url, useLowQualityCovers) title = MdUtil.cleanString(it.title[lang] ?: it.title["en"]!!)
} thumbnail_url = "https://coverapi.orell.dev/api/v1/mdaltimage/manga/${it.id}/cover"
} }
MangasPage(similarMangas, false) }
} else MangasPage(mutableListOf(), false) return MangasPage(mangaList, false)
} }
} }

View File

@ -0,0 +1,20 @@
package exh.md.handlers.serializers
import kotlinx.serialization.Serializable
@Serializable
data class SimilarMangaResponse(
val id: String,
val title: Map<String, String>,
val contentRating: String,
val matches: List<Matches>,
val updatedAt: String
)
@Serializable
data class Matches(
val id: String,
val title: Map<String, String>,
val contentRating: String,
val score: Double
)

View File

@ -1,4 +1,4 @@
package exh.md.similar.ui package exh.md.similar
import android.os.Bundle import android.os.Bundle
import android.view.Menu import android.view.Menu

View File

@ -1,6 +1,7 @@
package exh.md.similar.ui package exh.md.similar
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.models.toMangaInfo
import eu.kanade.tachiyomi.source.model.MangasPage import eu.kanade.tachiyomi.source.model.MangasPage
import eu.kanade.tachiyomi.source.online.all.MangaDex import eu.kanade.tachiyomi.source.online.all.MangaDex
import eu.kanade.tachiyomi.ui.browse.source.browse.NoResultsException import eu.kanade.tachiyomi.ui.browse.source.browse.NoResultsException
@ -16,7 +17,7 @@ import rx.schedulers.Schedulers
class MangaDexSimilarPager(val manga: Manga, val source: MangaDex) : Pager() { class MangaDexSimilarPager(val manga: Manga, val source: MangaDex) : Pager() {
override fun requestNext(): Observable<MangasPage> { override fun requestNext(): Observable<MangasPage> {
return runAsObservable({ source.fetchMangaSimilar(manga) }) return runAsObservable({ source.getMangaSimilar(manga.toMangaInfo()) })
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.doOnNext { .doOnNext {

View File

@ -1,4 +1,4 @@
package exh.md.similar.ui package exh.md.similar
import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga

View File

@ -1,74 +0,0 @@
package exh.md.similar
import android.content.Context
import androidx.work.Constraints
import androidx.work.ExistingPeriodicWorkPolicy
import androidx.work.NetworkType
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.PeriodicWorkRequestBuilder
import androidx.work.WorkManager
import androidx.work.Worker
import androidx.work.WorkerParameters
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.util.concurrent.TimeUnit
class SimilarUpdateJob(private val context: Context, workerParams: WorkerParameters) :
Worker(context, workerParams) {
override fun doWork(): Result {
SimilarUpdateService.start(context)
return Result.success()
}
companion object {
const val TAG = "RelatedUpdate"
fun setupTask(context: Context, skipInitial: Boolean = false) {
val preferences = Injekt.get<PreferencesHelper>()
val enabled = preferences.mangadexSimilarEnabled().get()
val interval = preferences.mangadexSimilarUpdateInterval().get()
if (enabled) {
// We are enabled, so construct the constraints
val wifiRestriction = if (preferences.mangadexSimilarOnlyOverWifi().get()) {
NetworkType.UNMETERED
} else {
NetworkType.CONNECTED
}
val constraints = Constraints.Builder()
.setRequiredNetworkType(wifiRestriction)
.build()
// If we are not skipping the initial then run it right now
// Note that we won't run it if the constraints are not satisfied
if (!skipInitial) {
WorkManager.getInstance(context).enqueue(OneTimeWorkRequestBuilder<SimilarUpdateJob>().setConstraints(constraints).build())
}
// Finally build the periodic request
val request = PeriodicWorkRequestBuilder<SimilarUpdateJob>(
interval.toLong(),
TimeUnit.DAYS,
1,
TimeUnit.HOURS
)
.addTag(TAG)
.setConstraints(constraints)
.build()
if (interval > 0) {
WorkManager.getInstance(context).enqueueUniquePeriodicWork(TAG, ExistingPeriodicWorkPolicy.REPLACE, request)
} else {
WorkManager.getInstance(context).cancelAllWorkByTag(TAG)
}
} else {
WorkManager.getInstance(context).cancelAllWorkByTag(TAG)
}
}
fun doWorkNow(context: Context) {
WorkManager.getInstance(context).enqueue(OneTimeWorkRequestBuilder<SimilarUpdateJob>().build())
}
}
}

View File

@ -1,351 +0,0 @@
package exh.md.similar
import android.app.Service
import android.content.Context
import android.content.Intent
import android.graphics.BitmapFactory
import android.os.IBinder
import android.os.PowerManager
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.core.content.ContextCompat
import androidx.core.text.isDigitsOnly
import com.squareup.moshi.JsonReader
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.notification.NotificationReceiver
import eu.kanade.tachiyomi.data.notification.Notifications
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.NetworkHelper
import eu.kanade.tachiyomi.network.await
import eu.kanade.tachiyomi.util.lang.withIOContext
import eu.kanade.tachiyomi.util.system.acquireWakeLock
import eu.kanade.tachiyomi.util.system.isServiceRunning
import eu.kanade.tachiyomi.util.system.notificationManager
import exh.log.xLogE
import exh.md.similar.sql.models.MangaSimilarImpl
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancel
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import okio.buffer
import okio.sink
import okio.source
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.io.File
class SimilarUpdateService(
val db: DatabaseHelper = Injekt.get()
) : Service() {
private val client by lazy {
Injekt.get<NetworkHelper>().client.newBuilder()
// unzip interceptor which will add the correct headers
.addNetworkInterceptor { chain ->
val originalResponse = chain.proceed(chain.request())
originalResponse.newBuilder()
.header("Content-Encoding", "gzip")
.header("Content-Type", "application/json")
.build()
}
.build()
}
/**
* Wake lock that will be held until the service is destroyed.
*/
private lateinit var wakeLock: PowerManager.WakeLock
private val similarServiceScope = CoroutineScope(Dispatchers.IO + Job())
/**
* Subscription where the update is done.
*/
private var job: Job? = null
/**
* Pending intent of action that cancels the library update
*/
private val cancelIntent by lazy {
NotificationReceiver.cancelSimilarUpdatePendingBroadcast(this)
}
private val progressNotification by lazy {
NotificationCompat.Builder(this, Notifications.CHANNEL_SIMILAR)
.setLargeIcon(BitmapFactory.decodeResource(this.resources, R.mipmap.ic_launcher))
.setSmallIcon(R.drawable.ic_tachi)
.setOngoing(true)
.setContentTitle(getString(R.string.similar_loading_progress_start))
.setAutoCancel(true)
.addAction(
R.drawable.ic_close_24dp,
getString(android.R.string.cancel),
cancelIntent
)
}
/**
* Method called when the service is created. It injects dagger dependencies and acquire
* the wake lock.
*/
override fun onCreate() {
super.onCreate()
wakeLock = acquireWakeLock("SimilarUpdateService")
startForeground(Notifications.ID_SIMILAR_PROGRESS, progressNotification.build())
}
override fun stopService(name: Intent?): Boolean {
destroyJob()
return super.stopService(name)
}
override fun onDestroy() {
destroyJob()
super.onDestroy()
}
private fun destroyJob() {
job?.cancel()
if (similarServiceScope.isActive) similarServiceScope.cancel()
if (wakeLock.isHeld) {
wakeLock.release()
}
}
/**
* This method needs to be implemented, but it's not used/needed.
*/
override fun onBind(intent: Intent): IBinder? = null
/**
* Method called when the service receives an intent.
*
* @param intent the start intent from.
* @param flags the flags of the command.
* @param startId the start id of this command.
* @return the start value of the command.
*/
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
if (intent == null) return START_NOT_STICKY
// Unsubscribe from any previous subscription if needed.
job?.cancel()
val handler = CoroutineExceptionHandler { _, exception ->
xLogE("Similar manga update error", exception)
stopSelf(startId)
showResultNotification(true)
cancelProgressNotification()
}
job = similarServiceScope.launch(handler) {
updateSimilar()
}
job?.invokeOnCompletion { stopSelf(startId) }
return START_REDELIVER_INTENT
}
/**
* Method that updates the similar database for manga
*/
private suspend fun updateSimilar() = withIOContext {
val response = client
.newCall(GET(similarUrl))
.await()
if (!response.isSuccessful) {
throw Exception("Error trying to download similar file")
}
val destinationFile = File(filesDir, "neko-similar.json")
val buffer = withIOContext { destinationFile.sink().buffer() }
// write json to file
response.body?.byteStream()?.source()?.use { input ->
buffer.use { output ->
output.writeAll(input)
}
}
val listSimilar = getSimilar(destinationFile)
// Delete the old similar table
db.deleteAllSimilar().executeAsBlocking()
val totalManga = listSimilar.size
// Loop through each and insert into the database
val dataToInsert = listSimilar.mapIndexed { index, similarFromJson ->
showProgressNotification(index, totalManga)
if (similarFromJson.similarIds.size != similarFromJson.similarTitles.size) {
return@mapIndexed null
}
MangaSimilarImpl().apply {
id = index.toLong()
manga_id = similarFromJson.id.toLong()
matched_ids = similarFromJson.similarIds.joinToString(MangaSimilarImpl.DELIMITER)
matched_titles = similarFromJson.similarTitles.joinToString(MangaSimilarImpl.DELIMITER)
}
}.filterNotNull()
showProgressNotification(dataToInsert.size, totalManga)
if (dataToInsert.isNotEmpty()) {
db.insertSimilar(dataToInsert).executeAsBlocking()
}
destinationFile.delete()
showResultNotification(!this.isActive)
cancelProgressNotification()
}
private fun getSimilar(destinationFile: File): List<SimilarFromJson> {
val reader = JsonReader.of(destinationFile.source().buffer())
var processingManga = false
var processingTitles = false
var mangaId: String? = null
var similarIds = mutableListOf<String>()
var similarTitles = mutableListOf<String>()
val similars = mutableListOf<SimilarFromJson>()
while (reader.peek() != JsonReader.Token.END_DOCUMENT) {
when (reader.peek()) {
JsonReader.Token.BEGIN_OBJECT -> {
reader.beginObject()
}
JsonReader.Token.NAME -> {
val name = reader.nextName()
if (!processingManga && name.isDigitsOnly()) {
processingManga = true
// similar add id
mangaId = name
} else if (name == "m_titles") {
processingTitles = true
}
}
JsonReader.Token.BEGIN_ARRAY -> {
reader.beginArray()
}
JsonReader.Token.END_ARRAY -> {
reader.endArray()
if (processingTitles) {
processingManga = false
processingTitles = false
similars.add(SimilarFromJson(mangaId!!, similarIds.toList(), similarTitles.toList()))
mangaId = null
similarIds = mutableListOf()
similarTitles = mutableListOf()
}
}
JsonReader.Token.NUMBER -> {
similarIds.add(reader.nextInt().toString())
}
JsonReader.Token.STRING -> {
if (processingTitles) {
similarTitles.add(reader.nextString())
}
}
JsonReader.Token.END_OBJECT -> {
reader.endObject()
}
else -> Unit
}
}
return similars
}
data class SimilarFromJson(val id: String, val similarIds: List<String>, val similarTitles: List<String>)
/**
* Shows the notification containing the currently updating manga and the progress.
*
* @param current the current progress.
* @param total the total progress.
*/
private fun showProgressNotification(current: Int, total: Int) {
notificationManager.notify(
Notifications.ID_SIMILAR_PROGRESS,
progressNotification
.setContentTitle(
getString(
R.string.similar_loading_percent,
current,
total
)
)
.setProgress(total, current, false)
.build()
)
}
/**
* Shows the notification containing the result of the update done by the service.
*
* @param error if the result was a error.
*/
private fun showResultNotification(error: Boolean = false) {
val title = if (error) {
getString(R.string.similar_loading_complete_error)
} else {
getString(
R.string.similar_loading_complete
)
}
val result = NotificationCompat.Builder(this, Notifications.CHANNEL_SIMILAR)
.setContentTitle(title)
.setLargeIcon(BitmapFactory.decodeResource(this.resources, R.mipmap.ic_launcher))
.setSmallIcon(R.drawable.ic_tachi)
.setAutoCancel(true)
NotificationManagerCompat.from(this)
.notify(Notifications.ID_SIMILAR_COMPLETE, result.build())
}
/**
* Cancels the progress notification.
*/
private fun cancelProgressNotification() {
notificationManager.cancel(Notifications.ID_SIMILAR_PROGRESS)
}
companion object {
private const val similarUrl = "https://raw.githubusercontent.com/goldbattle/MangadexRecomendations/master/output/mangas_compressed.json.gz"
/**
* Returns the status of the service.
*
* @param context the application context.
* @return true if the service is running, false otherwise.
*/
fun isRunning(context: Context): Boolean {
return context.isServiceRunning(SimilarUpdateService::class.java)
}
/**
* Starts the service. It will be started only if there isn't another instance already
* running.
*
* @param context the application context.
*/
fun start(context: Context) {
if (!isRunning(context)) {
val intent = Intent(context, SimilarUpdateService::class.java)
ContextCompat.startForegroundService(context, intent)
}
}
/**
* Stops the service.
*
* @param context the application context.
*/
fun stop(context: Context) {
context.stopService(Intent(context, SimilarUpdateService::class.java))
}
}
}

View File

@ -1,63 +0,0 @@
package exh.md.similar.sql.mappers
import android.database.Cursor
import androidx.core.content.contentValuesOf
import com.pushtorefresh.storio.sqlite.SQLiteTypeMapping
import com.pushtorefresh.storio.sqlite.operations.delete.DefaultDeleteResolver
import com.pushtorefresh.storio.sqlite.operations.get.DefaultGetResolver
import com.pushtorefresh.storio.sqlite.operations.put.DefaultPutResolver
import com.pushtorefresh.storio.sqlite.queries.DeleteQuery
import com.pushtorefresh.storio.sqlite.queries.InsertQuery
import com.pushtorefresh.storio.sqlite.queries.UpdateQuery
import exh.md.similar.sql.models.MangaSimilar
import exh.md.similar.sql.models.MangaSimilarImpl
import exh.md.similar.sql.tables.SimilarTable.COL_ID
import exh.md.similar.sql.tables.SimilarTable.COL_MANGA_ID
import exh.md.similar.sql.tables.SimilarTable.COL_MANGA_SIMILAR_MATCHED_IDS
import exh.md.similar.sql.tables.SimilarTable.COL_MANGA_SIMILAR_MATCHED_TITLES
import exh.md.similar.sql.tables.SimilarTable.TABLE
class SimilarTypeMapping : SQLiteTypeMapping<MangaSimilar>(
SimilarPutResolver(),
SimilarGetResolver(),
SimilarDeleteResolver()
)
class SimilarPutResolver : DefaultPutResolver<MangaSimilar>() {
override fun mapToInsertQuery(obj: MangaSimilar) = InsertQuery.builder()
.table(TABLE)
.build()
override fun mapToUpdateQuery(obj: MangaSimilar) = UpdateQuery.builder()
.table(TABLE)
.where("$COL_ID = ?")
.whereArgs(obj.id)
.build()
override fun mapToContentValues(obj: MangaSimilar) = contentValuesOf(
COL_ID to obj.id,
COL_MANGA_ID to obj.manga_id,
COL_MANGA_SIMILAR_MATCHED_IDS to obj.matched_ids,
COL_MANGA_SIMILAR_MATCHED_TITLES to obj.matched_titles
)
}
class SimilarGetResolver : DefaultGetResolver<MangaSimilar>() {
override fun mapFromCursor(cursor: Cursor): MangaSimilar = MangaSimilarImpl().apply {
id = cursor.getLong(cursor.getColumnIndex(COL_ID))
manga_id = cursor.getLong(cursor.getColumnIndex(COL_MANGA_ID))
matched_ids = cursor.getString(cursor.getColumnIndex(COL_MANGA_SIMILAR_MATCHED_IDS))
matched_titles = cursor.getString(cursor.getColumnIndex(COL_MANGA_SIMILAR_MATCHED_TITLES))
}
}
class SimilarDeleteResolver : DefaultDeleteResolver<MangaSimilar>() {
override fun mapToDeleteQuery(obj: MangaSimilar) = DeleteQuery.builder()
.table(TABLE)
.where("$COL_ID = ?")
.whereArgs(obj.id)
.build()
}

View File

@ -1,31 +0,0 @@
package exh.md.similar.sql.models
import java.io.Serializable
/**
* Object containing the history statistics of a chapter
*/
interface MangaSimilar : Serializable {
/**
* Id of this similar manga object.
*/
var id: Long?
/**
* Id of matching manga
*/
var manga_id: Long?
/**
* JSONArray.toString() list of ids for this manga
* Example: [3467, 5907, 21052, 2141, 6139, 5602, 3999]
*/
var matched_ids: String
/**
* JSONArray.toString() list of titles for this manga
* Example: [Title1, Title2, ..., Title10]
*/
var matched_titles: String
}

View File

@ -1,32 +0,0 @@
package exh.md.similar.sql.models
class MangaSimilarImpl : MangaSimilar {
override var id: Long? = null
override var manga_id: Long? = null
override lateinit var matched_ids: String
override lateinit var matched_titles: String
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other == null || javaClass != other.javaClass) return false
other as MangaSimilar
if (id != other.id) return false
if (manga_id != other.manga_id) return false
if (matched_ids != other.matched_ids) return false
return matched_titles != other.matched_titles
}
override fun hashCode(): Int {
return id.hashCode() + manga_id.hashCode()
}
companion object {
const val DELIMITER = "|*|"
}
}

View File

@ -1,42 +0,0 @@
package exh.md.similar.sql.queries
import com.pushtorefresh.storio.sqlite.queries.DeleteQuery
import com.pushtorefresh.storio.sqlite.queries.Query
import eu.kanade.tachiyomi.data.database.DbProvider
import exh.md.similar.sql.models.MangaSimilar
import exh.md.similar.sql.tables.SimilarTable
interface SimilarQueries : DbProvider {
fun getAllSimilar() = db.get()
.listOfObjects(MangaSimilar::class.java)
.withQuery(
Query.builder()
.table(SimilarTable.TABLE)
.build()
)
.prepare()
fun getSimilar(manga_id: Long) = db.get()
.`object`(MangaSimilar::class.java)
.withQuery(
Query.builder()
.table(SimilarTable.TABLE)
.where("${SimilarTable.COL_MANGA_ID} = ?")
.whereArgs(manga_id)
.build()
)
.prepare()
fun insertSimilar(similar: MangaSimilar) = db.put().`object`(similar).prepare()
fun insertSimilar(similarList: List<MangaSimilar>) = db.put().objects(similarList).prepare()
fun deleteAllSimilar() = db.delete()
.byQuery(
DeleteQuery.builder()
.table(SimilarTable.TABLE)
.build()
)
.prepare()
}

View File

@ -1,27 +0,0 @@
package exh.md.similar.sql.tables
object SimilarTable {
const val TABLE = "manga_related"
const val COL_ID = "_id"
const val COL_MANGA_ID = "manga_id"
const val COL_MANGA_SIMILAR_MATCHED_IDS = "matched_ids"
const val COL_MANGA_SIMILAR_MATCHED_TITLES = "matched_titles"
val createTableQuery: String
get() =
"""CREATE TABLE $TABLE(
$COL_ID INTEGER NOT NULL PRIMARY KEY,
$COL_MANGA_ID INTEGER NOT NULL,
$COL_MANGA_SIMILAR_MATCHED_IDS TEXT NOT NULL,
$COL_MANGA_SIMILAR_MATCHED_TITLES TEXT NOT NULL,
UNIQUE ($COL_ID) ON CONFLICT REPLACE
)"""
val createMangaIdIndexQuery: String
get() = "CREATE INDEX ${TABLE}_${COL_MANGA_SIMILAR_MATCHED_IDS}_index ON $TABLE($COL_MANGA_SIMILAR_MATCHED_IDS)"
}

View File

@ -1,27 +0,0 @@
package exh.md.similar.ui
import android.app.Dialog
import android.os.Bundle
import com.afollestad.materialdialogs.MaterialDialog
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.ui.base.controller.DialogController
import exh.md.similar.SimilarUpdateJob
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
class EnableMangaDexSimilarDialogController : DialogController() {
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
val activity = activity!!
val preferences = Injekt.get<PreferencesHelper>()
return MaterialDialog(activity)
.title(text = activity.getString(R.string.similar_ask_to_enable_title))
.message(R.string.similar_ask_to_enable)
.negativeButton(R.string.similar_ask_to_enable_no, activity.getString(R.string.similar_ask_to_enable_no))
.positiveButton(R.string.similar_ask_to_enable_yes, activity.getString(R.string.similar_ask_to_enable_yes)) {
preferences.mangadexSimilarEnabled().set(true)
SimilarUpdateJob.setupTask(activity)
}
}
}

View File

@ -42,8 +42,6 @@ class MdUtil {
const val cdnUrl = "https://mangadex.org" // "https://s0.mangadex.org" const val cdnUrl = "https://mangadex.org" // "https://s0.mangadex.org"
const val baseUrl = "https://mangadex.org" const val baseUrl = "https://mangadex.org"
const val apiUrl = "https://api.mangadex.org" const val apiUrl = "https://api.mangadex.org"
const val apiUrlCdnCache = "https://cdn.statically.io/gh/goldbattle/MangadexRecomendations/master/output/api/"
const val apiUrlCache = "https://raw.githubusercontent.com/goldbattle/MangadexRecomendations/master/output/api/"
const val imageUrlCacheNotFound = "https://cdn.statically.io/img/raw.githubusercontent.com/CarlosEsco/Neko/master/.github/manga_cover_not_found.png" const val imageUrlCacheNotFound = "https://cdn.statically.io/img/raw.githubusercontent.com/CarlosEsco/Neko/master/.github/manga_cover_not_found.png"
const val atHomeUrl = "$apiUrl/at-home/server" const val atHomeUrl = "$apiUrl/at-home/server"
const val chapterUrl = "$apiUrl/chapter/" const val chapterUrl = "$apiUrl/chapter/"
@ -70,6 +68,10 @@ class MdUtil {
}.build().toString() }.build().toString()
} }
const val similarCache = "https://raw.githubusercontent.com/goldbattle/MangadexRecomendations/master/output/api/"
const val similarCacheCdn = "https://cdn.statically.io/gh/goldbattle/MangadexRecomendations/master/output/api/"
const val similarBaseApi = "https://api.similarmanga.com/similar/"
const val groupSearchUrl = "$baseUrl/groups/0/1/" const val groupSearchUrl = "$baseUrl/groups/0/1/"
const val apiCovers = "/covers" const val apiCovers = "/covers"
const val reportUrl = "https://api.mangadex.network/report" const val reportUrl = "https://api.mangadex.network/report"