Bn_mUB(Oeu=r6PS9x8>077SFEr>CP0Ix9!K=JNTpWNz-iW;&
zURi{I|CIBX0=buttVu;HykYy#Pvj?cSkg#EvDh2;?t3RydwA=`TY=(5ccH1pcZOFw
zT?|WNzB?awTlS=Oy{~ob9cdKPYvJF-==~y7oZzS?<709o9v25RGShu*nQ6Li-;iWo
z>5rPszbGStNj!G{U#Zz0&)b5jD4_44fDOAydXu0}1
ztUH{v{HC$nRqJn#yi^P=(IP5n(+1$(tFOA5cMIF{p!W$qka1jJM(xiNAADwn0`-ST
z?H3(9ZkHVVe7j2Yer^E##C>Q(#?RZ)9)JozEeVO_xB|b(h2sZ|~*BJlCvBns#+}
zz0Os3TTZN_!4wYE%m?iFc?uUw&+F}Jm1lpNS(2f)meyRhU6jH0G*mDV16kUg@Y*@z
z83pLoth2>Fd@(ZF%zsu;4iy6x%3)cPzvnR@6^c)_v3cE#II&+#aM*XyKi(r;>brpj
zY`K_>z7Sb(MoxF1v!kpUB<1!ON?jDE0h#9|4nSK7fL)dV```xD
zXtr0DD?iqzjhr-wAU6D8!;9=t3{N3hTqXAjO%8hpuni6$oz0uHjHX}E23|ym!v1DO$|a`Q&%iTJ9{)QqYWO=BPo@gCnVyB7%$rNSJ$wS7
z_vDQRb=p7HCDy*%WSlImd{1QJGYzkU81Zyye5XFUnP&ta_`wj9X(%p
zxLRJuF7D0z*#6a7_pTlNulPaZ{d^J;SOJ({Ohjq%8Ub|af_i6q@iMDh))>xbyLuA{
zm0~zHcFyVqo}Qan4JS#466RTa`lduMoVEx-EMt1abq}n#FhkXmIOkZAFd~Nz_N{@Z
zeuKzqp0MrG?3=VOG6^t;&bkMViqr^B)v#hYM3+nj!WjRe#*U&M-DJU~X^
zh0+gw$WNt|bnaJz@e8K0PUD`mW1?;omt3nWI4@r?fzpaNhPv
zOE&yW9ms+UZqIuR`3yy1!7-rx*~-2bQ-;ki3M>2$-lOo9Vn4&iq0#|_Y9_}^e=EBV
zF*T$3TU#FvoR&UBZ~*5^XfV4mpRGJw0LmAG11eqQ6?bBUF#GkdXW`2Gts2NeMP9%c
zU`As?^+Kd>J8VRy2~4|4A(!RFg_*dt3Q>Z1g}W6QGA4!o3EnTubEqoN>XN6W&-top
zeORV^Z)NPIZc)5Tey96SPRqkPT^-t2cOKNtZgqDYk+h#iqD7z)C_|t=fzIL`R|}(DqX=;}_d)^jT#>9T9L2t9@Y=BKHwuu6x1I@ac@(HdXQ}D`l@mi?%Rb!z6-C
zZ}Y)p^j7>ZFRJNG&tv+#etoB?z0?6t%G9DtZIx5r3NtS|(W*w=cIYBk@IC!i%Ze!j
zO}vE^&v-7FP@FSw6w@22kEYD
zDw@|eZ!O51XfMxJ36)~#(#Mnu^8Fv9cAuNy4GO@N=ow~>U1wYHIYg|jS%y#Z6K|~Q
zBan5U<}r`PU;;oyQe=+|wt)p`OZ!zF!4CNFn!bNXcxirIr1O)JAd)3TSz7|MPJTS{o4nbW(7#$#EB1In>{z#^+ECV*?%(;v
z+@g=}@*pml&i$_iauaJL76)3*gL5sjyXA_G+3Ldmq@LDA%}H#ku&}T<4CMnVG>+k!
z?CRI!=!lq&qJ#WoJNPr{oHv)dyA1D83nj>#AIP}htY5)X|I^JU{rXZguB&GEi`02G
z#5{yCSNV@@hICK&C6@#%4qAv8!A9@$$0Guq$BM=GC3kMTg3*{=>Mz=&`0X!Q$60$j
zOv+1!0~tF4QFtk&?0>|Zl+gjQ96}Iwu=W%Cq3xqg#SXTNHZ@6|69(JPSA7b4VI-W2
z$CJvxhuV^g@TSCC6a5dlNW2!qNr=sZK9FxBkKtk(P&e5M@r2`dk?u?;5S2j7OJ8ha5c;$R9N0c2?&3?ZF@kh(7zh0R5N>h4&`
z>l`%TmAdW*iNmJa%@wUwyp&*5oFZNcn*=xX?%`J*Y17%nK=-llo??R^jOooHB1=u!
z**f)`n?x*Qe$uFlZJwX1m5OcU4#zB5(U)t*-9>zNi7U3bybRr^tA`=c%+;aNF84M{
z?mry=E!e*Km_Xy@Lv1O)Yp8CP`#}K4hW92+9WzSbhUhgB)ldo<1TeiSL++xa
zZlT)B?o=B4MT5#IT3zxdLBrv$%s2Hc;5s9(QRvgK_CVTYQ+KUX?d^xYSgOR2;gg7@54wJJc>`a5
z#76_&5NwH%>eW)In$oh|^lNw6r?mB^k?z)l-1ND>$Bw#mdOwvf*5Zd%U#@sy*{$dP+SZ9(0-IxFcM~|YWmExDJ1l;x
zwk&&dT6#1bv6lIs4Zo5oL+B-D1Kg^Qu`G*uSa0J;DJZ0sv55gcJ5#=h(X7sm#*O;U
zYPNtU7~kPV*OFwV}zkNY>u~Jg$sJDp4~ZM&7X6ca{7H#%`V>!)%w0~8C9SR
zqE6P2LSEyNdz^auN5P57P?ak?Vf5P%3$(e8XrPlwG9%^lEhslqYgfTB+zVFsD3rgn
zQ}my;k=J>fFk-R0>UNk*BH4WvC9X@d+nkuJ1XzgcEvj^!ToRyt!`Blf2UbeJf0Sj~
zQJb1~cYIyR#Lb8~YT$b4)9*fZjV&v5l$gdU$cpN$uuINy%IUY!9$Bs=j(kCpdmNu1
zAxO~3PLY|<7m`vuxr&LXBAfi#F~Ki6^;F3%`hNdPltWpe9$YvKeG|;S`!@AaxBvav
z)#1J?9jPl@-{`x}P44mc<3ImLW<>jS1UC4{IUcfKB%pq?mY3Y`t}uf*exqJv)~4`N
zzr7~Kbxcd2Qs-LTOMNH5svPDpqurT#PbE{LZ5Zxyq717A0ORw&UVy30s5JJb0*NQ*
zx^Fm4CLPTT-|r_RIDq41$;}O#HmlX32m*=MHStN+Z}6o%r>~MTly_hgHthMykACY~
zWD4Nks~_K@NDk0~gl{S#ZOe?A367)tcEN*u
z9b}{3;GYrys5o)XbAvzQ{orTcfC+W;&?|LZ9;^rGAAuJIcAQ2I<(iny&c+SExmmg9
zbmJc8m6+y~aPjT*Ti(Z6D{5<~=8$CYPZIFtEP1^prHZ$QHWo=?MDd4{MRaam^`eQu
zGph*YF5*s37S<
zPI~!7{>NX8rjGX8d>8CzdEEC&$C~Ujymx6B=6SSvNMl-zH!6fJqdTZYv`B8A3p&15
zQb7Xzz!rehe5>S#x!&;p_x;-`OCg=Hvp-1<9<^_^NS$zn?kkSA=Am)Dx7TmakL-8q
zCl_f>3hN#)MuWoE)TTRMTI<8veG4S$ngfYrcerAN9=SZBkjxWXW=swLhoXXW-R8}>
zav?yl-BBOw$jtnY$sB%kxN}u|YbY{2WP=Ly!U7$hthLbZmv*X7_JLjnyuM(=B~acE
z^UN~t2hKEMh={n>j+H=!a;A`b^jjGFHVN_F5Q-N>6d5xHR7d=F%52cO3
zpUHt)n((EbUc!WSnIcuepz;!4?y2SU)((HKi<
zph(ifF5x9&(FjQt25WDe$z5{$zjly^^b8*ob((ks46}WVS<7Ck20oU5P|7`K>GnN9
z*S!Z{eF9DTYU7BLL^v~>7g-hmStc2Kw}hAaxsF%vX?|*1YSDKab`l=2@HFT9t;wYw
zCztJqf_wV{og0=EP8nquYA`i5RX>Y19(-!jP5&WWxXERmj4p5hbt|Y62P&$EOde{(+!-oDKAjAeB0>dqu~h!WN=$pA
zs3|>1=snp*qEVem`$aA}r(N0GqVE(3nWkdjTxXhGPxV&_`&XZpMwoe;Q)mCZE+pm8
zU?;ym?qH64ESz5>y#1eC*B>4pI_6^Fo^Euo8(&y3_k_-bWSntyq7Wn{B^_OypYJc@
z1Bw79uoCd3E;@h%9{5;JZ_Z_ZW*BO|7~?eMU^RQJIOddncI+rT^4pPi`RIWVB4&%G
z1VwkHhh(YcNk10#2qC+nc(mTu@89p;h`rnPJ~6qhRe4#(G`{)D&6}s-vOO3bk(Hmn
zIV}VnR0Ct-8&6Sn0Ti_eV+E3xoo!M0*qkDTiv*LiSy1|Q-|c3H&}II}I1+*b^Nfg!
zZuJg8)YQ~?_;P5oA&x2_k%ctXXfjGAXfh-+glU|gOG`>VY+a#K#dJen(4cL}6<|~1
zP3rYBS)Z+p?1}gr04~t>W$m5sERu`@v`P$$y^8tRKBGk$^MvVzaW^UF4BuYZdj~=T
zgCYmV{RjNFM~Ie@JC9?8KcFa-?vGG1z-mKw)i%ohi+eiG&{X$Ux+*7Ut)Zyn_eqzX
zez{d+Jno79q+-Dc|HY7wbzyeH
zQ=8;+zh^jAdKsk5o1drS6=r?C*@88Th-S3WKJ91tb
z7PiJ|LJgK{0F-9OED)HQlK!!`QGO^}wH@spTLEb8Zo+Af-)iE_E}}^(IESmV8~#hi
zre&$ZUzk3I7>yELZU$^2GB?Q)yXA%}YpCm2)GYyf6a9BD=y^cahY0QB*apcCdR%>d
zAUYbI%$YSsVI0ZY^F9*BfR$WxSmPRQ-&R!KprHo1#?YCgoW-ze3d5L{2BnU`2i2ZxGjR_uv<2q>rDy1?!Jg|7uuC{$&~9wo9+xj^=!j}3g4&l9WA9{c%V
ko4i^rk3-!5*CCvKY6*APhIB9Du|*N6C}_%;%b5rL54e#x@Bjb+
literal 0
HcmV?d00001
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