From 706163e7a644d8c023ff14cbb39d3f1012178a82 Mon Sep 17 00:00:00 2001 From: len Date: Fri, 20 Jan 2017 21:24:31 +0100 Subject: [PATCH] Move source and network outside data --- .../java/eu/kanade/tachiyomi/AppModule.kt | 4 +- .../tachiyomi/data/cache/ChapterCache.kt | 2 +- .../tachiyomi/data/database/models/Chapter.kt | 2 +- .../tachiyomi/data/database/models/Manga.kt | 2 +- .../data/download/DownloadManager.kt | 4 +- .../data/download/DownloadProvider.kt | 2 +- .../tachiyomi/data/download/DownloadStore.kt | 4 +- .../tachiyomi/data/download/Downloader.kt | 8 +- .../tachiyomi/data/download/model/Download.kt | 4 +- .../data/download/model/DownloadQueue.kt | 2 +- .../tachiyomi/data/glide/AppGlideModule.kt | 2 +- .../tachiyomi/data/glide/MangaModelLoader.kt | 4 +- .../data/library/LibraryUpdateService.kt | 6 +- .../data/preference/PreferencesHelper.kt | 2 +- .../tachiyomi/data/track/TrackService.kt | 2 +- .../data/track/anilist/AnilistApi.kt | 2 +- .../tachiyomi/data/track/kitsu/KitsuApi.kt | 2 +- .../data/track/myanimelist/MyanimelistApi.kt | 8 +- .../data/updater/UpdateDownloaderService.kt | 8 +- .../network/CloudflareInterceptor.kt | 158 ++-- .../{data => }/network/NetworkHelper.kt | 76 +- .../{data => }/network/OkHttpExtensions.kt | 138 ++-- .../{data => }/network/PersistentCookieJar.kt | 36 +- .../network/PersistentCookieStore.kt | 148 ++-- .../{data => }/network/ProgressListener.kt | 8 +- .../network/ProgressResponseBody.kt | 78 +- .../tachiyomi/{data => }/network/Requests.kt | 64 +- .../{data => }/source/CatalogueSource.kt | 90 +-- .../tachiyomi/{data => }/source/Source.kt | 86 +-- .../{data => }/source/SourceManager.kt | 318 ++++---- .../{data => }/source/model/Filter.kt | 2 +- .../{data => }/source/model/FilterList.kt | 12 +- .../{data => }/source/model/MangasPage.kt | 4 +- .../tachiyomi/{data => }/source/model/Page.kt | 94 +-- .../{data => }/source/model/SChapter.kt | 2 +- .../{data => }/source/model/SChapterImpl.kt | 2 +- .../{data => }/source/model/SManga.kt | 2 +- .../{data => }/source/model/SMangaImpl.kt | 2 +- .../{data => }/source/online/LoginSource.kt | 28 +- .../{data => }/source/online/OnlineSource.kt | 722 ++++++++--------- .../source/online/OnlineSourceFetcher.kt | 196 ++--- .../source/online/ParsedOnlineSource.kt | 400 +++++----- .../source/online/YamlOnlineSource.kt | 464 +++++------ .../source/online/YamlOnlineSourceMappings.kt | 466 +++++------ .../source/online/english/Batoto.kt | 730 +++++++++--------- .../source/online/english/Kissmanga.kt | 392 +++++----- .../source/online/english/Mangafox.kt | 444 +++++------ .../source/online/english/Mangahere.kt | 438 +++++------ .../source/online/english/Mangasee.kt | 484 ++++++------ .../source/online/english/Readmangatoday.kt | 436 +++++------ .../source/online/german/WieManga.kt | 242 +++--- .../source/online/russian/Mangachan.kt | 458 +++++------ .../source/online/russian/Mintmanga.kt | 366 ++++----- .../source/online/russian/Readmanga.kt | 364 ++++----- .../ui/catalogue/CatalogueFragment.kt | 4 +- .../tachiyomi/ui/catalogue/CataloguePager.kt | 6 +- .../ui/catalogue/CataloguePresenter.kt | 14 +- .../eu/kanade/tachiyomi/ui/catalogue/Pager.kt | 4 +- .../ui/catalogue/filter/CheckboxItem.kt | 2 +- .../ui/catalogue/filter/GroupItem.kt | 2 +- .../ui/catalogue/filter/HeaderItem.kt | 2 +- .../ui/catalogue/filter/SectionItems.kt | 2 +- .../ui/catalogue/filter/SelectItem.kt | 2 +- .../ui/catalogue/filter/SeparatorItem.kt | 2 +- .../ui/catalogue/filter/SortGroup.kt | 2 +- .../tachiyomi/ui/catalogue/filter/SortItem.kt | 2 +- .../tachiyomi/ui/catalogue/filter/TextItem.kt | 2 +- .../ui/catalogue/filter/TriStateItem.kt | 2 +- .../tachiyomi/ui/download/DownloadActivity.kt | 2 +- .../ui/latest_updates/LatestUpdatesPager.kt | 4 +- .../latest_updates/LatestUpdatesPresenter.kt | 6 +- .../tachiyomi/ui/library/LibraryPresenter.kt | 2 +- .../ui/manga/chapter/ChaptersPresenter.kt | 4 +- .../ui/manga/info/MangaInfoFragment.kt | 6 +- .../ui/manga/info/MangaInfoPresenter.kt | 4 +- .../tachiyomi/ui/reader/ChapterLoader.kt | 10 +- .../tachiyomi/ui/reader/ReaderActivity.kt | 2 +- .../tachiyomi/ui/reader/ReaderChapter.kt | 2 +- .../tachiyomi/ui/reader/ReaderPresenter.kt | 6 +- .../ui/reader/viewer/base/BaseReader.kt | 2 +- .../viewer/base/PageDecodeErrorLayout.kt | 2 +- .../ui/reader/viewer/pager/PageView.kt | 2 +- .../ui/reader/viewer/pager/PagerReader.kt | 2 +- .../reader/viewer/pager/PagerReaderAdapter.kt | 2 +- .../reader/viewer/webtoon/WebtoonAdapter.kt | 2 +- .../ui/reader/viewer/webtoon/WebtoonHolder.kt | 2 +- .../ui/reader/viewer/webtoon/WebtoonReader.kt | 2 +- .../recent_updates/RecentChaptersPresenter.kt | 2 +- .../ui/recently_read/RecentlyReadAdapter.kt | 2 +- .../ui/setting/SettingsAdvancedFragment.kt | 2 +- .../ui/setting/SettingsSourcesFragment.kt | 4 +- .../tachiyomi/util/ChapterSourceSync.kt | 6 +- .../preference/LoginCheckBoxPreference.kt | 4 +- .../widget/preference/SourceLoginDialog.kt | 6 +- .../data/library/LibraryUpdateServiceTest.kt | 6 +- 95 files changed, 4082 insertions(+), 4082 deletions(-) rename app/src/main/java/eu/kanade/tachiyomi/{data => }/network/CloudflareInterceptor.kt (96%) rename app/src/main/java/eu/kanade/tachiyomi/{data => }/network/NetworkHelper.kt (93%) rename app/src/main/java/eu/kanade/tachiyomi/{data => }/network/OkHttpExtensions.kt (95%) rename app/src/main/java/eu/kanade/tachiyomi/{data => }/network/PersistentCookieJar.kt (88%) rename app/src/main/java/eu/kanade/tachiyomi/{data => }/network/PersistentCookieStore.kt (95%) rename app/src/main/java/eu/kanade/tachiyomi/{data => }/network/ProgressListener.kt (70%) rename app/src/main/java/eu/kanade/tachiyomi/{data => }/network/ProgressResponseBody.kt (94%) rename app/src/main/java/eu/kanade/tachiyomi/{data => }/network/Requests.kt (92%) rename app/src/main/java/eu/kanade/tachiyomi/{data => }/source/CatalogueSource.kt (86%) rename app/src/main/java/eu/kanade/tachiyomi/{data => }/source/Source.kt (80%) rename app/src/main/java/eu/kanade/tachiyomi/{data => }/source/SourceManager.kt (89%) rename app/src/main/java/eu/kanade/tachiyomi/{data => }/source/model/Filter.kt (94%) rename app/src/main/java/eu/kanade/tachiyomi/{data => }/source/model/FilterList.kt (79%) rename app/src/main/java/eu/kanade/tachiyomi/{data => }/source/model/MangasPage.kt (61%) rename app/src/main/java/eu/kanade/tachiyomi/{data => }/source/model/Page.kt (88%) rename app/src/main/java/eu/kanade/tachiyomi/{data => }/source/model/SChapter.kt (86%) rename app/src/main/java/eu/kanade/tachiyomi/{data => }/source/model/SChapterImpl.kt (77%) rename app/src/main/java/eu/kanade/tachiyomi/{data => }/source/model/SManga.kt (91%) rename app/src/main/java/eu/kanade/tachiyomi/{data => }/source/model/SMangaImpl.kt (85%) rename app/src/main/java/eu/kanade/tachiyomi/{data => }/source/online/LoginSource.kt (71%) rename app/src/main/java/eu/kanade/tachiyomi/{data => }/source/online/OnlineSource.kt (94%) rename app/src/main/java/eu/kanade/tachiyomi/{data => }/source/online/OnlineSourceFetcher.kt (94%) rename app/src/main/java/eu/kanade/tachiyomi/{data => }/source/online/ParsedOnlineSource.kt (93%) rename app/src/main/java/eu/kanade/tachiyomi/{data => }/source/online/YamlOnlineSource.kt (95%) rename app/src/main/java/eu/kanade/tachiyomi/{data => }/source/online/YamlOnlineSourceMappings.kt (94%) rename app/src/main/java/eu/kanade/tachiyomi/{data => }/source/online/english/Batoto.kt (95%) rename app/src/main/java/eu/kanade/tachiyomi/{data => }/source/online/english/Kissmanga.kt (93%) rename app/src/main/java/eu/kanade/tachiyomi/{data => }/source/online/english/Mangafox.kt (95%) rename app/src/main/java/eu/kanade/tachiyomi/{data => }/source/online/english/Mangahere.kt (95%) rename app/src/main/java/eu/kanade/tachiyomi/{data => }/source/online/english/Mangasee.kt (95%) rename app/src/main/java/eu/kanade/tachiyomi/{data => }/source/online/english/Readmangatoday.kt (94%) rename app/src/main/java/eu/kanade/tachiyomi/{data => }/source/online/german/WieManga.kt (89%) rename app/src/main/java/eu/kanade/tachiyomi/{data => }/source/online/russian/Mangachan.kt (95%) rename app/src/main/java/eu/kanade/tachiyomi/{data => }/source/online/russian/Mintmanga.kt (95%) rename app/src/main/java/eu/kanade/tachiyomi/{data => }/source/online/russian/Readmanga.kt (95%) diff --git a/app/src/main/java/eu/kanade/tachiyomi/AppModule.kt b/app/src/main/java/eu/kanade/tachiyomi/AppModule.kt index 36fbadf571..cf4be77741 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/AppModule.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/AppModule.kt @@ -6,9 +6,9 @@ import eu.kanade.tachiyomi.data.cache.ChapterCache import eu.kanade.tachiyomi.data.cache.CoverCache import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.download.DownloadManager -import eu.kanade.tachiyomi.data.network.NetworkHelper +import eu.kanade.tachiyomi.network.NetworkHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper -import eu.kanade.tachiyomi.data.source.SourceManager +import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.data.track.TrackManager import uy.kohesive.injekt.api.InjektModule import uy.kohesive.injekt.api.InjektRegistrar diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/cache/ChapterCache.kt b/app/src/main/java/eu/kanade/tachiyomi/data/cache/ChapterCache.kt index 630a87f9be..6f75d0db64 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/cache/ChapterCache.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/cache/ChapterCache.kt @@ -6,7 +6,7 @@ import com.github.salomonbrys.kotson.fromJson import com.google.gson.Gson import com.jakewharton.disklrucache.DiskLruCache import eu.kanade.tachiyomi.data.database.models.Chapter -import eu.kanade.tachiyomi.data.source.model.Page +import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.util.DiskUtil import eu.kanade.tachiyomi.util.saveTo import okhttp3.Response diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Chapter.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Chapter.kt index 777f8f221d..589ed671db 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Chapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Chapter.kt @@ -1,6 +1,6 @@ package eu.kanade.tachiyomi.data.database.models -import eu.kanade.tachiyomi.data.source.model.SChapter +import eu.kanade.tachiyomi.source.model.SChapter import java.io.Serializable interface Chapter : SChapter, Serializable { diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Manga.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Manga.kt index afb0264910..7621f64d8b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Manga.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Manga.kt @@ -1,6 +1,6 @@ package eu.kanade.tachiyomi.data.database.models -import eu.kanade.tachiyomi.data.source.model.SManga +import eu.kanade.tachiyomi.source.model.SManga interface Manga : SManga { diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadManager.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadManager.kt index e2833c5542..0d4e790484 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadManager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadManager.kt @@ -6,8 +6,8 @@ import com.jakewharton.rxrelay.BehaviorRelay import eu.kanade.tachiyomi.data.database.models.Chapter import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.download.model.DownloadQueue -import eu.kanade.tachiyomi.data.source.Source -import eu.kanade.tachiyomi.data.source.model.Page +import eu.kanade.tachiyomi.source.Source +import eu.kanade.tachiyomi.source.model.Page import rx.Observable /** diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadProvider.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadProvider.kt index 018126b900..894395d118 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadProvider.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadProvider.kt @@ -7,7 +7,7 @@ import eu.kanade.tachiyomi.data.database.models.Chapter import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.getOrDefault -import eu.kanade.tachiyomi.data.source.Source +import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.util.DiskUtil import uy.kohesive.injekt.injectLazy diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadStore.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadStore.kt index 72d79c10b0..adbfe0d331 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadStore.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadStore.kt @@ -5,8 +5,8 @@ import com.google.gson.Gson import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.download.model.Download -import eu.kanade.tachiyomi.data.source.SourceManager -import eu.kanade.tachiyomi.data.source.online.OnlineSource +import eu.kanade.tachiyomi.source.SourceManager +import eu.kanade.tachiyomi.source.online.OnlineSource import uy.kohesive.injekt.injectLazy /** 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 537a020ed8..73929709a8 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt @@ -10,10 +10,10 @@ import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.download.model.Download import eu.kanade.tachiyomi.data.download.model.DownloadQueue import eu.kanade.tachiyomi.data.preference.PreferencesHelper -import eu.kanade.tachiyomi.data.source.SourceManager -import eu.kanade.tachiyomi.data.source.model.Page -import eu.kanade.tachiyomi.data.source.online.OnlineSource -import eu.kanade.tachiyomi.data.source.online.fetchAllImageUrlsFromPageList +import eu.kanade.tachiyomi.source.SourceManager +import eu.kanade.tachiyomi.source.model.Page +import eu.kanade.tachiyomi.source.online.OnlineSource +import eu.kanade.tachiyomi.source.online.fetchAllImageUrlsFromPageList import eu.kanade.tachiyomi.util.DynamicConcurrentMergeOperator import eu.kanade.tachiyomi.util.RetryWithDelay import eu.kanade.tachiyomi.util.plusAssign diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/model/Download.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/model/Download.kt index 48b2e0b623..bb9821e21c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/model/Download.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/model/Download.kt @@ -2,8 +2,8 @@ package eu.kanade.tachiyomi.data.download.model import eu.kanade.tachiyomi.data.database.models.Chapter import eu.kanade.tachiyomi.data.database.models.Manga -import eu.kanade.tachiyomi.data.source.model.Page -import eu.kanade.tachiyomi.data.source.online.OnlineSource +import eu.kanade.tachiyomi.source.model.Page +import eu.kanade.tachiyomi.source.online.OnlineSource import rx.subjects.PublishSubject class Download(val source: OnlineSource, val manga: Manga, val chapter: Chapter) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/model/DownloadQueue.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/model/DownloadQueue.kt index 2bb7febd2c..389dd38226 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/model/DownloadQueue.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/model/DownloadQueue.kt @@ -3,7 +3,7 @@ package eu.kanade.tachiyomi.data.download.model import com.jakewharton.rxrelay.PublishRelay import eu.kanade.tachiyomi.data.database.models.Chapter import eu.kanade.tachiyomi.data.download.DownloadStore -import eu.kanade.tachiyomi.data.source.model.Page +import eu.kanade.tachiyomi.source.model.Page import rx.Observable import rx.subjects.PublishSubject import java.util.concurrent.CopyOnWriteArrayList diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/glide/AppGlideModule.kt b/app/src/main/java/eu/kanade/tachiyomi/data/glide/AppGlideModule.kt index 6e1862373c..b1b722acbb 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/glide/AppGlideModule.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/glide/AppGlideModule.kt @@ -8,7 +8,7 @@ import com.bumptech.glide.load.engine.cache.InternalCacheDiskCacheFactory import com.bumptech.glide.load.model.GlideUrl import com.bumptech.glide.module.GlideModule import eu.kanade.tachiyomi.data.database.models.Manga -import eu.kanade.tachiyomi.data.network.NetworkHelper +import eu.kanade.tachiyomi.network.NetworkHelper import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import java.io.InputStream diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/glide/MangaModelLoader.kt b/app/src/main/java/eu/kanade/tachiyomi/data/glide/MangaModelLoader.kt index 0787d427cc..273ace468c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/glide/MangaModelLoader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/glide/MangaModelLoader.kt @@ -8,8 +8,8 @@ import com.bumptech.glide.load.model.* import com.bumptech.glide.load.model.stream.StreamModelLoader import eu.kanade.tachiyomi.data.cache.CoverCache import eu.kanade.tachiyomi.data.database.models.Manga -import eu.kanade.tachiyomi.data.source.SourceManager -import eu.kanade.tachiyomi.data.source.online.OnlineSource +import eu.kanade.tachiyomi.source.SourceManager +import eu.kanade.tachiyomi.source.online.OnlineSource import uy.kohesive.injekt.injectLazy import java.io.File import java.io.InputStream diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateService.kt b/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateService.kt index 620dac81d2..26d2ace8c0 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateService.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateService.kt @@ -20,9 +20,9 @@ import eu.kanade.tachiyomi.data.library.LibraryUpdateService.Companion.start import eu.kanade.tachiyomi.data.notification.NotificationReceiver import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.getOrDefault -import eu.kanade.tachiyomi.data.source.SourceManager -import eu.kanade.tachiyomi.data.source.model.SManga -import eu.kanade.tachiyomi.data.source.online.OnlineSource +import eu.kanade.tachiyomi.source.SourceManager +import eu.kanade.tachiyomi.source.model.SManga +import eu.kanade.tachiyomi.source.online.OnlineSource import eu.kanade.tachiyomi.ui.main.MainActivity import eu.kanade.tachiyomi.util.* import rx.Observable diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt index e1f3162623..b144feb340 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt @@ -7,7 +7,7 @@ import android.preference.PreferenceManager import com.f2prateek.rx.preferences.Preference import com.f2prateek.rx.preferences.RxSharedPreferences import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.data.source.Source +import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.data.track.TrackService import java.io.File diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/TrackService.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/TrackService.kt index 39b5db83fc..70b8e4cac6 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/TrackService.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/TrackService.kt @@ -3,7 +3,7 @@ package eu.kanade.tachiyomi.data.track import android.support.annotation.CallSuper import android.support.annotation.DrawableRes import eu.kanade.tachiyomi.data.database.models.Track -import eu.kanade.tachiyomi.data.network.NetworkHelper +import eu.kanade.tachiyomi.network.NetworkHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper import okhttp3.OkHttpClient import rx.Completable diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/AnilistApi.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/AnilistApi.kt index 82ec5fa4af..1e1e620cbb 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/AnilistApi.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/AnilistApi.kt @@ -5,7 +5,7 @@ import com.github.salomonbrys.kotson.int import com.github.salomonbrys.kotson.string import com.google.gson.JsonObject import eu.kanade.tachiyomi.data.database.models.Track -import eu.kanade.tachiyomi.data.network.POST +import eu.kanade.tachiyomi.network.POST import okhttp3.FormBody import okhttp3.OkHttpClient import okhttp3.ResponseBody diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/KitsuApi.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/KitsuApi.kt index 5f24267552..2ddca50b67 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/KitsuApi.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/KitsuApi.kt @@ -3,7 +3,7 @@ package eu.kanade.tachiyomi.data.track.kitsu import com.github.salomonbrys.kotson.* import com.google.gson.JsonObject import eu.kanade.tachiyomi.data.database.models.Track -import eu.kanade.tachiyomi.data.network.POST +import eu.kanade.tachiyomi.network.POST import okhttp3.FormBody import okhttp3.OkHttpClient import retrofit2.Retrofit diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyanimelistApi.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyanimelistApi.kt index d40985ca03..f9d11ba9f9 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyanimelistApi.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyanimelistApi.kt @@ -3,10 +3,10 @@ package eu.kanade.tachiyomi.data.track.myanimelist import android.net.Uri import android.util.Xml import eu.kanade.tachiyomi.data.database.models.Track -import eu.kanade.tachiyomi.data.network.GET -import eu.kanade.tachiyomi.data.network.POST -import eu.kanade.tachiyomi.data.network.asObservable -import eu.kanade.tachiyomi.data.network.asObservableSuccess +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.network.POST +import eu.kanade.tachiyomi.network.asObservable +import eu.kanade.tachiyomi.network.asObservableSuccess import eu.kanade.tachiyomi.data.track.TrackManager import eu.kanade.tachiyomi.util.selectInt import eu.kanade.tachiyomi.util.selectText diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdateDownloaderService.kt b/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdateDownloaderService.kt index 8d7e682166..774a3ff99b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdateDownloaderService.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdateDownloaderService.kt @@ -8,10 +8,10 @@ import android.content.Intent import android.content.IntentFilter import android.os.Build import eu.kanade.tachiyomi.BuildConfig -import eu.kanade.tachiyomi.data.network.GET -import eu.kanade.tachiyomi.data.network.NetworkHelper -import eu.kanade.tachiyomi.data.network.ProgressListener -import eu.kanade.tachiyomi.data.network.newCallWithProgress +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.network.NetworkHelper +import eu.kanade.tachiyomi.network.ProgressListener +import eu.kanade.tachiyomi.network.newCallWithProgress import eu.kanade.tachiyomi.util.registerLocalReceiver import eu.kanade.tachiyomi.util.saveTo import eu.kanade.tachiyomi.util.sendLocalBroadcastSync diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/network/CloudflareInterceptor.kt b/app/src/main/java/eu/kanade/tachiyomi/network/CloudflareInterceptor.kt similarity index 96% rename from app/src/main/java/eu/kanade/tachiyomi/data/network/CloudflareInterceptor.kt rename to app/src/main/java/eu/kanade/tachiyomi/network/CloudflareInterceptor.kt index 503b779a3d..2e28a976c5 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/network/CloudflareInterceptor.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/network/CloudflareInterceptor.kt @@ -1,80 +1,80 @@ -package eu.kanade.tachiyomi.data.network - -import com.squareup.duktape.Duktape -import okhttp3.HttpUrl -import okhttp3.Interceptor -import okhttp3.Request -import okhttp3.Response - -class CloudflareInterceptor(private val cookies: PersistentCookieStore) : Interceptor { - - //language=RegExp - private val operationPattern = Regex("""setTimeout\(function\(\)\{\s+(var (?:\w,)+f.+?\r?\n[\s\S]+?a\.value =.+?)\r?\n""") - - //language=RegExp - private val passPattern = Regex("""name="pass" value="(.+?)"""") - - //language=RegExp - private val challengePattern = Regex("""name="jschl_vc" value="(\w+)"""") - - override fun intercept(chain: Interceptor.Chain): Response { - val response = chain.proceed(chain.request()) - - // Check if we already solved a challenge - if (response.code() != 503 && - cookies.get(response.request().url()).any { it.name() == "cf_clearance" }) { - return response - } - - // Check if Cloudflare anti-bot is on - if ("URL=/cdn-cgi/" in response.header("Refresh", "") - && response.header("Server", "") == "cloudflare-nginx") { - return chain.proceed(resolveChallenge(response)) - } - - return response - } - - private fun resolveChallenge(response: Response): Request { - val duktape = Duktape.create() - try { - val originalRequest = response.request() - val domain = originalRequest.url().host() - val content = response.body().string() - - // CloudFlare requires waiting 4 seconds before resolving the challenge - Thread.sleep(4000) - - val operation = operationPattern.find(content)?.groups?.get(1)?.value - val challenge = challengePattern.find(content)?.groups?.get(1)?.value - val pass = passPattern.find(content)?.groups?.get(1)?.value - - if (operation == null || challenge == null || pass == null) { - throw RuntimeException("Failed resolving Cloudflare challenge") - } - - val js = operation - //language=RegExp - .replace(Regex("""a\.value =(.+?) \+.*"""), "$1") - //language=RegExp - .replace(Regex("""\s{3,}[a-z](?: = |\.).+"""), "") - .replace("\n", "") - - val result = (duktape.evaluate(js) as Double).toInt() - - val answer = "${result + domain.length}" - - val url = HttpUrl.parse("http://$domain/cdn-cgi/l/chk_jschl").newBuilder() - .addQueryParameter("jschl_vc", challenge) - .addQueryParameter("pass", pass) - .addQueryParameter("jschl_answer", answer) - .toString() - - val referer = originalRequest.url().toString() - return GET(url, originalRequest.headers().newBuilder().add("Referer", referer).build()) - } finally { - duktape.close() - } - } - +package eu.kanade.tachiyomi.network + +import com.squareup.duktape.Duktape +import okhttp3.HttpUrl +import okhttp3.Interceptor +import okhttp3.Request +import okhttp3.Response + +class CloudflareInterceptor(private val cookies: PersistentCookieStore) : Interceptor { + + //language=RegExp + private val operationPattern = Regex("""setTimeout\(function\(\)\{\s+(var (?:\w,)+f.+?\r?\n[\s\S]+?a\.value =.+?)\r?\n""") + + //language=RegExp + private val passPattern = Regex("""name="pass" value="(.+?)"""") + + //language=RegExp + private val challengePattern = Regex("""name="jschl_vc" value="(\w+)"""") + + override fun intercept(chain: Interceptor.Chain): Response { + val response = chain.proceed(chain.request()) + + // Check if we already solved a challenge + if (response.code() != 503 && + cookies.get(response.request().url()).any { it.name() == "cf_clearance" }) { + return response + } + + // Check if Cloudflare anti-bot is on + if ("URL=/cdn-cgi/" in response.header("Refresh", "") + && response.header("Server", "") == "cloudflare-nginx") { + return chain.proceed(resolveChallenge(response)) + } + + return response + } + + private fun resolveChallenge(response: Response): Request { + val duktape = Duktape.create() + try { + val originalRequest = response.request() + val domain = originalRequest.url().host() + val content = response.body().string() + + // CloudFlare requires waiting 4 seconds before resolving the challenge + Thread.sleep(4000) + + val operation = operationPattern.find(content)?.groups?.get(1)?.value + val challenge = challengePattern.find(content)?.groups?.get(1)?.value + val pass = passPattern.find(content)?.groups?.get(1)?.value + + if (operation == null || challenge == null || pass == null) { + throw RuntimeException("Failed resolving Cloudflare challenge") + } + + val js = operation + //language=RegExp + .replace(Regex("""a\.value =(.+?) \+.*"""), "$1") + //language=RegExp + .replace(Regex("""\s{3,}[a-z](?: = |\.).+"""), "") + .replace("\n", "") + + val result = (duktape.evaluate(js) as Double).toInt() + + val answer = "${result + domain.length}" + + val url = HttpUrl.parse("http://$domain/cdn-cgi/l/chk_jschl").newBuilder() + .addQueryParameter("jschl_vc", challenge) + .addQueryParameter("pass", pass) + .addQueryParameter("jschl_answer", answer) + .toString() + + val referer = originalRequest.url().toString() + return GET(url, originalRequest.headers().newBuilder().add("Referer", referer).build()) + } finally { + duktape.close() + } + } + } \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/network/NetworkHelper.kt b/app/src/main/java/eu/kanade/tachiyomi/network/NetworkHelper.kt similarity index 93% rename from app/src/main/java/eu/kanade/tachiyomi/data/network/NetworkHelper.kt rename to app/src/main/java/eu/kanade/tachiyomi/network/NetworkHelper.kt index 4e95ed564e..43befd3913 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/network/NetworkHelper.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/network/NetworkHelper.kt @@ -1,38 +1,38 @@ -package eu.kanade.tachiyomi.data.network - -import android.content.Context -import okhttp3.Cache -import okhttp3.OkHttpClient -import java.io.File - -class NetworkHelper(context: Context) { - - private val cacheDir = File(context.cacheDir, "network_cache") - - private val cacheSize = 5L * 1024 * 1024 // 5 MiB - - private val cookieManager = PersistentCookieJar(context) - - val client = OkHttpClient.Builder() - .cookieJar(cookieManager) - .cache(Cache(cacheDir, cacheSize)) - .build() - - val forceCacheClient = client.newBuilder() - .addNetworkInterceptor { chain -> - val originalResponse = chain.proceed(chain.request()) - originalResponse.newBuilder() - .removeHeader("Pragma") - .header("Cache-Control", "max-age=600") - .build() - } - .build() - - val cloudflareClient = client.newBuilder() - .addInterceptor(CloudflareInterceptor(cookies)) - .build() - - val cookies: PersistentCookieStore - get() = cookieManager.store - -} +package eu.kanade.tachiyomi.network + +import android.content.Context +import okhttp3.Cache +import okhttp3.OkHttpClient +import java.io.File + +class NetworkHelper(context: Context) { + + private val cacheDir = File(context.cacheDir, "network_cache") + + private val cacheSize = 5L * 1024 * 1024 // 5 MiB + + private val cookieManager = PersistentCookieJar(context) + + val client = OkHttpClient.Builder() + .cookieJar(cookieManager) + .cache(Cache(cacheDir, cacheSize)) + .build() + + val forceCacheClient = client.newBuilder() + .addNetworkInterceptor { chain -> + val originalResponse = chain.proceed(chain.request()) + originalResponse.newBuilder() + .removeHeader("Pragma") + .header("Cache-Control", "max-age=600") + .build() + } + .build() + + val cloudflareClient = client.newBuilder() + .addInterceptor(CloudflareInterceptor(cookies)) + .build() + + val cookies: PersistentCookieStore + get() = cookieManager.store + +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/network/OkHttpExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/network/OkHttpExtensions.kt similarity index 95% rename from app/src/main/java/eu/kanade/tachiyomi/data/network/OkHttpExtensions.kt rename to app/src/main/java/eu/kanade/tachiyomi/network/OkHttpExtensions.kt index 7b75bdd0b7..8016b1ac8a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/network/OkHttpExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/network/OkHttpExtensions.kt @@ -1,70 +1,70 @@ -package eu.kanade.tachiyomi.data.network - -import okhttp3.Call -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import rx.Observable -import rx.Producer -import rx.Subscription -import java.util.concurrent.atomic.AtomicBoolean - -fun Call.asObservable(): Observable { - return Observable.create { subscriber -> - // Since Call is a one-shot type, clone it for each new subscriber. - val call = clone() - - // Wrap the call in a helper which handles both unsubscription and backpressure. - val requestArbiter = object : AtomicBoolean(), Producer, Subscription { - override fun request(n: Long) { - if (n == 0L || !compareAndSet(false, true)) return - - try { - val response = call.execute() - if (!subscriber.isUnsubscribed) { - subscriber.onNext(response) - subscriber.onCompleted() - } - } catch (error: Exception) { - if (!subscriber.isUnsubscribed) { - subscriber.onError(error) - } - } - } - - override fun unsubscribe() { - call.cancel() - } - - override fun isUnsubscribed(): Boolean { - return call.isCanceled - } - } - - subscriber.add(requestArbiter) - subscriber.setProducer(requestArbiter) - } -} - -fun Call.asObservableSuccess(): Observable { - return asObservable().doOnNext { response -> - if (!response.isSuccessful) { - response.close() - throw Exception("HTTP error ${response.code()}") - } - } -} - -fun OkHttpClient.newCallWithProgress(request: Request, listener: ProgressListener): Call { - val progressClient = newBuilder() - .cache(null) - .addNetworkInterceptor { chain -> - val originalResponse = chain.proceed(chain.request()) - originalResponse.newBuilder() - .body(ProgressResponseBody(originalResponse.body(), listener)) - .build() - } - .build() - - return progressClient.newCall(request) +package eu.kanade.tachiyomi.network + +import okhttp3.Call +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.Response +import rx.Observable +import rx.Producer +import rx.Subscription +import java.util.concurrent.atomic.AtomicBoolean + +fun Call.asObservable(): Observable { + return Observable.create { subscriber -> + // Since Call is a one-shot type, clone it for each new subscriber. + val call = clone() + + // Wrap the call in a helper which handles both unsubscription and backpressure. + val requestArbiter = object : AtomicBoolean(), Producer, Subscription { + override fun request(n: Long) { + if (n == 0L || !compareAndSet(false, true)) return + + try { + val response = call.execute() + if (!subscriber.isUnsubscribed) { + subscriber.onNext(response) + subscriber.onCompleted() + } + } catch (error: Exception) { + if (!subscriber.isUnsubscribed) { + subscriber.onError(error) + } + } + } + + override fun unsubscribe() { + call.cancel() + } + + override fun isUnsubscribed(): Boolean { + return call.isCanceled + } + } + + subscriber.add(requestArbiter) + subscriber.setProducer(requestArbiter) + } +} + +fun Call.asObservableSuccess(): Observable { + return asObservable().doOnNext { response -> + if (!response.isSuccessful) { + response.close() + throw Exception("HTTP error ${response.code()}") + } + } +} + +fun OkHttpClient.newCallWithProgress(request: Request, listener: ProgressListener): Call { + val progressClient = newBuilder() + .cache(null) + .addNetworkInterceptor { chain -> + val originalResponse = chain.proceed(chain.request()) + originalResponse.newBuilder() + .body(ProgressResponseBody(originalResponse.body(), listener)) + .build() + } + .build() + + return progressClient.newCall(request) } \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/network/PersistentCookieJar.kt b/app/src/main/java/eu/kanade/tachiyomi/network/PersistentCookieJar.kt similarity index 88% rename from app/src/main/java/eu/kanade/tachiyomi/data/network/PersistentCookieJar.kt rename to app/src/main/java/eu/kanade/tachiyomi/network/PersistentCookieJar.kt index a53bc39c08..fda9799789 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/network/PersistentCookieJar.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/network/PersistentCookieJar.kt @@ -1,19 +1,19 @@ -package eu.kanade.tachiyomi.data.network - -import android.content.Context -import okhttp3.Cookie -import okhttp3.CookieJar -import okhttp3.HttpUrl - -class PersistentCookieJar(context: Context) : CookieJar { - - val store = PersistentCookieStore(context) - - override fun saveFromResponse(url: HttpUrl, cookies: List) { - store.addAll(url, cookies) - } - - override fun loadForRequest(url: HttpUrl): List { - return store.get(url) - } +package eu.kanade.tachiyomi.network + +import android.content.Context +import okhttp3.Cookie +import okhttp3.CookieJar +import okhttp3.HttpUrl + +class PersistentCookieJar(context: Context) : CookieJar { + + val store = PersistentCookieStore(context) + + override fun saveFromResponse(url: HttpUrl, cookies: List) { + store.addAll(url, cookies) + } + + override fun loadForRequest(url: HttpUrl): List { + return store.get(url) + } } \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/network/PersistentCookieStore.kt b/app/src/main/java/eu/kanade/tachiyomi/network/PersistentCookieStore.kt similarity index 95% rename from app/src/main/java/eu/kanade/tachiyomi/data/network/PersistentCookieStore.kt rename to app/src/main/java/eu/kanade/tachiyomi/network/PersistentCookieStore.kt index 73690e7ff7..1ded99eaac 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/network/PersistentCookieStore.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/network/PersistentCookieStore.kt @@ -1,75 +1,75 @@ -package eu.kanade.tachiyomi.data.network - -import android.content.Context -import okhttp3.Cookie -import okhttp3.HttpUrl -import java.net.URI -import java.util.concurrent.ConcurrentHashMap - -class PersistentCookieStore(context: Context) { - - private val cookieMap = ConcurrentHashMap>() - private val prefs = context.getSharedPreferences("cookie_store", Context.MODE_PRIVATE) - - init { - for ((key, value) in prefs.all) { - @Suppress("UNCHECKED_CAST") - val cookies = value as? Set - if (cookies != null) { - try { - val url = HttpUrl.parse("http://$key") - val nonExpiredCookies = cookies.map { Cookie.parse(url, it) } - .filter { !it.hasExpired() } - cookieMap.put(key, nonExpiredCookies) - } catch (e: Exception) { - // Ignore - } - } - } - } - - fun addAll(url: HttpUrl, cookies: List) { - synchronized(this) { - val key = url.uri().host - - // Append or replace the cookies for this domain. - val cookiesForDomain = cookieMap[key].orEmpty().toMutableList() - for (cookie in cookies) { - // Find a cookie with the same name. Replace it if found, otherwise add a new one. - val pos = cookiesForDomain.indexOfFirst { it.name() == cookie.name() } - if (pos == -1) { - cookiesForDomain.add(cookie) - } else { - cookiesForDomain[pos] = cookie - } - } - cookieMap.put(key, cookiesForDomain) - - // Get cookies to be stored in disk - val newValues = cookiesForDomain.asSequence() - .filter { it.persistent() && !it.hasExpired() } - .map { it.toString() } - .toSet() - - prefs.edit().putStringSet(key, newValues).apply() - } - } - - fun removeAll() { - synchronized(this) { - prefs.edit().clear().apply() - cookieMap.clear() - } - } - - fun get(url: HttpUrl) = get(url.uri().host) - - fun get(uri: URI) = get(uri.host) - - private fun get(url: String): List { - return cookieMap[url].orEmpty().filter { !it.hasExpired() } - } - - private fun Cookie.hasExpired() = System.currentTimeMillis() >= expiresAt() - +package eu.kanade.tachiyomi.network + +import android.content.Context +import okhttp3.Cookie +import okhttp3.HttpUrl +import java.net.URI +import java.util.concurrent.ConcurrentHashMap + +class PersistentCookieStore(context: Context) { + + private val cookieMap = ConcurrentHashMap>() + private val prefs = context.getSharedPreferences("cookie_store", Context.MODE_PRIVATE) + + init { + for ((key, value) in prefs.all) { + @Suppress("UNCHECKED_CAST") + val cookies = value as? Set + if (cookies != null) { + try { + val url = HttpUrl.parse("http://$key") + val nonExpiredCookies = cookies.map { Cookie.parse(url, it) } + .filter { !it.hasExpired() } + cookieMap.put(key, nonExpiredCookies) + } catch (e: Exception) { + // Ignore + } + } + } + } + + fun addAll(url: HttpUrl, cookies: List) { + synchronized(this) { + val key = url.uri().host + + // Append or replace the cookies for this domain. + val cookiesForDomain = cookieMap[key].orEmpty().toMutableList() + for (cookie in cookies) { + // Find a cookie with the same name. Replace it if found, otherwise add a new one. + val pos = cookiesForDomain.indexOfFirst { it.name() == cookie.name() } + if (pos == -1) { + cookiesForDomain.add(cookie) + } else { + cookiesForDomain[pos] = cookie + } + } + cookieMap.put(key, cookiesForDomain) + + // Get cookies to be stored in disk + val newValues = cookiesForDomain.asSequence() + .filter { it.persistent() && !it.hasExpired() } + .map { it.toString() } + .toSet() + + prefs.edit().putStringSet(key, newValues).apply() + } + } + + fun removeAll() { + synchronized(this) { + prefs.edit().clear().apply() + cookieMap.clear() + } + } + + fun get(url: HttpUrl) = get(url.uri().host) + + fun get(uri: URI) = get(uri.host) + + private fun get(url: String): List { + return cookieMap[url].orEmpty().filter { !it.hasExpired() } + } + + private fun Cookie.hasExpired() = System.currentTimeMillis() >= expiresAt() + } \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/network/ProgressListener.kt b/app/src/main/java/eu/kanade/tachiyomi/network/ProgressListener.kt similarity index 70% rename from app/src/main/java/eu/kanade/tachiyomi/data/network/ProgressListener.kt rename to app/src/main/java/eu/kanade/tachiyomi/network/ProgressListener.kt index f624e2b621..113f99763d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/network/ProgressListener.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/network/ProgressListener.kt @@ -1,5 +1,5 @@ -package eu.kanade.tachiyomi.data.network - -interface ProgressListener { - fun update(bytesRead: Long, contentLength: Long, done: Boolean) +package eu.kanade.tachiyomi.network + +interface ProgressListener { + fun update(bytesRead: Long, contentLength: Long, done: Boolean) } \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/network/ProgressResponseBody.kt b/app/src/main/java/eu/kanade/tachiyomi/network/ProgressResponseBody.kt similarity index 94% rename from app/src/main/java/eu/kanade/tachiyomi/data/network/ProgressResponseBody.kt rename to app/src/main/java/eu/kanade/tachiyomi/network/ProgressResponseBody.kt index 67c639b1ae..2e2c32f75e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/network/ProgressResponseBody.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/network/ProgressResponseBody.kt @@ -1,40 +1,40 @@ -package eu.kanade.tachiyomi.data.network - -import okhttp3.MediaType -import okhttp3.ResponseBody -import okio.* -import java.io.IOException - -class ProgressResponseBody(private val responseBody: ResponseBody, private val progressListener: ProgressListener) : ResponseBody() { - - private val bufferedSource: BufferedSource by lazy { - Okio.buffer(source(responseBody.source())) - } - - override fun contentType(): MediaType { - return responseBody.contentType() - } - - override fun contentLength(): Long { - return responseBody.contentLength() - } - - override fun source(): BufferedSource { - return bufferedSource - } - - private fun source(source: Source): Source { - return object : ForwardingSource(source) { - internal var totalBytesRead = 0L - - @Throws(IOException::class) - override fun read(sink: Buffer, byteCount: Long): Long { - val bytesRead = super.read(sink, byteCount) - // read() returns the number of bytes read, or -1 if this source is exhausted. - totalBytesRead += if (bytesRead != -1L) bytesRead else 0 - progressListener.update(totalBytesRead, responseBody.contentLength(), bytesRead == -1L) - return bytesRead - } - } - } +package eu.kanade.tachiyomi.network + +import okhttp3.MediaType +import okhttp3.ResponseBody +import okio.* +import java.io.IOException + +class ProgressResponseBody(private val responseBody: ResponseBody, private val progressListener: ProgressListener) : ResponseBody() { + + private val bufferedSource: BufferedSource by lazy { + Okio.buffer(source(responseBody.source())) + } + + override fun contentType(): MediaType { + return responseBody.contentType() + } + + override fun contentLength(): Long { + return responseBody.contentLength() + } + + override fun source(): BufferedSource { + return bufferedSource + } + + private fun source(source: Source): Source { + return object : ForwardingSource(source) { + internal var totalBytesRead = 0L + + @Throws(IOException::class) + override fun read(sink: Buffer, byteCount: Long): Long { + val bytesRead = super.read(sink, byteCount) + // read() returns the number of bytes read, or -1 if this source is exhausted. + totalBytesRead += if (bytesRead != -1L) bytesRead else 0 + progressListener.update(totalBytesRead, responseBody.contentLength(), bytesRead == -1L) + return bytesRead + } + } + } } \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/network/Requests.kt b/app/src/main/java/eu/kanade/tachiyomi/network/Requests.kt similarity index 92% rename from app/src/main/java/eu/kanade/tachiyomi/data/network/Requests.kt rename to app/src/main/java/eu/kanade/tachiyomi/network/Requests.kt index ec53e21a39..3b89d0d888 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/network/Requests.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/network/Requests.kt @@ -1,32 +1,32 @@ -package eu.kanade.tachiyomi.data.network - -import okhttp3.* -import java.util.concurrent.TimeUnit.MINUTES - -private val DEFAULT_CACHE_CONTROL = CacheControl.Builder().maxAge(10, MINUTES).build() -private val DEFAULT_HEADERS = Headers.Builder().build() -private val DEFAULT_BODY: RequestBody = FormBody.Builder().build() - -fun GET(url: String, - headers: Headers = DEFAULT_HEADERS, - cache: CacheControl = DEFAULT_CACHE_CONTROL): Request { - - return Request.Builder() - .url(url) - .headers(headers) - .cacheControl(cache) - .build() -} - -fun POST(url: String, - headers: Headers = DEFAULT_HEADERS, - body: RequestBody = DEFAULT_BODY, - cache: CacheControl = DEFAULT_CACHE_CONTROL): Request { - - return Request.Builder() - .url(url) - .post(body) - .headers(headers) - .cacheControl(cache) - .build() -} +package eu.kanade.tachiyomi.network + +import okhttp3.* +import java.util.concurrent.TimeUnit.MINUTES + +private val DEFAULT_CACHE_CONTROL = CacheControl.Builder().maxAge(10, MINUTES).build() +private val DEFAULT_HEADERS = Headers.Builder().build() +private val DEFAULT_BODY: RequestBody = FormBody.Builder().build() + +fun GET(url: String, + headers: Headers = DEFAULT_HEADERS, + cache: CacheControl = DEFAULT_CACHE_CONTROL): Request { + + return Request.Builder() + .url(url) + .headers(headers) + .cacheControl(cache) + .build() +} + +fun POST(url: String, + headers: Headers = DEFAULT_HEADERS, + body: RequestBody = DEFAULT_BODY, + cache: CacheControl = DEFAULT_CACHE_CONTROL): Request { + + return Request.Builder() + .url(url) + .post(body) + .headers(headers) + .cacheControl(cache) + .build() +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/source/CatalogueSource.kt b/app/src/main/java/eu/kanade/tachiyomi/source/CatalogueSource.kt similarity index 86% rename from app/src/main/java/eu/kanade/tachiyomi/data/source/CatalogueSource.kt rename to app/src/main/java/eu/kanade/tachiyomi/source/CatalogueSource.kt index f5b0dbf27b..f8d0ea4648 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/source/CatalogueSource.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/CatalogueSource.kt @@ -1,46 +1,46 @@ -package eu.kanade.tachiyomi.data.source - -import eu.kanade.tachiyomi.data.source.model.FilterList -import eu.kanade.tachiyomi.data.source.model.MangasPage -import rx.Observable - -interface CatalogueSource : Source { - - /** - * An ISO 639-1 compliant language code (two letters in lower case). - */ - val lang: String - - /** - * Whether the source has support for latest updates. - */ - val supportsLatest: Boolean - - /** - * Returns an observable containing a page with a list of manga. - * - * @param page the page number to retrieve. - */ - fun fetchPopularManga(page: Int): Observable - - /** - * Returns an observable containing a page with a list of manga. - * - * @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 - - /** - * Returns an observable containing a page with a list of latest manga updates. - * - * @param page the page number to retrieve. - */ - fun fetchLatestUpdates(page: Int): Observable - - /** - * Returns the list of filters for the source. - */ - fun getFilterList(): FilterList +package eu.kanade.tachiyomi.source + +import eu.kanade.tachiyomi.source.model.FilterList +import eu.kanade.tachiyomi.source.model.MangasPage +import rx.Observable + +interface CatalogueSource : Source { + + /** + * An ISO 639-1 compliant language code (two letters in lower case). + */ + val lang: String + + /** + * Whether the source has support for latest updates. + */ + val supportsLatest: Boolean + + /** + * Returns an observable containing a page with a list of manga. + * + * @param page the page number to retrieve. + */ + fun fetchPopularManga(page: Int): Observable + + /** + * Returns an observable containing a page with a list of manga. + * + * @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 + + /** + * Returns an observable containing a page with a list of latest manga updates. + * + * @param page the page number to retrieve. + */ + fun fetchLatestUpdates(page: Int): Observable + + /** + * Returns the list of filters for the source. + */ + fun getFilterList(): FilterList } \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/source/Source.kt b/app/src/main/java/eu/kanade/tachiyomi/source/Source.kt similarity index 80% rename from app/src/main/java/eu/kanade/tachiyomi/data/source/Source.kt rename to app/src/main/java/eu/kanade/tachiyomi/source/Source.kt index f1ec5ccb0c..666621bb4e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/source/Source.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/Source.kt @@ -1,44 +1,44 @@ -package eu.kanade.tachiyomi.data.source - -import eu.kanade.tachiyomi.data.source.model.Page -import eu.kanade.tachiyomi.data.source.model.SChapter -import eu.kanade.tachiyomi.data.source.model.SManga -import rx.Observable - -/** - * A basic interface for creating a source. It could be an online source, a local source, etc... - */ -interface Source { - - /** - * Id for the source. Must be unique. - */ - val id: Long - - /** - * Name of the source. - */ - val name: String - - /** - * Returns an observable with the updated details for a manga. - * - * @param manga the manga to update. - */ - fun fetchMangaDetails(manga: SManga): Observable - - /** - * Returns an observable with all the available chapters for a manga. - * - * @param manga the manga to update. - */ - fun fetchChapterList(manga: SManga): Observable> - - /** - * Returns an observable with the list of pages a chapter has. - * - * @param chapter the chapter. - */ - fun fetchPageList(chapter: SChapter): Observable> - +package eu.kanade.tachiyomi.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 + +/** + * A basic interface for creating a source. It could be an online source, a local source, etc... + */ +interface Source { + + /** + * Id for the source. Must be unique. + */ + val id: Long + + /** + * Name of the source. + */ + val name: String + + /** + * Returns an observable with the updated details for a manga. + * + * @param manga the manga to update. + */ + fun fetchMangaDetails(manga: SManga): Observable + + /** + * Returns an observable with all the available chapters for a manga. + * + * @param manga the manga to update. + */ + fun fetchChapterList(manga: SManga): Observable> + + /** + * Returns an observable with the list of pages a chapter has. + * + * @param chapter the chapter. + */ + fun fetchPageList(chapter: SChapter): Observable> + } \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/source/SourceManager.kt b/app/src/main/java/eu/kanade/tachiyomi/source/SourceManager.kt similarity index 89% rename from app/src/main/java/eu/kanade/tachiyomi/data/source/SourceManager.kt rename to app/src/main/java/eu/kanade/tachiyomi/source/SourceManager.kt index 84ebd02a2c..065cfadc0c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/source/SourceManager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/SourceManager.kt @@ -1,159 +1,159 @@ -package eu.kanade.tachiyomi.data.source - -import android.Manifest.permission.READ_EXTERNAL_STORAGE -import android.content.Context -import android.content.pm.ApplicationInfo -import android.content.pm.PackageManager -import android.os.Environment -import dalvik.system.PathClassLoader -import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.data.source.online.OnlineSource -import eu.kanade.tachiyomi.data.source.online.YamlOnlineSource -import eu.kanade.tachiyomi.data.source.online.english.* -import eu.kanade.tachiyomi.data.source.online.german.WieManga -import eu.kanade.tachiyomi.data.source.online.russian.Mangachan -import eu.kanade.tachiyomi.data.source.online.russian.Mintmanga -import eu.kanade.tachiyomi.data.source.online.russian.Readmanga -import eu.kanade.tachiyomi.util.hasPermission -import org.yaml.snakeyaml.Yaml -import timber.log.Timber -import java.io.File - -open class SourceManager(private val context: Context) { - - private val sourcesMap = mutableMapOf() - - init { - createSources() - } - - open fun get(sourceKey: Long): Source? { - return sourcesMap[sourceKey] - } - - fun getOnlineSources() = sourcesMap.values.filterIsInstance() - - fun getCatalogueSources() = sourcesMap.values.filterIsInstance() - - private fun createSources() { - createExtensionSources().forEach { registerSource(it) } - createYamlSources().forEach { registerSource(it) } - createInternalSources().forEach { registerSource(it) } - } - - private fun registerSource(source: Source, overwrite: Boolean = false) { - if (overwrite || !sourcesMap.containsKey(source.id)) { - sourcesMap.put(source.id, source) - } - } - - private fun createInternalSources(): List = listOf( - Batoto(), - Mangahere(), - Mangafox(), - Kissmanga(), - Readmanga(), - Mintmanga(), - Mangachan(), - Readmangatoday(), - Mangasee(), - WieManga() - ) - - private fun createYamlSources(): List { - val sources = mutableListOf() - - val parsersDir = File(Environment.getExternalStorageDirectory().absolutePath + - File.separator + context.getString(R.string.app_name), "parsers") - - if (parsersDir.exists() && context.hasPermission(READ_EXTERNAL_STORAGE)) { - val yaml = Yaml() - for (file in parsersDir.listFiles().filter { it.extension == "yml" }) { - try { - val map = file.inputStream().use { yaml.loadAs(it, Map::class.java) } - sources.add(YamlOnlineSource(map)) - } catch (e: Exception) { - Timber.e("Error loading source from file. Bad format?") - } - } - } - return sources - } - - private fun createExtensionSources(): List { - val pkgManager = context.packageManager - val flags = PackageManager.GET_CONFIGURATIONS or PackageManager.GET_SIGNATURES - val installedPkgs = pkgManager.getInstalledPackages(flags) - val extPkgs = installedPkgs.filter { it.reqFeatures.orEmpty().any { it.name == FEATURE } } - - val sources = mutableListOf() - for (pkgInfo in extPkgs) { - val appInfo = pkgManager.getApplicationInfo(pkgInfo.packageName, - PackageManager.GET_META_DATA) ?: continue - - - val data = appInfo.metaData - val extName = data.getString(NAME) - val version = data.getInt(VERSION) - val sourceClass = extendClassName(data.getString(SOURCE), pkgInfo.packageName) - - val ext = Extension(extName, appInfo, version, sourceClass) - if (!validateExtension(ext)) { - continue - } - - val instance = loadExtension(ext, pkgManager) - if (instance == null) { - Timber.e("Extension error: failed to instance $extName") - continue - } - sources.add(instance) - } - return sources - } - - private fun validateExtension(ext: Extension): Boolean { - if (ext.version < LIB_VERSION_MIN || ext.version > LIB_VERSION_MAX) { - Timber.e("Extension error: ${ext.name} has version ${ext.version}, while only versions " - + "$LIB_VERSION_MIN to $LIB_VERSION_MAX are allowed") - return false - } - return true - } - - private fun loadExtension(ext: Extension, pkgManager: PackageManager): OnlineSource? { - return try { - val classLoader = PathClassLoader(ext.appInfo.sourceDir, null, context.classLoader) - val resources = pkgManager.getResourcesForApplication(ext.appInfo) - - Class.forName(ext.sourceClass, false, classLoader).newInstance() as? OnlineSource - } catch (e: Exception) { - null - } catch (e: LinkageError) { - null - } - } - - private fun extendClassName(className: String, packageName: String): String { - return if (className.startsWith(".")) { - packageName + className - } else { - className - } - } - - class Extension(val name: String, - val appInfo: ApplicationInfo, - val version: Int, - val sourceClass: String) - - private companion object { - const val FEATURE = "tachiyomi.extension" - const val NAME = "tachiyomi.extension.name" - const val VERSION = "tachiyomi.extension.version" - const val SOURCE = "tachiyomi.extension.source" - const val LIB_VERSION_MIN = 1 - const val LIB_VERSION_MAX = 1 - } - -} +package eu.kanade.tachiyomi.source + +import android.Manifest.permission.READ_EXTERNAL_STORAGE +import android.content.Context +import android.content.pm.ApplicationInfo +import android.content.pm.PackageManager +import android.os.Environment +import dalvik.system.PathClassLoader +import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.source.online.OnlineSource +import eu.kanade.tachiyomi.source.online.YamlOnlineSource +import eu.kanade.tachiyomi.source.online.english.* +import eu.kanade.tachiyomi.source.online.german.WieManga +import eu.kanade.tachiyomi.source.online.russian.Mangachan +import eu.kanade.tachiyomi.source.online.russian.Mintmanga +import eu.kanade.tachiyomi.source.online.russian.Readmanga +import eu.kanade.tachiyomi.util.hasPermission +import org.yaml.snakeyaml.Yaml +import timber.log.Timber +import java.io.File + +open class SourceManager(private val context: Context) { + + private val sourcesMap = mutableMapOf() + + init { + createSources() + } + + open fun get(sourceKey: Long): Source? { + return sourcesMap[sourceKey] + } + + fun getOnlineSources() = sourcesMap.values.filterIsInstance() + + fun getCatalogueSources() = sourcesMap.values.filterIsInstance() + + private fun createSources() { + createExtensionSources().forEach { registerSource(it) } + createYamlSources().forEach { registerSource(it) } + createInternalSources().forEach { registerSource(it) } + } + + private fun registerSource(source: Source, overwrite: Boolean = false) { + if (overwrite || !sourcesMap.containsKey(source.id)) { + sourcesMap.put(source.id, source) + } + } + + private fun createInternalSources(): List = listOf( + Batoto(), + Mangahere(), + Mangafox(), + Kissmanga(), + Readmanga(), + Mintmanga(), + Mangachan(), + Readmangatoday(), + Mangasee(), + WieManga() + ) + + private fun createYamlSources(): List { + val sources = mutableListOf() + + val parsersDir = File(Environment.getExternalStorageDirectory().absolutePath + + File.separator + context.getString(R.string.app_name), "parsers") + + if (parsersDir.exists() && context.hasPermission(READ_EXTERNAL_STORAGE)) { + val yaml = Yaml() + for (file in parsersDir.listFiles().filter { it.extension == "yml" }) { + try { + val map = file.inputStream().use { yaml.loadAs(it, Map::class.java) } + sources.add(YamlOnlineSource(map)) + } catch (e: Exception) { + Timber.e("Error loading source from file. Bad format?") + } + } + } + return sources + } + + private fun createExtensionSources(): List { + val pkgManager = context.packageManager + val flags = PackageManager.GET_CONFIGURATIONS or PackageManager.GET_SIGNATURES + val installedPkgs = pkgManager.getInstalledPackages(flags) + val extPkgs = installedPkgs.filter { it.reqFeatures.orEmpty().any { it.name == FEATURE } } + + val sources = mutableListOf() + for (pkgInfo in extPkgs) { + val appInfo = pkgManager.getApplicationInfo(pkgInfo.packageName, + PackageManager.GET_META_DATA) ?: continue + + + val data = appInfo.metaData + val extName = data.getString(NAME) + val version = data.getInt(VERSION) + val sourceClass = extendClassName(data.getString(SOURCE), pkgInfo.packageName) + + val ext = Extension(extName, appInfo, version, sourceClass) + if (!validateExtension(ext)) { + continue + } + + val instance = loadExtension(ext, pkgManager) + if (instance == null) { + Timber.e("Extension error: failed to instance $extName") + continue + } + sources.add(instance) + } + return sources + } + + private fun validateExtension(ext: Extension): Boolean { + if (ext.version < LIB_VERSION_MIN || ext.version > LIB_VERSION_MAX) { + Timber.e("Extension error: ${ext.name} has version ${ext.version}, while only versions " + + "$LIB_VERSION_MIN to $LIB_VERSION_MAX are allowed") + return false + } + return true + } + + private fun loadExtension(ext: Extension, pkgManager: PackageManager): OnlineSource? { + return try { + val classLoader = PathClassLoader(ext.appInfo.sourceDir, null, context.classLoader) + val resources = pkgManager.getResourcesForApplication(ext.appInfo) + + Class.forName(ext.sourceClass, false, classLoader).newInstance() as? OnlineSource + } catch (e: Exception) { + null + } catch (e: LinkageError) { + null + } + } + + private fun extendClassName(className: String, packageName: String): String { + return if (className.startsWith(".")) { + packageName + className + } else { + className + } + } + + class Extension(val name: String, + val appInfo: ApplicationInfo, + val version: Int, + val sourceClass: String) + + private companion object { + const val FEATURE = "tachiyomi.extension" + const val NAME = "tachiyomi.extension.name" + const val VERSION = "tachiyomi.extension.version" + const val SOURCE = "tachiyomi.extension.source" + const val LIB_VERSION_MIN = 1 + const val LIB_VERSION_MAX = 1 + } + +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/source/model/Filter.kt b/app/src/main/java/eu/kanade/tachiyomi/source/model/Filter.kt similarity index 94% rename from app/src/main/java/eu/kanade/tachiyomi/data/source/model/Filter.kt rename to app/src/main/java/eu/kanade/tachiyomi/source/model/Filter.kt index 7ea621466a..1664d67ebb 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/source/model/Filter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/model/Filter.kt @@ -1,4 +1,4 @@ -package eu.kanade.tachiyomi.data.source.model +package eu.kanade.tachiyomi.source.model sealed class Filter(val name: String, var state: T) { open class Header(name: String) : Filter(name, 0) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/source/model/FilterList.kt b/app/src/main/java/eu/kanade/tachiyomi/source/model/FilterList.kt similarity index 79% rename from app/src/main/java/eu/kanade/tachiyomi/data/source/model/FilterList.kt rename to app/src/main/java/eu/kanade/tachiyomi/source/model/FilterList.kt index 3137a66b4c..36d8e144a0 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/source/model/FilterList.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/model/FilterList.kt @@ -1,7 +1,7 @@ -package eu.kanade.tachiyomi.data.source.model - -data class FilterList(val list: List>) : List> by list { - - constructor(vararg fs: Filter<*>) : this(if (fs.isNotEmpty()) fs.asList() else emptyList()) - +package eu.kanade.tachiyomi.source.model + +data class FilterList(val list: List>) : List> by list { + + constructor(vararg fs: Filter<*>) : this(if (fs.isNotEmpty()) fs.asList() else emptyList()) + } \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/source/model/MangasPage.kt b/app/src/main/java/eu/kanade/tachiyomi/source/model/MangasPage.kt similarity index 61% rename from app/src/main/java/eu/kanade/tachiyomi/data/source/model/MangasPage.kt rename to app/src/main/java/eu/kanade/tachiyomi/source/model/MangasPage.kt index a601f93c00..e359619fb8 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/source/model/MangasPage.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/model/MangasPage.kt @@ -1,3 +1,3 @@ -package eu.kanade.tachiyomi.data.source.model - +package eu.kanade.tachiyomi.source.model + data class MangasPage(val mangas: List, val hasNextPage: Boolean) \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/source/model/Page.kt b/app/src/main/java/eu/kanade/tachiyomi/source/model/Page.kt similarity index 88% rename from app/src/main/java/eu/kanade/tachiyomi/data/source/model/Page.kt rename to app/src/main/java/eu/kanade/tachiyomi/source/model/Page.kt index 2aa3c04b44..16a76b96b8 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/source/model/Page.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/model/Page.kt @@ -1,47 +1,47 @@ -package eu.kanade.tachiyomi.data.source.model - -import android.net.Uri -import eu.kanade.tachiyomi.data.network.ProgressListener -import eu.kanade.tachiyomi.ui.reader.ReaderChapter -import rx.subjects.Subject - -class Page( - val index: Int, - val url: String = "", - var imageUrl: String? = null, - @Transient var uri: Uri? = null -) : ProgressListener { - - val number: Int - get() = index + 1 - - @Transient lateinit var chapter: ReaderChapter - - @Transient @Volatile var status: Int = 0 - set(value) { - field = value - statusSubject?.onNext(value) - } - - @Transient @Volatile var progress: Int = 0 - - @Transient private var statusSubject: Subject? = null - - override fun update(bytesRead: Long, contentLength: Long, done: Boolean) { - progress = (100 * bytesRead / contentLength).toInt() - } - - fun setStatusSubject(subject: Subject?) { - this.statusSubject = subject - } - - companion object { - - const val QUEUE = 0 - const val LOAD_PAGE = 1 - const val DOWNLOAD_IMAGE = 2 - const val READY = 3 - const val ERROR = 4 - } - -} +package eu.kanade.tachiyomi.source.model + +import android.net.Uri +import eu.kanade.tachiyomi.network.ProgressListener +import eu.kanade.tachiyomi.ui.reader.ReaderChapter +import rx.subjects.Subject + +class Page( + val index: Int, + val url: String = "", + var imageUrl: String? = null, + @Transient var uri: Uri? = null +) : ProgressListener { + + val number: Int + get() = index + 1 + + @Transient lateinit var chapter: ReaderChapter + + @Transient @Volatile var status: Int = 0 + set(value) { + field = value + statusSubject?.onNext(value) + } + + @Transient @Volatile var progress: Int = 0 + + @Transient private var statusSubject: Subject? = null + + override fun update(bytesRead: Long, contentLength: Long, done: Boolean) { + progress = (100 * bytesRead / contentLength).toInt() + } + + fun setStatusSubject(subject: Subject?) { + this.statusSubject = subject + } + + companion object { + + const val QUEUE = 0 + const val LOAD_PAGE = 1 + const val DOWNLOAD_IMAGE = 2 + const val READY = 3 + const val ERROR = 4 + } + +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/source/model/SChapter.kt b/app/src/main/java/eu/kanade/tachiyomi/source/model/SChapter.kt similarity index 86% rename from app/src/main/java/eu/kanade/tachiyomi/data/source/model/SChapter.kt rename to app/src/main/java/eu/kanade/tachiyomi/source/model/SChapter.kt index 9bf515a874..a54a36b40d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/source/model/SChapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/model/SChapter.kt @@ -1,4 +1,4 @@ -package eu.kanade.tachiyomi.data.source.model +package eu.kanade.tachiyomi.source.model import java.io.Serializable diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/source/model/SChapterImpl.kt b/app/src/main/java/eu/kanade/tachiyomi/source/model/SChapterImpl.kt similarity index 77% rename from app/src/main/java/eu/kanade/tachiyomi/data/source/model/SChapterImpl.kt rename to app/src/main/java/eu/kanade/tachiyomi/source/model/SChapterImpl.kt index 4995e0f99c..026d437e03 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/source/model/SChapterImpl.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/model/SChapterImpl.kt @@ -1,4 +1,4 @@ -package eu.kanade.tachiyomi.data.source.model +package eu.kanade.tachiyomi.source.model class SChapterImpl : SChapter { diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/source/model/SManga.kt b/app/src/main/java/eu/kanade/tachiyomi/source/model/SManga.kt similarity index 91% rename from app/src/main/java/eu/kanade/tachiyomi/data/source/model/SManga.kt rename to app/src/main/java/eu/kanade/tachiyomi/source/model/SManga.kt index f7da4ca661..8a1ba1af0c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/source/model/SManga.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/model/SManga.kt @@ -1,4 +1,4 @@ -package eu.kanade.tachiyomi.data.source.model +package eu.kanade.tachiyomi.source.model import java.io.Serializable diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/source/model/SMangaImpl.kt b/app/src/main/java/eu/kanade/tachiyomi/source/model/SMangaImpl.kt similarity index 85% rename from app/src/main/java/eu/kanade/tachiyomi/data/source/model/SMangaImpl.kt rename to app/src/main/java/eu/kanade/tachiyomi/source/model/SMangaImpl.kt index 6c4499b405..30635897b8 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/source/model/SMangaImpl.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/model/SMangaImpl.kt @@ -1,4 +1,4 @@ -package eu.kanade.tachiyomi.data.source.model +package eu.kanade.tachiyomi.source.model class SMangaImpl : SManga { diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/LoginSource.kt b/app/src/main/java/eu/kanade/tachiyomi/source/online/LoginSource.kt similarity index 71% rename from app/src/main/java/eu/kanade/tachiyomi/data/source/online/LoginSource.kt rename to app/src/main/java/eu/kanade/tachiyomi/source/online/LoginSource.kt index 0c9917f7ce..61ec4fd35f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/LoginSource.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/online/LoginSource.kt @@ -1,15 +1,15 @@ -package eu.kanade.tachiyomi.data.source.online - -import eu.kanade.tachiyomi.data.source.Source -import okhttp3.Response -import rx.Observable - -interface LoginSource : Source { - - fun isLogged(): Boolean - - fun login(username: String, password: String): Observable - - fun isAuthenticationSuccessful(response: Response): Boolean - +package eu.kanade.tachiyomi.source.online + +import eu.kanade.tachiyomi.source.Source +import okhttp3.Response +import rx.Observable + +interface LoginSource : Source { + + fun isLogged(): Boolean + + fun login(username: String, password: String): Observable + + fun isAuthenticationSuccessful(response: Response): Boolean + } \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/OnlineSource.kt b/app/src/main/java/eu/kanade/tachiyomi/source/online/OnlineSource.kt similarity index 94% rename from app/src/main/java/eu/kanade/tachiyomi/data/source/online/OnlineSource.kt rename to app/src/main/java/eu/kanade/tachiyomi/source/online/OnlineSource.kt index dd1e4af3ac..804fdcb7ab 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/OnlineSource.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/online/OnlineSource.kt @@ -1,361 +1,361 @@ -package eu.kanade.tachiyomi.data.source.online - -import eu.kanade.tachiyomi.data.network.GET -import eu.kanade.tachiyomi.data.network.NetworkHelper -import eu.kanade.tachiyomi.data.network.asObservableSuccess -import eu.kanade.tachiyomi.data.network.newCallWithProgress -import eu.kanade.tachiyomi.data.preference.PreferencesHelper -import eu.kanade.tachiyomi.data.source.CatalogueSource -import eu.kanade.tachiyomi.data.source.model.* -import okhttp3.Headers -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import rx.Observable -import uy.kohesive.injekt.injectLazy -import java.net.URI -import java.net.URISyntaxException -import java.security.MessageDigest - -/** - * A simple implementation for sources from a website. - */ -abstract class OnlineSource : CatalogueSource { - - /** - * Network service. - */ - val network: NetworkHelper by injectLazy() - - /** - * Preferences helper. - */ - val preferences: PreferencesHelper by injectLazy() - - /** - * Base url of the website without the trailing slash, like: http://mysite.com - */ - abstract val baseUrl: String - - /** - * Version id used to generate the source id. If the site completely changes and urls are - * incompatible, you may increase this value and it'll be considered as a new source. - */ - open val versionId = 1 - - /** - * Id of the source. By default it uses a generated id using the first 16 characters (64 bits) - * of the MD5 of the string: sourcename/language/versionId - * Note the generated id sets the sign bit to 0. - */ - override val id by lazy { - val key = "${name.toLowerCase()}/$lang/$versionId" - val bytes = MessageDigest.getInstance("MD5").digest(key.toByteArray()) - (0..7).map { bytes[it].toLong() and 0xff shl 8*(7-it) }.reduce(Long::or) and Long.MAX_VALUE - } - - /** - * Headers used for requests. - */ - val headers: Headers by lazy { headersBuilder().build() } - - /** - * Default network client for doing requests. - */ - open val client: OkHttpClient - get() = network.client - - /** - * Headers builder for requests. Implementations can override this method for custom headers. - */ - open protected fun headersBuilder() = Headers.Builder().apply { - add("User-Agent", "Mozilla/5.0 (Windows NT 6.3; WOW64)") - } - - /** - * Visible name of the source. - */ - override fun toString() = "$name (${lang.toUpperCase()})" - - /** - * Returns an observable containing a page with a list of manga. Normally it's not needed to - * override this method. - * - * @param page the page number to retrieve. - */ - override fun fetchPopularManga(page: Int): Observable { - return client.newCall(popularMangaRequest(page)) - .asObservableSuccess() - .map { response -> - popularMangaParse(response) - } - } - - /** - * Returns the request for the popular manga given the page. - * - * @param page the page number to retrieve. - */ - abstract protected fun popularMangaRequest(page: Int): Request - - /** - * Parses the response from the site and returns a [MangasPage] object. - * - * @param response the response from the site. - */ - abstract protected fun popularMangaParse(response: Response): MangasPage - - /** - * Returns an observable containing a page with a list of manga. Normally it's not needed to - * override this method. - * - * @param page the page number to retrieve. - * @param query the search query. - * @param filters the list of filters to apply. - */ - override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable { - return client.newCall(searchMangaRequest(page, query, filters)) - .asObservableSuccess() - .map { response -> - searchMangaParse(response) - } - } - - /** - * Returns the request for the search manga given the page. - * - * @param page the page number to retrieve. - * @param query the search query. - * @param filters the list of filters to apply. - */ - abstract protected fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request - - /** - * Parses the response from the site and returns a [MangasPage] object. - * - * @param response the response from the site. - */ - abstract protected fun searchMangaParse(response: Response): MangasPage - - /** - * Returns an observable containing a page with a list of latest manga updates. - * - * @param page the page number to retrieve. - */ - override fun fetchLatestUpdates(page: Int): Observable { - return client.newCall(latestUpdatesRequest(page)) - .asObservableSuccess() - .map { response -> - latestUpdatesParse(response) - } - } - - /** - * Returns the request for latest manga given the page. - * - * @param page the page number to retrieve. - */ - abstract protected fun latestUpdatesRequest(page: Int): Request - - /** - * Parses the response from the site and returns a [MangasPage] object. - * - * @param response the response from the site. - */ - abstract protected fun latestUpdatesParse(response: Response): MangasPage - - /** - * Returns an observable with the updated details for a manga. Normally it's not needed to - * override this method. - * - * @param manga the manga to be updated. - */ - override fun fetchMangaDetails(manga: SManga): Observable { - return client.newCall(mangaDetailsRequest(manga)) - .asObservableSuccess() - .map { response -> - mangaDetailsParse(response).apply { initialized = true } - } - } - - /** - * Returns the request for the details of a manga. Override only if it's needed to change the - * url, send different headers or request method like POST. - * - * @param manga the manga to be updated. - */ - open fun mangaDetailsRequest(manga: SManga): Request { - return GET(baseUrl + manga.url, headers) - } - - /** - * Parses the response from the site and returns the details of a manga. - * - * @param response the response from the site. - */ - abstract protected 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. - * - * @param manga the manga to look for chapters. - */ - override fun fetchChapterList(manga: SManga): Observable> { - return client.newCall(chapterListRequest(manga)) - .asObservableSuccess() - .map { response -> - chapterListParse(response) - } - } - - /** - * Returns the request for updating the chapter list. Override only if it's needed to override - * the url, send different headers or request method like POST. - * - * @param manga the manga to look for chapters. - */ - open protected fun chapterListRequest(manga: SManga): Request { - return GET(baseUrl + manga.url, headers) - } - - /** - * Parses the response from the site and returns a list of chapters. - * - * @param response the response from the site. - */ - abstract protected fun chapterListParse(response: Response): List - - /** - * Returns an observable with the page list for a chapter. - * - * @param chapter the chapter whose page list has to be fetched. - */ - override fun fetchPageList(chapter: SChapter): Observable> { - return client.newCall(pageListRequest(chapter)) - .asObservableSuccess() - .map { response -> - pageListParse(response) - } - } - - /** - * Returns the request for getting the page list. Override only if it's needed to override the - * url, send different headers or request method like POST. - * - * @param chapter the chapter whose page list has to be fetched. - */ - open protected fun pageListRequest(chapter: SChapter): Request { - return GET(baseUrl + chapter.url, headers) - } - - /** - * Parses the response from the site and returns a list of pages. - * - * @param response the response from the site. - */ - abstract protected fun pageListParse(response: Response): List - - /** - * 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. - * - * @param page the page whose source image has to be fetched. - */ - open fun fetchImageUrl(page: Page): Observable { - return client.newCall(imageUrlRequest(page)) - .asObservableSuccess() - .map { imageUrlParse(it) } - } - - /** - * Returns the request for getting the url to the source image. Override only if it's needed to - * override the url, send different headers or request method like POST. - * - * @param page the chapter whose page list has to be fetched - */ - open protected fun imageUrlRequest(page: Page): Request { - return GET(page.url, headers) - } - - /** - * Parses the response from the site and returns the absolute url to the source image. - * - * @param response the response from the site. - */ - abstract protected 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. - */ - fun fetchImage(page: Page): Observable { - return client.newCallWithProgress(imageRequest(page), page) - .asObservableSuccess() - } - - /** - * Returns the request for getting the source image. Override only if it's needed to override - * the url, send different headers or request method like POST. - * - * @param page the chapter whose page list has to be fetched - */ - open protected fun imageRequest(page: Page): Request { - return GET(page.imageUrl!!, headers) - } - - /** - * Assigns the url of the chapter without the scheme and domain. It saves some redundancy from - * database and the urls could still work after a domain change. - * - * @param url the full url to the chapter. - */ - fun SChapter.setUrlWithoutDomain(url: String) { - this.url = getUrlWithoutDomain(url) - } - - /** - * Assigns the url of the manga without the scheme and domain. It saves some redundancy from - * database and the urls could still work after a domain change. - * - * @param url the full url to the manga. - */ - fun SManga.setUrlWithoutDomain(url: String) { - this.url = getUrlWithoutDomain(url) - } - - /** - * Returns the url of the given string without the scheme and domain. - * - * @param orig the full url. - */ - private fun getUrlWithoutDomain(orig: String): String { - try { - val uri = URI(orig) - var out = uri.path - if (uri.query != null) - out += "?" + uri.query - if (uri.fragment != null) - out += "#" + uri.fragment - return out - } catch (e: URISyntaxException) { - return orig - } - } - - /** - * Called before inserting a new chapter into database. Use it if you need to override chapter - * fields, like the title or the chapter number. Do not change anything to [manga]. - * - * @param chapter the chapter to be added. - * @param manga the manga of the chapter. - */ - open fun prepareNewChapter(chapter: SChapter, manga: SManga) { - } - - /** - * Returns the list of filters for the source. - */ - override fun getFilterList() = FilterList() -} +package eu.kanade.tachiyomi.source.online + +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.network.NetworkHelper +import eu.kanade.tachiyomi.network.asObservableSuccess +import eu.kanade.tachiyomi.network.newCallWithProgress +import eu.kanade.tachiyomi.data.preference.PreferencesHelper +import eu.kanade.tachiyomi.source.CatalogueSource +import eu.kanade.tachiyomi.source.model.* +import okhttp3.Headers +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.Response +import rx.Observable +import uy.kohesive.injekt.injectLazy +import java.net.URI +import java.net.URISyntaxException +import java.security.MessageDigest + +/** + * A simple implementation for sources from a website. + */ +abstract class OnlineSource : CatalogueSource { + + /** + * Network service. + */ + val network: NetworkHelper by injectLazy() + + /** + * Preferences helper. + */ + val preferences: PreferencesHelper by injectLazy() + + /** + * Base url of the website without the trailing slash, like: http://mysite.com + */ + abstract val baseUrl: String + + /** + * Version id used to generate the source id. If the site completely changes and urls are + * incompatible, you may increase this value and it'll be considered as a new source. + */ + open val versionId = 1 + + /** + * Id of the source. By default it uses a generated id using the first 16 characters (64 bits) + * of the MD5 of the string: sourcename/language/versionId + * Note the generated id sets the sign bit to 0. + */ + override val id by lazy { + val key = "${name.toLowerCase()}/$lang/$versionId" + val bytes = MessageDigest.getInstance("MD5").digest(key.toByteArray()) + (0..7).map { bytes[it].toLong() and 0xff shl 8*(7-it) }.reduce(Long::or) and Long.MAX_VALUE + } + + /** + * Headers used for requests. + */ + val headers: Headers by lazy { headersBuilder().build() } + + /** + * Default network client for doing requests. + */ + open val client: OkHttpClient + get() = network.client + + /** + * Headers builder for requests. Implementations can override this method for custom headers. + */ + open protected fun headersBuilder() = Headers.Builder().apply { + add("User-Agent", "Mozilla/5.0 (Windows NT 6.3; WOW64)") + } + + /** + * Visible name of the source. + */ + override fun toString() = "$name (${lang.toUpperCase()})" + + /** + * Returns an observable containing a page with a list of manga. Normally it's not needed to + * override this method. + * + * @param page the page number to retrieve. + */ + override fun fetchPopularManga(page: Int): Observable { + return client.newCall(popularMangaRequest(page)) + .asObservableSuccess() + .map { response -> + popularMangaParse(response) + } + } + + /** + * Returns the request for the popular manga given the page. + * + * @param page the page number to retrieve. + */ + abstract protected fun popularMangaRequest(page: Int): Request + + /** + * Parses the response from the site and returns a [MangasPage] object. + * + * @param response the response from the site. + */ + abstract protected fun popularMangaParse(response: Response): MangasPage + + /** + * Returns an observable containing a page with a list of manga. Normally it's not needed to + * override this method. + * + * @param page the page number to retrieve. + * @param query the search query. + * @param filters the list of filters to apply. + */ + override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable { + return client.newCall(searchMangaRequest(page, query, filters)) + .asObservableSuccess() + .map { response -> + searchMangaParse(response) + } + } + + /** + * Returns the request for the search manga given the page. + * + * @param page the page number to retrieve. + * @param query the search query. + * @param filters the list of filters to apply. + */ + abstract protected fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request + + /** + * Parses the response from the site and returns a [MangasPage] object. + * + * @param response the response from the site. + */ + abstract protected fun searchMangaParse(response: Response): MangasPage + + /** + * Returns an observable containing a page with a list of latest manga updates. + * + * @param page the page number to retrieve. + */ + override fun fetchLatestUpdates(page: Int): Observable { + return client.newCall(latestUpdatesRequest(page)) + .asObservableSuccess() + .map { response -> + latestUpdatesParse(response) + } + } + + /** + * Returns the request for latest manga given the page. + * + * @param page the page number to retrieve. + */ + abstract protected fun latestUpdatesRequest(page: Int): Request + + /** + * Parses the response from the site and returns a [MangasPage] object. + * + * @param response the response from the site. + */ + abstract protected fun latestUpdatesParse(response: Response): MangasPage + + /** + * Returns an observable with the updated details for a manga. Normally it's not needed to + * override this method. + * + * @param manga the manga to be updated. + */ + override fun fetchMangaDetails(manga: SManga): Observable { + return client.newCall(mangaDetailsRequest(manga)) + .asObservableSuccess() + .map { response -> + mangaDetailsParse(response).apply { initialized = true } + } + } + + /** + * Returns the request for the details of a manga. Override only if it's needed to change the + * url, send different headers or request method like POST. + * + * @param manga the manga to be updated. + */ + open fun mangaDetailsRequest(manga: SManga): Request { + return GET(baseUrl + manga.url, headers) + } + + /** + * Parses the response from the site and returns the details of a manga. + * + * @param response the response from the site. + */ + abstract protected 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. + * + * @param manga the manga to look for chapters. + */ + override fun fetchChapterList(manga: SManga): Observable> { + return client.newCall(chapterListRequest(manga)) + .asObservableSuccess() + .map { response -> + chapterListParse(response) + } + } + + /** + * Returns the request for updating the chapter list. Override only if it's needed to override + * the url, send different headers or request method like POST. + * + * @param manga the manga to look for chapters. + */ + open protected fun chapterListRequest(manga: SManga): Request { + return GET(baseUrl + manga.url, headers) + } + + /** + * Parses the response from the site and returns a list of chapters. + * + * @param response the response from the site. + */ + abstract protected fun chapterListParse(response: Response): List + + /** + * Returns an observable with the page list for a chapter. + * + * @param chapter the chapter whose page list has to be fetched. + */ + override fun fetchPageList(chapter: SChapter): Observable> { + return client.newCall(pageListRequest(chapter)) + .asObservableSuccess() + .map { response -> + pageListParse(response) + } + } + + /** + * Returns the request for getting the page list. Override only if it's needed to override the + * url, send different headers or request method like POST. + * + * @param chapter the chapter whose page list has to be fetched. + */ + open protected fun pageListRequest(chapter: SChapter): Request { + return GET(baseUrl + chapter.url, headers) + } + + /** + * Parses the response from the site and returns a list of pages. + * + * @param response the response from the site. + */ + abstract protected fun pageListParse(response: Response): List + + /** + * 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. + * + * @param page the page whose source image has to be fetched. + */ + open fun fetchImageUrl(page: Page): Observable { + return client.newCall(imageUrlRequest(page)) + .asObservableSuccess() + .map { imageUrlParse(it) } + } + + /** + * Returns the request for getting the url to the source image. Override only if it's needed to + * override the url, send different headers or request method like POST. + * + * @param page the chapter whose page list has to be fetched + */ + open protected fun imageUrlRequest(page: Page): Request { + return GET(page.url, headers) + } + + /** + * Parses the response from the site and returns the absolute url to the source image. + * + * @param response the response from the site. + */ + abstract protected 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. + */ + fun fetchImage(page: Page): Observable { + return client.newCallWithProgress(imageRequest(page), page) + .asObservableSuccess() + } + + /** + * Returns the request for getting the source image. Override only if it's needed to override + * the url, send different headers or request method like POST. + * + * @param page the chapter whose page list has to be fetched + */ + open protected fun imageRequest(page: Page): Request { + return GET(page.imageUrl!!, headers) + } + + /** + * Assigns the url of the chapter without the scheme and domain. It saves some redundancy from + * database and the urls could still work after a domain change. + * + * @param url the full url to the chapter. + */ + fun SChapter.setUrlWithoutDomain(url: String) { + this.url = getUrlWithoutDomain(url) + } + + /** + * Assigns the url of the manga without the scheme and domain. It saves some redundancy from + * database and the urls could still work after a domain change. + * + * @param url the full url to the manga. + */ + fun SManga.setUrlWithoutDomain(url: String) { + this.url = getUrlWithoutDomain(url) + } + + /** + * Returns the url of the given string without the scheme and domain. + * + * @param orig the full url. + */ + private fun getUrlWithoutDomain(orig: String): String { + try { + val uri = URI(orig) + var out = uri.path + if (uri.query != null) + out += "?" + uri.query + if (uri.fragment != null) + out += "#" + uri.fragment + return out + } catch (e: URISyntaxException) { + return orig + } + } + + /** + * Called before inserting a new chapter into database. Use it if you need to override chapter + * fields, like the title or the chapter number. Do not change anything to [manga]. + * + * @param chapter the chapter to be added. + * @param manga the manga of the chapter. + */ + open fun prepareNewChapter(chapter: SChapter, manga: SManga) { + } + + /** + * Returns the list of filters for the source. + */ + override fun getFilterList() = FilterList() +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/OnlineSourceFetcher.kt b/app/src/main/java/eu/kanade/tachiyomi/source/online/OnlineSourceFetcher.kt similarity index 94% rename from app/src/main/java/eu/kanade/tachiyomi/data/source/online/OnlineSourceFetcher.kt rename to app/src/main/java/eu/kanade/tachiyomi/source/online/OnlineSourceFetcher.kt index ce2e140984..b2857b775e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/OnlineSourceFetcher.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/online/OnlineSourceFetcher.kt @@ -1,98 +1,98 @@ -package eu.kanade.tachiyomi.data.source.online - -import android.net.Uri -import eu.kanade.tachiyomi.data.cache.ChapterCache -import eu.kanade.tachiyomi.data.database.models.Chapter -import eu.kanade.tachiyomi.data.source.model.Page -import rx.Observable -import uy.kohesive.injekt.injectLazy - - -// TODO: this should be handled with a different approach. - -/** - * Chapter cache. - */ -private val chapterCache: ChapterCache by injectLazy() - -/** - * Returns an observable with the page list for a chapter. It tries to return the page list from - * the local cache, otherwise fallbacks to network. - * - * @param chapter the chapter whose page list has to be fetched. - */ -fun OnlineSource.fetchPageListFromCacheThenNet(chapter: Chapter): Observable> { - return chapterCache - .getPageListFromCache(chapter) - .onErrorResumeNext { fetchPageList(chapter) } -} - -/** - * Returns an observable of the page with the downloaded image. - * - * @param page the page whose source image has to be downloaded. - */ -fun OnlineSource.fetchImageFromCacheThenNet(page: Page): Observable { - return if (page.imageUrl.isNullOrEmpty()) - getImageUrl(page).flatMap { getCachedImage(it) } - else - getCachedImage(page) -} - -fun OnlineSource.getImageUrl(page: Page): Observable { - page.status = Page.LOAD_PAGE - return fetchImageUrl(page) - .doOnError { page.status = Page.ERROR } - .onErrorReturn { null } - .doOnNext { page.imageUrl = it } - .map { page } -} - -/** - * Returns an observable of the page that gets the image from the chapter or fallbacks to - * network and copies it to the cache calling [cacheImage]. - * - * @param page the page. - */ -fun OnlineSource.getCachedImage(page: Page): Observable { - val imageUrl = page.imageUrl ?: return Observable.just(page) - - return Observable.just(page) - .flatMap { - if (!chapterCache.isImageInCache(imageUrl)) { - cacheImage(page) - } else { - Observable.just(page) - } - } - .doOnNext { - page.uri = Uri.fromFile(chapterCache.getImageFile(imageUrl)) - page.status = Page.READY - } - .doOnError { page.status = Page.ERROR } - .onErrorReturn { page } -} - -/** - * Returns an observable of the page that downloads the image to [ChapterCache]. - * - * @param page the page. - */ -private fun OnlineSource.cacheImage(page: Page): Observable { - page.status = Page.DOWNLOAD_IMAGE - return fetchImage(page) - .doOnNext { chapterCache.putImageToCache(page.imageUrl!!, it) } - .map { page } -} - -fun OnlineSource.fetchAllImageUrlsFromPageList(pages: List): Observable { - return Observable.from(pages) - .filter { !it.imageUrl.isNullOrEmpty() } - .mergeWith(fetchRemainingImageUrlsFromPageList(pages)) -} - -fun OnlineSource.fetchRemainingImageUrlsFromPageList(pages: List): Observable { - return Observable.from(pages) - .filter { it.imageUrl.isNullOrEmpty() } - .concatMap { getImageUrl(it) } -} +package eu.kanade.tachiyomi.source.online + +import android.net.Uri +import eu.kanade.tachiyomi.data.cache.ChapterCache +import eu.kanade.tachiyomi.data.database.models.Chapter +import eu.kanade.tachiyomi.source.model.Page +import rx.Observable +import uy.kohesive.injekt.injectLazy + + +// TODO: this should be handled with a different approach. + +/** + * Chapter cache. + */ +private val chapterCache: ChapterCache by injectLazy() + +/** + * Returns an observable with the page list for a chapter. It tries to return the page list from + * the local cache, otherwise fallbacks to network. + * + * @param chapter the chapter whose page list has to be fetched. + */ +fun OnlineSource.fetchPageListFromCacheThenNet(chapter: Chapter): Observable> { + return chapterCache + .getPageListFromCache(chapter) + .onErrorResumeNext { fetchPageList(chapter) } +} + +/** + * Returns an observable of the page with the downloaded image. + * + * @param page the page whose source image has to be downloaded. + */ +fun OnlineSource.fetchImageFromCacheThenNet(page: Page): Observable { + return if (page.imageUrl.isNullOrEmpty()) + getImageUrl(page).flatMap { getCachedImage(it) } + else + getCachedImage(page) +} + +fun OnlineSource.getImageUrl(page: Page): Observable { + page.status = Page.LOAD_PAGE + return fetchImageUrl(page) + .doOnError { page.status = Page.ERROR } + .onErrorReturn { null } + .doOnNext { page.imageUrl = it } + .map { page } +} + +/** + * Returns an observable of the page that gets the image from the chapter or fallbacks to + * network and copies it to the cache calling [cacheImage]. + * + * @param page the page. + */ +fun OnlineSource.getCachedImage(page: Page): Observable { + val imageUrl = page.imageUrl ?: return Observable.just(page) + + return Observable.just(page) + .flatMap { + if (!chapterCache.isImageInCache(imageUrl)) { + cacheImage(page) + } else { + Observable.just(page) + } + } + .doOnNext { + page.uri = Uri.fromFile(chapterCache.getImageFile(imageUrl)) + page.status = Page.READY + } + .doOnError { page.status = Page.ERROR } + .onErrorReturn { page } +} + +/** + * Returns an observable of the page that downloads the image to [ChapterCache]. + * + * @param page the page. + */ +private fun OnlineSource.cacheImage(page: Page): Observable { + page.status = Page.DOWNLOAD_IMAGE + return fetchImage(page) + .doOnNext { chapterCache.putImageToCache(page.imageUrl!!, it) } + .map { page } +} + +fun OnlineSource.fetchAllImageUrlsFromPageList(pages: List): Observable { + return Observable.from(pages) + .filter { !it.imageUrl.isNullOrEmpty() } + .mergeWith(fetchRemainingImageUrlsFromPageList(pages)) +} + +fun OnlineSource.fetchRemainingImageUrlsFromPageList(pages: List): Observable { + return Observable.from(pages) + .filter { it.imageUrl.isNullOrEmpty() } + .concatMap { getImageUrl(it) } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/ParsedOnlineSource.kt b/app/src/main/java/eu/kanade/tachiyomi/source/online/ParsedOnlineSource.kt similarity index 93% rename from app/src/main/java/eu/kanade/tachiyomi/data/source/online/ParsedOnlineSource.kt rename to app/src/main/java/eu/kanade/tachiyomi/source/online/ParsedOnlineSource.kt index bbacc26e46..5124a9b57f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/ParsedOnlineSource.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/online/ParsedOnlineSource.kt @@ -1,200 +1,200 @@ -package eu.kanade.tachiyomi.data.source.online - -import eu.kanade.tachiyomi.data.source.model.MangasPage -import eu.kanade.tachiyomi.data.source.model.Page -import eu.kanade.tachiyomi.data.source.model.SChapter -import eu.kanade.tachiyomi.data.source.model.SManga -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element - -/** - * A simple implementation for sources from a website using Jsoup, an HTML parser. - */ -abstract class ParsedOnlineSource : OnlineSource() { - - /** - * Parses the response from the site and returns a [MangasPage] object. - * - * @param response the response from the site. - */ - override fun popularMangaParse(response: Response): MangasPage { - val document = response.asJsoup() - - val mangas = document.select(popularMangaSelector()).map { element -> - popularMangaFromElement(element) - } - - val hasNextPage = popularMangaNextPageSelector()?.let { selector -> - document.select(selector).first() - } != null - - return MangasPage(mangas, hasNextPage) - } - - /** - * Returns the Jsoup selector that returns a list of [Element] corresponding to each manga. - */ - abstract protected fun popularMangaSelector(): String - - /** - * Returns a manga from the given [element]. Most sites only show the title and the url, it's - * totally fine to fill only those two values. - * - * @param element an element obtained from [popularMangaSelector]. - */ - abstract protected fun popularMangaFromElement(element: Element): SManga - - /** - * Returns the Jsoup selector that returns the tag linking to the next page, or null if - * there's no next page. - */ - abstract protected fun popularMangaNextPageSelector(): String? - - /** - * Parses the response from the site and returns a [MangasPage] object. - * - * @param response the response from the site. - */ - override fun searchMangaParse(response: Response): MangasPage { - val document = response.asJsoup() - - val mangas = document.select(searchMangaSelector()).map { element -> - searchMangaFromElement(element) - } - - val hasNextPage = searchMangaNextPageSelector()?.let { selector -> - document.select(selector).first() - } != null - - return MangasPage(mangas, hasNextPage) - } - - /** - * Returns the Jsoup selector that returns a list of [Element] corresponding to each manga. - */ - abstract protected fun searchMangaSelector(): String - - /** - * Returns a manga from the given [element]. Most sites only show the title and the url, it's - * totally fine to fill only those two values. - * - * @param element an element obtained from [searchMangaSelector]. - */ - abstract protected fun searchMangaFromElement(element: Element): SManga - - /** - * Returns the Jsoup selector that returns the tag linking to the next page, or null if - * there's no next page. - */ - abstract protected fun searchMangaNextPageSelector(): String? - - /** - * Parses the response from the site and returns a [MangasPage] object. - * - * @param response the response from the site. - */ - override fun latestUpdatesParse(response: Response): MangasPage { - val document = response.asJsoup() - - val mangas = document.select(latestUpdatesSelector()).map { element -> - latestUpdatesFromElement(element) - } - - val hasNextPage = latestUpdatesNextPageSelector()?.let { selector -> - document.select(selector).first() - } != null - - return MangasPage(mangas, hasNextPage) - } - - /** - * Returns the Jsoup selector that returns a list of [Element] corresponding to each manga. - */ - abstract protected fun latestUpdatesSelector(): String - - /** - * Returns a manga from the given [element]. Most sites only show the title and the url, it's - * totally fine to fill only those two values. - * - * @param element an element obtained from [latestUpdatesSelector]. - */ - abstract protected fun latestUpdatesFromElement(element: Element): SManga - - /** - * Returns the Jsoup selector that returns the tag linking to the next page, or null if - * there's no next page. - */ - abstract protected fun latestUpdatesNextPageSelector(): String? - - /** - * Parses the response from the site and returns the details of a manga. - * - * @param response the response from the site. - */ - override fun mangaDetailsParse(response: Response): SManga { - return mangaDetailsParse(response.asJsoup()) - } - - /** - * Returns the details of the manga from the given [document]. - * - * @param document the parsed document. - */ - abstract protected fun mangaDetailsParse(document: Document): SManga - - /** - * Parses the response from the site and returns a list of chapters. - * - * @param response the response from the site. - */ - override fun chapterListParse(response: Response): List { - val document = response.asJsoup() - return document.select(chapterListSelector()).map { chapterFromElement(it) } - } - - /** - * Returns the Jsoup selector that returns a list of [Element] corresponding to each chapter. - */ - abstract protected fun chapterListSelector(): String - - /** - * Returns a chapter from the given element. - * - * @param element an element obtained from [chapterListSelector]. - */ - abstract protected fun chapterFromElement(element: Element): SChapter - - /** - * Parses the response from the site and returns the page list. - * - * @param response the response from the site. - */ - override fun pageListParse(response: Response): List { - return pageListParse(response.asJsoup()) - } - - /** - * Returns a page list from the given document. - * - * @param document the parsed document. - */ - abstract protected fun pageListParse(document: Document): List - - /** - * Parse the response from the site and returns the absolute url to the source image. - * - * @param response the response from the site. - */ - override fun imageUrlParse(response: Response): String { - return imageUrlParse(response.asJsoup()) - } - - /** - * Returns the absolute url to the source image from the document. - * - * @param document the parsed document. - */ - abstract protected fun imageUrlParse(document: Document): String -} +package eu.kanade.tachiyomi.source.online + +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.util.asJsoup +import okhttp3.Response +import org.jsoup.nodes.Document +import org.jsoup.nodes.Element + +/** + * A simple implementation for sources from a website using Jsoup, an HTML parser. + */ +abstract class ParsedOnlineSource : OnlineSource() { + + /** + * Parses the response from the site and returns a [MangasPage] object. + * + * @param response the response from the site. + */ + override fun popularMangaParse(response: Response): MangasPage { + val document = response.asJsoup() + + val mangas = document.select(popularMangaSelector()).map { element -> + popularMangaFromElement(element) + } + + val hasNextPage = popularMangaNextPageSelector()?.let { selector -> + document.select(selector).first() + } != null + + return MangasPage(mangas, hasNextPage) + } + + /** + * Returns the Jsoup selector that returns a list of [Element] corresponding to each manga. + */ + abstract protected fun popularMangaSelector(): String + + /** + * Returns a manga from the given [element]. Most sites only show the title and the url, it's + * totally fine to fill only those two values. + * + * @param element an element obtained from [popularMangaSelector]. + */ + abstract protected fun popularMangaFromElement(element: Element): SManga + + /** + * Returns the Jsoup selector that returns the tag linking to the next page, or null if + * there's no next page. + */ + abstract protected fun popularMangaNextPageSelector(): String? + + /** + * Parses the response from the site and returns a [MangasPage] object. + * + * @param response the response from the site. + */ + override fun searchMangaParse(response: Response): MangasPage { + val document = response.asJsoup() + + val mangas = document.select(searchMangaSelector()).map { element -> + searchMangaFromElement(element) + } + + val hasNextPage = searchMangaNextPageSelector()?.let { selector -> + document.select(selector).first() + } != null + + return MangasPage(mangas, hasNextPage) + } + + /** + * Returns the Jsoup selector that returns a list of [Element] corresponding to each manga. + */ + abstract protected fun searchMangaSelector(): String + + /** + * Returns a manga from the given [element]. Most sites only show the title and the url, it's + * totally fine to fill only those two values. + * + * @param element an element obtained from [searchMangaSelector]. + */ + abstract protected fun searchMangaFromElement(element: Element): SManga + + /** + * Returns the Jsoup selector that returns the tag linking to the next page, or null if + * there's no next page. + */ + abstract protected fun searchMangaNextPageSelector(): String? + + /** + * Parses the response from the site and returns a [MangasPage] object. + * + * @param response the response from the site. + */ + override fun latestUpdatesParse(response: Response): MangasPage { + val document = response.asJsoup() + + val mangas = document.select(latestUpdatesSelector()).map { element -> + latestUpdatesFromElement(element) + } + + val hasNextPage = latestUpdatesNextPageSelector()?.let { selector -> + document.select(selector).first() + } != null + + return MangasPage(mangas, hasNextPage) + } + + /** + * Returns the Jsoup selector that returns a list of [Element] corresponding to each manga. + */ + abstract protected fun latestUpdatesSelector(): String + + /** + * Returns a manga from the given [element]. Most sites only show the title and the url, it's + * totally fine to fill only those two values. + * + * @param element an element obtained from [latestUpdatesSelector]. + */ + abstract protected fun latestUpdatesFromElement(element: Element): SManga + + /** + * Returns the Jsoup selector that returns the tag linking to the next page, or null if + * there's no next page. + */ + abstract protected fun latestUpdatesNextPageSelector(): String? + + /** + * Parses the response from the site and returns the details of a manga. + * + * @param response the response from the site. + */ + override fun mangaDetailsParse(response: Response): SManga { + return mangaDetailsParse(response.asJsoup()) + } + + /** + * Returns the details of the manga from the given [document]. + * + * @param document the parsed document. + */ + abstract protected fun mangaDetailsParse(document: Document): SManga + + /** + * Parses the response from the site and returns a list of chapters. + * + * @param response the response from the site. + */ + override fun chapterListParse(response: Response): List { + val document = response.asJsoup() + return document.select(chapterListSelector()).map { chapterFromElement(it) } + } + + /** + * Returns the Jsoup selector that returns a list of [Element] corresponding to each chapter. + */ + abstract protected fun chapterListSelector(): String + + /** + * Returns a chapter from the given element. + * + * @param element an element obtained from [chapterListSelector]. + */ + abstract protected fun chapterFromElement(element: Element): SChapter + + /** + * Parses the response from the site and returns the page list. + * + * @param response the response from the site. + */ + override fun pageListParse(response: Response): List { + return pageListParse(response.asJsoup()) + } + + /** + * Returns a page list from the given document. + * + * @param document the parsed document. + */ + abstract protected fun pageListParse(document: Document): List + + /** + * Parse the response from the site and returns the absolute url to the source image. + * + * @param response the response from the site. + */ + override fun imageUrlParse(response: Response): String { + return imageUrlParse(response.asJsoup()) + } + + /** + * Returns the absolute url to the source image from the document. + * + * @param document the parsed document. + */ + abstract protected fun imageUrlParse(document: Document): String +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/YamlOnlineSource.kt b/app/src/main/java/eu/kanade/tachiyomi/source/online/YamlOnlineSource.kt similarity index 95% rename from app/src/main/java/eu/kanade/tachiyomi/data/source/online/YamlOnlineSource.kt rename to app/src/main/java/eu/kanade/tachiyomi/source/online/YamlOnlineSource.kt index c5e7789aeb..5b26c77ba3 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/YamlOnlineSource.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/online/YamlOnlineSource.kt @@ -1,232 +1,232 @@ -package eu.kanade.tachiyomi.data.source.online - -import eu.kanade.tachiyomi.data.network.GET -import eu.kanade.tachiyomi.data.network.POST -import eu.kanade.tachiyomi.data.source.model.* -import eu.kanade.tachiyomi.util.asJsoup -import eu.kanade.tachiyomi.util.attrOrText -import okhttp3.Request -import okhttp3.Response -import org.jsoup.Jsoup -import org.jsoup.nodes.Element -import java.text.SimpleDateFormat -import java.util.* - -class YamlOnlineSource(mappings: Map<*, *>) : OnlineSource() { - - val map = YamlSourceNode(mappings) - - override val name: String - get() = map.name - - override val baseUrl = map.host.let { - if (it.endsWith("/")) it.dropLast(1) else it - } - - override val lang = map.lang.toLowerCase() - - override val supportsLatest = map.latestupdates != null - - override val client = when (map.client) { - "cloudflare" -> network.cloudflareClient - else -> network.client - } - - override val id = map.id.let { - (it as? Int ?: (lang.toUpperCase().hashCode() + 31 * it.hashCode()) and 0x7fffffff).toLong() - } - - // Ugly, but needed after the changes - var popularNextPage: String? = null - var searchNextPage: String? = null - var latestNextPage: String? = null - - override fun popularMangaRequest(page: Int): Request { - val url = if (page == 1) { - popularNextPage = null - map.popular.url - } else { - popularNextPage!! - } - return when (map.popular.method?.toLowerCase()) { - "post" -> POST(url, headers, map.popular.createForm()) - else -> GET(url, headers) - } - } - - override fun popularMangaParse(response: Response): MangasPage { - val document = response.asJsoup() - - val mangas = document.select(map.popular.manga_css).map { element -> - SManga.create().apply { - title = element.text() - setUrlWithoutDomain(element.attr("href")) - } - } - - popularNextPage = map.popular.next_url_css?.let { selector -> - document.select(selector).first()?.absUrl("href") - } - - return MangasPage(mangas, popularNextPage != null) - } - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val url = if (page == 1) { - searchNextPage = null - map.search.url.replace("\$query", query) - } else { - searchNextPage!! - } - return when (map.search.method?.toLowerCase()) { - "post" -> POST(url, headers, map.search.createForm()) - else -> GET(url, headers) - } - } - - override fun searchMangaParse(response: Response): MangasPage { - val document = response.asJsoup() - - val mangas = document.select(map.search.manga_css).map { element -> - SManga.create().apply { - title = element.text() - setUrlWithoutDomain(element.attr("href")) - } - } - - searchNextPage = map.search.next_url_css?.let { selector -> - document.select(selector).first()?.absUrl("href") - } - - return MangasPage(mangas, searchNextPage != null) - } - - override fun latestUpdatesRequest(page: Int): Request { - val url = if (page == 1) { - latestNextPage = null - map.latestupdates!!.url - } else { - latestNextPage!! - } - return when (map.latestupdates!!.method?.toLowerCase()) { - "post" -> POST(url, headers, map.latestupdates.createForm()) - else -> GET(url, headers) - } - } - - override fun latestUpdatesParse(response: Response): MangasPage { - val document = response.asJsoup() - - val mangas = document.select(map.latestupdates!!.manga_css).map { element -> - SManga.create().apply { - title = element.text() - setUrlWithoutDomain(element.attr("href")) - } - } - - popularNextPage = map.latestupdates.next_url_css?.let { selector -> - document.select(selector).first()?.absUrl("href") - } - - return MangasPage(mangas, popularNextPage != null) - } - - override fun mangaDetailsParse(response: Response): SManga { - val document = response.asJsoup() - - val manga = SManga.create() - with(map.manga) { - val pool = parts.get(document) - - manga.author = author?.process(document, pool) - manga.artist = artist?.process(document, pool) - manga.description = summary?.process(document, pool) - manga.thumbnail_url = cover?.process(document, pool) - manga.genre = genres?.process(document, pool) - manga.status = status?.getStatus(document, pool) ?: SManga.UNKNOWN - } - return manga - } - - override fun chapterListParse(response: Response): List { - val document = response.asJsoup() - - val chapters = mutableListOf() - with(map.chapters) { - val pool = emptyMap() - val dateFormat = SimpleDateFormat(date?.format, Locale.ENGLISH) - - for (element in document.select(chapter_css)) { - val chapter = SChapter.create() - element.select(title).first().let { - chapter.name = it.text() - chapter.setUrlWithoutDomain(it.attr("href")) - } - val dateElement = element.select(date?.select).first() - chapter.date_upload = date?.getDate(dateElement, pool, dateFormat)?.time ?: 0 - chapters.add(chapter) - } - } - return chapters - } - - override fun pageListParse(response: Response): List { - val body = response.body().string() - val url = response.request().url().toString() - - val pages = mutableListOf() - - // TODO lazy initialization in Kotlin 1.1 - val document = Jsoup.parse(body, url) - - with(map.pages) { - // Capture a list of values where page urls will be resolved. - val capturedPages = if (pages_regex != null) - pages_regex!!.toRegex().findAll(body).map { it.value }.toList() - else if (pages_css != null) - document.select(pages_css).map { it.attrOrText(pages_attr!!) } - else - null - - // For each captured value, obtain the url and create a new page. - capturedPages?.forEach { value -> - // If the captured value isn't an url, we have to use replaces with the chapter url. - val pageUrl = if (replace != null && replacement != null) - url.replace(replace!!.toRegex(), replacement!!.replace("\$value", value)) - else - value - - pages.add(Page(pages.size, pageUrl)) - } - - // Capture a list of images. - val capturedImages = if (image_regex != null) - image_regex!!.toRegex().findAll(body).map { it.groups[1]?.value }.toList() - else if (image_css != null) - document.select(image_css).map { it.absUrl(image_attr) } - else - null - - // Assign the image url to each page - capturedImages?.forEachIndexed { i, url -> - val page = pages.getOrElse(i) { Page(i, "").apply { pages.add(this) } } - page.imageUrl = url - } - } - return pages - } - - override fun imageUrlParse(response: Response): String { - val body = response.body().string() - val url = response.request().url().toString() - - with(map.pages) { - return if (image_regex != null) - image_regex!!.toRegex().find(body)!!.groups[1]!!.value - else if (image_css != null) - Jsoup.parse(body, url).select(image_css).first().absUrl(image_attr) - else - throw Exception("image_regex and image_css are null") - } - } -} +package eu.kanade.tachiyomi.source.online + +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.network.POST +import eu.kanade.tachiyomi.source.model.* +import eu.kanade.tachiyomi.util.asJsoup +import eu.kanade.tachiyomi.util.attrOrText +import okhttp3.Request +import okhttp3.Response +import org.jsoup.Jsoup +import org.jsoup.nodes.Element +import java.text.SimpleDateFormat +import java.util.* + +class YamlOnlineSource(mappings: Map<*, *>) : OnlineSource() { + + val map = YamlSourceNode(mappings) + + override val name: String + get() = map.name + + override val baseUrl = map.host.let { + if (it.endsWith("/")) it.dropLast(1) else it + } + + override val lang = map.lang.toLowerCase() + + override val supportsLatest = map.latestupdates != null + + override val client = when (map.client) { + "cloudflare" -> network.cloudflareClient + else -> network.client + } + + override val id = map.id.let { + (it as? Int ?: (lang.toUpperCase().hashCode() + 31 * it.hashCode()) and 0x7fffffff).toLong() + } + + // Ugly, but needed after the changes + var popularNextPage: String? = null + var searchNextPage: String? = null + var latestNextPage: String? = null + + override fun popularMangaRequest(page: Int): Request { + val url = if (page == 1) { + popularNextPage = null + map.popular.url + } else { + popularNextPage!! + } + return when (map.popular.method?.toLowerCase()) { + "post" -> POST(url, headers, map.popular.createForm()) + else -> GET(url, headers) + } + } + + override fun popularMangaParse(response: Response): MangasPage { + val document = response.asJsoup() + + val mangas = document.select(map.popular.manga_css).map { element -> + SManga.create().apply { + title = element.text() + setUrlWithoutDomain(element.attr("href")) + } + } + + popularNextPage = map.popular.next_url_css?.let { selector -> + document.select(selector).first()?.absUrl("href") + } + + return MangasPage(mangas, popularNextPage != null) + } + + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { + val url = if (page == 1) { + searchNextPage = null + map.search.url.replace("\$query", query) + } else { + searchNextPage!! + } + return when (map.search.method?.toLowerCase()) { + "post" -> POST(url, headers, map.search.createForm()) + else -> GET(url, headers) + } + } + + override fun searchMangaParse(response: Response): MangasPage { + val document = response.asJsoup() + + val mangas = document.select(map.search.manga_css).map { element -> + SManga.create().apply { + title = element.text() + setUrlWithoutDomain(element.attr("href")) + } + } + + searchNextPage = map.search.next_url_css?.let { selector -> + document.select(selector).first()?.absUrl("href") + } + + return MangasPage(mangas, searchNextPage != null) + } + + override fun latestUpdatesRequest(page: Int): Request { + val url = if (page == 1) { + latestNextPage = null + map.latestupdates!!.url + } else { + latestNextPage!! + } + return when (map.latestupdates!!.method?.toLowerCase()) { + "post" -> POST(url, headers, map.latestupdates.createForm()) + else -> GET(url, headers) + } + } + + override fun latestUpdatesParse(response: Response): MangasPage { + val document = response.asJsoup() + + val mangas = document.select(map.latestupdates!!.manga_css).map { element -> + SManga.create().apply { + title = element.text() + setUrlWithoutDomain(element.attr("href")) + } + } + + popularNextPage = map.latestupdates.next_url_css?.let { selector -> + document.select(selector).first()?.absUrl("href") + } + + return MangasPage(mangas, popularNextPage != null) + } + + override fun mangaDetailsParse(response: Response): SManga { + val document = response.asJsoup() + + val manga = SManga.create() + with(map.manga) { + val pool = parts.get(document) + + manga.author = author?.process(document, pool) + manga.artist = artist?.process(document, pool) + manga.description = summary?.process(document, pool) + manga.thumbnail_url = cover?.process(document, pool) + manga.genre = genres?.process(document, pool) + manga.status = status?.getStatus(document, pool) ?: SManga.UNKNOWN + } + return manga + } + + override fun chapterListParse(response: Response): List { + val document = response.asJsoup() + + val chapters = mutableListOf() + with(map.chapters) { + val pool = emptyMap() + val dateFormat = SimpleDateFormat(date?.format, Locale.ENGLISH) + + for (element in document.select(chapter_css)) { + val chapter = SChapter.create() + element.select(title).first().let { + chapter.name = it.text() + chapter.setUrlWithoutDomain(it.attr("href")) + } + val dateElement = element.select(date?.select).first() + chapter.date_upload = date?.getDate(dateElement, pool, dateFormat)?.time ?: 0 + chapters.add(chapter) + } + } + return chapters + } + + override fun pageListParse(response: Response): List { + val body = response.body().string() + val url = response.request().url().toString() + + val pages = mutableListOf() + + // TODO lazy initialization in Kotlin 1.1 + val document = Jsoup.parse(body, url) + + with(map.pages) { + // Capture a list of values where page urls will be resolved. + val capturedPages = if (pages_regex != null) + pages_regex!!.toRegex().findAll(body).map { it.value }.toList() + else if (pages_css != null) + document.select(pages_css).map { it.attrOrText(pages_attr!!) } + else + null + + // For each captured value, obtain the url and create a new page. + capturedPages?.forEach { value -> + // If the captured value isn't an url, we have to use replaces with the chapter url. + val pageUrl = if (replace != null && replacement != null) + url.replace(replace!!.toRegex(), replacement!!.replace("\$value", value)) + else + value + + pages.add(Page(pages.size, pageUrl)) + } + + // Capture a list of images. + val capturedImages = if (image_regex != null) + image_regex!!.toRegex().findAll(body).map { it.groups[1]?.value }.toList() + else if (image_css != null) + document.select(image_css).map { it.absUrl(image_attr) } + else + null + + // Assign the image url to each page + capturedImages?.forEachIndexed { i, url -> + val page = pages.getOrElse(i) { Page(i, "").apply { pages.add(this) } } + page.imageUrl = url + } + } + return pages + } + + override fun imageUrlParse(response: Response): String { + val body = response.body().string() + val url = response.request().url().toString() + + with(map.pages) { + return if (image_regex != null) + image_regex!!.toRegex().find(body)!!.groups[1]!!.value + else if (image_css != null) + Jsoup.parse(body, url).select(image_css).first().absUrl(image_attr) + else + throw Exception("image_regex and image_css are null") + } + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/YamlOnlineSourceMappings.kt b/app/src/main/java/eu/kanade/tachiyomi/source/online/YamlOnlineSourceMappings.kt similarity index 94% rename from app/src/main/java/eu/kanade/tachiyomi/data/source/online/YamlOnlineSourceMappings.kt rename to app/src/main/java/eu/kanade/tachiyomi/source/online/YamlOnlineSourceMappings.kt index b3c0a3bbb6..ba07594c3b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/YamlOnlineSourceMappings.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/online/YamlOnlineSourceMappings.kt @@ -1,234 +1,234 @@ -@file:Suppress("UNCHECKED_CAST") - -package eu.kanade.tachiyomi.data.source.online - -import eu.kanade.tachiyomi.data.source.model.SManga -import okhttp3.FormBody -import okhttp3.RequestBody -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import java.text.ParseException -import java.text.SimpleDateFormat -import java.util.* - -private fun toMap(map: Any?) = map as? Map - -class YamlSourceNode(uncheckedMap: Map<*, *>) { - - val map = toMap(uncheckedMap)!! - - val id: Any by map - - val name: String by map - - val host: String by map - - val lang: String by map - - val client: String? - get() = map["client"] as? String - - val popular = PopularNode(toMap(map["popular"])!!) - - val latestupdates = toMap(map["latest_updates"])?.let { LatestUpdatesNode(it) } - - val search = SearchNode(toMap(map["search"])!!) - - val manga = MangaNode(toMap(map["manga"])!!) - - val chapters = ChaptersNode(toMap(map["chapters"])!!) - - val pages = PagesNode(toMap(map["pages"])!!) -} - -interface RequestableNode { - - val map: Map - - val url: String - get() = map["url"] as String - - val method: String? - get() = map["method"] as? String - - val payload: Map? - get() = map["payload"] as? Map - - fun createForm(): RequestBody { - return FormBody.Builder().apply { - payload?.let { - for ((key, value) in it) { - add(key, value) - } - } - }.build() - } - -} - -class PopularNode(override val map: Map): RequestableNode { - - val manga_css: String by map - - val next_url_css: String? - get() = map["next_url_css"] as? String - -} - - -class LatestUpdatesNode(override val map: Map): RequestableNode { - - val manga_css: String by map - - val next_url_css: String? - get() = map["next_url_css"] as? String - -} - - -class SearchNode(override val map: Map): RequestableNode { - - val manga_css: String by map - - val next_url_css: String? - get() = map["next_url_css"] as? String -} - -class MangaNode(private val map: Map) { - - val parts = CacheNode(toMap(map["parts"]) ?: emptyMap()) - - val artist = toMap(map["artist"])?.let { SelectableNode(it) } - - val author = toMap(map["author"])?.let { SelectableNode(it) } - - val summary = toMap(map["summary"])?.let { SelectableNode(it) } - - val status = toMap(map["status"])?.let { StatusNode(it) } - - val genres = toMap(map["genres"])?.let { SelectableNode(it) } - - val cover = toMap(map["cover"])?.let { CoverNode(it) } - -} - -class ChaptersNode(private val map: Map) { - - val chapter_css: String by map - - val title: String by map - - val date = toMap(toMap(map["date"]))?.let { DateNode(it) } -} - -class CacheNode(private val map: Map) { - - fun get(document: Document) = map.mapValues { document.select(it.value as String).first() } -} - -open class SelectableNode(private val map: Map) { - - val select: String by map - - val from: String? - get() = map["from"] as? String - - open val attr: String? - get() = map["attr"] as? String - - val capture: String? - get() = map["capture"] as? String - - fun process(document: Element, cache: Map): String { - val parent = from?.let { cache[it] } ?: document - val node = parent.select(select).first() - var text = attr?.let { node.attr(it) } ?: node.text() - capture?.let { - text = Regex(it).find(text)?.groupValues?.get(1) ?: text - } - return text - } -} - -class StatusNode(private val map: Map) : SelectableNode(map) { - - val complete: String? - get() = map["complete"] as? String - - val ongoing: String? - get() = map["ongoing"] as? String - - val licensed: String? - get() = map["licensed"] as? String - - fun getStatus(document: Element, cache: Map): Int { - val text = process(document, cache) - complete?.let { - if (text.contains(it)) return SManga.COMPLETED - } - ongoing?.let { - if (text.contains(it)) return SManga.ONGOING - } - licensed?.let { - if (text.contains(it)) return SManga.LICENSED - } - return SManga.UNKNOWN - } -} - -class CoverNode(private val map: Map) : SelectableNode(map) { - - override val attr: String? - get() = map["attr"] as? String ?: "src" -} - -class DateNode(private val map: Map) : SelectableNode(map) { - - val format: String by map - - fun getDate(document: Element, cache: Map, formatter: SimpleDateFormat): Date { - val text = process(document, cache) - try { - return formatter.parse(text) - } catch (exception: ParseException) {} - - for (i in 0..7) { - (map["day$i"] as? List)?.let { - it.find { it.toRegex().containsMatchIn(text) }?.let { - return Calendar.getInstance().apply { add(Calendar.DATE, -i) }.time - } - } - } - - return Date(0) - } - -} - -class PagesNode(private val map: Map) { - - val pages_regex: String? - get() = map["pages_regex"] as? String - - val pages_css: String? - get() = map["pages_css"] as? String - - val pages_attr: String? - get() = map["pages_attr"] as? String ?: "value" - - val replace: String? - get() = map["url_replace"] as? String - - val replacement: String? - get() = map["url_replacement"] as? String - - val image_regex: String? - get() = map["image_regex"] as? String - - val image_css: String? - get() = map["image_css"] as? String - - val image_attr: String - get() = map["image_attr"] as? String ?: "src" - +@file:Suppress("UNCHECKED_CAST") + +package eu.kanade.tachiyomi.source.online + +import eu.kanade.tachiyomi.source.model.SManga +import okhttp3.FormBody +import okhttp3.RequestBody +import org.jsoup.nodes.Document +import org.jsoup.nodes.Element +import java.text.ParseException +import java.text.SimpleDateFormat +import java.util.* + +private fun toMap(map: Any?) = map as? Map + +class YamlSourceNode(uncheckedMap: Map<*, *>) { + + val map = toMap(uncheckedMap)!! + + val id: Any by map + + val name: String by map + + val host: String by map + + val lang: String by map + + val client: String? + get() = map["client"] as? String + + val popular = PopularNode(toMap(map["popular"])!!) + + val latestupdates = toMap(map["latest_updates"])?.let { LatestUpdatesNode(it) } + + val search = SearchNode(toMap(map["search"])!!) + + val manga = MangaNode(toMap(map["manga"])!!) + + val chapters = ChaptersNode(toMap(map["chapters"])!!) + + val pages = PagesNode(toMap(map["pages"])!!) +} + +interface RequestableNode { + + val map: Map + + val url: String + get() = map["url"] as String + + val method: String? + get() = map["method"] as? String + + val payload: Map? + get() = map["payload"] as? Map + + fun createForm(): RequestBody { + return FormBody.Builder().apply { + payload?.let { + for ((key, value) in it) { + add(key, value) + } + } + }.build() + } + +} + +class PopularNode(override val map: Map): RequestableNode { + + val manga_css: String by map + + val next_url_css: String? + get() = map["next_url_css"] as? String + +} + + +class LatestUpdatesNode(override val map: Map): RequestableNode { + + val manga_css: String by map + + val next_url_css: String? + get() = map["next_url_css"] as? String + +} + + +class SearchNode(override val map: Map): RequestableNode { + + val manga_css: String by map + + val next_url_css: String? + get() = map["next_url_css"] as? String +} + +class MangaNode(private val map: Map) { + + val parts = CacheNode(toMap(map["parts"]) ?: emptyMap()) + + val artist = toMap(map["artist"])?.let { SelectableNode(it) } + + val author = toMap(map["author"])?.let { SelectableNode(it) } + + val summary = toMap(map["summary"])?.let { SelectableNode(it) } + + val status = toMap(map["status"])?.let { StatusNode(it) } + + val genres = toMap(map["genres"])?.let { SelectableNode(it) } + + val cover = toMap(map["cover"])?.let { CoverNode(it) } + +} + +class ChaptersNode(private val map: Map) { + + val chapter_css: String by map + + val title: String by map + + val date = toMap(toMap(map["date"]))?.let { DateNode(it) } +} + +class CacheNode(private val map: Map) { + + fun get(document: Document) = map.mapValues { document.select(it.value as String).first() } +} + +open class SelectableNode(private val map: Map) { + + val select: String by map + + val from: String? + get() = map["from"] as? String + + open val attr: String? + get() = map["attr"] as? String + + val capture: String? + get() = map["capture"] as? String + + fun process(document: Element, cache: Map): String { + val parent = from?.let { cache[it] } ?: document + val node = parent.select(select).first() + var text = attr?.let { node.attr(it) } ?: node.text() + capture?.let { + text = Regex(it).find(text)?.groupValues?.get(1) ?: text + } + return text + } +} + +class StatusNode(private val map: Map) : SelectableNode(map) { + + val complete: String? + get() = map["complete"] as? String + + val ongoing: String? + get() = map["ongoing"] as? String + + val licensed: String? + get() = map["licensed"] as? String + + fun getStatus(document: Element, cache: Map): Int { + val text = process(document, cache) + complete?.let { + if (text.contains(it)) return SManga.COMPLETED + } + ongoing?.let { + if (text.contains(it)) return SManga.ONGOING + } + licensed?.let { + if (text.contains(it)) return SManga.LICENSED + } + return SManga.UNKNOWN + } +} + +class CoverNode(private val map: Map) : SelectableNode(map) { + + override val attr: String? + get() = map["attr"] as? String ?: "src" +} + +class DateNode(private val map: Map) : SelectableNode(map) { + + val format: String by map + + fun getDate(document: Element, cache: Map, formatter: SimpleDateFormat): Date { + val text = process(document, cache) + try { + return formatter.parse(text) + } catch (exception: ParseException) {} + + for (i in 0..7) { + (map["day$i"] as? List)?.let { + it.find { it.toRegex().containsMatchIn(text) }?.let { + return Calendar.getInstance().apply { add(Calendar.DATE, -i) }.time + } + } + } + + return Date(0) + } + +} + +class PagesNode(private val map: Map) { + + val pages_regex: String? + get() = map["pages_regex"] as? String + + val pages_css: String? + get() = map["pages_css"] as? String + + val pages_attr: String? + get() = map["pages_attr"] as? String ?: "value" + + val replace: String? + get() = map["url_replace"] as? String + + val replacement: String? + get() = map["url_replacement"] as? String + + val image_regex: String? + get() = map["image_regex"] as? String + + val image_css: String? + get() = map["image_css"] as? String + + val image_attr: String + get() = map["image_attr"] as? String ?: "src" + } \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Batoto.kt b/app/src/main/java/eu/kanade/tachiyomi/source/online/english/Batoto.kt similarity index 95% rename from app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Batoto.kt rename to app/src/main/java/eu/kanade/tachiyomi/source/online/english/Batoto.kt index e695b2c289..043ddff773 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Batoto.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/online/english/Batoto.kt @@ -1,366 +1,366 @@ -package eu.kanade.tachiyomi.data.source.online.english - -import android.text.Html -import eu.kanade.tachiyomi.data.network.GET -import eu.kanade.tachiyomi.data.network.POST -import eu.kanade.tachiyomi.data.network.asObservable -import eu.kanade.tachiyomi.data.source.model.* -import eu.kanade.tachiyomi.data.source.online.LoginSource -import eu.kanade.tachiyomi.data.source.online.ParsedOnlineSource -import eu.kanade.tachiyomi.util.asJsoup -import eu.kanade.tachiyomi.util.selectText -import okhttp3.FormBody -import okhttp3.HttpUrl -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import rx.Observable -import java.net.URI -import java.text.ParseException -import java.text.SimpleDateFormat -import java.util.* -import java.util.regex.Pattern - -class Batoto : ParsedOnlineSource(), LoginSource { - - override val id: Long = 1 - - override val name = "Batoto" - - override val baseUrl = "http://bato.to" - - override val lang = "en" - - override val supportsLatest = true - - private val datePattern = Pattern.compile("(\\d+|A|An)\\s+(.*?)s? ago.*") - - private val dateFields = HashMap().apply { - put("second", Calendar.SECOND) - put("minute", Calendar.MINUTE) - put("hour", Calendar.HOUR) - put("day", Calendar.DATE) - put("week", Calendar.WEEK_OF_YEAR) - put("month", Calendar.MONTH) - put("year", Calendar.YEAR) - } - - private val staffNotice = Pattern.compile("=+Batoto Staff Notice=+([^=]+)==+", Pattern.CASE_INSENSITIVE) - - override fun headersBuilder() = super.headersBuilder() - .add("Cookie", "lang_option=English") - - private val pageHeaders = super.headersBuilder() - .add("Referer", "http://bato.to/reader") - .build() - - override fun popularMangaRequest(page: Int): Request { - return GET("$baseUrl/search_ajax?order_cond=views&order=desc&p=$page", headers) - } - - override fun latestUpdatesRequest(page: Int): Request { - return GET("$baseUrl/search_ajax?order_cond=update&order=desc&p=$page", headers) - } - - override fun popularMangaSelector() = "tr:has(a)" - - override fun latestUpdatesSelector() = "tr:has(a)" - - override fun popularMangaFromElement(element: Element): SManga { - val manga = SManga.create() - element.select("a[href^=http://bato.to]").first().let { - manga.setUrlWithoutDomain(it.attr("href")) - manga.title = it.text().trim() - } - return manga - } - - override fun latestUpdatesFromElement(element: Element): SManga { - return popularMangaFromElement(element) - } - - override fun popularMangaNextPageSelector() = "#show_more_row" - - override fun latestUpdatesNextPageSelector() = "#show_more_row" - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val url = HttpUrl.parse("$baseUrl/search_ajax").newBuilder() - if (!query.isEmpty()) url.addQueryParameter("name", query).addQueryParameter("name_cond", "c") - var genres = "" - filters.forEach { filter -> - when (filter) { - is Status -> if (!filter.isIgnored()) { - url.addQueryParameter("completed", if (filter.isExcluded()) "i" else "c") - } - is GenreList -> { - filter.state.forEach { filter -> - when (filter) { - is Genre -> if (!filter.isIgnored()) { - genres += (if (filter.isExcluded()) ";e" else ";i") + filter.id - } - is SelectField -> { - val sel = filter.values[filter.state].value - if (!sel.isEmpty()) url.addQueryParameter(filter.key, sel) - } - } - } - } - is TextField -> { - if (!filter.state.isEmpty()) url.addQueryParameter(filter.key, filter.state) - } - is SelectField -> { - val sel = filter.values[filter.state].value - if (!sel.isEmpty()) url.addQueryParameter(filter.key, sel) - } - is Flag -> { - val sel = if (filter.state) filter.valTrue else filter.valFalse - if (!sel.isEmpty()) url.addQueryParameter(filter.key, sel) - } - is OrderBy -> { - url.addQueryParameter("order_cond", arrayOf("title", "author", "artist", "rating", "views", "update")[filter.state!!.index]) - url.addQueryParameter("order", if (filter.state?.ascending == true) "asc" else "desc") - } - } - } - if (!genres.isEmpty()) url.addQueryParameter("genres", genres) - url.addQueryParameter("p", page.toString()) - return GET(url.toString(), headers) - } - - override fun searchMangaSelector() = popularMangaSelector() - - override fun searchMangaFromElement(element: Element): SManga { - return popularMangaFromElement(element) - } - - override fun searchMangaNextPageSelector() = popularMangaNextPageSelector() - - override fun mangaDetailsRequest(manga: SManga): Request { - val mangaId = manga.url.substringAfterLast("r") - return GET("$baseUrl/comic_pop?id=$mangaId", headers) - } - - override fun mangaDetailsParse(document: Document): SManga { - val tbody = document.select("tbody").first() - val artistElement = tbody.select("tr:contains(Author/Artist:)").first() - - val manga = SManga.create() - manga.author = artistElement.selectText("td:eq(1)") - manga.artist = artistElement.selectText("td:eq(2)") ?: manga.author - manga.description = tbody.selectText("tr:contains(Description:) > td:eq(1)") - manga.thumbnail_url = document.select("img[src^=http://img.bato.to/forums/uploads/]").first()?.attr("src") - manga.status = parseStatus(document.selectText("tr:contains(Status:) > td:eq(1)")) - manga.genre = tbody.select("tr:contains(Genres:) img").map { it.attr("alt") }.joinToString(", ") - return manga - } - - private fun parseStatus(status: String?) = when (status) { - "Ongoing" -> SManga.ONGOING - "Complete" -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - - override fun chapterListParse(response: Response): List { - val body = response.body().string() - val matcher = staffNotice.matcher(body) - if (matcher.find()) { - @Suppress("DEPRECATION") - val notice = Html.fromHtml(matcher.group(1)).toString().trim() - throw Exception(notice) - } - - val document = response.asJsoup(body) - return document.select(chapterListSelector()).map { chapterFromElement(it) } - } - - override fun chapterListSelector() = "tr.row.lang_English.chapter_row" - - override fun chapterFromElement(element: Element): SChapter { - val urlElement = element.select("a[href^=http://bato.to/reader").first() - - val chapter = SChapter.create() - chapter.setUrlWithoutDomain(urlElement.attr("href")) - chapter.name = urlElement.text() - chapter.date_upload = element.select("td").getOrNull(4)?.let { - parseDateFromElement(it) - } ?: 0 - return chapter - } - - private fun parseDateFromElement(dateElement: Element): Long { - val dateAsString = dateElement.text() - - var date: Date - try { - date = SimpleDateFormat("dd MMMMM yyyy - hh:mm a", Locale.ENGLISH).parse(dateAsString) - } catch (e: ParseException) { - val m = datePattern.matcher(dateAsString) - - if (m.matches()) { - val number = m.group(1) - val amount = if (number.contains("A")) 1 else Integer.parseInt(m.group(1)) - val unit = m.group(2) - - date = Calendar.getInstance().apply { - add(dateFields[unit]!!, -amount) - }.time - } else { - return 0 - } - } - - return date.time - } - - override fun pageListRequest(chapter: SChapter): Request { - val id = chapter.url.substringAfterLast("#") - return GET("$baseUrl/areader?id=$id&p=1", pageHeaders) - } - - override fun pageListParse(document: Document): List { - val pages = mutableListOf() - val selectElement = document.select("#page_select").first() - if (selectElement != null) { - for ((i, element) in selectElement.select("option").withIndex()) { - pages.add(Page(i, element.attr("value"))) - } - pages.getOrNull(0)?.imageUrl = imageUrlParse(document) - } else { - // For webtoons in one page - for ((i, element) in document.select("div > img").withIndex()) { - pages.add(Page(i, "", element.attr("src"))) - } - } - return pages - } - - override fun imageUrlRequest(page: Page): Request { - val pageUrl = page.url - val start = pageUrl.indexOf("#") + 1 - val end = pageUrl.indexOf("_", start) - val id = pageUrl.substring(start, end) - return GET("$baseUrl/areader?id=$id&p=${pageUrl.substring(end + 1)}", pageHeaders) - } - - override fun imageUrlParse(document: Document): String { - return document.select("#comic_page").first().attr("src") - } - - override fun login(username: String, password: String) = - client.newCall(GET("$baseUrl/forums/index.php?app=core&module=global§ion=login", headers)) - .asObservable() - .flatMap { doLogin(it, username, password) } - .map { isAuthenticationSuccessful(it) } - - private fun doLogin(response: Response, username: String, password: String): Observable { - val doc = response.asJsoup() - val form = doc.select("#login").first() - val url = form.attr("action") - val authKey = form.select("input[name=auth_key]").first() - - val payload = FormBody.Builder().apply { - add(authKey.attr("name"), authKey.attr("value")) - add("ips_username", username) - add("ips_password", password) - add("invisible", "1") - add("rememberMe", "1") - }.build() - - return client.newCall(POST(url, headers, payload)).asObservable() - } - - override fun isAuthenticationSuccessful(response: Response) = - response.priorResponse() != null && response.priorResponse().code() == 302 - - override fun isLogged(): Boolean { - return network.cookies.get(URI(baseUrl)).any { it.name() == "pass_hash" } - } - - override fun fetchChapterList(manga: SManga): Observable> { - if (!isLogged()) { - val username = preferences.sourceUsername(this) - val password = preferences.sourcePassword(this) - - if (username.isNullOrEmpty() || password.isNullOrEmpty()) { - return Observable.error(Exception("User not logged")) - } else { - return login(username, password).flatMap { super.fetchChapterList(manga) } - } - - } else { - return super.fetchChapterList(manga) - } - } - - private data class ListValue(val name: String, val value: String) { - override fun toString(): String = name - } - - private class Status : Filter.TriState("Completed") - private class Genre(name: String, val id: Int) : Filter.TriState(name) - private class TextField(name: String, val key: String) : Filter.Text(name) - private class SelectField(name: String, val key: String, values: Array, state: Int = 0) : Filter.Select(name, values, state) - private class Flag(name: String, val key: String, val valTrue: String, val valFalse: String) : Filter.CheckBox(name) - private class GenreList(genres: List>) : Filter.Group>("Genres", genres) - private class OrderBy : Filter.Sort("Order by", - arrayOf("Title", "Author", "Artist", "Rating", "Views", "Last Update"), - Filter.Sort.Selection(4, false)) - - override fun getFilterList() = FilterList( - TextField("Author", "artist_name"), - SelectField("Type", "type", arrayOf(ListValue("Any", ""), ListValue("Manga (Jp)", "jp"), ListValue("Manhwa (Kr)", "kr"), ListValue("Manhua (Cn)", "cn"), ListValue("Artbook", "ar"), ListValue("Other", "ot"))), - Status(), - Flag("Exclude mature", "mature", "m", ""), - OrderBy(), - GenreList(getGenreList()) - ) - - // [...document.querySelectorAll("#advanced_options div.genre_buttons")].map((el,i) => { - // const onClick=el.getAttribute('onclick');const id=onClick.substr(14,onClick.length-16);return `Genre("${el.textContent.trim()}", ${id})` - // }).join(',\n') - // on https://bato.to/search - private fun getGenreList() = listOf( - SelectField("Inclusion mode", "genre_cond", arrayOf(ListValue("And (all selected genres)", "and"), ListValue("Or (any selected genres) ", "or"))), - Genre("4-Koma", 40), - Genre("Action", 1), - Genre("Adventure", 2), - Genre("Award Winning", 39), - Genre("Comedy", 3), - Genre("Cooking", 41), - Genre("Doujinshi", 9), - Genre("Drama", 10), - Genre("Ecchi", 12), - Genre("Fantasy", 13), - Genre("Gender Bender", 15), - Genre("Harem", 17), - Genre("Historical", 20), - Genre("Horror", 22), - Genre("Josei", 34), - Genre("Martial Arts", 27), - Genre("Mecha", 30), - Genre("Medical", 42), - Genre("Music", 37), - Genre("Mystery", 4), - Genre("Oneshot", 38), - Genre("Psychological", 5), - Genre("Romance", 6), - Genre("School Life", 7), - Genre("Sci-fi", 8), - Genre("Seinen", 32), - Genre("Shoujo", 35), - Genre("Shoujo Ai", 16), - Genre("Shounen", 33), - Genre("Shounen Ai", 19), - Genre("Slice of Life", 21), - Genre("Smut", 23), - Genre("Sports", 25), - Genre("Supernatural", 26), - Genre("Tragedy", 28), - Genre("Webtoon", 36), - Genre("Yaoi", 29), - Genre("Yuri", 31), - Genre("[no chapters]", 44) - ) - +package eu.kanade.tachiyomi.source.online.english + +import android.text.Html +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.network.POST +import eu.kanade.tachiyomi.network.asObservable +import eu.kanade.tachiyomi.source.model.* +import eu.kanade.tachiyomi.source.online.LoginSource +import eu.kanade.tachiyomi.source.online.ParsedOnlineSource +import eu.kanade.tachiyomi.util.asJsoup +import eu.kanade.tachiyomi.util.selectText +import okhttp3.FormBody +import okhttp3.HttpUrl +import okhttp3.Request +import okhttp3.Response +import org.jsoup.nodes.Document +import org.jsoup.nodes.Element +import rx.Observable +import java.net.URI +import java.text.ParseException +import java.text.SimpleDateFormat +import java.util.* +import java.util.regex.Pattern + +class Batoto : ParsedOnlineSource(), LoginSource { + + override val id: Long = 1 + + override val name = "Batoto" + + override val baseUrl = "http://bato.to" + + override val lang = "en" + + override val supportsLatest = true + + private val datePattern = Pattern.compile("(\\d+|A|An)\\s+(.*?)s? ago.*") + + private val dateFields = HashMap().apply { + put("second", Calendar.SECOND) + put("minute", Calendar.MINUTE) + put("hour", Calendar.HOUR) + put("day", Calendar.DATE) + put("week", Calendar.WEEK_OF_YEAR) + put("month", Calendar.MONTH) + put("year", Calendar.YEAR) + } + + private val staffNotice = Pattern.compile("=+Batoto Staff Notice=+([^=]+)==+", Pattern.CASE_INSENSITIVE) + + override fun headersBuilder() = super.headersBuilder() + .add("Cookie", "lang_option=English") + + private val pageHeaders = super.headersBuilder() + .add("Referer", "http://bato.to/reader") + .build() + + override fun popularMangaRequest(page: Int): Request { + return GET("$baseUrl/search_ajax?order_cond=views&order=desc&p=$page", headers) + } + + override fun latestUpdatesRequest(page: Int): Request { + return GET("$baseUrl/search_ajax?order_cond=update&order=desc&p=$page", headers) + } + + override fun popularMangaSelector() = "tr:has(a)" + + override fun latestUpdatesSelector() = "tr:has(a)" + + override fun popularMangaFromElement(element: Element): SManga { + val manga = SManga.create() + element.select("a[href^=http://bato.to]").first().let { + manga.setUrlWithoutDomain(it.attr("href")) + manga.title = it.text().trim() + } + return manga + } + + override fun latestUpdatesFromElement(element: Element): SManga { + return popularMangaFromElement(element) + } + + override fun popularMangaNextPageSelector() = "#show_more_row" + + override fun latestUpdatesNextPageSelector() = "#show_more_row" + + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { + val url = HttpUrl.parse("$baseUrl/search_ajax").newBuilder() + if (!query.isEmpty()) url.addQueryParameter("name", query).addQueryParameter("name_cond", "c") + var genres = "" + filters.forEach { filter -> + when (filter) { + is Status -> if (!filter.isIgnored()) { + url.addQueryParameter("completed", if (filter.isExcluded()) "i" else "c") + } + is GenreList -> { + filter.state.forEach { filter -> + when (filter) { + is Genre -> if (!filter.isIgnored()) { + genres += (if (filter.isExcluded()) ";e" else ";i") + filter.id + } + is SelectField -> { + val sel = filter.values[filter.state].value + if (!sel.isEmpty()) url.addQueryParameter(filter.key, sel) + } + } + } + } + is TextField -> { + if (!filter.state.isEmpty()) url.addQueryParameter(filter.key, filter.state) + } + is SelectField -> { + val sel = filter.values[filter.state].value + if (!sel.isEmpty()) url.addQueryParameter(filter.key, sel) + } + is Flag -> { + val sel = if (filter.state) filter.valTrue else filter.valFalse + if (!sel.isEmpty()) url.addQueryParameter(filter.key, sel) + } + is OrderBy -> { + url.addQueryParameter("order_cond", arrayOf("title", "author", "artist", "rating", "views", "update")[filter.state!!.index]) + url.addQueryParameter("order", if (filter.state?.ascending == true) "asc" else "desc") + } + } + } + if (!genres.isEmpty()) url.addQueryParameter("genres", genres) + url.addQueryParameter("p", page.toString()) + return GET(url.toString(), headers) + } + + override fun searchMangaSelector() = popularMangaSelector() + + override fun searchMangaFromElement(element: Element): SManga { + return popularMangaFromElement(element) + } + + override fun searchMangaNextPageSelector() = popularMangaNextPageSelector() + + override fun mangaDetailsRequest(manga: SManga): Request { + val mangaId = manga.url.substringAfterLast("r") + return GET("$baseUrl/comic_pop?id=$mangaId", headers) + } + + override fun mangaDetailsParse(document: Document): SManga { + val tbody = document.select("tbody").first() + val artistElement = tbody.select("tr:contains(Author/Artist:)").first() + + val manga = SManga.create() + manga.author = artistElement.selectText("td:eq(1)") + manga.artist = artistElement.selectText("td:eq(2)") ?: manga.author + manga.description = tbody.selectText("tr:contains(Description:) > td:eq(1)") + manga.thumbnail_url = document.select("img[src^=http://img.bato.to/forums/uploads/]").first()?.attr("src") + manga.status = parseStatus(document.selectText("tr:contains(Status:) > td:eq(1)")) + manga.genre = tbody.select("tr:contains(Genres:) img").map { it.attr("alt") }.joinToString(", ") + return manga + } + + private fun parseStatus(status: String?) = when (status) { + "Ongoing" -> SManga.ONGOING + "Complete" -> SManga.COMPLETED + else -> SManga.UNKNOWN + } + + override fun chapterListParse(response: Response): List { + val body = response.body().string() + val matcher = staffNotice.matcher(body) + if (matcher.find()) { + @Suppress("DEPRECATION") + val notice = Html.fromHtml(matcher.group(1)).toString().trim() + throw Exception(notice) + } + + val document = response.asJsoup(body) + return document.select(chapterListSelector()).map { chapterFromElement(it) } + } + + override fun chapterListSelector() = "tr.row.lang_English.chapter_row" + + override fun chapterFromElement(element: Element): SChapter { + val urlElement = element.select("a[href^=http://bato.to/reader").first() + + val chapter = SChapter.create() + chapter.setUrlWithoutDomain(urlElement.attr("href")) + chapter.name = urlElement.text() + chapter.date_upload = element.select("td").getOrNull(4)?.let { + parseDateFromElement(it) + } ?: 0 + return chapter + } + + private fun parseDateFromElement(dateElement: Element): Long { + val dateAsString = dateElement.text() + + var date: Date + try { + date = SimpleDateFormat("dd MMMMM yyyy - hh:mm a", Locale.ENGLISH).parse(dateAsString) + } catch (e: ParseException) { + val m = datePattern.matcher(dateAsString) + + if (m.matches()) { + val number = m.group(1) + val amount = if (number.contains("A")) 1 else Integer.parseInt(m.group(1)) + val unit = m.group(2) + + date = Calendar.getInstance().apply { + add(dateFields[unit]!!, -amount) + }.time + } else { + return 0 + } + } + + return date.time + } + + override fun pageListRequest(chapter: SChapter): Request { + val id = chapter.url.substringAfterLast("#") + return GET("$baseUrl/areader?id=$id&p=1", pageHeaders) + } + + override fun pageListParse(document: Document): List { + val pages = mutableListOf() + val selectElement = document.select("#page_select").first() + if (selectElement != null) { + for ((i, element) in selectElement.select("option").withIndex()) { + pages.add(Page(i, element.attr("value"))) + } + pages.getOrNull(0)?.imageUrl = imageUrlParse(document) + } else { + // For webtoons in one page + for ((i, element) in document.select("div > img").withIndex()) { + pages.add(Page(i, "", element.attr("src"))) + } + } + return pages + } + + override fun imageUrlRequest(page: Page): Request { + val pageUrl = page.url + val start = pageUrl.indexOf("#") + 1 + val end = pageUrl.indexOf("_", start) + val id = pageUrl.substring(start, end) + return GET("$baseUrl/areader?id=$id&p=${pageUrl.substring(end + 1)}", pageHeaders) + } + + override fun imageUrlParse(document: Document): String { + return document.select("#comic_page").first().attr("src") + } + + override fun login(username: String, password: String) = + client.newCall(GET("$baseUrl/forums/index.php?app=core&module=global§ion=login", headers)) + .asObservable() + .flatMap { doLogin(it, username, password) } + .map { isAuthenticationSuccessful(it) } + + private fun doLogin(response: Response, username: String, password: String): Observable { + val doc = response.asJsoup() + val form = doc.select("#login").first() + val url = form.attr("action") + val authKey = form.select("input[name=auth_key]").first() + + val payload = FormBody.Builder().apply { + add(authKey.attr("name"), authKey.attr("value")) + add("ips_username", username) + add("ips_password", password) + add("invisible", "1") + add("rememberMe", "1") + }.build() + + return client.newCall(POST(url, headers, payload)).asObservable() + } + + override fun isAuthenticationSuccessful(response: Response) = + response.priorResponse() != null && response.priorResponse().code() == 302 + + override fun isLogged(): Boolean { + return network.cookies.get(URI(baseUrl)).any { it.name() == "pass_hash" } + } + + override fun fetchChapterList(manga: SManga): Observable> { + if (!isLogged()) { + val username = preferences.sourceUsername(this) + val password = preferences.sourcePassword(this) + + if (username.isNullOrEmpty() || password.isNullOrEmpty()) { + return Observable.error(Exception("User not logged")) + } else { + return login(username, password).flatMap { super.fetchChapterList(manga) } + } + + } else { + return super.fetchChapterList(manga) + } + } + + private data class ListValue(val name: String, val value: String) { + override fun toString(): String = name + } + + private class Status : Filter.TriState("Completed") + private class Genre(name: String, val id: Int) : Filter.TriState(name) + private class TextField(name: String, val key: String) : Filter.Text(name) + private class SelectField(name: String, val key: String, values: Array, state: Int = 0) : Filter.Select(name, values, state) + private class Flag(name: String, val key: String, val valTrue: String, val valFalse: String) : Filter.CheckBox(name) + private class GenreList(genres: List>) : Filter.Group>("Genres", genres) + private class OrderBy : Filter.Sort("Order by", + arrayOf("Title", "Author", "Artist", "Rating", "Views", "Last Update"), + Filter.Sort.Selection(4, false)) + + override fun getFilterList() = FilterList( + TextField("Author", "artist_name"), + SelectField("Type", "type", arrayOf(ListValue("Any", ""), ListValue("Manga (Jp)", "jp"), ListValue("Manhwa (Kr)", "kr"), ListValue("Manhua (Cn)", "cn"), ListValue("Artbook", "ar"), ListValue("Other", "ot"))), + Status(), + Flag("Exclude mature", "mature", "m", ""), + OrderBy(), + GenreList(getGenreList()) + ) + + // [...document.querySelectorAll("#advanced_options div.genre_buttons")].map((el,i) => { + // const onClick=el.getAttribute('onclick');const id=onClick.substr(14,onClick.length-16);return `Genre("${el.textContent.trim()}", ${id})` + // }).join(',\n') + // on https://bato.to/search + private fun getGenreList() = listOf( + SelectField("Inclusion mode", "genre_cond", arrayOf(ListValue("And (all selected genres)", "and"), ListValue("Or (any selected genres) ", "or"))), + Genre("4-Koma", 40), + Genre("Action", 1), + Genre("Adventure", 2), + Genre("Award Winning", 39), + Genre("Comedy", 3), + Genre("Cooking", 41), + Genre("Doujinshi", 9), + Genre("Drama", 10), + Genre("Ecchi", 12), + Genre("Fantasy", 13), + Genre("Gender Bender", 15), + Genre("Harem", 17), + Genre("Historical", 20), + Genre("Horror", 22), + Genre("Josei", 34), + Genre("Martial Arts", 27), + Genre("Mecha", 30), + Genre("Medical", 42), + Genre("Music", 37), + Genre("Mystery", 4), + Genre("Oneshot", 38), + Genre("Psychological", 5), + Genre("Romance", 6), + Genre("School Life", 7), + Genre("Sci-fi", 8), + Genre("Seinen", 32), + Genre("Shoujo", 35), + Genre("Shoujo Ai", 16), + Genre("Shounen", 33), + Genre("Shounen Ai", 19), + Genre("Slice of Life", 21), + Genre("Smut", 23), + Genre("Sports", 25), + Genre("Supernatural", 26), + Genre("Tragedy", 28), + Genre("Webtoon", 36), + Genre("Yaoi", 29), + Genre("Yuri", 31), + Genre("[no chapters]", 44) + ) + } \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Kissmanga.kt b/app/src/main/java/eu/kanade/tachiyomi/source/online/english/Kissmanga.kt similarity index 93% rename from app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Kissmanga.kt rename to app/src/main/java/eu/kanade/tachiyomi/source/online/english/Kissmanga.kt index 4e3d3ea8b5..b3845008ed 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Kissmanga.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/online/english/Kissmanga.kt @@ -1,197 +1,197 @@ -package eu.kanade.tachiyomi.data.source.online.english - -import eu.kanade.tachiyomi.data.network.GET -import eu.kanade.tachiyomi.data.network.POST -import eu.kanade.tachiyomi.data.source.model.* -import eu.kanade.tachiyomi.data.source.online.ParsedOnlineSource -import okhttp3.FormBody -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import java.text.SimpleDateFormat -import java.util.regex.Pattern - -class Kissmanga : ParsedOnlineSource() { - - override val id: Long = 4 - - override val name = "Kissmanga" - - override val baseUrl = "http://kissmanga.com" - - override val lang = "en" - - override val supportsLatest = true - - override val client: OkHttpClient = network.cloudflareClient - - override fun popularMangaSelector() = "table.listing tr:gt(1)" - - override fun latestUpdatesSelector() = "table.listing tr:gt(1)" - - override fun popularMangaRequest(page: Int): Request { - return GET("$baseUrl/MangaList/MostPopular?page=$page", headers) - } - - override fun latestUpdatesRequest(page: Int): Request { - return GET("http://kissmanga.com/MangaList/LatestUpdate?page=$page", headers) - } - - override fun popularMangaFromElement(element: Element): SManga { - val manga = SManga.create() - element.select("td a:eq(0)").first().let { - manga.setUrlWithoutDomain(it.attr("href")) - manga.title = it.text() - } - return manga - } - - override fun latestUpdatesFromElement(element: Element): SManga { - return popularMangaFromElement(element) - } - - override fun popularMangaNextPageSelector() = "li > a:contains(› Next)" - - override fun latestUpdatesNextPageSelector(): String = "ul.pager > li > a:contains(Next)" - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val form = FormBody.Builder().apply { - add("mangaName", query) - - for (filter in if (filters.isEmpty()) getFilterList() else filters) { - when (filter) { - is Author -> add("authorArtist", filter.state) - is Status -> add("status", arrayOf("", "Completed", "Ongoing")[filter.state]) - is GenreList -> filter.state.forEach { genre -> add("genres", genre.state.toString()) } - } - } - } - return POST("$baseUrl/AdvanceSearch", headers, form.build()) - } - - override fun searchMangaSelector() = popularMangaSelector() - - override fun searchMangaFromElement(element: Element): SManga { - return popularMangaFromElement(element) - } - - override fun searchMangaNextPageSelector() = null - - override fun mangaDetailsParse(document: Document): SManga { - val infoElement = document.select("div.barContent").first() - - val manga = SManga.create() - manga.author = infoElement.select("p:has(span:contains(Author:)) > a").first()?.text() - manga.genre = infoElement.select("p:has(span:contains(Genres:)) > *:gt(0)").text() - manga.description = infoElement.select("p:has(span:contains(Summary:)) ~ p").text() - manga.status = infoElement.select("p:has(span:contains(Status:))").first()?.text().orEmpty().let { parseStatus(it) } - manga.thumbnail_url = document.select(".rightBox:eq(0) img").first()?.attr("src") - return manga - } - - fun parseStatus(status: String) = when { - status.contains("Ongoing") -> SManga.ONGOING - status.contains("Completed") -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - - override fun chapterListSelector() = "table.listing tr:gt(1)" - - override fun chapterFromElement(element: Element): SChapter { - val urlElement = element.select("a").first() - - val chapter = SChapter.create() - chapter.setUrlWithoutDomain(urlElement.attr("href")) - chapter.name = urlElement.text() - chapter.date_upload = element.select("td:eq(1)").first()?.text()?.let { - SimpleDateFormat("MM/dd/yyyy").parse(it).time - } ?: 0 - return chapter - } - - override fun pageListRequest(chapter: SChapter) = POST(baseUrl + chapter.url, headers) - - override fun pageListParse(response: Response): List { - val pages = mutableListOf() - //language=RegExp - val p = Pattern.compile("""lstImages.push\("(.+?)"""") - val m = p.matcher(response.body().string()) - - var i = 0 - while (m.find()) { - pages.add(Page(i++, "", m.group(1))) - } - return pages - } - - override fun pageListParse(document: Document): List { - throw Exception("Not used") - } - - override fun imageUrlRequest(page: Page) = GET(page.url) - - override fun imageUrlParse(document: Document) = "" - - private class Status : Filter.TriState("Completed") - private class Author : Filter.Text("Author") - private class Genre(name: String) : Filter.TriState(name) - private class GenreList(genres: List) : Filter.Group("Genres", genres) - - override fun getFilterList() = FilterList( - Author(), - Status(), - GenreList(getGenreList()) - ) - - // $("select[name=\"genres\"]").map((i,el) => `Genre("${$(el).next().text().trim()}", ${i})`).get().join(',\n') - // on http://kissmanga.com/AdvanceSearch - private fun getGenreList() = listOf( - Genre("4-Koma"), - Genre("Action"), - Genre("Adult"), - Genre("Adventure"), - Genre("Comedy"), - Genre("Comic"), - Genre("Cooking"), - Genre("Doujinshi"), - Genre("Drama"), - Genre("Ecchi"), - Genre("Fantasy"), - Genre("Gender Bender"), - Genre("Harem"), - Genre("Historical"), - Genre("Horror"), - Genre("Josei"), - Genre("Lolicon"), - Genre("Manga"), - Genre("Manhua"), - Genre("Manhwa"), - Genre("Martial Arts"), - Genre("Mature"), - Genre("Mecha"), - Genre("Medical"), - Genre("Music"), - Genre("Mystery"), - Genre("One shot"), - Genre("Psychological"), - Genre("Romance"), - Genre("School Life"), - Genre("Sci-fi"), - Genre("Seinen"), - Genre("Shotacon"), - Genre("Shoujo"), - Genre("Shoujo Ai"), - Genre("Shounen"), - Genre("Shounen Ai"), - Genre("Slice of Life"), - Genre("Smut"), - Genre("Sports"), - Genre("Supernatural"), - Genre("Tragedy"), - Genre("Webtoon"), - Genre("Yaoi"), - Genre("Yuri") - ) +package eu.kanade.tachiyomi.source.online.english + +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.network.POST +import eu.kanade.tachiyomi.source.model.* +import eu.kanade.tachiyomi.source.online.ParsedOnlineSource +import okhttp3.FormBody +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.Response +import org.jsoup.nodes.Document +import org.jsoup.nodes.Element +import java.text.SimpleDateFormat +import java.util.regex.Pattern + +class Kissmanga : ParsedOnlineSource() { + + override val id: Long = 4 + + override val name = "Kissmanga" + + override val baseUrl = "http://kissmanga.com" + + override val lang = "en" + + override val supportsLatest = true + + override val client: OkHttpClient = network.cloudflareClient + + override fun popularMangaSelector() = "table.listing tr:gt(1)" + + override fun latestUpdatesSelector() = "table.listing tr:gt(1)" + + override fun popularMangaRequest(page: Int): Request { + return GET("$baseUrl/MangaList/MostPopular?page=$page", headers) + } + + override fun latestUpdatesRequest(page: Int): Request { + return GET("http://kissmanga.com/MangaList/LatestUpdate?page=$page", headers) + } + + override fun popularMangaFromElement(element: Element): SManga { + val manga = SManga.create() + element.select("td a:eq(0)").first().let { + manga.setUrlWithoutDomain(it.attr("href")) + manga.title = it.text() + } + return manga + } + + override fun latestUpdatesFromElement(element: Element): SManga { + return popularMangaFromElement(element) + } + + override fun popularMangaNextPageSelector() = "li > a:contains(› Next)" + + override fun latestUpdatesNextPageSelector(): String = "ul.pager > li > a:contains(Next)" + + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { + val form = FormBody.Builder().apply { + add("mangaName", query) + + for (filter in if (filters.isEmpty()) getFilterList() else filters) { + when (filter) { + is Author -> add("authorArtist", filter.state) + is Status -> add("status", arrayOf("", "Completed", "Ongoing")[filter.state]) + is GenreList -> filter.state.forEach { genre -> add("genres", genre.state.toString()) } + } + } + } + return POST("$baseUrl/AdvanceSearch", headers, form.build()) + } + + override fun searchMangaSelector() = popularMangaSelector() + + override fun searchMangaFromElement(element: Element): SManga { + return popularMangaFromElement(element) + } + + override fun searchMangaNextPageSelector() = null + + override fun mangaDetailsParse(document: Document): SManga { + val infoElement = document.select("div.barContent").first() + + val manga = SManga.create() + manga.author = infoElement.select("p:has(span:contains(Author:)) > a").first()?.text() + manga.genre = infoElement.select("p:has(span:contains(Genres:)) > *:gt(0)").text() + manga.description = infoElement.select("p:has(span:contains(Summary:)) ~ p").text() + manga.status = infoElement.select("p:has(span:contains(Status:))").first()?.text().orEmpty().let { parseStatus(it) } + manga.thumbnail_url = document.select(".rightBox:eq(0) img").first()?.attr("src") + return manga + } + + fun parseStatus(status: String) = when { + status.contains("Ongoing") -> SManga.ONGOING + status.contains("Completed") -> SManga.COMPLETED + else -> SManga.UNKNOWN + } + + override fun chapterListSelector() = "table.listing tr:gt(1)" + + override fun chapterFromElement(element: Element): SChapter { + val urlElement = element.select("a").first() + + val chapter = SChapter.create() + chapter.setUrlWithoutDomain(urlElement.attr("href")) + chapter.name = urlElement.text() + chapter.date_upload = element.select("td:eq(1)").first()?.text()?.let { + SimpleDateFormat("MM/dd/yyyy").parse(it).time + } ?: 0 + return chapter + } + + override fun pageListRequest(chapter: SChapter) = POST(baseUrl + chapter.url, headers) + + override fun pageListParse(response: Response): List { + val pages = mutableListOf() + //language=RegExp + val p = Pattern.compile("""lstImages.push\("(.+?)"""") + val m = p.matcher(response.body().string()) + + var i = 0 + while (m.find()) { + pages.add(Page(i++, "", m.group(1))) + } + return pages + } + + override fun pageListParse(document: Document): List { + throw Exception("Not used") + } + + override fun imageUrlRequest(page: Page) = GET(page.url) + + override fun imageUrlParse(document: Document) = "" + + private class Status : Filter.TriState("Completed") + private class Author : Filter.Text("Author") + private class Genre(name: String) : Filter.TriState(name) + private class GenreList(genres: List) : Filter.Group("Genres", genres) + + override fun getFilterList() = FilterList( + Author(), + Status(), + GenreList(getGenreList()) + ) + + // $("select[name=\"genres\"]").map((i,el) => `Genre("${$(el).next().text().trim()}", ${i})`).get().join(',\n') + // on http://kissmanga.com/AdvanceSearch + private fun getGenreList() = listOf( + Genre("4-Koma"), + Genre("Action"), + Genre("Adult"), + Genre("Adventure"), + Genre("Comedy"), + Genre("Comic"), + Genre("Cooking"), + Genre("Doujinshi"), + Genre("Drama"), + Genre("Ecchi"), + Genre("Fantasy"), + Genre("Gender Bender"), + Genre("Harem"), + Genre("Historical"), + Genre("Horror"), + Genre("Josei"), + Genre("Lolicon"), + Genre("Manga"), + Genre("Manhua"), + Genre("Manhwa"), + Genre("Martial Arts"), + Genre("Mature"), + Genre("Mecha"), + Genre("Medical"), + Genre("Music"), + Genre("Mystery"), + Genre("One shot"), + Genre("Psychological"), + Genre("Romance"), + Genre("School Life"), + Genre("Sci-fi"), + Genre("Seinen"), + Genre("Shotacon"), + Genre("Shoujo"), + Genre("Shoujo Ai"), + Genre("Shounen"), + Genre("Shounen Ai"), + Genre("Slice of Life"), + Genre("Smut"), + Genre("Sports"), + Genre("Supernatural"), + Genre("Tragedy"), + Genre("Webtoon"), + Genre("Yaoi"), + Genre("Yuri") + ) } \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Mangafox.kt b/app/src/main/java/eu/kanade/tachiyomi/source/online/english/Mangafox.kt similarity index 95% rename from app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Mangafox.kt rename to app/src/main/java/eu/kanade/tachiyomi/source/online/english/Mangafox.kt index 800abd1c59..2f547f82ff 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Mangafox.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/online/english/Mangafox.kt @@ -1,223 +1,223 @@ -package eu.kanade.tachiyomi.data.source.online.english - -import eu.kanade.tachiyomi.data.network.GET -import eu.kanade.tachiyomi.data.source.model.* -import eu.kanade.tachiyomi.data.source.online.ParsedOnlineSource -import okhttp3.HttpUrl -import okhttp3.Request -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import java.text.ParseException -import java.text.SimpleDateFormat -import java.util.* - -class Mangafox : ParsedOnlineSource() { - - override val id: Long = 3 - - override val name = "Mangafox" - - override val baseUrl = "http://mangafox.me" - - override val lang = "en" - - override val supportsLatest = true - - override fun popularMangaSelector() = "div#mangalist > ul.list > li" - - override fun popularMangaRequest(page: Int): Request { - val pageStr = if (page != 1) "$page.htm" else "" - return GET("$baseUrl/directory/$pageStr", headers) - } - - override fun latestUpdatesSelector() = "div#mangalist > ul.list > li" - - override fun latestUpdatesRequest(page: Int): Request { - val pageStr = if (page != 1) "$page.htm" else "" - return GET("$baseUrl/directory/$pageStr?latest") - } - - override fun popularMangaFromElement(element: Element): SManga { - val manga = SManga.create() - element.select("a.title").first().let { - manga.setUrlWithoutDomain(it.attr("href")) - manga.title = it.text() - } - return manga - } - - override fun latestUpdatesFromElement(element: Element): SManga { - return popularMangaFromElement(element) - } - - override fun popularMangaNextPageSelector() = "a:has(span.next)" - - override fun latestUpdatesNextPageSelector() = "a:has(span.next)" - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val url = HttpUrl.parse("$baseUrl/search.php?name_method=cw&author_method=cw&artist_method=cw&advopts=1").newBuilder().addQueryParameter("name", query) - (if (filters.isEmpty()) getFilterList() else filters).forEach { filter -> - when (filter) { - is Status -> url.addQueryParameter(filter.id, filter.state.toString()) - is GenreList -> filter.state.forEach { genre -> url.addQueryParameter(genre.id, genre.state.toString()) } - is TextField -> url.addQueryParameter(filter.key, filter.state) - is Type -> url.addQueryParameter("type", if(filter.state == 0) "" else filter.state.toString()) - is OrderBy -> { - url.addQueryParameter("sort", arrayOf("name", "rating", "views", "total_chapters", "last_chapter_time")[filter.state!!.index]) - url.addQueryParameter("order", if (filter.state?.ascending == true) "az" else "za") - } - } - } - url.addQueryParameter("page", page.toString()) - return GET(url.toString(), headers) - } - - override fun searchMangaSelector() = "div#mangalist > ul.list > li" - - override fun searchMangaFromElement(element: Element): SManga { - val manga = SManga.create() - element.select("a.title").first().let { - manga.setUrlWithoutDomain(it.attr("href")) - manga.title = it.text() - } - return manga - } - - override fun searchMangaNextPageSelector() = "a:has(span.next)" - - override fun mangaDetailsParse(document: Document): SManga { - val infoElement = document.select("div#title").first() - val rowElement = infoElement.select("table > tbody > tr:eq(1)").first() - val sideInfoElement = document.select("#series_info").first() - - val manga = SManga.create() - manga.author = rowElement.select("td:eq(1)").first()?.text() - manga.artist = rowElement.select("td:eq(2)").first()?.text() - manga.genre = rowElement.select("td:eq(3)").first()?.text() - manga.description = infoElement.select("p.summary").first()?.text() - manga.status = sideInfoElement.select(".data").first()?.text().orEmpty().let { parseStatus(it) } - manga.thumbnail_url = sideInfoElement.select("div.cover > img").first()?.attr("src") - return manga - } - - private fun parseStatus(status: String) = when { - status.contains("Ongoing") -> SManga.ONGOING - status.contains("Completed") -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - - override fun chapterListSelector() = "div#chapters li div" - - override fun chapterFromElement(element: Element): SChapter { - val urlElement = element.select("a.tips").first() - - val chapter = SChapter.create() - chapter.setUrlWithoutDomain(urlElement.attr("href")) - chapter.name = urlElement.text() - chapter.date_upload = element.select("span.date").first()?.text()?.let { parseChapterDate(it) } ?: 0 - return chapter - } - - private fun parseChapterDate(date: String): Long { - return if ("Today" in date || " ago" in date) { - Calendar.getInstance().apply { - set(Calendar.HOUR_OF_DAY, 0) - set(Calendar.MINUTE, 0) - set(Calendar.SECOND, 0) - set(Calendar.MILLISECOND, 0) - }.timeInMillis - } else if ("Yesterday" in date) { - Calendar.getInstance().apply { - add(Calendar.DATE, -1) - set(Calendar.HOUR_OF_DAY, 0) - set(Calendar.MINUTE, 0) - set(Calendar.SECOND, 0) - set(Calendar.MILLISECOND, 0) - }.timeInMillis - } else { - try { - SimpleDateFormat("MMM d, yyyy", Locale.ENGLISH).parse(date).time - } catch (e: ParseException) { - 0L - } - } - } - - override fun pageListParse(document: Document): List { - val url = document.baseUri().substringBeforeLast('/') - - val pages = mutableListOf() - document.select("select.m").first()?.select("option:not([value=0])")?.forEach { - pages.add(Page(pages.size, "$url/${it.attr("value")}.html")) - } - return pages - } - - override fun imageUrlParse(document: Document): String { - val url = document.getElementById("image").attr("src") - return if ("compressed?token=" !in url) { - url - } else { - "http://mangafox.me/media/logo.png" - } - } - - private class Status(val id: String = "is_completed") : Filter.TriState("Completed") - private class Genre(name: String, val id: String = "genres[$name]") : Filter.TriState(name) - private class TextField(name: String, val key: String) : Filter.Text(name) - private class Type : Filter.Select("Type", arrayOf("Any", "Japanese Manga", "Korean Manhwa", "Chinese Manhua")) - private class OrderBy : Filter.Sort("Order by", - arrayOf("Series name", "Rating", "Views", "Total chapters", "Last chapter"), - Filter.Sort.Selection(2, false)) - private class GenreList(genres: List) : Filter.Group("Genres", genres) - - override fun getFilterList() = FilterList( - TextField("Author", "author"), - TextField("Artist", "artist"), - Type(), - Status(), - OrderBy(), - GenreList(getGenreList()) - ) - - // $('select.genres').map((i,el)=>`Genre("${$(el).next().text().trim()}", "${$(el).attr('name')}")`).get().join(',\n') - // on http://mangafox.me/search.php - private fun getGenreList() = listOf( - Genre("Action"), - Genre("Adult"), - Genre("Adventure"), - Genre("Comedy"), - Genre("Doujinshi"), - Genre("Drama"), - Genre("Ecchi"), - Genre("Fantasy"), - Genre("Gender Bender"), - Genre("Harem"), - Genre("Historical"), - Genre("Horror"), - Genre("Josei"), - Genre("Martial Arts"), - Genre("Mature"), - Genre("Mecha"), - Genre("Mystery"), - Genre("One Shot"), - Genre("Psychological"), - Genre("Romance"), - Genre("School Life"), - Genre("Sci-fi"), - Genre("Seinen"), - Genre("Shoujo"), - Genre("Shoujo Ai"), - Genre("Shounen"), - Genre("Shounen Ai"), - Genre("Slice of Life"), - Genre("Smut"), - Genre("Sports"), - Genre("Supernatural"), - Genre("Tragedy"), - Genre("Webtoons"), - Genre("Yaoi"), - Genre("Yuri") - ) - +package eu.kanade.tachiyomi.source.online.english + +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.source.model.* +import eu.kanade.tachiyomi.source.online.ParsedOnlineSource +import okhttp3.HttpUrl +import okhttp3.Request +import org.jsoup.nodes.Document +import org.jsoup.nodes.Element +import java.text.ParseException +import java.text.SimpleDateFormat +import java.util.* + +class Mangafox : ParsedOnlineSource() { + + override val id: Long = 3 + + override val name = "Mangafox" + + override val baseUrl = "http://mangafox.me" + + override val lang = "en" + + override val supportsLatest = true + + override fun popularMangaSelector() = "div#mangalist > ul.list > li" + + override fun popularMangaRequest(page: Int): Request { + val pageStr = if (page != 1) "$page.htm" else "" + return GET("$baseUrl/directory/$pageStr", headers) + } + + override fun latestUpdatesSelector() = "div#mangalist > ul.list > li" + + override fun latestUpdatesRequest(page: Int): Request { + val pageStr = if (page != 1) "$page.htm" else "" + return GET("$baseUrl/directory/$pageStr?latest") + } + + override fun popularMangaFromElement(element: Element): SManga { + val manga = SManga.create() + element.select("a.title").first().let { + manga.setUrlWithoutDomain(it.attr("href")) + manga.title = it.text() + } + return manga + } + + override fun latestUpdatesFromElement(element: Element): SManga { + return popularMangaFromElement(element) + } + + override fun popularMangaNextPageSelector() = "a:has(span.next)" + + override fun latestUpdatesNextPageSelector() = "a:has(span.next)" + + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { + val url = HttpUrl.parse("$baseUrl/search.php?name_method=cw&author_method=cw&artist_method=cw&advopts=1").newBuilder().addQueryParameter("name", query) + (if (filters.isEmpty()) getFilterList() else filters).forEach { filter -> + when (filter) { + is Status -> url.addQueryParameter(filter.id, filter.state.toString()) + is GenreList -> filter.state.forEach { genre -> url.addQueryParameter(genre.id, genre.state.toString()) } + is TextField -> url.addQueryParameter(filter.key, filter.state) + is Type -> url.addQueryParameter("type", if(filter.state == 0) "" else filter.state.toString()) + is OrderBy -> { + url.addQueryParameter("sort", arrayOf("name", "rating", "views", "total_chapters", "last_chapter_time")[filter.state!!.index]) + url.addQueryParameter("order", if (filter.state?.ascending == true) "az" else "za") + } + } + } + url.addQueryParameter("page", page.toString()) + return GET(url.toString(), headers) + } + + override fun searchMangaSelector() = "div#mangalist > ul.list > li" + + override fun searchMangaFromElement(element: Element): SManga { + val manga = SManga.create() + element.select("a.title").first().let { + manga.setUrlWithoutDomain(it.attr("href")) + manga.title = it.text() + } + return manga + } + + override fun searchMangaNextPageSelector() = "a:has(span.next)" + + override fun mangaDetailsParse(document: Document): SManga { + val infoElement = document.select("div#title").first() + val rowElement = infoElement.select("table > tbody > tr:eq(1)").first() + val sideInfoElement = document.select("#series_info").first() + + val manga = SManga.create() + manga.author = rowElement.select("td:eq(1)").first()?.text() + manga.artist = rowElement.select("td:eq(2)").first()?.text() + manga.genre = rowElement.select("td:eq(3)").first()?.text() + manga.description = infoElement.select("p.summary").first()?.text() + manga.status = sideInfoElement.select(".data").first()?.text().orEmpty().let { parseStatus(it) } + manga.thumbnail_url = sideInfoElement.select("div.cover > img").first()?.attr("src") + return manga + } + + private fun parseStatus(status: String) = when { + status.contains("Ongoing") -> SManga.ONGOING + status.contains("Completed") -> SManga.COMPLETED + else -> SManga.UNKNOWN + } + + override fun chapterListSelector() = "div#chapters li div" + + override fun chapterFromElement(element: Element): SChapter { + val urlElement = element.select("a.tips").first() + + val chapter = SChapter.create() + chapter.setUrlWithoutDomain(urlElement.attr("href")) + chapter.name = urlElement.text() + chapter.date_upload = element.select("span.date").first()?.text()?.let { parseChapterDate(it) } ?: 0 + return chapter + } + + private fun parseChapterDate(date: String): Long { + return if ("Today" in date || " ago" in date) { + Calendar.getInstance().apply { + set(Calendar.HOUR_OF_DAY, 0) + set(Calendar.MINUTE, 0) + set(Calendar.SECOND, 0) + set(Calendar.MILLISECOND, 0) + }.timeInMillis + } else if ("Yesterday" in date) { + Calendar.getInstance().apply { + add(Calendar.DATE, -1) + set(Calendar.HOUR_OF_DAY, 0) + set(Calendar.MINUTE, 0) + set(Calendar.SECOND, 0) + set(Calendar.MILLISECOND, 0) + }.timeInMillis + } else { + try { + SimpleDateFormat("MMM d, yyyy", Locale.ENGLISH).parse(date).time + } catch (e: ParseException) { + 0L + } + } + } + + override fun pageListParse(document: Document): List { + val url = document.baseUri().substringBeforeLast('/') + + val pages = mutableListOf() + document.select("select.m").first()?.select("option:not([value=0])")?.forEach { + pages.add(Page(pages.size, "$url/${it.attr("value")}.html")) + } + return pages + } + + override fun imageUrlParse(document: Document): String { + val url = document.getElementById("image").attr("src") + return if ("compressed?token=" !in url) { + url + } else { + "http://mangafox.me/media/logo.png" + } + } + + private class Status(val id: String = "is_completed") : Filter.TriState("Completed") + private class Genre(name: String, val id: String = "genres[$name]") : Filter.TriState(name) + private class TextField(name: String, val key: String) : Filter.Text(name) + private class Type : Filter.Select("Type", arrayOf("Any", "Japanese Manga", "Korean Manhwa", "Chinese Manhua")) + private class OrderBy : Filter.Sort("Order by", + arrayOf("Series name", "Rating", "Views", "Total chapters", "Last chapter"), + Filter.Sort.Selection(2, false)) + private class GenreList(genres: List) : Filter.Group("Genres", genres) + + override fun getFilterList() = FilterList( + TextField("Author", "author"), + TextField("Artist", "artist"), + Type(), + Status(), + OrderBy(), + GenreList(getGenreList()) + ) + + // $('select.genres').map((i,el)=>`Genre("${$(el).next().text().trim()}", "${$(el).attr('name')}")`).get().join(',\n') + // on http://mangafox.me/search.php + private fun getGenreList() = listOf( + Genre("Action"), + Genre("Adult"), + Genre("Adventure"), + Genre("Comedy"), + Genre("Doujinshi"), + Genre("Drama"), + Genre("Ecchi"), + Genre("Fantasy"), + Genre("Gender Bender"), + Genre("Harem"), + Genre("Historical"), + Genre("Horror"), + Genre("Josei"), + Genre("Martial Arts"), + Genre("Mature"), + Genre("Mecha"), + Genre("Mystery"), + Genre("One Shot"), + Genre("Psychological"), + Genre("Romance"), + Genre("School Life"), + Genre("Sci-fi"), + Genre("Seinen"), + Genre("Shoujo"), + Genre("Shoujo Ai"), + Genre("Shounen"), + Genre("Shounen Ai"), + Genre("Slice of Life"), + Genre("Smut"), + Genre("Sports"), + Genre("Supernatural"), + Genre("Tragedy"), + Genre("Webtoons"), + Genre("Yaoi"), + Genre("Yuri") + ) + } \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Mangahere.kt b/app/src/main/java/eu/kanade/tachiyomi/source/online/english/Mangahere.kt similarity index 95% rename from app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Mangahere.kt rename to app/src/main/java/eu/kanade/tachiyomi/source/online/english/Mangahere.kt index 5ea7246264..880e234432 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Mangahere.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/online/english/Mangahere.kt @@ -1,220 +1,220 @@ -package eu.kanade.tachiyomi.data.source.online.english - -import eu.kanade.tachiyomi.data.network.GET -import eu.kanade.tachiyomi.data.source.model.* -import eu.kanade.tachiyomi.data.source.online.ParsedOnlineSource -import okhttp3.HttpUrl -import okhttp3.Request -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import java.text.ParseException -import java.text.SimpleDateFormat -import java.util.* - -class Mangahere : ParsedOnlineSource() { - - override val id: Long = 2 - - override val name = "Mangahere" - - override val baseUrl = "http://www.mangahere.co" - - override val lang = "en" - - override val supportsLatest = true - - override fun popularMangaSelector() = "div.directory_list > ul > li" - - override fun latestUpdatesSelector() = "div.directory_list > ul > li" - - override fun popularMangaRequest(page: Int): Request { - return GET("$baseUrl/directory/$page.htm?views.za", headers) - } - - override fun latestUpdatesRequest(page: Int): Request { - return GET("$baseUrl/directory/$page.htm?last_chapter_time.za", headers) - } - - private fun mangaFromElement(query: String, element: Element): SManga { - val manga = SManga.create() - element.select(query).first().let { - manga.setUrlWithoutDomain(it.attr("href")) - manga.title = if (it.hasAttr("title")) it.attr("title") else if (it.hasAttr("rel")) it.attr("rel") else it.text() - } - return manga - } - - override fun popularMangaFromElement(element: Element): SManga { - return mangaFromElement("div.title > a", element) - } - - override fun latestUpdatesFromElement(element: Element): SManga { - return popularMangaFromElement(element) - } - - override fun popularMangaNextPageSelector() = "div.next-page > a.next" - - override fun latestUpdatesNextPageSelector() = "div.next-page > a.next" - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val url = HttpUrl.parse("$baseUrl/search.php?name_method=cw&author_method=cw&artist_method=cw&advopts=1").newBuilder().addQueryParameter("name", query) - (if (filters.isEmpty()) getFilterList() else filters).forEach { filter -> - when (filter) { - is Status -> url.addQueryParameter("is_completed", arrayOf("", "1", "0")[filter.state]) - is GenreList -> filter.state.forEach { genre -> url.addQueryParameter(genre.id, genre.state.toString()) } - is TextField -> url.addQueryParameter(filter.key, filter.state) - is Type -> url.addQueryParameter("direction", arrayOf("", "rl", "lr")[filter.state]) - is OrderBy -> { - url.addQueryParameter("sort", arrayOf("name", "rating", "views", "total_chapters", "last_chapter_time")[filter.state!!.index]) - url.addQueryParameter("order", if (filter.state?.ascending == true) "az" else "za") - } - } - } - url.addQueryParameter("page", page.toString()) - return GET(url.toString(), headers) - } - - override fun searchMangaSelector() = "div.result_search > dl:has(dt)" - - override fun searchMangaFromElement(element: Element): SManga { - return mangaFromElement("a.manga_info", element) - } - - override fun searchMangaNextPageSelector() = "div.next-page > a.next" - - override fun mangaDetailsParse(document: Document): SManga { - val detailElement = document.select(".manga_detail_top").first() - val infoElement = detailElement.select(".detail_topText").first() - - val manga = SManga.create() - manga.author = infoElement.select("a[href^=http://www.mangahere.co/author/]").first()?.text() - manga.artist = infoElement.select("a[href^=http://www.mangahere.co/artist/]").first()?.text() - manga.genre = infoElement.select("li:eq(3)").first()?.text()?.substringAfter("Genre(s):") - manga.description = infoElement.select("#show").first()?.text()?.substringBeforeLast("Show less") - manga.status = infoElement.select("li:eq(6)").first()?.text().orEmpty().let { parseStatus(it) } - manga.thumbnail_url = detailElement.select("img.img").first()?.attr("src") - return manga - } - - private fun parseStatus(status: String) = when { - status.contains("Ongoing") -> SManga.ONGOING - status.contains("Completed") -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - - override fun chapterListSelector() = ".detail_list > ul:not([class]) > li" - - override fun chapterFromElement(element: Element): SChapter { - val parentEl = element.select("span.left").first() - - val urlElement = parentEl.select("a").first() - - var volume = parentEl.select("span.mr6")?.first()?.text()?.trim() ?: "" - if (volume.length > 0) { - volume = " - " + volume - } - - var title = parentEl?.textNodes()?.last()?.text()?.trim() ?: "" - if (title.length > 0) { - title = " - " + title - } - - val chapter = SChapter.create() - chapter.setUrlWithoutDomain(urlElement.attr("href")) - chapter.name = urlElement.text() + volume + title - chapter.date_upload = element.select("span.right").first()?.text()?.let { parseChapterDate(it) } ?: 0 - return chapter - } - - private fun parseChapterDate(date: String): Long { - return if ("Today" in date) { - Calendar.getInstance().apply { - set(Calendar.HOUR_OF_DAY, 0) - set(Calendar.MINUTE, 0) - set(Calendar.SECOND, 0) - set(Calendar.MILLISECOND, 0) - }.timeInMillis - } else if ("Yesterday" in date) { - Calendar.getInstance().apply { - add(Calendar.DATE, -1) - set(Calendar.HOUR_OF_DAY, 0) - set(Calendar.MINUTE, 0) - set(Calendar.SECOND, 0) - set(Calendar.MILLISECOND, 0) - }.timeInMillis - } else { - try { - SimpleDateFormat("MMM d, yyyy", Locale.ENGLISH).parse(date).time - } catch (e: ParseException) { - 0L - } - } - } - - override fun pageListParse(document: Document): List { - val pages = mutableListOf() - document.select("select.wid60").first()?.getElementsByTag("option")?.forEach { - pages.add(Page(pages.size, it.attr("value"))) - } - pages.getOrNull(0)?.imageUrl = imageUrlParse(document) - return pages - } - - override fun imageUrlParse(document: Document) = document.getElementById("image").attr("src") - - private class Status : Filter.TriState("Completed") - private class Genre(name: String, val id: String = "genres[$name]") : Filter.TriState(name) - private class TextField(name: String, val key: String) : Filter.Text(name) - private class Type : Filter.Select("Type", arrayOf("Any", "Japanese Manga (read from right to left)", "Korean Manhwa (read from left to right)")) - private class OrderBy : Filter.Sort("Order by", - arrayOf("Series name", "Rating", "Views", "Total chapters", "Last chapter"), - Filter.Sort.Selection(2, false)) - private class GenreList(genres: List) : Filter.Group("Genres", genres) - - override fun getFilterList() = FilterList( - TextField("Author", "author"), - TextField("Artist", "artist"), - Type(), - Status(), - OrderBy(), - GenreList(getGenreList()) - ) - - // [...document.querySelectorAll("select[id^='genres'")].map((el,i) => `Genre("${el.nextSibling.nextSibling.textContent.trim()}", "${el.getAttribute('name')}")`).join(',\n') - // http://www.mangahere.co/advsearch.htm - private fun getGenreList() = listOf( - Genre("Action"), - Genre("Adventure"), - Genre("Comedy"), - Genre("Doujinshi"), - Genre("Drama"), - Genre("Ecchi"), - Genre("Fantasy"), - Genre("Gender Bender"), - Genre("Harem"), - Genre("Historical"), - Genre("Horror"), - Genre("Josei"), - Genre("Martial Arts"), - Genre("Mature"), - Genre("Mecha"), - Genre("Mystery"), - Genre("One Shot"), - Genre("Psychological"), - Genre("Romance"), - Genre("School Life"), - Genre("Sci-fi"), - Genre("Seinen"), - Genre("Shoujo"), - Genre("Shoujo Ai"), - Genre("Shounen"), - Genre("Shounen Ai"), - Genre("Slice of Life"), - Genre("Sports"), - Genre("Supernatural"), - Genre("Tragedy"), - Genre("Yaoi"), - Genre("Yuri") - ) - +package eu.kanade.tachiyomi.source.online.english + +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.source.model.* +import eu.kanade.tachiyomi.source.online.ParsedOnlineSource +import okhttp3.HttpUrl +import okhttp3.Request +import org.jsoup.nodes.Document +import org.jsoup.nodes.Element +import java.text.ParseException +import java.text.SimpleDateFormat +import java.util.* + +class Mangahere : ParsedOnlineSource() { + + override val id: Long = 2 + + override val name = "Mangahere" + + override val baseUrl = "http://www.mangahere.co" + + override val lang = "en" + + override val supportsLatest = true + + override fun popularMangaSelector() = "div.directory_list > ul > li" + + override fun latestUpdatesSelector() = "div.directory_list > ul > li" + + override fun popularMangaRequest(page: Int): Request { + return GET("$baseUrl/directory/$page.htm?views.za", headers) + } + + override fun latestUpdatesRequest(page: Int): Request { + return GET("$baseUrl/directory/$page.htm?last_chapter_time.za", headers) + } + + private fun mangaFromElement(query: String, element: Element): SManga { + val manga = SManga.create() + element.select(query).first().let { + manga.setUrlWithoutDomain(it.attr("href")) + manga.title = if (it.hasAttr("title")) it.attr("title") else if (it.hasAttr("rel")) it.attr("rel") else it.text() + } + return manga + } + + override fun popularMangaFromElement(element: Element): SManga { + return mangaFromElement("div.title > a", element) + } + + override fun latestUpdatesFromElement(element: Element): SManga { + return popularMangaFromElement(element) + } + + override fun popularMangaNextPageSelector() = "div.next-page > a.next" + + override fun latestUpdatesNextPageSelector() = "div.next-page > a.next" + + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { + val url = HttpUrl.parse("$baseUrl/search.php?name_method=cw&author_method=cw&artist_method=cw&advopts=1").newBuilder().addQueryParameter("name", query) + (if (filters.isEmpty()) getFilterList() else filters).forEach { filter -> + when (filter) { + is Status -> url.addQueryParameter("is_completed", arrayOf("", "1", "0")[filter.state]) + is GenreList -> filter.state.forEach { genre -> url.addQueryParameter(genre.id, genre.state.toString()) } + is TextField -> url.addQueryParameter(filter.key, filter.state) + is Type -> url.addQueryParameter("direction", arrayOf("", "rl", "lr")[filter.state]) + is OrderBy -> { + url.addQueryParameter("sort", arrayOf("name", "rating", "views", "total_chapters", "last_chapter_time")[filter.state!!.index]) + url.addQueryParameter("order", if (filter.state?.ascending == true) "az" else "za") + } + } + } + url.addQueryParameter("page", page.toString()) + return GET(url.toString(), headers) + } + + override fun searchMangaSelector() = "div.result_search > dl:has(dt)" + + override fun searchMangaFromElement(element: Element): SManga { + return mangaFromElement("a.manga_info", element) + } + + override fun searchMangaNextPageSelector() = "div.next-page > a.next" + + override fun mangaDetailsParse(document: Document): SManga { + val detailElement = document.select(".manga_detail_top").first() + val infoElement = detailElement.select(".detail_topText").first() + + val manga = SManga.create() + manga.author = infoElement.select("a[href^=http://www.mangahere.co/author/]").first()?.text() + manga.artist = infoElement.select("a[href^=http://www.mangahere.co/artist/]").first()?.text() + manga.genre = infoElement.select("li:eq(3)").first()?.text()?.substringAfter("Genre(s):") + manga.description = infoElement.select("#show").first()?.text()?.substringBeforeLast("Show less") + manga.status = infoElement.select("li:eq(6)").first()?.text().orEmpty().let { parseStatus(it) } + manga.thumbnail_url = detailElement.select("img.img").first()?.attr("src") + return manga + } + + private fun parseStatus(status: String) = when { + status.contains("Ongoing") -> SManga.ONGOING + status.contains("Completed") -> SManga.COMPLETED + else -> SManga.UNKNOWN + } + + override fun chapterListSelector() = ".detail_list > ul:not([class]) > li" + + override fun chapterFromElement(element: Element): SChapter { + val parentEl = element.select("span.left").first() + + val urlElement = parentEl.select("a").first() + + var volume = parentEl.select("span.mr6")?.first()?.text()?.trim() ?: "" + if (volume.length > 0) { + volume = " - " + volume + } + + var title = parentEl?.textNodes()?.last()?.text()?.trim() ?: "" + if (title.length > 0) { + title = " - " + title + } + + val chapter = SChapter.create() + chapter.setUrlWithoutDomain(urlElement.attr("href")) + chapter.name = urlElement.text() + volume + title + chapter.date_upload = element.select("span.right").first()?.text()?.let { parseChapterDate(it) } ?: 0 + return chapter + } + + private fun parseChapterDate(date: String): Long { + return if ("Today" in date) { + Calendar.getInstance().apply { + set(Calendar.HOUR_OF_DAY, 0) + set(Calendar.MINUTE, 0) + set(Calendar.SECOND, 0) + set(Calendar.MILLISECOND, 0) + }.timeInMillis + } else if ("Yesterday" in date) { + Calendar.getInstance().apply { + add(Calendar.DATE, -1) + set(Calendar.HOUR_OF_DAY, 0) + set(Calendar.MINUTE, 0) + set(Calendar.SECOND, 0) + set(Calendar.MILLISECOND, 0) + }.timeInMillis + } else { + try { + SimpleDateFormat("MMM d, yyyy", Locale.ENGLISH).parse(date).time + } catch (e: ParseException) { + 0L + } + } + } + + override fun pageListParse(document: Document): List { + val pages = mutableListOf() + document.select("select.wid60").first()?.getElementsByTag("option")?.forEach { + pages.add(Page(pages.size, it.attr("value"))) + } + pages.getOrNull(0)?.imageUrl = imageUrlParse(document) + return pages + } + + override fun imageUrlParse(document: Document) = document.getElementById("image").attr("src") + + private class Status : Filter.TriState("Completed") + private class Genre(name: String, val id: String = "genres[$name]") : Filter.TriState(name) + private class TextField(name: String, val key: String) : Filter.Text(name) + private class Type : Filter.Select("Type", arrayOf("Any", "Japanese Manga (read from right to left)", "Korean Manhwa (read from left to right)")) + private class OrderBy : Filter.Sort("Order by", + arrayOf("Series name", "Rating", "Views", "Total chapters", "Last chapter"), + Filter.Sort.Selection(2, false)) + private class GenreList(genres: List) : Filter.Group("Genres", genres) + + override fun getFilterList() = FilterList( + TextField("Author", "author"), + TextField("Artist", "artist"), + Type(), + Status(), + OrderBy(), + GenreList(getGenreList()) + ) + + // [...document.querySelectorAll("select[id^='genres'")].map((el,i) => `Genre("${el.nextSibling.nextSibling.textContent.trim()}", "${el.getAttribute('name')}")`).join(',\n') + // http://www.mangahere.co/advsearch.htm + private fun getGenreList() = listOf( + Genre("Action"), + Genre("Adventure"), + Genre("Comedy"), + Genre("Doujinshi"), + Genre("Drama"), + Genre("Ecchi"), + Genre("Fantasy"), + Genre("Gender Bender"), + Genre("Harem"), + Genre("Historical"), + Genre("Horror"), + Genre("Josei"), + Genre("Martial Arts"), + Genre("Mature"), + Genre("Mecha"), + Genre("Mystery"), + Genre("One Shot"), + Genre("Psychological"), + Genre("Romance"), + Genre("School Life"), + Genre("Sci-fi"), + Genre("Seinen"), + Genre("Shoujo"), + Genre("Shoujo Ai"), + Genre("Shounen"), + Genre("Shounen Ai"), + Genre("Slice of Life"), + Genre("Sports"), + Genre("Supernatural"), + Genre("Tragedy"), + Genre("Yaoi"), + Genre("Yuri") + ) + } \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Mangasee.kt b/app/src/main/java/eu/kanade/tachiyomi/source/online/english/Mangasee.kt similarity index 95% rename from app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Mangasee.kt rename to app/src/main/java/eu/kanade/tachiyomi/source/online/english/Mangasee.kt index 9f9f3cd575..f82bcf5c1f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Mangasee.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/online/english/Mangasee.kt @@ -1,243 +1,243 @@ -package eu.kanade.tachiyomi.data.source.online.english - -import eu.kanade.tachiyomi.data.network.POST -import eu.kanade.tachiyomi.data.source.model.* -import eu.kanade.tachiyomi.data.source.online.ParsedOnlineSource -import okhttp3.FormBody -import okhttp3.HttpUrl -import okhttp3.Request -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import java.text.SimpleDateFormat -import java.util.regex.Pattern - -class Mangasee : ParsedOnlineSource() { - - override val id: Long = 9 - - override val name = "Mangasee" - - override val baseUrl = "http://mangaseeonline.net" - - override val lang = "en" - - override val supportsLatest = true - - private val recentUpdatesPattern = Pattern.compile("(.*?)\\s(\\d+\\.?\\d*)\\s?(Completed)?") - - private val indexPattern = Pattern.compile("-index-(.*?)-") - - override fun popularMangaSelector() = "div.requested > div.row" - - override fun popularMangaRequest(page: Int): Request { - val (body, requestUrl) = convertQueryToPost(page, "$baseUrl/search/request.php?sortBy=popularity&sortOrder=descending") - return POST(requestUrl, headers, body.build()) - } - - override fun popularMangaFromElement(element: Element): SManga { - val manga = SManga.create() - element.select("a.resultLink").first().let { - manga.setUrlWithoutDomain(it.attr("href")) - manga.title = it.text() - } - return manga - } - - override fun popularMangaNextPageSelector() = "button.requestMore" - - override fun searchMangaSelector() = "div.requested > div.row" - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val url = HttpUrl.parse("$baseUrl/search/request.php").newBuilder() - if (!query.isEmpty()) url.addQueryParameter("keyword", query) - val genres = mutableListOf() - val genresNo = mutableListOf() - for (filter in if (filters.isEmpty()) getFilterList() else filters) { - when (filter) { - is Sort -> { - if (filter.state?.index != 0) - url.addQueryParameter("sortBy", if (filter.state?.index == 1) "dateUpdated" else "popularity") - if (filter.state?.ascending != true) - url.addQueryParameter("sortOrder", "descending") - } - is SelectField -> if (filter.state != 0) url.addQueryParameter(filter.key, filter.values[filter.state]) - is TextField -> if (!filter.state.isEmpty()) url.addQueryParameter(filter.key, filter.state) - is GenreList -> filter.state.forEach { genre -> - when (genre.state) { - Filter.TriState.STATE_INCLUDE -> genres.add(genre.name) - Filter.TriState.STATE_EXCLUDE -> genresNo.add(genre.name) - } - } - } - } - if (genres.isNotEmpty()) url.addQueryParameter("genre", genres.joinToString(",")) - if (genresNo.isNotEmpty()) url.addQueryParameter("genreNo", genresNo.joinToString(",")) - - val (body, requestUrl) = convertQueryToPost(page, url.toString()) - return POST(requestUrl, headers, body.build()) - } - - private fun convertQueryToPost(page: Int, url: String): Pair { - val url = HttpUrl.parse(url) - val body = FormBody.Builder().add("page", page.toString()) - for (i in 0..url.querySize() - 1) { - body.add(url.queryParameterName(i), url.queryParameterValue(i)) - } - val requestUrl = url.scheme() + "://" + url.host() + url.encodedPath() - return Pair(body, requestUrl) - } - - override fun searchMangaFromElement(element: Element): SManga { - val manga = SManga.create() - element.select("a.resultLink").first().let { - manga.setUrlWithoutDomain(it.attr("href")) - manga.title = it.text() - } - return manga - } - - override fun searchMangaNextPageSelector() = "button.requestMore" - - override fun mangaDetailsParse(document: Document): SManga { - val detailElement = document.select("div.well > div.row").first() - - val manga = SManga.create() - manga.author = detailElement.select("a[href^=/search/?author=]").first()?.text() - manga.genre = detailElement.select("span.details > div.row > div:has(b:contains(Genre(s))) > a").map { it.text() }.joinToString() - manga.description = detailElement.select("strong:contains(Description:) + div").first()?.text() - manga.status = detailElement.select("a[href^=/search/?status=]").first()?.text().orEmpty().let { parseStatus(it) } - manga.thumbnail_url = detailElement.select("div > img").first()?.absUrl("src") - return manga - } - - private fun parseStatus(status: String) = when { - status.contains("Ongoing (Scan)") -> SManga.ONGOING - status.contains("Complete (Scan)") -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - - override fun chapterListSelector() = "div.chapter-list > a" - - override fun chapterFromElement(element: Element): SChapter { - val urlElement = element.select("a").first() - - val chapter = SChapter.create() - chapter.setUrlWithoutDomain(urlElement.attr("href")) - chapter.name = element.select("span.chapterLabel").first().text()?.let { it } ?: "" - chapter.date_upload = element.select("time").first()?.attr("datetime")?.let { parseChapterDate(it) } ?: 0 - return chapter - } - - private fun parseChapterDate(dateAsString: String): Long { - return SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ").parse(dateAsString).time - } - - override fun pageListParse(document: Document): List { - val fullUrl = document.baseUri() - val url = fullUrl.substringBeforeLast('/') - - val pages = mutableListOf() - - val series = document.select("input.IndexName").first().attr("value") - val chapter = document.select("span.CurChapter").first().text() - var index = "" - - val m = indexPattern.matcher(fullUrl) - if (m.find()) { - val indexNumber = m.group(1) - index = "-index-$indexNumber" - } - - document.select("div.ContainerNav").first().select("select.PageSelect > option").forEach { - pages.add(Page(pages.size, "$url/$series-chapter-$chapter$index-page-${pages.size + 1}.html")) - } - pages.getOrNull(0)?.imageUrl = imageUrlParse(document) - return pages - } - - override fun imageUrlParse(document: Document): String = document.select("img.CurImage").attr("src") - - override fun latestUpdatesNextPageSelector() = "button.requestMore" - - override fun latestUpdatesSelector(): String = "a.latestSeries" - - override fun latestUpdatesRequest(page: Int): Request { - val url = "http://mangaseeonline.net/home/latest.request.php" - val (body, requestUrl) = convertQueryToPost(page, url) - return POST(requestUrl, headers, body.build()) - } - - override fun latestUpdatesFromElement(element: Element): SManga { - val manga = SManga.create() - element.select("a.latestSeries").first().let { - val chapterUrl = it.attr("href") - val indexOfMangaUrl = chapterUrl.indexOf("-chapter-") - val indexOfLastPath = chapterUrl.lastIndexOf("/") - val mangaUrl = chapterUrl.substring(indexOfLastPath, indexOfMangaUrl) - val defaultText = it.select("p.clamp2").text() - val m = recentUpdatesPattern.matcher(defaultText) - val title = if (m.matches()) m.group(1) else defaultText - manga.setUrlWithoutDomain("/manga" + mangaUrl) - manga.title = title - } - return manga - } - - private class Sort : Filter.Sort("Sort", arrayOf("Alphabetically", "Date updated", "Popularity"), Filter.Sort.Selection(2, false)) - private class Genre(name: String) : Filter.TriState(name) - private class TextField(name: String, val key: String) : Filter.Text(name) - private class SelectField(name: String, val key: String, values: Array, state: Int = 0) : Filter.Select(name, values, state) - private class GenreList(genres: List) : Filter.Group("Genres", genres) - - override fun getFilterList() = FilterList( - TextField("Years", "year"), - TextField("Author", "author"), - SelectField("Scan Status", "status", arrayOf("Any", "Complete", "Discontinued", "Hiatus", "Incomplete", "Ongoing")), - SelectField("Publish Status", "pstatus", arrayOf("Any", "Cancelled", "Complete", "Discontinued", "Hiatus", "Incomplete", "Ongoing", "Unfinished")), - SelectField("Type", "type", arrayOf("Any", "Doujinshi", "Manga", "Manhua", "Manhwa", "OEL", "One-shot")), - Sort(), - GenreList(getGenreList()) - ) - - // [...document.querySelectorAll("label.triStateCheckBox input")].map(el => `Filter("${el.getAttribute('name')}", "${el.nextSibling.textContent.trim()}")`).join(',\n') - // http://mangasee.co/advanced-search/ - private fun getGenreList() = listOf( - Genre("Action"), - Genre("Adult"), - Genre("Adventure"), - Genre("Comedy"), - Genre("Doujinshi"), - Genre("Drama"), - Genre("Ecchi"), - Genre("Fantasy"), - Genre("Gender Bender"), - Genre("Harem"), - Genre("Hentai"), - Genre("Historical"), - Genre("Horror"), - Genre("Josei"), - Genre("Lolicon"), - Genre("Martial Arts"), - Genre("Mature"), - Genre("Mecha"), - Genre("Mystery"), - Genre("Psychological"), - Genre("Romance"), - Genre("School Life"), - Genre("Sci-fi"), - Genre("Seinen"), - Genre("Shotacon"), - Genre("Shoujo"), - Genre("Shoujo Ai"), - Genre("Shounen"), - Genre("Shounen Ai"), - Genre("Slice of Life"), - Genre("Smut"), - Genre("Sports"), - Genre("Supernatural"), - Genre("Tragedy"), - Genre("Yaoi"), - Genre("Yuri") - ) - +package eu.kanade.tachiyomi.source.online.english + +import eu.kanade.tachiyomi.network.POST +import eu.kanade.tachiyomi.source.model.* +import eu.kanade.tachiyomi.source.online.ParsedOnlineSource +import okhttp3.FormBody +import okhttp3.HttpUrl +import okhttp3.Request +import org.jsoup.nodes.Document +import org.jsoup.nodes.Element +import java.text.SimpleDateFormat +import java.util.regex.Pattern + +class Mangasee : ParsedOnlineSource() { + + override val id: Long = 9 + + override val name = "Mangasee" + + override val baseUrl = "http://mangaseeonline.net" + + override val lang = "en" + + override val supportsLatest = true + + private val recentUpdatesPattern = Pattern.compile("(.*?)\\s(\\d+\\.?\\d*)\\s?(Completed)?") + + private val indexPattern = Pattern.compile("-index-(.*?)-") + + override fun popularMangaSelector() = "div.requested > div.row" + + override fun popularMangaRequest(page: Int): Request { + val (body, requestUrl) = convertQueryToPost(page, "$baseUrl/search/request.php?sortBy=popularity&sortOrder=descending") + return POST(requestUrl, headers, body.build()) + } + + override fun popularMangaFromElement(element: Element): SManga { + val manga = SManga.create() + element.select("a.resultLink").first().let { + manga.setUrlWithoutDomain(it.attr("href")) + manga.title = it.text() + } + return manga + } + + override fun popularMangaNextPageSelector() = "button.requestMore" + + override fun searchMangaSelector() = "div.requested > div.row" + + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { + val url = HttpUrl.parse("$baseUrl/search/request.php").newBuilder() + if (!query.isEmpty()) url.addQueryParameter("keyword", query) + val genres = mutableListOf() + val genresNo = mutableListOf() + for (filter in if (filters.isEmpty()) getFilterList() else filters) { + when (filter) { + is Sort -> { + if (filter.state?.index != 0) + url.addQueryParameter("sortBy", if (filter.state?.index == 1) "dateUpdated" else "popularity") + if (filter.state?.ascending != true) + url.addQueryParameter("sortOrder", "descending") + } + is SelectField -> if (filter.state != 0) url.addQueryParameter(filter.key, filter.values[filter.state]) + is TextField -> if (!filter.state.isEmpty()) url.addQueryParameter(filter.key, filter.state) + is GenreList -> filter.state.forEach { genre -> + when (genre.state) { + Filter.TriState.STATE_INCLUDE -> genres.add(genre.name) + Filter.TriState.STATE_EXCLUDE -> genresNo.add(genre.name) + } + } + } + } + if (genres.isNotEmpty()) url.addQueryParameter("genre", genres.joinToString(",")) + if (genresNo.isNotEmpty()) url.addQueryParameter("genreNo", genresNo.joinToString(",")) + + val (body, requestUrl) = convertQueryToPost(page, url.toString()) + return POST(requestUrl, headers, body.build()) + } + + private fun convertQueryToPost(page: Int, url: String): Pair { + val url = HttpUrl.parse(url) + val body = FormBody.Builder().add("page", page.toString()) + for (i in 0..url.querySize() - 1) { + body.add(url.queryParameterName(i), url.queryParameterValue(i)) + } + val requestUrl = url.scheme() + "://" + url.host() + url.encodedPath() + return Pair(body, requestUrl) + } + + override fun searchMangaFromElement(element: Element): SManga { + val manga = SManga.create() + element.select("a.resultLink").first().let { + manga.setUrlWithoutDomain(it.attr("href")) + manga.title = it.text() + } + return manga + } + + override fun searchMangaNextPageSelector() = "button.requestMore" + + override fun mangaDetailsParse(document: Document): SManga { + val detailElement = document.select("div.well > div.row").first() + + val manga = SManga.create() + manga.author = detailElement.select("a[href^=/search/?author=]").first()?.text() + manga.genre = detailElement.select("span.details > div.row > div:has(b:contains(Genre(s))) > a").map { it.text() }.joinToString() + manga.description = detailElement.select("strong:contains(Description:) + div").first()?.text() + manga.status = detailElement.select("a[href^=/search/?status=]").first()?.text().orEmpty().let { parseStatus(it) } + manga.thumbnail_url = detailElement.select("div > img").first()?.absUrl("src") + return manga + } + + private fun parseStatus(status: String) = when { + status.contains("Ongoing (Scan)") -> SManga.ONGOING + status.contains("Complete (Scan)") -> SManga.COMPLETED + else -> SManga.UNKNOWN + } + + override fun chapterListSelector() = "div.chapter-list > a" + + override fun chapterFromElement(element: Element): SChapter { + val urlElement = element.select("a").first() + + val chapter = SChapter.create() + chapter.setUrlWithoutDomain(urlElement.attr("href")) + chapter.name = element.select("span.chapterLabel").first().text()?.let { it } ?: "" + chapter.date_upload = element.select("time").first()?.attr("datetime")?.let { parseChapterDate(it) } ?: 0 + return chapter + } + + private fun parseChapterDate(dateAsString: String): Long { + return SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ").parse(dateAsString).time + } + + override fun pageListParse(document: Document): List { + val fullUrl = document.baseUri() + val url = fullUrl.substringBeforeLast('/') + + val pages = mutableListOf() + + val series = document.select("input.IndexName").first().attr("value") + val chapter = document.select("span.CurChapter").first().text() + var index = "" + + val m = indexPattern.matcher(fullUrl) + if (m.find()) { + val indexNumber = m.group(1) + index = "-index-$indexNumber" + } + + document.select("div.ContainerNav").first().select("select.PageSelect > option").forEach { + pages.add(Page(pages.size, "$url/$series-chapter-$chapter$index-page-${pages.size + 1}.html")) + } + pages.getOrNull(0)?.imageUrl = imageUrlParse(document) + return pages + } + + override fun imageUrlParse(document: Document): String = document.select("img.CurImage").attr("src") + + override fun latestUpdatesNextPageSelector() = "button.requestMore" + + override fun latestUpdatesSelector(): String = "a.latestSeries" + + override fun latestUpdatesRequest(page: Int): Request { + val url = "http://mangaseeonline.net/home/latest.request.php" + val (body, requestUrl) = convertQueryToPost(page, url) + return POST(requestUrl, headers, body.build()) + } + + override fun latestUpdatesFromElement(element: Element): SManga { + val manga = SManga.create() + element.select("a.latestSeries").first().let { + val chapterUrl = it.attr("href") + val indexOfMangaUrl = chapterUrl.indexOf("-chapter-") + val indexOfLastPath = chapterUrl.lastIndexOf("/") + val mangaUrl = chapterUrl.substring(indexOfLastPath, indexOfMangaUrl) + val defaultText = it.select("p.clamp2").text() + val m = recentUpdatesPattern.matcher(defaultText) + val title = if (m.matches()) m.group(1) else defaultText + manga.setUrlWithoutDomain("/manga" + mangaUrl) + manga.title = title + } + return manga + } + + private class Sort : Filter.Sort("Sort", arrayOf("Alphabetically", "Date updated", "Popularity"), Filter.Sort.Selection(2, false)) + private class Genre(name: String) : Filter.TriState(name) + private class TextField(name: String, val key: String) : Filter.Text(name) + private class SelectField(name: String, val key: String, values: Array, state: Int = 0) : Filter.Select(name, values, state) + private class GenreList(genres: List) : Filter.Group("Genres", genres) + + override fun getFilterList() = FilterList( + TextField("Years", "year"), + TextField("Author", "author"), + SelectField("Scan Status", "status", arrayOf("Any", "Complete", "Discontinued", "Hiatus", "Incomplete", "Ongoing")), + SelectField("Publish Status", "pstatus", arrayOf("Any", "Cancelled", "Complete", "Discontinued", "Hiatus", "Incomplete", "Ongoing", "Unfinished")), + SelectField("Type", "type", arrayOf("Any", "Doujinshi", "Manga", "Manhua", "Manhwa", "OEL", "One-shot")), + Sort(), + GenreList(getGenreList()) + ) + + // [...document.querySelectorAll("label.triStateCheckBox input")].map(el => `Filter("${el.getAttribute('name')}", "${el.nextSibling.textContent.trim()}")`).join(',\n') + // http://mangasee.co/advanced-search/ + private fun getGenreList() = listOf( + Genre("Action"), + Genre("Adult"), + Genre("Adventure"), + Genre("Comedy"), + Genre("Doujinshi"), + Genre("Drama"), + Genre("Ecchi"), + Genre("Fantasy"), + Genre("Gender Bender"), + Genre("Harem"), + Genre("Hentai"), + Genre("Historical"), + Genre("Horror"), + Genre("Josei"), + Genre("Lolicon"), + Genre("Martial Arts"), + Genre("Mature"), + Genre("Mecha"), + Genre("Mystery"), + Genre("Psychological"), + Genre("Romance"), + Genre("School Life"), + Genre("Sci-fi"), + Genre("Seinen"), + Genre("Shotacon"), + Genre("Shoujo"), + Genre("Shoujo Ai"), + Genre("Shounen"), + Genre("Shounen Ai"), + Genre("Slice of Life"), + Genre("Smut"), + Genre("Sports"), + Genre("Supernatural"), + Genre("Tragedy"), + Genre("Yaoi"), + Genre("Yuri") + ) + } \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Readmangatoday.kt b/app/src/main/java/eu/kanade/tachiyomi/source/online/english/Readmangatoday.kt similarity index 94% rename from app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Readmangatoday.kt rename to app/src/main/java/eu/kanade/tachiyomi/source/online/english/Readmangatoday.kt index daef7561de..06f2d8b999 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Readmangatoday.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/online/english/Readmangatoday.kt @@ -1,219 +1,219 @@ -package eu.kanade.tachiyomi.data.source.online.english - -import eu.kanade.tachiyomi.data.network.GET -import eu.kanade.tachiyomi.data.network.POST -import eu.kanade.tachiyomi.data.source.model.* -import eu.kanade.tachiyomi.data.source.online.ParsedOnlineSource -import okhttp3.Headers -import okhttp3.OkHttpClient -import okhttp3.Request -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import java.util.* - -class Readmangatoday : ParsedOnlineSource() { - - override val id: Long = 8 - - override val name = "ReadMangaToday" - - override val baseUrl = "http://www.readmanga.today" - - override val lang = "en" - - override val supportsLatest = true - - override val client: OkHttpClient get() = network.cloudflareClient - - /** - * Search only returns data with this set - */ - override fun headersBuilder() = Headers.Builder().apply { - add("User-Agent", "Mozilla/5.0 (Windows NT 6.3; WOW64)") - add("X-Requested-With", "XMLHttpRequest") - } - - override fun popularMangaRequest(page: Int): Request { - return GET("$baseUrl/hot-manga/$page", headers) - } - - override fun latestUpdatesRequest(page: Int): Request { - return GET("$baseUrl/latest-releases/$page", headers) - } - - override fun popularMangaSelector() = "div.hot-manga > div.style-list > div.box" - - override fun latestUpdatesSelector() = "div.hot-manga > div.style-grid > div.box" - - override fun popularMangaFromElement(element: Element): SManga { - val manga = SManga.create() - element.select("div.title > h2 > a").first().let { - manga.setUrlWithoutDomain(it.attr("href")) - manga.title = it.attr("title") - } - return manga - } - - override fun latestUpdatesFromElement(element: Element): SManga { - return popularMangaFromElement(element) - } - - override fun popularMangaNextPageSelector() = "div.hot-manga > ul.pagination > li > a:contains(»)" - - override fun latestUpdatesNextPageSelector() = "div.hot-manga > ul.pagination > li > a:contains(»)" - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val builder = okhttp3.FormBody.Builder() - builder.add("manga-name", query) - (if (filters.isEmpty()) getFilterList() else filters).forEach { filter -> - when (filter) { - is TextField -> builder.add(filter.key, filter.state) - is Type -> builder.add("type", arrayOf("all", "japanese", "korean", "chinese")[filter.state]) - is Status -> builder.add("status", arrayOf("both", "completed", "ongoing")[filter.state]) - is GenreList -> filter.state.forEach { genre -> - when (genre.state) { - Filter.TriState.STATE_INCLUDE -> builder.add("include[]", genre.id.toString()) - Filter.TriState.STATE_EXCLUDE -> builder.add("exclude[]", genre.id.toString()) - } - } - } - } - return POST("$baseUrl/service/advanced_search", headers, builder.build()) - } - - override fun searchMangaSelector() = "div.style-list > div.box" - - override fun searchMangaFromElement(element: Element): SManga { - val manga = SManga.create() - element.select("div.title > h2 > a").first().let { - manga.setUrlWithoutDomain(it.attr("href")) - manga.title = it.attr("title") - } - return manga - } - - override fun searchMangaNextPageSelector() = "div.next-page > a.next" - - override fun mangaDetailsParse(document: Document): SManga { - val detailElement = document.select("div.movie-meta").first() - - val manga = SManga.create() - manga.author = document.select("ul.cast-list li.director > ul a").first()?.text() - manga.artist = document.select("ul.cast-list li:not(.director) > ul a").first()?.text() - manga.genre = detailElement.select("dl.dl-horizontal > dd:eq(5)").first()?.text() - manga.description = detailElement.select("li.movie-detail").first()?.text() - manga.status = detailElement.select("dl.dl-horizontal > dd:eq(3)").first()?.text().orEmpty().let { parseStatus(it) } - manga.thumbnail_url = detailElement.select("img.img-responsive").first()?.attr("src") - return manga - } - - private fun parseStatus(status: String) = when { - status.contains("Ongoing") -> SManga.ONGOING - status.contains("Completed") -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - - override fun chapterListSelector() = "ul.chp_lst > li" - - override fun chapterFromElement(element: Element): SChapter { - val urlElement = element.select("a").first() - - val chapter = SChapter.create() - chapter.setUrlWithoutDomain(urlElement.attr("href")) - chapter.name = urlElement.select("span.val").text() - chapter.date_upload = element.select("span.dte").first()?.text()?.let { parseChapterDate(it) } ?: 0 - return chapter - } - - private fun parseChapterDate(date: String): Long { - val dateWords: List = date.split(" ") - - if (dateWords.size == 3) { - val timeAgo = Integer.parseInt(dateWords[0]) - val date: Calendar = Calendar.getInstance() - - if (dateWords[1].contains("Minute")) { - date.add(Calendar.MINUTE, -timeAgo) - } else if (dateWords[1].contains("Hour")) { - date.add(Calendar.HOUR_OF_DAY, -timeAgo) - } else if (dateWords[1].contains("Day")) { - date.add(Calendar.DAY_OF_YEAR, -timeAgo) - } else if (dateWords[1].contains("Week")) { - date.add(Calendar.WEEK_OF_YEAR, -timeAgo) - } else if (dateWords[1].contains("Month")) { - date.add(Calendar.MONTH, -timeAgo) - } else if (dateWords[1].contains("Year")) { - date.add(Calendar.YEAR, -timeAgo) - } - - return date.timeInMillis - } - - return 0L - } - - override fun pageListParse(document: Document): List { - val pages = mutableListOf() - document.select("ul.list-switcher-2 > li > select.jump-menu").first().getElementsByTag("option").forEach { - pages.add(Page(pages.size, it.attr("value"))) - } - pages.getOrNull(0)?.imageUrl = imageUrlParse(document) - return pages - } - - override fun imageUrlParse(document: Document) = document.select("img.img-responsive-2").first().attr("src") - - private class Status : Filter.TriState("Completed") - private class Genre(name: String, val id: Int) : Filter.TriState(name) - private class TextField(name: String, val key: String) : Filter.Text(name) - private class Type : Filter.Select("Type", arrayOf("All", "Japanese Manga", "Korean Manhwa", "Chinese Manhua")) - private class GenreList(genres: List) : Filter.Group("Genres", genres) - - override fun getFilterList() = FilterList( - TextField("Author", "author-name"), - TextField("Artist", "artist-name"), - Type(), - Status(), - GenreList(getGenreList()) - ) - - // [...document.querySelectorAll("ul.manga-cat span")].map(el => `Genre("${el.nextSibling.textContent.trim()}", ${el.getAttribute('data-id')})`).join(',\n') - // http://www.readmanga.today/advanced-search - private fun getGenreList() = listOf( - Genre("Action", 2), - Genre("Adventure", 4), - Genre("Comedy", 5), - Genre("Doujinshi", 6), - Genre("Drama", 7), - Genre("Ecchi", 8), - Genre("Fantasy", 9), - Genre("Gender Bender", 10), - Genre("Harem", 11), - Genre("Historical", 12), - Genre("Horror", 13), - Genre("Josei", 14), - Genre("Lolicon", 15), - Genre("Martial Arts", 16), - Genre("Mature", 17), - Genre("Mecha", 18), - Genre("Mystery", 19), - Genre("One shot", 20), - Genre("Psychological", 21), - Genre("Romance", 22), - Genre("School Life", 23), - Genre("Sci-fi", 24), - Genre("Seinen", 25), - Genre("Shotacon", 26), - Genre("Shoujo", 27), - Genre("Shoujo Ai", 28), - Genre("Shounen", 29), - Genre("Shounen Ai", 30), - Genre("Slice of Life", 31), - Genre("Smut", 32), - Genre("Sports", 33), - Genre("Supernatural", 34), - Genre("Tragedy", 35), - Genre("Yaoi", 36), - Genre("Yuri", 37) - ) +package eu.kanade.tachiyomi.source.online.english + +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.network.POST +import eu.kanade.tachiyomi.source.model.* +import eu.kanade.tachiyomi.source.online.ParsedOnlineSource +import okhttp3.Headers +import okhttp3.OkHttpClient +import okhttp3.Request +import org.jsoup.nodes.Document +import org.jsoup.nodes.Element +import java.util.* + +class Readmangatoday : ParsedOnlineSource() { + + override val id: Long = 8 + + override val name = "ReadMangaToday" + + override val baseUrl = "http://www.readmanga.today" + + override val lang = "en" + + override val supportsLatest = true + + override val client: OkHttpClient get() = network.cloudflareClient + + /** + * Search only returns data with this set + */ + override fun headersBuilder() = Headers.Builder().apply { + add("User-Agent", "Mozilla/5.0 (Windows NT 6.3; WOW64)") + add("X-Requested-With", "XMLHttpRequest") + } + + override fun popularMangaRequest(page: Int): Request { + return GET("$baseUrl/hot-manga/$page", headers) + } + + override fun latestUpdatesRequest(page: Int): Request { + return GET("$baseUrl/latest-releases/$page", headers) + } + + override fun popularMangaSelector() = "div.hot-manga > div.style-list > div.box" + + override fun latestUpdatesSelector() = "div.hot-manga > div.style-grid > div.box" + + override fun popularMangaFromElement(element: Element): SManga { + val manga = SManga.create() + element.select("div.title > h2 > a").first().let { + manga.setUrlWithoutDomain(it.attr("href")) + manga.title = it.attr("title") + } + return manga + } + + override fun latestUpdatesFromElement(element: Element): SManga { + return popularMangaFromElement(element) + } + + override fun popularMangaNextPageSelector() = "div.hot-manga > ul.pagination > li > a:contains(»)" + + override fun latestUpdatesNextPageSelector() = "div.hot-manga > ul.pagination > li > a:contains(»)" + + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { + val builder = okhttp3.FormBody.Builder() + builder.add("manga-name", query) + (if (filters.isEmpty()) getFilterList() else filters).forEach { filter -> + when (filter) { + is TextField -> builder.add(filter.key, filter.state) + is Type -> builder.add("type", arrayOf("all", "japanese", "korean", "chinese")[filter.state]) + is Status -> builder.add("status", arrayOf("both", "completed", "ongoing")[filter.state]) + is GenreList -> filter.state.forEach { genre -> + when (genre.state) { + Filter.TriState.STATE_INCLUDE -> builder.add("include[]", genre.id.toString()) + Filter.TriState.STATE_EXCLUDE -> builder.add("exclude[]", genre.id.toString()) + } + } + } + } + return POST("$baseUrl/service/advanced_search", headers, builder.build()) + } + + override fun searchMangaSelector() = "div.style-list > div.box" + + override fun searchMangaFromElement(element: Element): SManga { + val manga = SManga.create() + element.select("div.title > h2 > a").first().let { + manga.setUrlWithoutDomain(it.attr("href")) + manga.title = it.attr("title") + } + return manga + } + + override fun searchMangaNextPageSelector() = "div.next-page > a.next" + + override fun mangaDetailsParse(document: Document): SManga { + val detailElement = document.select("div.movie-meta").first() + + val manga = SManga.create() + manga.author = document.select("ul.cast-list li.director > ul a").first()?.text() + manga.artist = document.select("ul.cast-list li:not(.director) > ul a").first()?.text() + manga.genre = detailElement.select("dl.dl-horizontal > dd:eq(5)").first()?.text() + manga.description = detailElement.select("li.movie-detail").first()?.text() + manga.status = detailElement.select("dl.dl-horizontal > dd:eq(3)").first()?.text().orEmpty().let { parseStatus(it) } + manga.thumbnail_url = detailElement.select("img.img-responsive").first()?.attr("src") + return manga + } + + private fun parseStatus(status: String) = when { + status.contains("Ongoing") -> SManga.ONGOING + status.contains("Completed") -> SManga.COMPLETED + else -> SManga.UNKNOWN + } + + override fun chapterListSelector() = "ul.chp_lst > li" + + override fun chapterFromElement(element: Element): SChapter { + val urlElement = element.select("a").first() + + val chapter = SChapter.create() + chapter.setUrlWithoutDomain(urlElement.attr("href")) + chapter.name = urlElement.select("span.val").text() + chapter.date_upload = element.select("span.dte").first()?.text()?.let { parseChapterDate(it) } ?: 0 + return chapter + } + + private fun parseChapterDate(date: String): Long { + val dateWords: List = date.split(" ") + + if (dateWords.size == 3) { + val timeAgo = Integer.parseInt(dateWords[0]) + val date: Calendar = Calendar.getInstance() + + if (dateWords[1].contains("Minute")) { + date.add(Calendar.MINUTE, -timeAgo) + } else if (dateWords[1].contains("Hour")) { + date.add(Calendar.HOUR_OF_DAY, -timeAgo) + } else if (dateWords[1].contains("Day")) { + date.add(Calendar.DAY_OF_YEAR, -timeAgo) + } else if (dateWords[1].contains("Week")) { + date.add(Calendar.WEEK_OF_YEAR, -timeAgo) + } else if (dateWords[1].contains("Month")) { + date.add(Calendar.MONTH, -timeAgo) + } else if (dateWords[1].contains("Year")) { + date.add(Calendar.YEAR, -timeAgo) + } + + return date.timeInMillis + } + + return 0L + } + + override fun pageListParse(document: Document): List { + val pages = mutableListOf() + document.select("ul.list-switcher-2 > li > select.jump-menu").first().getElementsByTag("option").forEach { + pages.add(Page(pages.size, it.attr("value"))) + } + pages.getOrNull(0)?.imageUrl = imageUrlParse(document) + return pages + } + + override fun imageUrlParse(document: Document) = document.select("img.img-responsive-2").first().attr("src") + + private class Status : Filter.TriState("Completed") + private class Genre(name: String, val id: Int) : Filter.TriState(name) + private class TextField(name: String, val key: String) : Filter.Text(name) + private class Type : Filter.Select("Type", arrayOf("All", "Japanese Manga", "Korean Manhwa", "Chinese Manhua")) + private class GenreList(genres: List) : Filter.Group("Genres", genres) + + override fun getFilterList() = FilterList( + TextField("Author", "author-name"), + TextField("Artist", "artist-name"), + Type(), + Status(), + GenreList(getGenreList()) + ) + + // [...document.querySelectorAll("ul.manga-cat span")].map(el => `Genre("${el.nextSibling.textContent.trim()}", ${el.getAttribute('data-id')})`).join(',\n') + // http://www.readmanga.today/advanced-search + private fun getGenreList() = listOf( + Genre("Action", 2), + Genre("Adventure", 4), + Genre("Comedy", 5), + Genre("Doujinshi", 6), + Genre("Drama", 7), + Genre("Ecchi", 8), + Genre("Fantasy", 9), + Genre("Gender Bender", 10), + Genre("Harem", 11), + Genre("Historical", 12), + Genre("Horror", 13), + Genre("Josei", 14), + Genre("Lolicon", 15), + Genre("Martial Arts", 16), + Genre("Mature", 17), + Genre("Mecha", 18), + Genre("Mystery", 19), + Genre("One shot", 20), + Genre("Psychological", 21), + Genre("Romance", 22), + Genre("School Life", 23), + Genre("Sci-fi", 24), + Genre("Seinen", 25), + Genre("Shotacon", 26), + Genre("Shoujo", 27), + Genre("Shoujo Ai", 28), + Genre("Shounen", 29), + Genre("Shounen Ai", 30), + Genre("Slice of Life", 31), + Genre("Smut", 32), + Genre("Sports", 33), + Genre("Supernatural", 34), + Genre("Tragedy", 35), + Genre("Yaoi", 36), + Genre("Yuri", 37) + ) } \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/german/WieManga.kt b/app/src/main/java/eu/kanade/tachiyomi/source/online/german/WieManga.kt similarity index 89% rename from app/src/main/java/eu/kanade/tachiyomi/data/source/online/german/WieManga.kt rename to app/src/main/java/eu/kanade/tachiyomi/source/online/german/WieManga.kt index 3e0ae6c62b..e6699d538c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/german/WieManga.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/online/german/WieManga.kt @@ -1,122 +1,122 @@ -package eu.kanade.tachiyomi.data.source.online.german - -import eu.kanade.tachiyomi.data.network.GET -import eu.kanade.tachiyomi.data.source.model.FilterList -import eu.kanade.tachiyomi.data.source.model.Page -import eu.kanade.tachiyomi.data.source.model.SChapter -import eu.kanade.tachiyomi.data.source.model.SManga -import eu.kanade.tachiyomi.data.source.online.ParsedOnlineSource -import okhttp3.Request -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import java.text.SimpleDateFormat - -class WieManga : ParsedOnlineSource() { - - override val id: Long = 10 - - override val name = "Wie Manga!" - - override val baseUrl = "http://www.wiemanga.com" - - override val lang = "de" - - override val supportsLatest = true - - override fun popularMangaSelector() = ".booklist td > div" - - override fun latestUpdatesSelector() = ".booklist td > div" - - override fun popularMangaRequest(page: Int): Request { - return GET("$baseUrl/list/Hot-Book/", headers) - } - - override fun latestUpdatesRequest(page: Int): Request { - return GET("$baseUrl/list/New-Update/", headers) - } - - override fun popularMangaFromElement(element: Element): SManga { - val image = element.select("dt img") - val title = element.select("dd a:first-child") - - val manga = SManga.create() - manga.setUrlWithoutDomain(title.attr("href")) - manga.title = title.text() - manga.thumbnail_url = image.attr("src") - return manga - } - - override fun latestUpdatesFromElement(element: Element): SManga { - return popularMangaFromElement(element) - } - - override fun popularMangaNextPageSelector() = null - - override fun latestUpdatesNextPageSelector() = null - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - return GET("$baseUrl/search/?wd=$query", headers) - } - - override fun searchMangaSelector() = ".searchresult td > div" - - override fun searchMangaFromElement(element: Element): SManga { - val image = element.select(".resultimg img") - val title = element.select(".resultbookname") - - val manga = SManga.create() - manga.setUrlWithoutDomain(title.attr("href")) - manga.title = title.text() - manga.thumbnail_url = image.attr("src") - return manga - } - - override fun searchMangaNextPageSelector() = ".pagetor a.l" - - override fun mangaDetailsParse(document: Document): SManga { - val imageElement = document.select(".bookmessgae tr > td:nth-child(1)").first() - val infoElement = document.select(".bookmessgae tr > td:nth-child(2)").first() - - val manga = SManga.create() - manga.author = infoElement.select("dd:nth-of-type(2) a").first()?.text() - manga.artist = infoElement.select("dd:nth-of-type(3) a").first()?.text() - manga.description = infoElement.select("dl > dt:last-child").first()?.text()?.replaceFirst("Beschreibung", "") - manga.thumbnail_url = imageElement.select("img").first()?.attr("src") - - if (manga.author == "RSS") - manga.author = null - - if (manga.artist == "RSS") - manga.artist = null - return manga - } - - override fun chapterListSelector() = ".chapterlist tr:not(:first-child)" - - override fun chapterFromElement(element: Element): SChapter { - val urlElement = element.select(".col1 a").first() - val dateElement = element.select(".col3 a").first() - - val chapter = SChapter.create() - chapter.setUrlWithoutDomain(urlElement.attr("href")) - chapter.name = urlElement.text() - chapter.date_upload = dateElement?.text()?.let { parseChapterDate(it) } ?: 0 - return chapter - } - - private fun parseChapterDate(date: String): Long { - return SimpleDateFormat("yyyy-MM-dd hh:mm:ss").parse(date).time - } - - override fun pageListParse(document: Document): List { - val pages = mutableListOf() - - document.select("select#page").first().select("option").forEach { - pages.add(Page(pages.size, it.attr("value"))) - } - return pages - } - - override fun imageUrlParse(document: Document) = document.select("img#comicpic").first().attr("src") - +package eu.kanade.tachiyomi.source.online.german + +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.source.model.FilterList +import eu.kanade.tachiyomi.source.model.Page +import eu.kanade.tachiyomi.source.model.SChapter +import eu.kanade.tachiyomi.source.model.SManga +import eu.kanade.tachiyomi.source.online.ParsedOnlineSource +import okhttp3.Request +import org.jsoup.nodes.Document +import org.jsoup.nodes.Element +import java.text.SimpleDateFormat + +class WieManga : ParsedOnlineSource() { + + override val id: Long = 10 + + override val name = "Wie Manga!" + + override val baseUrl = "http://www.wiemanga.com" + + override val lang = "de" + + override val supportsLatest = true + + override fun popularMangaSelector() = ".booklist td > div" + + override fun latestUpdatesSelector() = ".booklist td > div" + + override fun popularMangaRequest(page: Int): Request { + return GET("$baseUrl/list/Hot-Book/", headers) + } + + override fun latestUpdatesRequest(page: Int): Request { + return GET("$baseUrl/list/New-Update/", headers) + } + + override fun popularMangaFromElement(element: Element): SManga { + val image = element.select("dt img") + val title = element.select("dd a:first-child") + + val manga = SManga.create() + manga.setUrlWithoutDomain(title.attr("href")) + manga.title = title.text() + manga.thumbnail_url = image.attr("src") + return manga + } + + override fun latestUpdatesFromElement(element: Element): SManga { + return popularMangaFromElement(element) + } + + override fun popularMangaNextPageSelector() = null + + override fun latestUpdatesNextPageSelector() = null + + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { + return GET("$baseUrl/search/?wd=$query", headers) + } + + override fun searchMangaSelector() = ".searchresult td > div" + + override fun searchMangaFromElement(element: Element): SManga { + val image = element.select(".resultimg img") + val title = element.select(".resultbookname") + + val manga = SManga.create() + manga.setUrlWithoutDomain(title.attr("href")) + manga.title = title.text() + manga.thumbnail_url = image.attr("src") + return manga + } + + override fun searchMangaNextPageSelector() = ".pagetor a.l" + + override fun mangaDetailsParse(document: Document): SManga { + val imageElement = document.select(".bookmessgae tr > td:nth-child(1)").first() + val infoElement = document.select(".bookmessgae tr > td:nth-child(2)").first() + + val manga = SManga.create() + manga.author = infoElement.select("dd:nth-of-type(2) a").first()?.text() + manga.artist = infoElement.select("dd:nth-of-type(3) a").first()?.text() + manga.description = infoElement.select("dl > dt:last-child").first()?.text()?.replaceFirst("Beschreibung", "") + manga.thumbnail_url = imageElement.select("img").first()?.attr("src") + + if (manga.author == "RSS") + manga.author = null + + if (manga.artist == "RSS") + manga.artist = null + return manga + } + + override fun chapterListSelector() = ".chapterlist tr:not(:first-child)" + + override fun chapterFromElement(element: Element): SChapter { + val urlElement = element.select(".col1 a").first() + val dateElement = element.select(".col3 a").first() + + val chapter = SChapter.create() + chapter.setUrlWithoutDomain(urlElement.attr("href")) + chapter.name = urlElement.text() + chapter.date_upload = dateElement?.text()?.let { parseChapterDate(it) } ?: 0 + return chapter + } + + private fun parseChapterDate(date: String): Long { + return SimpleDateFormat("yyyy-MM-dd hh:mm:ss").parse(date).time + } + + override fun pageListParse(document: Document): List { + val pages = mutableListOf() + + document.select("select#page").first().select("option").forEach { + pages.add(Page(pages.size, it.attr("value"))) + } + return pages + } + + override fun imageUrlParse(document: Document) = document.select("img#comicpic").first().attr("src") + } \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/russian/Mangachan.kt b/app/src/main/java/eu/kanade/tachiyomi/source/online/russian/Mangachan.kt similarity index 95% rename from app/src/main/java/eu/kanade/tachiyomi/data/source/online/russian/Mangachan.kt rename to app/src/main/java/eu/kanade/tachiyomi/source/online/russian/Mangachan.kt index 468e97ef33..0e2fc993a6 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/russian/Mangachan.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/online/russian/Mangachan.kt @@ -1,230 +1,230 @@ -package eu.kanade.tachiyomi.data.source.online.russian - -import eu.kanade.tachiyomi.data.network.GET -import eu.kanade.tachiyomi.data.source.model.* -import eu.kanade.tachiyomi.data.source.online.ParsedOnlineSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import java.text.SimpleDateFormat -import java.util.* - -class Mangachan : ParsedOnlineSource() { - - override val id: Long = 7 - - override val name = "Mangachan" - - override val baseUrl = "http://mangachan.me" - - override val lang = "ru" - - override val supportsLatest = true - - override fun popularMangaRequest(page: Int): Request { - return GET("$baseUrl/mostfavorites?offset=${20 * (page - 1)}", headers) - } - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val url = if (query.isNotEmpty()) { - "$baseUrl/?do=search&subaction=search&story=$query" - } else { - val filt = filters.filterIsInstance().filter { !it.isIgnored() } - if (filt.isNotEmpty()) { - var genres = "" - filt.forEach { genres += (if (it.isExcluded()) "-" else "") + it.id + '+' } - "$baseUrl/tags/${genres.dropLast(1)}" - } else { - "$baseUrl/?do=search&subaction=search&story=$query" - } - } - return GET(url, headers) - } - - override fun latestUpdatesRequest(page: Int): Request { - return GET("$baseUrl/newestch?page=$page") - } - - override fun popularMangaSelector() = "div.content_row" - - override fun latestUpdatesSelector() = "ul.area_rightNews li" - - override fun searchMangaSelector() = popularMangaSelector() - - override fun popularMangaFromElement(element: Element): SManga { - val manga = SManga.create() - element.select("h2 > a").first().let { - manga.setUrlWithoutDomain(it.attr("href")) - manga.title = it.text() - } - return manga - } - - override fun latestUpdatesFromElement(element: Element): SManga { - val manga = SManga.create() - element.select("a:nth-child(1)").first().let { - manga.setUrlWithoutDomain(it.attr("href")) - manga.title = it.text() - } - return manga - } - - override fun searchMangaFromElement(element: Element): SManga { - return popularMangaFromElement(element) - } - - override fun popularMangaNextPageSelector() = "a:contains(Вперед)" - - override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector() - - override fun searchMangaNextPageSelector() = "a:contains(Далее)" - - private fun searchGenresNextPageSelector() = popularMangaNextPageSelector() - - override fun searchMangaParse(response: Response): MangasPage { - val document = response.asJsoup() - val mangas = document.select(searchMangaSelector()).map { element -> - searchMangaFromElement(element) - } - - // FIXME -// val allIgnore = filters.all { it.state == Filter.TriState.STATE_IGNORE } -// searchMangaNextPageSelector().let { selector -> -// if (page.nextPageUrl.isNullOrEmpty() && allIgnore) { -// val onClick = document.select(selector).first()?.attr("onclick") -// val pageNum = onClick?.substring(23, onClick.indexOf("); return(false)")) -// page.nextPageUrl = searchMangaInitialUrl(query, emptyList()) + "&search_start=" + pageNum -// } -// } -// -// searchGenresNextPageSelector().let { selector -> -// if (page.nextPageUrl.isNullOrEmpty() && !allIgnore) { -// val url = document.select(selector).first()?.attr("href") -// page.nextPageUrl = searchMangaInitialUrl(query, filters) + url -// } -// } - - return MangasPage(mangas, false) - } - - override fun mangaDetailsParse(document: Document): SManga { - val infoElement = document.select("table.mangatitle").first() - val descElement = document.select("div#description").first() - val imgElement = document.select("img#cover").first() - - val manga = SManga.create() - manga.author = infoElement.select("tr:eq(2) > td:eq(1)").text() - manga.genre = infoElement.select("tr:eq(5) > td:eq(1)").text() - manga.status = parseStatus(infoElement.select("tr:eq(4) > td:eq(1)").text()) - manga.description = descElement.textNodes().first().text() - manga.thumbnail_url = baseUrl + imgElement.attr("src") - return manga - } - - private fun parseStatus(element: String): Int { - when { - element.contains("перевод завершен") -> return SManga.COMPLETED - element.contains("перевод продолжается") -> return SManga.ONGOING - else -> return SManga.UNKNOWN - } - } - - override fun chapterListSelector() = "table.table_cha tr:gt(1)" - - override fun chapterFromElement(element: Element): SChapter { - val urlElement = element.select("a").first() - - val chapter = SChapter.create() - chapter.setUrlWithoutDomain(urlElement.attr("href")) - chapter.name = urlElement.text() - chapter.date_upload = element.select("div.date").first()?.text()?.let { - SimpleDateFormat("yyyy-MM-dd", Locale.US).parse(it).time - } ?: 0 - return chapter - } - - override fun pageListParse(response: Response): List { - val html = response.body().string() - val beginIndex = html.indexOf("fullimg\":[") + 10 - val endIndex = html.indexOf(",]", beginIndex) - val trimmedHtml = html.substring(beginIndex, endIndex).replace("\"", "") - val pageUrls = trimmedHtml.split(',') - - return pageUrls.mapIndexed { i, url -> Page(i, "", url) } - } - - override fun pageListParse(document: Document): List { - throw Exception("Not used") - } - - override fun imageUrlParse(document: Document) = "" - - private class Genre(name: String, val id: String = name.replace(' ', '_')) : Filter.TriState(name) - - /* [...document.querySelectorAll("li.sidetag > a:nth-child(1)")].map((el,i) => - * { const link=el.getAttribute('href');const id=link.substr(6,link.length); - * return `Genre("${id.replace("_", " ")}")` }).join(',\n') - * on http://mangachan.me/ - */ - override fun getFilterList() = FilterList( - Genre("18 плюс"), - Genre("bdsm"), - Genre("арт"), - Genre("биография"), - Genre("боевик"), - Genre("боевые искусства"), - Genre("вампиры"), - Genre("веб"), - Genre("гарем"), - Genre("гендерная интрига"), - Genre("героическое фэнтези"), - Genre("детектив"), - Genre("дзёсэй"), - Genre("додзинси"), - Genre("драма"), - Genre("игра"), - Genre("инцест"), - Genre("искусство"), - Genre("история"), - Genre("киберпанк"), - Genre("кодомо"), - Genre("комедия"), - Genre("литРПГ"), - Genre("магия"), - Genre("махо-сёдзё"), - Genre("меха"), - Genre("мистика"), - Genre("музыка"), - Genre("научная фантастика"), - Genre("повседневность"), - Genre("постапокалиптика"), - Genre("приключения"), - Genre("психология"), - Genre("романтика"), - Genre("самурайский боевик"), - Genre("сборник"), - Genre("сверхъестественное"), - Genre("сказка"), - Genre("спорт"), - Genre("супергерои"), - Genre("сэйнэн"), - Genre("сёдзё"), - Genre("сёдзё-ай"), - Genre("сёнэн"), - Genre("сёнэн-ай"), - Genre("тентакли"), - Genre("трагедия"), - Genre("триллер"), - Genre("ужасы"), - Genre("фантастика"), - Genre("фурри"), - Genre("фэнтези"), - Genre("школа"), - Genre("эротика"), - Genre("юри"), - Genre("яой"), - Genre("ёнкома") - ) +package eu.kanade.tachiyomi.source.online.russian + +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.source.model.* +import eu.kanade.tachiyomi.source.online.ParsedOnlineSource +import eu.kanade.tachiyomi.util.asJsoup +import okhttp3.Request +import okhttp3.Response +import org.jsoup.nodes.Document +import org.jsoup.nodes.Element +import java.text.SimpleDateFormat +import java.util.* + +class Mangachan : ParsedOnlineSource() { + + override val id: Long = 7 + + override val name = "Mangachan" + + override val baseUrl = "http://mangachan.me" + + override val lang = "ru" + + override val supportsLatest = true + + override fun popularMangaRequest(page: Int): Request { + return GET("$baseUrl/mostfavorites?offset=${20 * (page - 1)}", headers) + } + + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { + val url = if (query.isNotEmpty()) { + "$baseUrl/?do=search&subaction=search&story=$query" + } else { + val filt = filters.filterIsInstance().filter { !it.isIgnored() } + if (filt.isNotEmpty()) { + var genres = "" + filt.forEach { genres += (if (it.isExcluded()) "-" else "") + it.id + '+' } + "$baseUrl/tags/${genres.dropLast(1)}" + } else { + "$baseUrl/?do=search&subaction=search&story=$query" + } + } + return GET(url, headers) + } + + override fun latestUpdatesRequest(page: Int): Request { + return GET("$baseUrl/newestch?page=$page") + } + + override fun popularMangaSelector() = "div.content_row" + + override fun latestUpdatesSelector() = "ul.area_rightNews li" + + override fun searchMangaSelector() = popularMangaSelector() + + override fun popularMangaFromElement(element: Element): SManga { + val manga = SManga.create() + element.select("h2 > a").first().let { + manga.setUrlWithoutDomain(it.attr("href")) + manga.title = it.text() + } + return manga + } + + override fun latestUpdatesFromElement(element: Element): SManga { + val manga = SManga.create() + element.select("a:nth-child(1)").first().let { + manga.setUrlWithoutDomain(it.attr("href")) + manga.title = it.text() + } + return manga + } + + override fun searchMangaFromElement(element: Element): SManga { + return popularMangaFromElement(element) + } + + override fun popularMangaNextPageSelector() = "a:contains(Вперед)" + + override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector() + + override fun searchMangaNextPageSelector() = "a:contains(Далее)" + + private fun searchGenresNextPageSelector() = popularMangaNextPageSelector() + + override fun searchMangaParse(response: Response): MangasPage { + val document = response.asJsoup() + val mangas = document.select(searchMangaSelector()).map { element -> + searchMangaFromElement(element) + } + + // FIXME +// val allIgnore = filters.all { it.state == Filter.TriState.STATE_IGNORE } +// searchMangaNextPageSelector().let { selector -> +// if (page.nextPageUrl.isNullOrEmpty() && allIgnore) { +// val onClick = document.select(selector).first()?.attr("onclick") +// val pageNum = onClick?.substring(23, onClick.indexOf("); return(false)")) +// page.nextPageUrl = searchMangaInitialUrl(query, emptyList()) + "&search_start=" + pageNum +// } +// } +// +// searchGenresNextPageSelector().let { selector -> +// if (page.nextPageUrl.isNullOrEmpty() && !allIgnore) { +// val url = document.select(selector).first()?.attr("href") +// page.nextPageUrl = searchMangaInitialUrl(query, filters) + url +// } +// } + + return MangasPage(mangas, false) + } + + override fun mangaDetailsParse(document: Document): SManga { + val infoElement = document.select("table.mangatitle").first() + val descElement = document.select("div#description").first() + val imgElement = document.select("img#cover").first() + + val manga = SManga.create() + manga.author = infoElement.select("tr:eq(2) > td:eq(1)").text() + manga.genre = infoElement.select("tr:eq(5) > td:eq(1)").text() + manga.status = parseStatus(infoElement.select("tr:eq(4) > td:eq(1)").text()) + manga.description = descElement.textNodes().first().text() + manga.thumbnail_url = baseUrl + imgElement.attr("src") + return manga + } + + private fun parseStatus(element: String): Int { + when { + element.contains("перевод завершен") -> return SManga.COMPLETED + element.contains("перевод продолжается") -> return SManga.ONGOING + else -> return SManga.UNKNOWN + } + } + + override fun chapterListSelector() = "table.table_cha tr:gt(1)" + + override fun chapterFromElement(element: Element): SChapter { + val urlElement = element.select("a").first() + + val chapter = SChapter.create() + chapter.setUrlWithoutDomain(urlElement.attr("href")) + chapter.name = urlElement.text() + chapter.date_upload = element.select("div.date").first()?.text()?.let { + SimpleDateFormat("yyyy-MM-dd", Locale.US).parse(it).time + } ?: 0 + return chapter + } + + override fun pageListParse(response: Response): List { + val html = response.body().string() + val beginIndex = html.indexOf("fullimg\":[") + 10 + val endIndex = html.indexOf(",]", beginIndex) + val trimmedHtml = html.substring(beginIndex, endIndex).replace("\"", "") + val pageUrls = trimmedHtml.split(',') + + return pageUrls.mapIndexed { i, url -> Page(i, "", url) } + } + + override fun pageListParse(document: Document): List { + throw Exception("Not used") + } + + override fun imageUrlParse(document: Document) = "" + + private class Genre(name: String, val id: String = name.replace(' ', '_')) : Filter.TriState(name) + + /* [...document.querySelectorAll("li.sidetag > a:nth-child(1)")].map((el,i) => + * { const link=el.getAttribute('href');const id=link.substr(6,link.length); + * return `Genre("${id.replace("_", " ")}")` }).join(',\n') + * on http://mangachan.me/ + */ + override fun getFilterList() = FilterList( + Genre("18 плюс"), + Genre("bdsm"), + Genre("арт"), + Genre("биография"), + Genre("боевик"), + Genre("боевые искусства"), + Genre("вампиры"), + Genre("веб"), + Genre("гарем"), + Genre("гендерная интрига"), + Genre("героическое фэнтези"), + Genre("детектив"), + Genre("дзёсэй"), + Genre("додзинси"), + Genre("драма"), + Genre("игра"), + Genre("инцест"), + Genre("искусство"), + Genre("история"), + Genre("киберпанк"), + Genre("кодомо"), + Genre("комедия"), + Genre("литРПГ"), + Genre("магия"), + Genre("махо-сёдзё"), + Genre("меха"), + Genre("мистика"), + Genre("музыка"), + Genre("научная фантастика"), + Genre("повседневность"), + Genre("постапокалиптика"), + Genre("приключения"), + Genre("психология"), + Genre("романтика"), + Genre("самурайский боевик"), + Genre("сборник"), + Genre("сверхъестественное"), + Genre("сказка"), + Genre("спорт"), + Genre("супергерои"), + Genre("сэйнэн"), + Genre("сёдзё"), + Genre("сёдзё-ай"), + Genre("сёнэн"), + Genre("сёнэн-ай"), + Genre("тентакли"), + Genre("трагедия"), + Genre("триллер"), + Genre("ужасы"), + Genre("фантастика"), + Genre("фурри"), + Genre("фэнтези"), + Genre("школа"), + Genre("эротика"), + Genre("юри"), + Genre("яой"), + Genre("ёнкома") + ) } \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/russian/Mintmanga.kt b/app/src/main/java/eu/kanade/tachiyomi/source/online/russian/Mintmanga.kt similarity index 95% rename from app/src/main/java/eu/kanade/tachiyomi/data/source/online/russian/Mintmanga.kt rename to app/src/main/java/eu/kanade/tachiyomi/source/online/russian/Mintmanga.kt index 929747c2c1..d4466de687 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/russian/Mintmanga.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/online/russian/Mintmanga.kt @@ -1,184 +1,184 @@ -package eu.kanade.tachiyomi.data.source.online.russian - -import eu.kanade.tachiyomi.data.network.GET -import eu.kanade.tachiyomi.data.source.model.* -import eu.kanade.tachiyomi.data.source.online.ParsedOnlineSource -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import java.text.SimpleDateFormat -import java.util.* -import java.util.regex.Pattern - -class Mintmanga : ParsedOnlineSource() { - - override val id: Long = 6 - - override val name = "Mintmanga" - - override val baseUrl = "http://mintmanga.com" - - override val lang = "ru" - - override val supportsLatest = true - - override fun popularMangaRequest(page: Int): Request { - return GET("$baseUrl/list?sortType=rate&offset=${70 * (page - 1)}&max=70", headers) - } - - override fun latestUpdatesRequest(page: Int): Request { - return GET("$baseUrl/list?sortType=updated&offset=${70 * (page - 1)}&max=70", headers) - } - - override fun popularMangaSelector() = "div.desc" - - override fun latestUpdatesSelector() = "div.desc" - - override fun popularMangaFromElement(element: Element): SManga { - val manga = SManga.create() - element.select("h3 > a").first().let { - manga.setUrlWithoutDomain(it.attr("href")) - manga.title = it.attr("title") - } - return manga - } - - override fun latestUpdatesFromElement(element: Element): SManga { - return popularMangaFromElement(element) - } - - override fun popularMangaNextPageSelector() = "a.nextLink" - - override fun latestUpdatesNextPageSelector() = "a.nextLink" - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val genres = filters.filterIsInstance().map { it.id + arrayOf("=", "=in", "=ex")[it.state] }.joinToString("&") - return GET("$baseUrl/search?q=$query&$genres", headers) - } - - override fun searchMangaSelector() = popularMangaSelector() - - override fun searchMangaFromElement(element: Element): SManga { - return popularMangaFromElement(element) - } - - // max 200 results - override fun searchMangaNextPageSelector() = null - - override fun mangaDetailsParse(document: Document): SManga { - val infoElement = document.select("div.leftContent").first() - - val manga = SManga.create() - manga.author = infoElement.select("span.elem_author").first()?.text() - manga.genre = infoElement.select("span.elem_genre").text().replace(" ,", ",") - manga.description = infoElement.select("div.manga-description").text() - manga.status = parseStatus(infoElement.html()) - manga.thumbnail_url = infoElement.select("img").attr("data-full") - return manga - } - - private fun parseStatus(element: String): Int { - when { - element.contains("

Запрещена публикация произведения по копирайту

") -> return SManga.LICENSED - element.contains("

Сингл") || element.contains("Перевод: завершен") -> return SManga.COMPLETED - element.contains("Перевод: продолжается") -> return SManga.ONGOING - else -> return SManga.UNKNOWN - } - } - - override fun chapterListSelector() = "div.chapters-link tbody tr" - - override fun chapterFromElement(element: Element): SChapter { - val urlElement = element.select("a").first() - - val chapter = SChapter.create() - chapter.setUrlWithoutDomain(urlElement.attr("href") + "?mature=1") - chapter.name = urlElement.text().replace(" новое", "") - chapter.date_upload = element.select("td:eq(1)").first()?.text()?.let { - SimpleDateFormat("dd/MM/yy", Locale.US).parse(it).time - } ?: 0 - return chapter - } - - override fun prepareNewChapter(chapter: SChapter, manga: SManga) { - chapter.chapter_number = -2f - } - - override fun pageListParse(response: Response): List { - val html = response.body().string() - val beginIndex = html.indexOf("rm_h.init( [") - val endIndex = html.indexOf("], 0, false);", beginIndex) - val trimmedHtml = html.substring(beginIndex, endIndex) - - val p = Pattern.compile("'.+?','.+?',\".+?\"") - val m = p.matcher(trimmedHtml) - - val pages = mutableListOf() - - var i = 0 - while (m.find()) { - val urlParts = m.group().replace("[\"\']+".toRegex(), "").split(',') - pages.add(Page(i++, "", urlParts[1] + urlParts[0] + urlParts[2])) - } - return pages - } - - override fun pageListParse(document: Document): List { - throw Exception("Not used") - } - - override fun imageUrlParse(document: Document) = "" - - private class Genre(name: String, val id: String) : Filter.TriState(name) - - /* [...document.querySelectorAll("tr.advanced_option:nth-child(1) > td:nth-child(3) span.js-link")].map((el,i) => { - * const onClick=el.getAttribute('onclick');const id=onClick.substr(31,onClick.length-33); - * return `Genre("${el.textContent.trim()}", "${id}")` }).join(',\n') - * on http://mintmanga.com/search - */ - override fun getFilterList() = FilterList( - Genre("арт", "el_2220"), - Genre("бара", "el_1353"), - Genre("боевик", "el_1346"), - Genre("боевые искусства", "el_1334"), - Genre("вампиры", "el_1339"), - Genre("гарем", "el_1333"), - Genre("гендерная интрига", "el_1347"), - Genre("героическое фэнтези", "el_1337"), - Genre("детектив", "el_1343"), - Genre("дзёсэй", "el_1349"), - Genre("додзинси", "el_1332"), - Genre("драма", "el_1310"), - Genre("игра", "el_5229"), - Genre("история", "el_1311"), - Genre("киберпанк", "el_1351"), - Genre("комедия", "el_1328"), - Genre("меха", "el_1318"), - Genre("мистика", "el_1324"), - Genre("научная фантастика", "el_1325"), - Genre("повседневность", "el_1327"), - Genre("постапокалиптика", "el_1342"), - Genre("приключения", "el_1322"), - Genre("психология", "el_1335"), - Genre("романтика", "el_1313"), - Genre("самурайский боевик", "el_1316"), - Genre("сверхъестественное", "el_1350"), - Genre("сёдзё", "el_1314"), - Genre("сёдзё-ай", "el_1320"), - Genre("сёнэн", "el_1326"), - Genre("сёнэн-ай", "el_1330"), - Genre("спорт", "el_1321"), - Genre("сэйнэн", "el_1329"), - Genre("трагедия", "el_1344"), - Genre("триллер", "el_1341"), - Genre("ужасы", "el_1317"), - Genre("фантастика", "el_1331"), - Genre("фэнтези", "el_1323"), - Genre("школа", "el_1319"), - Genre("эротика", "el_1340"), - Genre("этти", "el_1354"), - Genre("юри", "el_1315"), - Genre("яой", "el_1336") - ) +package eu.kanade.tachiyomi.source.online.russian + +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.source.model.* +import eu.kanade.tachiyomi.source.online.ParsedOnlineSource +import okhttp3.Request +import okhttp3.Response +import org.jsoup.nodes.Document +import org.jsoup.nodes.Element +import java.text.SimpleDateFormat +import java.util.* +import java.util.regex.Pattern + +class Mintmanga : ParsedOnlineSource() { + + override val id: Long = 6 + + override val name = "Mintmanga" + + override val baseUrl = "http://mintmanga.com" + + override val lang = "ru" + + override val supportsLatest = true + + override fun popularMangaRequest(page: Int): Request { + return GET("$baseUrl/list?sortType=rate&offset=${70 * (page - 1)}&max=70", headers) + } + + override fun latestUpdatesRequest(page: Int): Request { + return GET("$baseUrl/list?sortType=updated&offset=${70 * (page - 1)}&max=70", headers) + } + + override fun popularMangaSelector() = "div.desc" + + override fun latestUpdatesSelector() = "div.desc" + + override fun popularMangaFromElement(element: Element): SManga { + val manga = SManga.create() + element.select("h3 > a").first().let { + manga.setUrlWithoutDomain(it.attr("href")) + manga.title = it.attr("title") + } + return manga + } + + override fun latestUpdatesFromElement(element: Element): SManga { + return popularMangaFromElement(element) + } + + override fun popularMangaNextPageSelector() = "a.nextLink" + + override fun latestUpdatesNextPageSelector() = "a.nextLink" + + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { + val genres = filters.filterIsInstance().map { it.id + arrayOf("=", "=in", "=ex")[it.state] }.joinToString("&") + return GET("$baseUrl/search?q=$query&$genres", headers) + } + + override fun searchMangaSelector() = popularMangaSelector() + + override fun searchMangaFromElement(element: Element): SManga { + return popularMangaFromElement(element) + } + + // max 200 results + override fun searchMangaNextPageSelector() = null + + override fun mangaDetailsParse(document: Document): SManga { + val infoElement = document.select("div.leftContent").first() + + val manga = SManga.create() + manga.author = infoElement.select("span.elem_author").first()?.text() + manga.genre = infoElement.select("span.elem_genre").text().replace(" ,", ",") + manga.description = infoElement.select("div.manga-description").text() + manga.status = parseStatus(infoElement.html()) + manga.thumbnail_url = infoElement.select("img").attr("data-full") + return manga + } + + private fun parseStatus(element: String): Int { + when { + element.contains("

Запрещена публикация произведения по копирайту

") -> return SManga.LICENSED + element.contains("

Сингл") || element.contains("Перевод: завершен") -> return SManga.COMPLETED + element.contains("Перевод: продолжается") -> return SManga.ONGOING + else -> return SManga.UNKNOWN + } + } + + override fun chapterListSelector() = "div.chapters-link tbody tr" + + override fun chapterFromElement(element: Element): SChapter { + val urlElement = element.select("a").first() + + val chapter = SChapter.create() + chapter.setUrlWithoutDomain(urlElement.attr("href") + "?mature=1") + chapter.name = urlElement.text().replace(" новое", "") + chapter.date_upload = element.select("td:eq(1)").first()?.text()?.let { + SimpleDateFormat("dd/MM/yy", Locale.US).parse(it).time + } ?: 0 + return chapter + } + + override fun prepareNewChapter(chapter: SChapter, manga: SManga) { + chapter.chapter_number = -2f + } + + override fun pageListParse(response: Response): List { + val html = response.body().string() + val beginIndex = html.indexOf("rm_h.init( [") + val endIndex = html.indexOf("], 0, false);", beginIndex) + val trimmedHtml = html.substring(beginIndex, endIndex) + + val p = Pattern.compile("'.+?','.+?',\".+?\"") + val m = p.matcher(trimmedHtml) + + val pages = mutableListOf() + + var i = 0 + while (m.find()) { + val urlParts = m.group().replace("[\"\']+".toRegex(), "").split(',') + pages.add(Page(i++, "", urlParts[1] + urlParts[0] + urlParts[2])) + } + return pages + } + + override fun pageListParse(document: Document): List { + throw Exception("Not used") + } + + override fun imageUrlParse(document: Document) = "" + + private class Genre(name: String, val id: String) : Filter.TriState(name) + + /* [...document.querySelectorAll("tr.advanced_option:nth-child(1) > td:nth-child(3) span.js-link")].map((el,i) => { + * const onClick=el.getAttribute('onclick');const id=onClick.substr(31,onClick.length-33); + * return `Genre("${el.textContent.trim()}", "${id}")` }).join(',\n') + * on http://mintmanga.com/search + */ + override fun getFilterList() = FilterList( + Genre("арт", "el_2220"), + Genre("бара", "el_1353"), + Genre("боевик", "el_1346"), + Genre("боевые искусства", "el_1334"), + Genre("вампиры", "el_1339"), + Genre("гарем", "el_1333"), + Genre("гендерная интрига", "el_1347"), + Genre("героическое фэнтези", "el_1337"), + Genre("детектив", "el_1343"), + Genre("дзёсэй", "el_1349"), + Genre("додзинси", "el_1332"), + Genre("драма", "el_1310"), + Genre("игра", "el_5229"), + Genre("история", "el_1311"), + Genre("киберпанк", "el_1351"), + Genre("комедия", "el_1328"), + Genre("меха", "el_1318"), + Genre("мистика", "el_1324"), + Genre("научная фантастика", "el_1325"), + Genre("повседневность", "el_1327"), + Genre("постапокалиптика", "el_1342"), + Genre("приключения", "el_1322"), + Genre("психология", "el_1335"), + Genre("романтика", "el_1313"), + Genre("самурайский боевик", "el_1316"), + Genre("сверхъестественное", "el_1350"), + Genre("сёдзё", "el_1314"), + Genre("сёдзё-ай", "el_1320"), + Genre("сёнэн", "el_1326"), + Genre("сёнэн-ай", "el_1330"), + Genre("спорт", "el_1321"), + Genre("сэйнэн", "el_1329"), + Genre("трагедия", "el_1344"), + Genre("триллер", "el_1341"), + Genre("ужасы", "el_1317"), + Genre("фантастика", "el_1331"), + Genre("фэнтези", "el_1323"), + Genre("школа", "el_1319"), + Genre("эротика", "el_1340"), + Genre("этти", "el_1354"), + Genre("юри", "el_1315"), + Genre("яой", "el_1336") + ) } \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/russian/Readmanga.kt b/app/src/main/java/eu/kanade/tachiyomi/source/online/russian/Readmanga.kt similarity index 95% rename from app/src/main/java/eu/kanade/tachiyomi/data/source/online/russian/Readmanga.kt rename to app/src/main/java/eu/kanade/tachiyomi/source/online/russian/Readmanga.kt index de74721b36..5c19ac62a4 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/russian/Readmanga.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/online/russian/Readmanga.kt @@ -1,183 +1,183 @@ -package eu.kanade.tachiyomi.data.source.online.russian - -import eu.kanade.tachiyomi.data.network.GET -import eu.kanade.tachiyomi.data.source.model.* -import eu.kanade.tachiyomi.data.source.online.ParsedOnlineSource -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import java.text.SimpleDateFormat -import java.util.* -import java.util.regex.Pattern - -class Readmanga : ParsedOnlineSource() { - - override val id: Long = 5 - - override val name = "Readmanga" - - override val baseUrl = "http://readmanga.me" - - override val lang = "ru" - - override val supportsLatest = true - - override fun popularMangaSelector() = "div.desc" - - override fun latestUpdatesSelector() = "div.desc" - - override fun popularMangaRequest(page: Int): Request { - return GET("$baseUrl/list?sortType=rate&offset=${70 * (page - 1)}&max=70", headers) - } - - override fun latestUpdatesRequest(page: Int): Request { - return GET("$baseUrl/list?sortType=updated&offset=${70 * (page - 1)}&max=70", headers) - } - - override fun popularMangaFromElement(element: Element): SManga { - val manga = SManga.create() - element.select("h3 > a").first().let { - manga.setUrlWithoutDomain(it.attr("href")) - manga.title = it.attr("title") - } - return manga - } - - override fun latestUpdatesFromElement(element: Element): SManga { - return popularMangaFromElement(element) - } - - override fun popularMangaNextPageSelector() = "a.nextLink" - - override fun latestUpdatesNextPageSelector() = "a.nextLink" - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val genres = filters.filterIsInstance().map { it.id + arrayOf("=", "=in", "=ex")[it.state] }.joinToString("&") - return GET("$baseUrl/search?q=$query&$genres", headers) - } - - override fun searchMangaSelector() = popularMangaSelector() - - override fun searchMangaFromElement(element: Element): SManga { - return popularMangaFromElement(element) - } - - // max 200 results - override fun searchMangaNextPageSelector() = null - - override fun mangaDetailsParse(document: Document): SManga { - val infoElement = document.select("div.leftContent").first() - - val manga = SManga.create() - manga.author = infoElement.select("span.elem_author").first()?.text() - manga.genre = infoElement.select("span.elem_genre").text().replace(" ,", ",") - manga.description = infoElement.select("div.manga-description").text() - manga.status = parseStatus(infoElement.html()) - manga.thumbnail_url = infoElement.select("img").attr("data-full") - return manga - } - - private fun parseStatus(element: String): Int { - when { - element.contains("

Запрещена публикация произведения по копирайту

") -> return SManga.LICENSED - element.contains("

Сингл") || element.contains("Перевод: завершен") -> return SManga.COMPLETED - element.contains("Перевод: продолжается") -> return SManga.ONGOING - else -> return SManga.UNKNOWN - } - } - - override fun chapterListSelector() = "div.chapters-link tbody tr" - - override fun chapterFromElement(element: Element): SChapter { - val urlElement = element.select("a").first() - - val chapter = SChapter.create() - chapter.setUrlWithoutDomain(urlElement.attr("href") + "?mature=1") - chapter.name = urlElement.text().replace(" новое", "") - chapter.date_upload = element.select("td:eq(1)").first()?.text()?.let { - SimpleDateFormat("dd/MM/yy", Locale.US).parse(it).time - } ?: 0 - return chapter - } - - override fun prepareNewChapter(chapter: SChapter, manga: SManga) { - chapter.chapter_number = -2f - } - - override fun pageListParse(response: Response): List { - val html = response.body().string() - val beginIndex = html.indexOf("rm_h.init( [") - val endIndex = html.indexOf("], 0, false);", beginIndex) - val trimmedHtml = html.substring(beginIndex, endIndex) - - val p = Pattern.compile("'.+?','.+?',\".+?\"") - val m = p.matcher(trimmedHtml) - - val pages = mutableListOf() - - var i = 0 - while (m.find()) { - val urlParts = m.group().replace("[\"\']+".toRegex(), "").split(',') - pages.add(Page(i++, "", urlParts[1] + urlParts[0] + urlParts[2])) - } - return pages - } - - override fun pageListParse(document: Document): List { - throw Exception("Not used") - } - - override fun imageUrlParse(document: Document) = "" - - private class Genre(name: String, val id: String) : Filter.TriState(name) - - /* [...document.querySelectorAll("tr.advanced_option:nth-child(1) > td:nth-child(3) span.js-link")].map((el,i) => { - * const onClick=el.getAttribute('onclick');const id=onClick.substr(31,onClick.length-33); - * return `Genre("${el.textContent.trim()}", "${id}")` }).join(',\n') - * on http://readmanga.me/search - */ - override fun getFilterList() = FilterList( - Genre("арт", "el_5685"), - Genre("боевик", "el_2155"), - Genre("боевые искусства", "el_2143"), - Genre("вампиры", "el_2148"), - Genre("гарем", "el_2142"), - Genre("гендерная интрига", "el_2156"), - Genre("героическое фэнтези", "el_2146"), - Genre("детектив", "el_2152"), - Genre("дзёсэй", "el_2158"), - Genre("додзинси", "el_2141"), - Genre("драма", "el_2118"), - Genre("игра", "el_2154"), - Genre("история", "el_2119"), - Genre("киберпанк", "el_8032"), - Genre("кодомо", "el_2137"), - Genre("комедия", "el_2136"), - Genre("махо-сёдзё", "el_2147"), - Genre("меха", "el_2126"), - Genre("мистика", "el_2132"), - Genre("научная фантастика", "el_2133"), - Genre("повседневность", "el_2135"), - Genre("постапокалиптика", "el_2151"), - Genre("приключения", "el_2130"), - Genre("психология", "el_2144"), - Genre("романтика", "el_2121"), - Genre("самурайский боевик", "el_2124"), - Genre("сверхъестественное", "el_2159"), - Genre("сёдзё", "el_2122"), - Genre("сёдзё-ай", "el_2128"), - Genre("сёнэн", "el_2134"), - Genre("сёнэн-ай", "el_2139"), - Genre("спорт", "el_2129"), - Genre("сэйнэн", "el_2138"), - Genre("трагедия", "el_2153"), - Genre("триллер", "el_2150"), - Genre("ужасы", "el_2125"), - Genre("фантастика", "el_2140"), - Genre("фэнтези", "el_2131"), - Genre("школа", "el_2127"), - Genre("этти", "el_2149"), - Genre("юри", "el_2123") - ) +package eu.kanade.tachiyomi.source.online.russian + +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.source.model.* +import eu.kanade.tachiyomi.source.online.ParsedOnlineSource +import okhttp3.Request +import okhttp3.Response +import org.jsoup.nodes.Document +import org.jsoup.nodes.Element +import java.text.SimpleDateFormat +import java.util.* +import java.util.regex.Pattern + +class Readmanga : ParsedOnlineSource() { + + override val id: Long = 5 + + override val name = "Readmanga" + + override val baseUrl = "http://readmanga.me" + + override val lang = "ru" + + override val supportsLatest = true + + override fun popularMangaSelector() = "div.desc" + + override fun latestUpdatesSelector() = "div.desc" + + override fun popularMangaRequest(page: Int): Request { + return GET("$baseUrl/list?sortType=rate&offset=${70 * (page - 1)}&max=70", headers) + } + + override fun latestUpdatesRequest(page: Int): Request { + return GET("$baseUrl/list?sortType=updated&offset=${70 * (page - 1)}&max=70", headers) + } + + override fun popularMangaFromElement(element: Element): SManga { + val manga = SManga.create() + element.select("h3 > a").first().let { + manga.setUrlWithoutDomain(it.attr("href")) + manga.title = it.attr("title") + } + return manga + } + + override fun latestUpdatesFromElement(element: Element): SManga { + return popularMangaFromElement(element) + } + + override fun popularMangaNextPageSelector() = "a.nextLink" + + override fun latestUpdatesNextPageSelector() = "a.nextLink" + + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { + val genres = filters.filterIsInstance().map { it.id + arrayOf("=", "=in", "=ex")[it.state] }.joinToString("&") + return GET("$baseUrl/search?q=$query&$genres", headers) + } + + override fun searchMangaSelector() = popularMangaSelector() + + override fun searchMangaFromElement(element: Element): SManga { + return popularMangaFromElement(element) + } + + // max 200 results + override fun searchMangaNextPageSelector() = null + + override fun mangaDetailsParse(document: Document): SManga { + val infoElement = document.select("div.leftContent").first() + + val manga = SManga.create() + manga.author = infoElement.select("span.elem_author").first()?.text() + manga.genre = infoElement.select("span.elem_genre").text().replace(" ,", ",") + manga.description = infoElement.select("div.manga-description").text() + manga.status = parseStatus(infoElement.html()) + manga.thumbnail_url = infoElement.select("img").attr("data-full") + return manga + } + + private fun parseStatus(element: String): Int { + when { + element.contains("

Запрещена публикация произведения по копирайту

") -> return SManga.LICENSED + element.contains("

Сингл") || element.contains("Перевод: завершен") -> return SManga.COMPLETED + element.contains("Перевод: продолжается") -> return SManga.ONGOING + else -> return SManga.UNKNOWN + } + } + + override fun chapterListSelector() = "div.chapters-link tbody tr" + + override fun chapterFromElement(element: Element): SChapter { + val urlElement = element.select("a").first() + + val chapter = SChapter.create() + chapter.setUrlWithoutDomain(urlElement.attr("href") + "?mature=1") + chapter.name = urlElement.text().replace(" новое", "") + chapter.date_upload = element.select("td:eq(1)").first()?.text()?.let { + SimpleDateFormat("dd/MM/yy", Locale.US).parse(it).time + } ?: 0 + return chapter + } + + override fun prepareNewChapter(chapter: SChapter, manga: SManga) { + chapter.chapter_number = -2f + } + + override fun pageListParse(response: Response): List { + val html = response.body().string() + val beginIndex = html.indexOf("rm_h.init( [") + val endIndex = html.indexOf("], 0, false);", beginIndex) + val trimmedHtml = html.substring(beginIndex, endIndex) + + val p = Pattern.compile("'.+?','.+?',\".+?\"") + val m = p.matcher(trimmedHtml) + + val pages = mutableListOf() + + var i = 0 + while (m.find()) { + val urlParts = m.group().replace("[\"\']+".toRegex(), "").split(',') + pages.add(Page(i++, "", urlParts[1] + urlParts[0] + urlParts[2])) + } + return pages + } + + override fun pageListParse(document: Document): List { + throw Exception("Not used") + } + + override fun imageUrlParse(document: Document) = "" + + private class Genre(name: String, val id: String) : Filter.TriState(name) + + /* [...document.querySelectorAll("tr.advanced_option:nth-child(1) > td:nth-child(3) span.js-link")].map((el,i) => { + * const onClick=el.getAttribute('onclick');const id=onClick.substr(31,onClick.length-33); + * return `Genre("${el.textContent.trim()}", "${id}")` }).join(',\n') + * on http://readmanga.me/search + */ + override fun getFilterList() = FilterList( + Genre("арт", "el_5685"), + Genre("боевик", "el_2155"), + Genre("боевые искусства", "el_2143"), + Genre("вампиры", "el_2148"), + Genre("гарем", "el_2142"), + Genre("гендерная интрига", "el_2156"), + Genre("героическое фэнтези", "el_2146"), + Genre("детектив", "el_2152"), + Genre("дзёсэй", "el_2158"), + Genre("додзинси", "el_2141"), + Genre("драма", "el_2118"), + Genre("игра", "el_2154"), + Genre("история", "el_2119"), + Genre("киберпанк", "el_8032"), + Genre("кодомо", "el_2137"), + Genre("комедия", "el_2136"), + Genre("махо-сёдзё", "el_2147"), + Genre("меха", "el_2126"), + Genre("мистика", "el_2132"), + Genre("научная фантастика", "el_2133"), + Genre("повседневность", "el_2135"), + Genre("постапокалиптика", "el_2151"), + Genre("приключения", "el_2130"), + Genre("психология", "el_2144"), + Genre("романтика", "el_2121"), + Genre("самурайский боевик", "el_2124"), + Genre("сверхъестественное", "el_2159"), + Genre("сёдзё", "el_2122"), + Genre("сёдзё-ай", "el_2128"), + Genre("сёнэн", "el_2134"), + Genre("сёнэн-ай", "el_2139"), + Genre("спорт", "el_2129"), + Genre("сэйнэн", "el_2138"), + Genre("трагедия", "el_2153"), + Genre("триллер", "el_2150"), + Genre("ужасы", "el_2125"), + Genre("фантастика", "el_2140"), + Genre("фэнтези", "el_2131"), + Genre("школа", "el_2127"), + Genre("этти", "el_2149"), + Genre("юри", "el_2123") + ) } \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueFragment.kt index 1f48e966d0..3b32cee5da 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueFragment.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueFragment.kt @@ -15,8 +15,8 @@ import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.items.IFlexible import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.database.models.Manga -import eu.kanade.tachiyomi.data.source.model.FilterList -import eu.kanade.tachiyomi.data.source.online.LoginSource +import eu.kanade.tachiyomi.source.model.FilterList +import eu.kanade.tachiyomi.source.online.LoginSource import eu.kanade.tachiyomi.ui.base.fragment.BaseRxFragment import eu.kanade.tachiyomi.ui.main.MainActivity import eu.kanade.tachiyomi.ui.manga.MangaActivity diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CataloguePager.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CataloguePager.kt index 355eccf83c..b95798a7d0 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CataloguePager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CataloguePager.kt @@ -1,8 +1,8 @@ package eu.kanade.tachiyomi.ui.catalogue -import eu.kanade.tachiyomi.data.source.CatalogueSource -import eu.kanade.tachiyomi.data.source.model.FilterList -import eu.kanade.tachiyomi.data.source.model.MangasPage +import eu.kanade.tachiyomi.source.CatalogueSource +import eu.kanade.tachiyomi.source.model.FilterList +import eu.kanade.tachiyomi.source.model.MangasPage import rx.Observable import rx.android.schedulers.AndroidSchedulers import rx.schedulers.Schedulers diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CataloguePresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CataloguePresenter.kt index a77d745aea..4856bfa79a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CataloguePresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CataloguePresenter.kt @@ -8,13 +8,13 @@ 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.getOrDefault -import eu.kanade.tachiyomi.data.source.CatalogueSource -import eu.kanade.tachiyomi.data.source.Source -import eu.kanade.tachiyomi.data.source.SourceManager -import eu.kanade.tachiyomi.data.source.model.Filter -import eu.kanade.tachiyomi.data.source.model.FilterList -import eu.kanade.tachiyomi.data.source.model.SManga -import eu.kanade.tachiyomi.data.source.online.LoginSource +import eu.kanade.tachiyomi.source.CatalogueSource +import eu.kanade.tachiyomi.source.Source +import eu.kanade.tachiyomi.source.SourceManager +import eu.kanade.tachiyomi.source.model.Filter +import eu.kanade.tachiyomi.source.model.FilterList +import eu.kanade.tachiyomi.source.model.SManga +import eu.kanade.tachiyomi.source.online.LoginSource import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter import eu.kanade.tachiyomi.ui.catalogue.filter.* import rx.Observable diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/Pager.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/Pager.kt index 7d228e4cbb..a0f3d55e2d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/Pager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/Pager.kt @@ -1,8 +1,8 @@ package eu.kanade.tachiyomi.ui.catalogue import com.jakewharton.rxrelay.PublishRelay -import eu.kanade.tachiyomi.data.source.model.MangasPage -import eu.kanade.tachiyomi.data.source.model.SManga +import eu.kanade.tachiyomi.source.model.MangasPage +import eu.kanade.tachiyomi.source.model.SManga import rx.Observable /** diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/filter/CheckboxItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/filter/CheckboxItem.kt index 5e27cc8242..d9bab855ea 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/filter/CheckboxItem.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/filter/CheckboxItem.kt @@ -8,7 +8,7 @@ import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.items.AbstractFlexibleItem import eu.davidea.viewholders.FlexibleViewHolder import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.data.source.model.Filter +import eu.kanade.tachiyomi.source.model.Filter open class CheckboxItem(val filter: Filter.CheckBox) : AbstractFlexibleItem() { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/filter/GroupItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/filter/GroupItem.kt index 9854c8ae7b..c023ce5962 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/filter/GroupItem.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/filter/GroupItem.kt @@ -10,7 +10,7 @@ import eu.davidea.flexibleadapter.items.AbstractExpandableHeaderItem import eu.davidea.flexibleadapter.items.ISectionable import eu.davidea.viewholders.ExpandableViewHolder import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.data.source.model.Filter +import eu.kanade.tachiyomi.source.model.Filter import eu.kanade.tachiyomi.util.setVectorCompat class GroupItem(val filter: Filter.Group<*>) : AbstractExpandableHeaderItem>() { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/filter/HeaderItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/filter/HeaderItem.kt index 6302e204d5..a766121675 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/filter/HeaderItem.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/filter/HeaderItem.kt @@ -9,7 +9,7 @@ import android.widget.TextView import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.items.AbstractHeaderItem import eu.davidea.viewholders.FlexibleViewHolder -import eu.kanade.tachiyomi.data.source.model.Filter +import eu.kanade.tachiyomi.source.model.Filter class HeaderItem(val filter: Filter.Header) : AbstractHeaderItem() { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/filter/SectionItems.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/filter/SectionItems.kt index 338c231123..71302c8d42 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/filter/SectionItems.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/filter/SectionItems.kt @@ -1,7 +1,7 @@ package eu.kanade.tachiyomi.ui.catalogue.filter import eu.davidea.flexibleadapter.items.ISectionable -import eu.kanade.tachiyomi.data.source.model.Filter +import eu.kanade.tachiyomi.source.model.Filter class TriStateSectionItem(filter: Filter.TriState) : TriStateItem(filter), ISectionable { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/filter/SelectItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/filter/SelectItem.kt index e9a10d535d..9b153cdc1c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/filter/SelectItem.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/filter/SelectItem.kt @@ -10,7 +10,7 @@ import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.items.AbstractFlexibleItem import eu.davidea.viewholders.FlexibleViewHolder import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.data.source.model.Filter +import eu.kanade.tachiyomi.source.model.Filter import eu.kanade.tachiyomi.widget.IgnoreFirstSpinnerListener open class SelectItem(val filter: Filter.Select<*>) : AbstractFlexibleItem() { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/filter/SeparatorItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/filter/SeparatorItem.kt index 10f25505aa..8420f2f7de 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/filter/SeparatorItem.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/filter/SeparatorItem.kt @@ -8,7 +8,7 @@ import android.view.ViewGroup import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.items.AbstractHeaderItem import eu.davidea.viewholders.FlexibleViewHolder -import eu.kanade.tachiyomi.data.source.model.Filter +import eu.kanade.tachiyomi.source.model.Filter class SeparatorItem(val filter: Filter.Separator) : AbstractHeaderItem() { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/filter/SortGroup.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/filter/SortGroup.kt index b397538108..26c92aea46 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/filter/SortGroup.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/filter/SortGroup.kt @@ -7,7 +7,7 @@ import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.items.AbstractExpandableHeaderItem import eu.davidea.flexibleadapter.items.ISectionable import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.data.source.model.Filter +import eu.kanade.tachiyomi.source.model.Filter import eu.kanade.tachiyomi.util.setVectorCompat class SortGroup(val filter: Filter.Sort) : AbstractExpandableHeaderItem>() { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/filter/SortItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/filter/SortItem.kt index 6bc1a1252b..5646fbc265 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/filter/SortItem.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/filter/SortItem.kt @@ -10,7 +10,7 @@ import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.items.AbstractSectionableItem import eu.davidea.viewholders.FlexibleViewHolder import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.data.source.model.Filter +import eu.kanade.tachiyomi.source.model.Filter import eu.kanade.tachiyomi.util.getResourceColor class SortItem(val name: String, val group: SortGroup) : AbstractSectionableItem(group) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/filter/TextItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/filter/TextItem.kt index e700520ffb..4056190ebc 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/filter/TextItem.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/filter/TextItem.kt @@ -9,7 +9,7 @@ import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.items.AbstractFlexibleItem import eu.davidea.viewholders.FlexibleViewHolder import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.data.source.model.Filter +import eu.kanade.tachiyomi.source.model.Filter import eu.kanade.tachiyomi.widget.SimpleTextWatcher open class TextItem(val filter: Filter.Text) : AbstractFlexibleItem() { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/filter/TriStateItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/filter/TriStateItem.kt index fe3fae3a74..0c834b3373 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/filter/TriStateItem.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/filter/TriStateItem.kt @@ -9,7 +9,7 @@ import android.widget.CheckedTextView import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.items.AbstractFlexibleItem import eu.davidea.viewholders.FlexibleViewHolder -import eu.kanade.tachiyomi.data.source.model.Filter +import eu.kanade.tachiyomi.source.model.Filter import eu.kanade.tachiyomi.util.dpToPx import eu.kanade.tachiyomi.util.getResourceColor import eu.kanade.tachiyomi.R as TR diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadActivity.kt index 755b8d0cac..9a8d010fef 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadActivity.kt @@ -7,7 +7,7 @@ import android.view.MenuItem import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.download.DownloadService import eu.kanade.tachiyomi.data.download.model.Download -import eu.kanade.tachiyomi.data.source.model.Page +import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.ui.base.activity.BaseRxActivity import eu.kanade.tachiyomi.util.plusAssign import kotlinx.android.synthetic.main.activity_main.* diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/latest_updates/LatestUpdatesPager.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/latest_updates/LatestUpdatesPager.kt index 9af8705105..7cdcdff66a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/latest_updates/LatestUpdatesPager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/latest_updates/LatestUpdatesPager.kt @@ -1,7 +1,7 @@ package eu.kanade.tachiyomi.ui.latest_updates -import eu.kanade.tachiyomi.data.source.CatalogueSource -import eu.kanade.tachiyomi.data.source.model.MangasPage +import eu.kanade.tachiyomi.source.CatalogueSource +import eu.kanade.tachiyomi.source.model.MangasPage import eu.kanade.tachiyomi.ui.catalogue.Pager import rx.Observable import rx.android.schedulers.AndroidSchedulers diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/latest_updates/LatestUpdatesPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/latest_updates/LatestUpdatesPresenter.kt index 03575aac20..bcb27c418f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/latest_updates/LatestUpdatesPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/latest_updates/LatestUpdatesPresenter.kt @@ -1,8 +1,8 @@ package eu.kanade.tachiyomi.ui.latest_updates -import eu.kanade.tachiyomi.data.source.CatalogueSource -import eu.kanade.tachiyomi.data.source.Source -import eu.kanade.tachiyomi.data.source.model.FilterList +import eu.kanade.tachiyomi.source.CatalogueSource +import eu.kanade.tachiyomi.source.Source +import eu.kanade.tachiyomi.source.model.FilterList import eu.kanade.tachiyomi.ui.catalogue.CataloguePresenter import eu.kanade.tachiyomi.ui.catalogue.Pager diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt index 8b78c95b1b..9e05ed763a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt @@ -13,7 +13,7 @@ import eu.kanade.tachiyomi.data.database.models.MangaCategory import eu.kanade.tachiyomi.data.download.DownloadManager import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.getOrDefault -import eu.kanade.tachiyomi.data.source.SourceManager +import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter import eu.kanade.tachiyomi.util.combineLatest import eu.kanade.tachiyomi.util.isNullOrUnsubscribed diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersPresenter.kt index 91eb91e5f5..79c328fe98 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersPresenter.kt @@ -9,8 +9,8 @@ import eu.kanade.tachiyomi.data.download.DownloadManager import eu.kanade.tachiyomi.data.download.DownloadService import eu.kanade.tachiyomi.data.download.model.Download import eu.kanade.tachiyomi.data.preference.PreferencesHelper -import eu.kanade.tachiyomi.data.source.Source -import eu.kanade.tachiyomi.data.source.SourceManager +import eu.kanade.tachiyomi.source.Source +import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter import eu.kanade.tachiyomi.ui.manga.MangaEvent import eu.kanade.tachiyomi.ui.manga.info.ChapterCountEvent diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoFragment.kt index 1f17394ed2..c33097e7a1 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoFragment.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoFragment.kt @@ -14,9 +14,9 @@ import com.bumptech.glide.load.engine.DiskCacheStrategy import com.bumptech.glide.load.resource.bitmap.CenterCrop import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.database.models.Manga -import eu.kanade.tachiyomi.data.source.Source -import eu.kanade.tachiyomi.data.source.model.SManga -import eu.kanade.tachiyomi.data.source.online.OnlineSource +import eu.kanade.tachiyomi.source.Source +import eu.kanade.tachiyomi.source.model.SManga +import eu.kanade.tachiyomi.source.online.OnlineSource import eu.kanade.tachiyomi.ui.base.fragment.BaseRxFragment import eu.kanade.tachiyomi.ui.manga.MangaActivity import eu.kanade.tachiyomi.util.getResourceColor diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoPresenter.kt index 1de6f34e1e..048c54c73a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoPresenter.kt @@ -4,8 +4,8 @@ import android.os.Bundle import eu.kanade.tachiyomi.data.cache.CoverCache import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.database.models.Manga -import eu.kanade.tachiyomi.data.source.Source -import eu.kanade.tachiyomi.data.source.SourceManager +import eu.kanade.tachiyomi.source.Source +import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter import eu.kanade.tachiyomi.ui.manga.MangaEvent import eu.kanade.tachiyomi.util.SharedData diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ChapterLoader.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ChapterLoader.kt index 9516d9690f..3258c220e1 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ChapterLoader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ChapterLoader.kt @@ -2,11 +2,11 @@ package eu.kanade.tachiyomi.ui.reader import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.download.DownloadManager -import eu.kanade.tachiyomi.data.source.Source -import eu.kanade.tachiyomi.data.source.model.Page -import eu.kanade.tachiyomi.data.source.online.OnlineSource -import eu.kanade.tachiyomi.data.source.online.fetchImageFromCacheThenNet -import eu.kanade.tachiyomi.data.source.online.fetchPageListFromCacheThenNet +import eu.kanade.tachiyomi.source.Source +import eu.kanade.tachiyomi.source.model.Page +import eu.kanade.tachiyomi.source.online.OnlineSource +import eu.kanade.tachiyomi.source.online.fetchImageFromCacheThenNet +import eu.kanade.tachiyomi.source.online.fetchPageListFromCacheThenNet import eu.kanade.tachiyomi.util.plusAssign import rx.Observable import rx.schedulers.Schedulers diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt index 6afb528d16..36b121b526 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt @@ -20,7 +20,7 @@ import eu.kanade.tachiyomi.data.database.models.Chapter import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.getOrDefault -import eu.kanade.tachiyomi.data.source.model.Page +import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.ui.base.activity.BaseRxActivity import eu.kanade.tachiyomi.ui.reader.viewer.base.BaseReader import eu.kanade.tachiyomi.ui.reader.viewer.pager.horizontal.LeftToRightReader diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderChapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderChapter.kt index 56de9c9721..e9bd9a0c40 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderChapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderChapter.kt @@ -1,7 +1,7 @@ package eu.kanade.tachiyomi.ui.reader import eu.kanade.tachiyomi.data.database.models.Chapter -import eu.kanade.tachiyomi.data.source.model.Page +import eu.kanade.tachiyomi.source.model.Page class ReaderChapter(c: Chapter) : Chapter by c { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt index 5f0a941742..7881623631 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt @@ -13,9 +13,9 @@ import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Track import eu.kanade.tachiyomi.data.download.DownloadManager import eu.kanade.tachiyomi.data.preference.PreferencesHelper -import eu.kanade.tachiyomi.data.source.SourceManager -import eu.kanade.tachiyomi.data.source.model.Page -import eu.kanade.tachiyomi.data.source.online.OnlineSource +import eu.kanade.tachiyomi.source.SourceManager +import eu.kanade.tachiyomi.source.model.Page +import eu.kanade.tachiyomi.source.online.OnlineSource import eu.kanade.tachiyomi.data.track.TrackManager import eu.kanade.tachiyomi.data.track.TrackUpdateService import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/base/BaseReader.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/base/BaseReader.kt index 946a26e5f1..878ab38df1 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/base/BaseReader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/base/BaseReader.kt @@ -2,7 +2,7 @@ package eu.kanade.tachiyomi.ui.reader.viewer.base import com.davemorrissey.labs.subscaleview.decoder.* import eu.kanade.tachiyomi.data.preference.getOrDefault -import eu.kanade.tachiyomi.data.source.model.Page +import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.ui.base.fragment.BaseFragment import eu.kanade.tachiyomi.ui.reader.ReaderActivity import eu.kanade.tachiyomi.ui.reader.ReaderChapter diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/base/PageDecodeErrorLayout.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/base/PageDecodeErrorLayout.kt index 5623565511..b9314d0c44 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/base/PageDecodeErrorLayout.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/base/PageDecodeErrorLayout.kt @@ -4,7 +4,7 @@ import android.net.Uri import android.support.v4.content.ContextCompat import android.view.View import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.data.source.model.Page +import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.ui.reader.ReaderActivity import kotlinx.android.synthetic.main.page_decode_error.view.* diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PageView.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PageView.kt index a7dc205f75..26326cb323 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PageView.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PageView.kt @@ -10,7 +10,7 @@ import com.davemorrissey.labs.subscaleview.ImageSource import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView import com.hippo.unifile.UniFile import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.data.source.model.Page +import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.ui.reader.ReaderActivity import eu.kanade.tachiyomi.ui.reader.viewer.base.PageDecodeErrorLayout import eu.kanade.tachiyomi.ui.reader.viewer.pager.horizontal.RightToLeftReader diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerReader.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerReader.kt index 529436eb93..3e3581cb11 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerReader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerReader.kt @@ -6,7 +6,7 @@ import android.view.MotionEvent import android.view.ViewGroup import android.view.ViewGroup.LayoutParams.MATCH_PARENT import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.data.source.model.Page +import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.ui.reader.ReaderChapter import eu.kanade.tachiyomi.ui.reader.viewer.base.BaseReader import eu.kanade.tachiyomi.ui.reader.viewer.pager.horizontal.LeftToRightReader diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerReaderAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerReaderAdapter.kt index 7f765207cc..0fa7a808f0 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerReaderAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerReaderAdapter.kt @@ -4,7 +4,7 @@ import android.support.v4.view.PagerAdapter import android.view.View import android.view.ViewGroup import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.data.source.model.Page +import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.util.inflate import eu.kanade.tachiyomi.widget.ViewPagerAdapter diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonAdapter.kt index d706540de2..ea6c6dca95 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonAdapter.kt @@ -4,7 +4,7 @@ import android.support.v7.widget.RecyclerView import android.view.View import android.view.ViewGroup import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.data.source.model.Page +import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.util.inflate /** diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonHolder.kt index 207ae2658c..b9acac1580 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonHolder.kt @@ -8,7 +8,7 @@ import com.davemorrissey.labs.subscaleview.ImageSource import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView import com.hippo.unifile.UniFile import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.data.source.model.Page +import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.ui.reader.ReaderActivity import eu.kanade.tachiyomi.ui.reader.viewer.base.PageDecodeErrorLayout import eu.kanade.tachiyomi.util.inflate diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonReader.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonReader.kt index 391ef68ce5..f160f0ce6a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonReader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonReader.kt @@ -5,7 +5,7 @@ import android.support.v7.widget.RecyclerView import android.view.* import android.view.ViewGroup.LayoutParams.MATCH_PARENT import android.view.ViewGroup.LayoutParams.WRAP_CONTENT -import eu.kanade.tachiyomi.data.source.model.Page +import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.ui.reader.ReaderChapter import eu.kanade.tachiyomi.ui.reader.viewer.base.BaseReader import eu.kanade.tachiyomi.widget.PreCachingLayoutManager diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChaptersPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChaptersPresenter.kt index 69ffdaeb13..55d6f3e71f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChaptersPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChaptersPresenter.kt @@ -8,7 +8,7 @@ import eu.kanade.tachiyomi.data.download.DownloadManager import eu.kanade.tachiyomi.data.download.DownloadService import eu.kanade.tachiyomi.data.download.model.Download import eu.kanade.tachiyomi.data.preference.PreferencesHelper -import eu.kanade.tachiyomi.data.source.SourceManager +import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter import rx.Observable import rx.android.schedulers.AndroidSchedulers diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recently_read/RecentlyReadAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recently_read/RecentlyReadAdapter.kt index e80c640dd8..e95b457ef5 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/recently_read/RecentlyReadAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recently_read/RecentlyReadAdapter.kt @@ -4,7 +4,7 @@ import android.view.ViewGroup import eu.davidea.flexibleadapter4.FlexibleAdapter import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.database.models.MangaChapterHistory -import eu.kanade.tachiyomi.data.source.SourceManager +import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.util.inflate import uy.kohesive.injekt.injectLazy diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAdvancedFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAdvancedFragment.kt index f35ff2ceb7..35bb43079b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAdvancedFragment.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAdvancedFragment.kt @@ -9,7 +9,7 @@ import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.cache.ChapterCache import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.library.LibraryUpdateService -import eu.kanade.tachiyomi.data.network.NetworkHelper +import eu.kanade.tachiyomi.network.NetworkHelper import eu.kanade.tachiyomi.util.plusAssign import eu.kanade.tachiyomi.util.toast import rx.Observable diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsSourcesFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsSourcesFragment.kt index 5360469e16..20a4f82acc 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsSourcesFragment.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsSourcesFragment.kt @@ -7,8 +7,8 @@ import android.support.v7.preference.XpPreferenceFragment import android.view.View import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.getOrDefault -import eu.kanade.tachiyomi.data.source.SourceManager -import eu.kanade.tachiyomi.data.source.online.OnlineSource +import eu.kanade.tachiyomi.source.SourceManager +import eu.kanade.tachiyomi.source.online.OnlineSource import eu.kanade.tachiyomi.widget.preference.LoginCheckBoxPreference import eu.kanade.tachiyomi.widget.preference.SourceLoginDialog import eu.kanade.tachiyomi.widget.preference.SwitchPreferenceCategory diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/ChapterSourceSync.kt b/app/src/main/java/eu/kanade/tachiyomi/util/ChapterSourceSync.kt index 5b503e2d27..abb681c52e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/ChapterSourceSync.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/ChapterSourceSync.kt @@ -3,9 +3,9 @@ package eu.kanade.tachiyomi.util import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.database.models.Chapter import eu.kanade.tachiyomi.data.database.models.Manga -import eu.kanade.tachiyomi.data.source.Source -import eu.kanade.tachiyomi.data.source.model.SChapter -import eu.kanade.tachiyomi.data.source.online.OnlineSource +import eu.kanade.tachiyomi.source.Source +import eu.kanade.tachiyomi.source.model.SChapter +import eu.kanade.tachiyomi.source.online.OnlineSource import java.util.* /** diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/preference/LoginCheckBoxPreference.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/preference/LoginCheckBoxPreference.kt index fad34089ac..7a3d76caca 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/widget/preference/LoginCheckBoxPreference.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/widget/preference/LoginCheckBoxPreference.kt @@ -6,8 +6,8 @@ import android.support.v7.preference.PreferenceViewHolder import android.util.AttributeSet import android.view.View import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.data.source.online.LoginSource -import eu.kanade.tachiyomi.data.source.online.OnlineSource +import eu.kanade.tachiyomi.source.online.LoginSource +import eu.kanade.tachiyomi.source.online.OnlineSource import eu.kanade.tachiyomi.util.getResourceColor import eu.kanade.tachiyomi.util.setVectorCompat import kotlinx.android.synthetic.main.pref_item_source.view.* diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/preference/SourceLoginDialog.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/preference/SourceLoginDialog.kt index 48e6549e2c..b197ba6a86 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/widget/preference/SourceLoginDialog.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/widget/preference/SourceLoginDialog.kt @@ -3,9 +3,9 @@ package eu.kanade.tachiyomi.widget.preference import android.os.Bundle import android.view.View import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.data.source.Source -import eu.kanade.tachiyomi.data.source.SourceManager -import eu.kanade.tachiyomi.data.source.online.LoginSource +import eu.kanade.tachiyomi.source.Source +import eu.kanade.tachiyomi.source.SourceManager +import eu.kanade.tachiyomi.source.online.LoginSource import eu.kanade.tachiyomi.util.toast import kotlinx.android.synthetic.main.pref_account_login.view.* import rx.android.schedulers.AndroidSchedulers diff --git a/app/src/test/java/eu/kanade/tachiyomi/data/library/LibraryUpdateServiceTest.kt b/app/src/test/java/eu/kanade/tachiyomi/data/library/LibraryUpdateServiceTest.kt index 5179e39a87..4da9011efa 100644 --- a/app/src/test/java/eu/kanade/tachiyomi/data/library/LibraryUpdateServiceTest.kt +++ b/app/src/test/java/eu/kanade/tachiyomi/data/library/LibraryUpdateServiceTest.kt @@ -8,9 +8,9 @@ import eu.kanade.tachiyomi.BuildConfig import eu.kanade.tachiyomi.CustomRobolectricGradleTestRunner import eu.kanade.tachiyomi.data.database.models.Chapter import eu.kanade.tachiyomi.data.database.models.Manga -import eu.kanade.tachiyomi.data.source.SourceManager -import eu.kanade.tachiyomi.data.source.model.SChapter -import eu.kanade.tachiyomi.data.source.online.OnlineSource +import eu.kanade.tachiyomi.source.SourceManager +import eu.kanade.tachiyomi.source.model.SChapter +import eu.kanade.tachiyomi.source.online.OnlineSource import org.assertj.core.api.Assertions.assertThat import org.junit.Before import org.junit.Test