diff --git a/src/de/einfach/AndroidManifest.xml b/src/de/einfach/AndroidManifest.xml
new file mode 100644
index 000000000..da5c29686
--- /dev/null
+++ b/src/de/einfach/AndroidManifest.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/de/einfach/build.gradle b/src/de/einfach/build.gradle
new file mode 100644
index 000000000..87115650b
--- /dev/null
+++ b/src/de/einfach/build.gradle
@@ -0,0 +1,25 @@
+plugins {
+ alias(libs.plugins.android.application)
+ alias(libs.plugins.kotlin.android)
+}
+
+ext {
+ extName = 'Einfach'
+ pkgNameSuffix = 'de.einfach'
+ extClass = '.Einfach'
+ extVersionCode = 1
+ libVersion = '13'
+}
+
+dependencies {
+ implementation(project(":lib-dood-extractor"))
+ implementation(project(":lib-filemoon-extractor"))
+ implementation(project(":lib-mixdrop-extractor"))
+ implementation(project(":lib-playlist-utils"))
+ implementation(project(":lib-streamtape-extractor"))
+ implementation(project(":lib-streamwish-extractor"))
+ implementation(project(":lib-voe-extractor"))
+ implementation("dev.datlag.jsunpacker:jsunpacker:1.0.1")
+}
+
+apply from: "$rootDir/common.gradle"
diff --git a/src/de/einfach/res/mipmap-hdpi/ic_launcher.png b/src/de/einfach/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 000000000..76d30abe6
Binary files /dev/null and b/src/de/einfach/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/src/de/einfach/res/mipmap-mdpi/ic_launcher.png b/src/de/einfach/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 000000000..fd4493044
Binary files /dev/null and b/src/de/einfach/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/src/de/einfach/res/mipmap-xhdpi/ic_launcher.png b/src/de/einfach/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 000000000..7401c072a
Binary files /dev/null and b/src/de/einfach/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/src/de/einfach/res/mipmap-xxhdpi/ic_launcher.png b/src/de/einfach/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 000000000..9e44d1e1a
Binary files /dev/null and b/src/de/einfach/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/src/de/einfach/res/mipmap-xxxhdpi/ic_launcher.png b/src/de/einfach/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 000000000..399267758
Binary files /dev/null and b/src/de/einfach/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/src/de/einfach/src/eu/kanade/tachiyomi/animeextension/de/einfach/Einfach.kt b/src/de/einfach/src/eu/kanade/tachiyomi/animeextension/de/einfach/Einfach.kt
new file mode 100644
index 000000000..f86c3ef90
--- /dev/null
+++ b/src/de/einfach/src/eu/kanade/tachiyomi/animeextension/de/einfach/Einfach.kt
@@ -0,0 +1,310 @@
+package eu.kanade.tachiyomi.animeextension.de.einfach
+
+import android.app.Application
+import android.util.Base64
+import androidx.preference.ListPreference
+import androidx.preference.MultiSelectListPreference
+import androidx.preference.PreferenceScreen
+import eu.kanade.tachiyomi.animeextension.de.einfach.extractors.MyStreamExtractor
+import eu.kanade.tachiyomi.animeextension.de.einfach.extractors.UnpackerExtractor
+import eu.kanade.tachiyomi.animeextension.de.einfach.extractors.VidozaExtractor
+import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
+import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
+import eu.kanade.tachiyomi.animesource.model.AnimesPage
+import eu.kanade.tachiyomi.animesource.model.SAnime
+import eu.kanade.tachiyomi.animesource.model.SEpisode
+import eu.kanade.tachiyomi.animesource.model.Video
+import eu.kanade.tachiyomi.animesource.online.ParsedAnimeHttpSource
+import eu.kanade.tachiyomi.lib.doodextractor.DoodExtractor
+import eu.kanade.tachiyomi.lib.filemoonextractor.FilemoonExtractor
+import eu.kanade.tachiyomi.lib.mixdropextractor.MixDropExtractor
+import eu.kanade.tachiyomi.lib.streamtapeextractor.StreamTapeExtractor
+import eu.kanade.tachiyomi.lib.streamwishextractor.StreamWishExtractor
+import eu.kanade.tachiyomi.lib.voeextractor.VoeExtractor
+import eu.kanade.tachiyomi.network.GET
+import eu.kanade.tachiyomi.network.asObservableSuccess
+import eu.kanade.tachiyomi.util.asJsoup
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.async
+import kotlinx.coroutines.awaitAll
+import kotlinx.coroutines.runBlocking
+import okhttp3.Response
+import org.jsoup.Jsoup
+import org.jsoup.nodes.Document
+import org.jsoup.nodes.Element
+import rx.Observable
+import uy.kohesive.injekt.Injekt
+import uy.kohesive.injekt.api.get
+import java.text.SimpleDateFormat
+import java.util.Locale
+
+class Einfach : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
+
+ override val name = "Einfach"
+
+ override val baseUrl = "https://einfach.to"
+
+ override val lang = "de"
+
+ override val supportsLatest = true
+
+ private val preferences by lazy {
+ Injekt.get().getSharedPreferences("source_$id", 0x0000)
+ }
+
+ // ============================== Popular ===============================
+ // Actually the source doesn't provide a popular entries page, and the
+ // "sort by views" filter isn't working, so we'll use the latest series updates instead.
+ override fun popularAnimeRequest(page: Int) = GET("$baseUrl/series/page/$page")
+
+ override fun popularAnimeSelector() = "article.box > div.bx > a.tip"
+
+ override fun popularAnimeFromElement(element: Element) = SAnime.create().apply {
+ setUrlWithoutDomain(element.attr("href"))
+ title = element.attr("title")
+ thumbnail_url = element.selectFirst("img")?.run {
+ absUrl("data-lazy-src").ifEmpty { absUrl("src") }
+ }
+ }
+
+ override fun popularAnimeNextPageSelector() = "div.pagination > a.next"
+
+ // =============================== Latest ===============================
+ override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/filme/page/$page")
+
+ override fun latestUpdatesSelector() = popularAnimeSelector()
+
+ override fun latestUpdatesFromElement(element: Element) = popularAnimeFromElement(element)
+
+ override fun latestUpdatesNextPageSelector() = popularAnimeNextPageSelector()
+
+ // =============================== Search ===============================
+ override fun fetchSearchAnime(page: Int, query: String, filters: AnimeFilterList): Observable {
+ return if (query.startsWith(PREFIX_SEARCH)) { // URL intent handler
+ val path = query.removePrefix(PREFIX_SEARCH)
+ client.newCall(GET("$baseUrl/$path"))
+ .asObservableSuccess()
+ .map(::searchAnimeByPathParse)
+ } else {
+ super.fetchSearchAnime(page, query, filters)
+ }
+ }
+
+ private fun searchAnimeByPathParse(response: Response): AnimesPage {
+ val details = animeDetailsParse(response.use { it.asJsoup() })
+ return AnimesPage(listOf(details), false)
+ }
+
+ override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList) =
+ GET("$baseUrl/page/$page/?s=$query")
+
+ override fun searchAnimeSelector() = popularAnimeSelector()
+
+ override fun searchAnimeFromElement(element: Element) = popularAnimeFromElement(element)
+
+ override fun searchAnimeNextPageSelector() = popularAnimeNextPageSelector()
+
+ // =========================== Anime Details ============================
+ override fun animeDetailsParse(document: Document) = SAnime.create().apply {
+ val info = document.selectFirst("article div > div.infl")!!
+ title = info.selectFirst("h1.entry-title")!!.text()
+ thumbnail_url = info.selectFirst("img")?.run {
+ absUrl("data-lazy-src").ifEmpty { absUrl("src") }
+ }
+
+ artist = info.getInfo("Stars:")
+ genre = info.getInfo("Genre:")
+ author = info.getInfo("Network:")
+ status = parseStatus(info.getInfo("Status:").orEmpty())
+
+ description = info.selectFirst("div.entry-content > p")?.ownText()
+ }
+
+ private fun Element.getInfo(label: String) =
+ selectFirst("li:has(b:contains($label)) > span.colspan")?.text()?.trim()
+
+ private fun parseStatus(status: String) = when (status) {
+ "Ongoing" -> SAnime.ONGOING
+ else -> SAnime.COMPLETED
+ }
+
+ // ============================== Episodes ==============================
+ override fun fetchEpisodeList(anime: SAnime): Observable> {
+ if (anime.url.contains("/filme/")) {
+ val episode = SEpisode.create().apply {
+ url = anime.url
+ name = "Movie - ${anime.title}"
+ episode_number = 1F
+ }
+ return Observable.just(listOf(episode))
+ }
+
+ return super.fetchEpisodeList(anime)
+ }
+
+ override fun episodeListParse(response: Response) =
+ super.episodeListParse(response).reversed()
+
+ override fun episodeListSelector() = "div.epsdlist > ul > li > a"
+
+ override fun episodeFromElement(element: Element) = SEpisode.create().apply {
+ setUrlWithoutDomain(element.attr("href"))
+ val eplnum = element.selectFirst(".epl-num")?.text().orEmpty().trim()
+ episode_number = eplnum.substringAfterLast(" ").toFloatOrNull() ?: 1F
+
+ name = eplnum.ifBlank { "S1 EP 1" } + " - " + element.selectFirst(".epl-title")?.text().orEmpty()
+ date_upload = element.selectFirst(".epl-date")?.text().orEmpty().toDate()
+ }
+
+ // ============================ Video Links =============================
+ override fun videoListParse(response: Response): List