diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml new file mode 100644 index 0000000..d79c722 --- /dev/null +++ b/.github/workflows/docs.yaml @@ -0,0 +1,33 @@ +name: Generate docs + +on: + push: + branches: + - main + paths-ignore: + - '**.md' + - '**.yaml' + workflow_dispatch: + +permissions: + contents: write +jobs: + publish: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Set up JDK + uses: actions/setup-java@v3 + with: + java-version: 11 + distribution: adopt + + - name: Build documentation + run: ./gradlew :library:dokkaHtml + + - name: Publish documentation + uses: JamesIves/github-pages-deploy-action@releases/v4 + with: + BRANCH: gh-pages + FOLDER: library/build/docs diff --git a/build.gradle b/build.gradle index c3b9582..d4caf87 100644 --- a/build.gradle +++ b/build.gradle @@ -15,6 +15,10 @@ buildscript { } } +plugins { + id 'org.jetbrains.dokka' version '1.7.20' apply false +} + allprojects { repositories { mavenCentral() diff --git a/library/Module.md b/library/Module.md new file mode 100644 index 0000000..710af7f --- /dev/null +++ b/library/Module.md @@ -0,0 +1,30 @@ +# Module extensions-lib +The Aniyomi API exposed to extensions via stubs. + +# Package androidx.preference +Android preferences classes exposed to extensions. + +## NOTE +Not all classes from [*androidx.preference*](https://developer.android.com/reference/androidx/preference/package-summary) package +were implemented as stubs, so if you need to use a non-implemented class, you must add the following into the extension's build.gradle file: +```kotlin +dependencies { + compileOnly("androidx.preference:preference-ktx:1.2.0") +} +``` + +# Package eu.kanade.tachiyomi.animesource +Classes and interfaces for more complex extensions, like ones with multiple sources or user preferences. + +# Package eu.kanade.tachiyomi.animesource.model +Required data classes to interact with Aniyomi. + +# Package eu.kanade.tachiyomi.animesource.online +Single-source creation classes, sufficient to most extensions. + +# Package eu.kanade.tachiyomi.network +Useful methods for creating http requests and manipulate responses. + +# Package eu.kanade.tachiyomi.network.interceptor +Useful methods to slow down http requests and prevent IP-ban or accidental DDoS. + diff --git a/library/build.gradle b/library/build.gradle index 8c9ee38..ce81de0 100644 --- a/library/build.gradle +++ b/library/build.gradle @@ -1,7 +1,11 @@ +import org.jetbrains.dokka.gradle.DokkaTask +import org.jetbrains.dokka.DokkaConfiguration.Visibility + plugins { id 'com.android.library' id 'kotlin-android' id 'maven-publish' + id 'org.jetbrains.dokka' } android { @@ -33,6 +37,44 @@ task androidSourcesJar(type: Jar) { from android.sourceSets.main.java.srcDirs } +tasks.withType(DokkaTask.class) { + dokkaSourceSets { + named("main") { + moduleName.set("extensions-lib") + moduleVersion.set(android.defaultConfig.versionName) + outputDirectory.set(file("build/docs/")) + // Speedup doc generation + // offlineMode.set(true) + includes.from("Module.md") + + perPackageOption { + matchingRegex.set("android.content") + suppress.set(true) + } + + documentedVisibilities.set([ + Visibility.PUBLIC, + Visibility.PROTECTED + ]) + + externalDocumentationLink { + url.set(new URL("https://square.github.io/okhttp/4.x/")) + // https://github.com/square/okhttp/issues/7338 + packageListUrl.set(new URL("https://colinwhite.me/okhttp3-package-list")) + } + + externalDocumentationLink { + url.set(new URL("https://jsoup.org/apidocs/")) + packageListUrl.set(new URL("https://jsoup.org/apidocs/element-list")) + } + + externalDocumentationLink { + url.set(new URL("https://reactivex.io/RxJava/1.x/javadoc/")) + } + } + } +} + project.afterEvaluate { publishing { publications { diff --git a/library/src/main/java/eu/kanade/tachiyomi/AppInfo.kt b/library/src/main/java/eu/kanade/tachiyomi/AppInfo.kt index a759b6b..553f5d7 100644 --- a/library/src/main/java/eu/kanade/tachiyomi/AppInfo.kt +++ b/library/src/main/java/eu/kanade/tachiyomi/AppInfo.kt @@ -1,5 +1,9 @@ package eu.kanade.tachiyomi +/** + * Info about the installed aniyomi app (NOT THE EXTENSION!). + * Can be useful for temporary fixes, preferences, header values, logging, etc. + */ object AppInfo { fun getVersionCode(): Int = throw Exception("Stub!") fun getVersionName(): String = throw Exception("Stub!") diff --git a/library/src/main/java/eu/kanade/tachiyomi/animesource/AnimeSourceFactory.kt b/library/src/main/java/eu/kanade/tachiyomi/animesource/AnimeSourceFactory.kt index ca5755b..8b03648 100644 --- a/library/src/main/java/eu/kanade/tachiyomi/animesource/AnimeSourceFactory.kt +++ b/library/src/main/java/eu/kanade/tachiyomi/animesource/AnimeSourceFactory.kt @@ -1,7 +1,30 @@ package eu.kanade.tachiyomi.animesource /** - * A factory for creating sources at runtime. + * A factory for creating multiple sources at runtime. Use this in case of a source + * that supports multiple languages or mirrors of the same website. + * + * **Note:** Your animesource factory classname must be used in the `extClass` param + * of your `build.gradle` file instead of the true animesource class. + * + * **Example usage:** + * ```kotlin + * class SomeSourceFactory : AnimeSourceFactory { + * override fun createSources() = listOf( + * SomeSource("SomeSource ENG", "en", "https://somesource.en"), + * SomeSource("SomeSource ESP", "es", "https://somesource.es"), + * SomeSource("SomeSource RUS", "ru", "https://somesource.ru"), + * ) + * } + * + * class SomeSource( + * override val name: String, + * override val lang: String, + * override val baseUrl: String, + * ) : ParsedAnimeHttpSource() { + * // some code + * } + * ``` */ interface AnimeSourceFactory { /** diff --git a/library/src/main/java/eu/kanade/tachiyomi/animesource/ConfigurableAnimeSource.kt b/library/src/main/java/eu/kanade/tachiyomi/animesource/ConfigurableAnimeSource.kt index 00deb11..ef4fbc7 100644 --- a/library/src/main/java/eu/kanade/tachiyomi/animesource/ConfigurableAnimeSource.kt +++ b/library/src/main/java/eu/kanade/tachiyomi/animesource/ConfigurableAnimeSource.kt @@ -2,8 +2,64 @@ package eu.kanade.tachiyomi.animesource import androidx.preference.PreferenceScreen +/** + * A interface to add user preferences to the source. + * + * The usual implementation looks like this: + * ``` + * import android.app.Application + * import android.content.SharedPreferences + * import androidx.preference.PreferenceScreen + * import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource + * import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource + * import uy.kohesive.injekt.Injekt + * import uy.kohesive.injekt.api.get + * // some other imports... + * + * class SomeSource : ConfigurableAnimeSource, AnimeHttpSource() { + * // some code... + * + * private val preferences: SharedPreferences by lazy { + * Injekt.get().getSharedPreferences("source_$id", 0x0000) + * } + * + * override fun setupPreferenceScreen(screen: PreferenceScreen) { + * // some preferences... + * } + * } + * ``` + */ interface ConfigurableAnimeSource { + /** + * Implementations must override this method to add the user preferences. + * + * You can use some stubbed inheritors of [androidx.preference.Preference] here. + * + * **Common usage example:** + * ``` + * // ============================== Settings ============================== + * override fun setupPreferenceScreen(screen: PreferenceScreen) { + * val videoQualityPref = ListPreference(screen.context).apply { + * key = PREF_QUALITY_KEY // String, like "pref_quality" + * title = PREF_QUALITY_TITLE // String, like "Preferred quality:" + * entries = PREF_QUALITY_ENTRIES // Array, like arrayOf("240p", "720p"...) + * // Another Array. Can be different from the property above, as long it have the same size + * // and equivalent values per index. + * entryValues = PREF_QUALITY_VALUES + * setDefaultValue(PREF_QUALITY_DEFAULT) + * summary = "%s" + * setOnPreferenceChangeListener { _, newValue -> + * val selected = newValue as String + * val index = findIndexOfValue(selected) + * val entry = entryValues[index] as String + * preferences.edit().putString(key, entry).commit() + * } + * } + * screen.addPreference(videoQualityPref) + * } + * ``` + */ fun setupPreferenceScreen(screen: PreferenceScreen) } diff --git a/library/src/main/java/eu/kanade/tachiyomi/animesource/model/AnimeFilter.kt b/library/src/main/java/eu/kanade/tachiyomi/animesource/model/AnimeFilter.kt index 9246030..b92134a 100644 --- a/library/src/main/java/eu/kanade/tachiyomi/animesource/model/AnimeFilter.kt +++ b/library/src/main/java/eu/kanade/tachiyomi/animesource/model/AnimeFilter.kt @@ -1,11 +1,45 @@ package eu.kanade.tachiyomi.animesource.model sealed class AnimeFilter(val name: String, var state: T) { + /** + * A simple header. Useful for separating sections in the list or showing any note or warning to the user. + */ open class Header(name: String) : AnimeFilter(name, 0) + + /** + * A line separator. Useful for visual distinction between sections. + */ open class Separator(name: String = "") : AnimeFilter(name, 0) + + /** + * A select control, similar to HTML's ``. + * + * @param name The placeholder text. + * @param state The text written on it. + */ abstract class Text(name: String, state: String = "") : AnimeFilter(name, state) + + /** + * A checkbox control, similar to HTML's ``. + * + * @param name The checkbox text + * @param state A boolean that will be `true` if it's checked. + */ abstract class CheckBox(name: String, state: Boolean = false) : AnimeFilter(name, state) + + /** + * A enhanced checkbox control that supports an excluding state. + * The state can be compared with `STATE_IGNORE`, `STATE_INCLUDE` and `STATE_EXCLUDE` constants of the class. + */ abstract class TriState(name: String, state: Int = STATE_IGNORE) : AnimeFilter(name, state) { fun isIgnored() = state == STATE_IGNORE fun isIncluded() = state == STATE_INCLUDE @@ -17,11 +51,23 @@ sealed class AnimeFilter(val name: String, var state: T) { const val STATE_EXCLUDE = 2 } } + + /** + * A group of filters. + * Usually used for multiple related [CheckBox]/[TriState] instances, like in a genres filter + * + * @param name The filter group name + * @param state a `List` with all the states. + */ abstract class Group(name: String, state: List): AnimeFilter>(name, state) + /** + * A control for sorting, with support for the ordering. + * The state indicates which item index is selected and if the sorting is ascending. + */ abstract class Sort(name: String, val values: Array, state: Selection? = null) : AnimeFilter(name, state) { data class Selection(val index: Int, val ascending: Boolean) } -} \ No newline at end of file +} diff --git a/library/src/main/java/eu/kanade/tachiyomi/animesource/model/SAnime.kt b/library/src/main/java/eu/kanade/tachiyomi/animesource/model/SAnime.kt index 21226a8..f9c6bc0 100644 --- a/library/src/main/java/eu/kanade/tachiyomi/animesource/model/SAnime.kt +++ b/library/src/main/java/eu/kanade/tachiyomi/animesource/model/SAnime.kt @@ -12,14 +12,27 @@ interface SAnime { var description: String? + /** + * A string containing list of all genres separated with `", "` + */ var genre: String? + /** + * An "enum" value. Refer to the values in the [SAnime companion object](https://github.com/jmir1/extensions-lib/blob/a2afb04d892e94d21cd4ade7094dca27f4c0c180/library/src/main/java/eu/kanade/tachiyomi/animesource/model/SAnime.kt#L25). + */ var status: Int var thumbnail_url: String? + /** + * Useful to exclude animes/movies that will always only have the same episode list + * from the global updates. + */ var update_strategy: UpdateStrategy + /** + * Tells the app if it should call [fetchAnimeDetails]. + */ var initialized: Boolean companion object { @@ -36,4 +49,4 @@ interface SAnime { } } -} \ No newline at end of file +} diff --git a/library/src/main/java/eu/kanade/tachiyomi/animesource/model/Video.kt b/library/src/main/java/eu/kanade/tachiyomi/animesource/model/Video.kt index f35c5cd..e3082fc 100644 --- a/library/src/main/java/eu/kanade/tachiyomi/animesource/model/Video.kt +++ b/library/src/main/java/eu/kanade/tachiyomi/animesource/model/Video.kt @@ -3,8 +3,14 @@ package eu.kanade.tachiyomi.animesource.model import android.net.Uri import okhttp3.Headers +/** + * A sub/dub track. + */ data class Track(val url: String, val lang: String) +/** + * The instance that contains the data needed to watch a video. + */ data class Video(val url: String, val quality: String, var videoUrl: String?, diff --git a/library/src/main/java/eu/kanade/tachiyomi/animesource/online/AnimeHttpSource.kt b/library/src/main/java/eu/kanade/tachiyomi/animesource/online/AnimeHttpSource.kt index 0972802..8e0dbcb 100644 --- a/library/src/main/java/eu/kanade/tachiyomi/animesource/online/AnimeHttpSource.kt +++ b/library/src/main/java/eu/kanade/tachiyomi/animesource/online/AnimeHttpSource.kt @@ -11,6 +11,7 @@ import rx.Observable /** * A simple implementation for sources from a website. + * Usually requires the usage of json serialization or similar techniques. */ @Suppress("unused", "unused_parameter") abstract class AnimeHttpSource : AnimeCatalogueSource { @@ -34,22 +35,48 @@ abstract class AnimeHttpSource : AnimeCatalogueSource { /** * 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 + * Implementations MUST ONLY override this property to use a hardcoded ID when the source + * name or language were changed. * Note the generated id sets the sign bit to 0. */ override val id: Long = throw Exception("Stub!") /** - * Headers used for requests. + * Headers used for requests. Result of [headersBuilder] */ val headers: Headers = throw Exception("Stub!") /** - * Default network client for doing requests. + * Default network client for doing requests. Implementations can override this property + * for custom [OkHttpClient] instances. + * + * **Usage example:** + * ``` + * import okhttp3.Dns + * ..... + * override val client: OkHttpClient = + * network.client + * .newBuilder() + * .addInterceptor(RecaptchaDestroyer()) + * .dns(Dns.SYSTEM) + * .build() + * ``` */ open val client: OkHttpClient = throw Exception("Stub!") /** * Headers builder for requests. Implementations can override this method for custom headers. + * + * **Usage examples:** + * ``` + * // Adds headers to the default [Headers.Builder] instance, retaining + * // headers like the default(or user-made) User-Agent. + * override fun headersBuilder() = super.headersBuilder().add("Referer", baseUrl) + * ``` + * ``` + * // Creates a new, empty [Headers.Builder] instance and adds a single header. + * override fun headersBuilder() = Headers.Builder().add("Referer", baseUrl) + * ``` */ protected open fun headersBuilder(): Headers.Builder { throw Exception("Stub!") @@ -88,7 +115,8 @@ abstract class AnimeHttpSource : AnimeCatalogueSource { /** * Returns an observable containing a page with a list of anime. Normally it's not needed to - * override this method. + * override this method, but can be useful to change the usual workflow and use functions with + * different signatures from [searchAnimeRequest] or [searchAnimeParse]. * * @param page the page number to retrieve. * @param query the search query. @@ -99,7 +127,7 @@ abstract class AnimeHttpSource : AnimeCatalogueSource { } /** - * Returns the request for the search anime given the page. + * Returns the request for the search anime given the page and filters. * * @param page the page number to retrieve. * @param query the search query. @@ -174,6 +202,12 @@ abstract class AnimeHttpSource : AnimeCatalogueSource { throw Exception("Stub!") } + /** + * Returns an observable with the video list for an episode. Normally it's not needed to + * override this method. + * + * @param episode the episode to look for videos. + */ override fun fetchVideoList(episode: SEpisode): Observable> { throw Exception("Stub!") } @@ -219,18 +253,42 @@ abstract class AnimeHttpSource : AnimeCatalogueSource { } /** - * Sorts the video list - * Override this according to the user's preference + * Sorts the video list. + * Override this according to the user's preference. + * + * **Usage examples:** + * ``` + * // Sorts by quality + * override fun List