diff --git a/src/pl/desuonline/AndroidManifest.xml b/src/pl/desuonline/AndroidManifest.xml
new file mode 100644
index 000000000..0978de1ae
--- /dev/null
+++ b/src/pl/desuonline/AndroidManifest.xml
@@ -0,0 +1,4 @@
+
+
+
diff --git a/src/pl/desuonline/build.gradle b/src/pl/desuonline/build.gradle
new file mode 100644
index 000000000..21169db53
--- /dev/null
+++ b/src/pl/desuonline/build.gradle
@@ -0,0 +1,12 @@
+apply plugin: 'com.android.application'
+apply plugin: 'kotlin-android'
+
+ext {
+ extName = 'desu-online'
+ pkgNameSuffix = 'pl.desuonline'
+ extClass = '.DesuOnline'
+ extVersionCode = 1
+ libVersion = '13'
+}
+
+apply from: "$rootDir/common.gradle"
diff --git a/src/pl/desuonline/res/mipmap-hdpi/ic_launcher.png b/src/pl/desuonline/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 000000000..53298291e
Binary files /dev/null and b/src/pl/desuonline/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/src/pl/desuonline/res/mipmap-mdpi/ic_launcher.png b/src/pl/desuonline/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 000000000..38c510159
Binary files /dev/null and b/src/pl/desuonline/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/src/pl/desuonline/res/mipmap-xhdpi/ic_launcher.png b/src/pl/desuonline/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 000000000..5e9ffea12
Binary files /dev/null and b/src/pl/desuonline/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/src/pl/desuonline/res/mipmap-xxhdpi/ic_launcher.png b/src/pl/desuonline/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 000000000..987160c05
Binary files /dev/null and b/src/pl/desuonline/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/src/pl/desuonline/res/mipmap-xxxhdpi/ic_launcher.png b/src/pl/desuonline/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 000000000..f3a06cc82
Binary files /dev/null and b/src/pl/desuonline/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/src/pl/desuonline/res/web_hi_res_512.png b/src/pl/desuonline/res/web_hi_res_512.png
new file mode 100644
index 000000000..641868dd6
Binary files /dev/null and b/src/pl/desuonline/res/web_hi_res_512.png differ
diff --git a/src/pl/desuonline/src/eu/kanade/tachiyomi/animeextension/pl/desuonline/DesuOnline.kt b/src/pl/desuonline/src/eu/kanade/tachiyomi/animeextension/pl/desuonline/DesuOnline.kt
new file mode 100644
index 000000000..b52c3eda6
--- /dev/null
+++ b/src/pl/desuonline/src/eu/kanade/tachiyomi/animeextension/pl/desuonline/DesuOnline.kt
@@ -0,0 +1,238 @@
+package eu.kanade.tachiyomi.animeextension.pl.desuonline
+
+import android.app.Application
+import android.content.SharedPreferences
+import android.util.Base64
+import androidx.preference.ListPreference
+import androidx.preference.PreferenceScreen
+import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
+import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
+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.network.GET
+import eu.kanade.tachiyomi.network.POST
+import eu.kanade.tachiyomi.util.asJsoup
+import kotlinx.serialization.decodeFromString
+import kotlinx.serialization.json.Json
+import kotlinx.serialization.json.JsonObject
+import kotlinx.serialization.json.jsonObject
+import kotlinx.serialization.json.jsonPrimitive
+import okhttp3.HttpUrl.Companion.toHttpUrl
+import okhttp3.MediaType.Companion.toMediaType
+import okhttp3.RequestBody
+import okhttp3.RequestBody.Companion.toRequestBody
+import okhttp3.Response
+import org.jsoup.nodes.Document
+import org.jsoup.nodes.Element
+import uy.kohesive.injekt.Injekt
+import uy.kohesive.injekt.api.get
+import java.text.SimpleDateFormat
+import java.util.Locale
+
+class DesuOnline : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
+
+ override val name = "desu-online"
+
+ override val baseUrl = "https://desu-online.pl"
+
+ override val lang = "pl"
+
+ override val supportsLatest = true
+
+ override val client = network.cloudflareClient
+
+ private val json = Json {
+ ignoreUnknownKeys = true
+ }
+
+ private val preferences: SharedPreferences by lazy {
+ Injekt.get().getSharedPreferences("source_$id", 0x0000)
+ }
+
+ // ============================== Popular ===============================
+
+ override fun popularAnimeRequest(page: Int) = GET("$baseUrl/anime/?page=$page&order=popular")
+
+ override fun popularAnimeSelector() = "div.listupd div.bsx > a"
+
+ override fun popularAnimeNextPageSelector() = "div.pagination > a.next, div.hpage > a.r"
+
+ override fun popularAnimeFromElement(element: Element): SAnime {
+ val animeTitle = element.select("div.tt > h2").text().trim()
+ val img = element.select("div.limit > img")
+ return SAnime.create().apply {
+ setUrlWithoutDomain(element.attr("href"))
+ title = animeTitle
+ thumbnail_url = img.attr("data-src")
+ }
+ }
+
+ // =========================== Anime Details ============================
+
+ override fun animeDetailsParse(document: Document): SAnime {
+ val img = document.select("div.thumb > img").ifEmpty { null }
+ val studio = document.select("div.info-content > div.spe > span:contains(Studio:)").ifEmpty { null }
+ val statusSpan = document.select("div.info-content > div.spe > span:contains(Status:)").ifEmpty { null }
+ val desc = document.select("div[itemprop=description] > p:last-child").ifEmpty { null }
+ val director = document.select("div.info-content > div.spe > span:contains(Reżyser:)").ifEmpty { null }
+ val genres = document.select("div.genxed > a")
+ return SAnime.create().apply {
+ title = document.select("h1.entry-title").text()
+ thumbnail_url = img?.attr("data-src")
+ author = studio?.text()?.substringAfter("Studio: ")
+ status = parseStatus(statusSpan?.text()?.substringAfter("Status: "))
+ description = desc?.text()?.trim()
+ artist = director?.text()?.substringAfter("Reżyser: ")
+ genre = genres.joinToString { it.text() }
+ }
+ }
+
+ // ============================== Episodes ==============================
+
+ override fun episodeListSelector() = "div.eplister > ul > li"
+
+ override fun episodeFromElement(element: Element): SEpisode {
+ val a = element.select("a")
+ val epNum = a.select("div.epl-num").text()
+ val epTitle = a.select("div.epl-title").text()
+ val date = a.select("div.epl-date").text()
+ return SEpisode.create().apply {
+ setUrlWithoutDomain(a.attr("href"))
+ name = "Odcinek $epNum: $epTitle"
+ date_upload = parseDate(date)
+ episode_number = epNum.substringBefore(" ").toFloatOrNull() ?: 0F
+ }
+ }
+
+ // ============================ Video Links =============================
+
+ override fun videoListParse(response: Response): List