diff --git a/lib/dataimage/build.gradle b/lib/dataimage/build.gradle
new file mode 100644
index 000000000..3fdb185de
--- /dev/null
+++ b/lib/dataimage/build.gradle
@@ -0,0 +1,30 @@
+apply plugin: 'com.android.library'
+apply plugin: 'kotlin-android'
+
+android {
+ compileSdkVersion 27
+ buildToolsVersion '29.0.3'
+
+ defaultConfig {
+ minSdkVersion 16
+ targetSdkVersion 27
+ versionCode 1
+ versionName '1.0.0'
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ }
+ }
+}
+
+repositories {
+ mavenCentral()
+}
+
+dependencies {
+ compileOnly "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
+ compileOnly 'com.squareup.okhttp3:okhttp:3.10.0'
+ compileOnly 'org.jsoup:jsoup:1.13.1'
+}
diff --git a/lib/dataimage/src/main/AndroidManifest.xml b/lib/dataimage/src/main/AndroidManifest.xml
new file mode 100644
index 000000000..11d9e3ff5
--- /dev/null
+++ b/lib/dataimage/src/main/AndroidManifest.xml
@@ -0,0 +1,2 @@
+
diff --git a/lib/dataimage/src/main/java/eu/kanade/tachiyomi/lib/dataimage/DataImageInterceptor.kt b/lib/dataimage/src/main/java/eu/kanade/tachiyomi/lib/dataimage/DataImageInterceptor.kt
new file mode 100644
index 000000000..df1cc81f6
--- /dev/null
+++ b/lib/dataimage/src/main/java/eu/kanade/tachiyomi/lib/dataimage/DataImageInterceptor.kt
@@ -0,0 +1,65 @@
+package eu.kanade.tachiyomi.lib.dataimage
+
+import android.util.Base64
+import okhttp3.Interceptor
+import okhttp3.MediaType
+import okhttp3.Protocol
+import okhttp3.Response
+import okhttp3.ResponseBody
+import org.jsoup.nodes.Element
+
+/**
+ * If a source provides images via a data:image string instead of a URL, use these functions and interceptor
+ */
+
+/**
+ * Use if the attribute tag could have a data:image string or URL
+ * Transforms data:image in to a fake URL that OkHttp won't die on
+ */
+fun Element.dataImageAsUrl(attr: String): String {
+ return if (this.attr(attr).startsWith("data")) {
+ "https://127.0.0.1/?" + this.attr(attr).substringAfter(":")
+ } else {
+ this.attr("abs:$attr")
+ }
+}
+
+/**
+ * Use if the attribute tag has a data:image string but real URLs are on a different attribute
+ */
+fun Element.dataImageAsUrlOrNull(attr: String): String? {
+ return if (this.attr(attr).startsWith("data")) {
+ "https://127.0.0.1/?" + this.attr(attr).substringAfter(":")
+ } else {
+ null
+ }
+}
+
+/**
+ * Interceptor that detects the URLs we created with the above functions, base64 decodes the data if necessary,
+ * and builds a response with a valid image that Tachiyomi can display
+ */
+class DataImageInterceptor : Interceptor {
+ private val mediaTypePattern = Regex("""(^[^;,]*)[;,]""")
+
+ override fun intercept(chain: Interceptor.Chain): Response {
+ val url = chain.request().url().toString()
+ return if (url.startsWith("https://127.0.0.1/?image")) {
+ val dataString = url.substringAfter("?")
+ val byteArray = if (dataString.contains("base64")) {
+ Base64.decode(dataString.substringAfter("base64,"), Base64.DEFAULT)
+ } else {
+ dataString.substringAfter(",").toByteArray()
+ }
+ val mediaType = MediaType.parse(mediaTypePattern.find(dataString)!!.value)
+ Response.Builder().body(ResponseBody.create(mediaType, byteArray))
+ .request(chain.request())
+ .protocol(Protocol.HTTP_1_0)
+ .code(200)
+ .message("")
+ .build()
+ } else {
+ chain.proceed(chain.request())
+ }
+ }
+}
diff --git a/settings.gradle b/settings.gradle
index 3cabe6210..dc61dc57a 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -4,6 +4,9 @@ project(':lib-ratelimit').projectDir = new File("lib/ratelimit")
include ':duktape-stub'
project(':duktape-stub').projectDir = new File("lib/duktape-stub")
+include ':lib-dataimage'
+project(':lib-dataimage').projectDir = new File("lib/dataimage")
+
new File(rootDir, "src").eachDir { dir ->
dir.eachDir { subdir ->
String name = ":${dir.name}-${subdir.name}"
diff --git a/src/tr/mangaship/build.gradle b/src/tr/mangaship/build.gradle
new file mode 100644
index 000000000..75a7fad96
--- /dev/null
+++ b/src/tr/mangaship/build.gradle
@@ -0,0 +1,17 @@
+apply plugin: 'com.android.application'
+apply plugin: 'kotlin-android'
+
+ext {
+ appName = 'Tachiyomi: Manga Ship'
+ pkgNameSuffix = 'tr.mangaship'
+ extClass = '.MangaShip'
+ extVersionCode = 1
+ libVersion = '1.2'
+}
+
+dependencies {
+ implementation project(':lib-dataimage')
+}
+
+apply from: "$rootDir/common.gradle"
+
diff --git a/src/tr/mangaship/res/mipmap-hdpi/ic_launcher.png b/src/tr/mangaship/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 000000000..9dce78c31
Binary files /dev/null and b/src/tr/mangaship/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/src/tr/mangaship/res/mipmap-mdpi/ic_launcher.png b/src/tr/mangaship/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 000000000..e50537d01
Binary files /dev/null and b/src/tr/mangaship/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/src/tr/mangaship/res/mipmap-xhdpi/ic_launcher.png b/src/tr/mangaship/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 000000000..395cee294
Binary files /dev/null and b/src/tr/mangaship/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/src/tr/mangaship/res/mipmap-xxhdpi/ic_launcher.png b/src/tr/mangaship/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 000000000..fca129e10
Binary files /dev/null and b/src/tr/mangaship/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/src/tr/mangaship/res/mipmap-xxxhdpi/ic_launcher.png b/src/tr/mangaship/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 000000000..a92a4a389
Binary files /dev/null and b/src/tr/mangaship/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/src/tr/mangaship/res/web_hi_res_512.png b/src/tr/mangaship/res/web_hi_res_512.png
new file mode 100644
index 000000000..9cb5ebcbc
Binary files /dev/null and b/src/tr/mangaship/res/web_hi_res_512.png differ
diff --git a/src/tr/mangaship/src/eu/kanade/tachiyomi/extension/tr/mangaship/MangaShip.kt b/src/tr/mangaship/src/eu/kanade/tachiyomi/extension/tr/mangaship/MangaShip.kt
new file mode 100644
index 000000000..1e1cfdc58
--- /dev/null
+++ b/src/tr/mangaship/src/eu/kanade/tachiyomi/extension/tr/mangaship/MangaShip.kt
@@ -0,0 +1,107 @@
+package eu.kanade.tachiyomi.extension.tr.mangaship
+
+import eu.kanade.tachiyomi.lib.dataimage.DataImageInterceptor
+import eu.kanade.tachiyomi.lib.dataimage.dataImageAsUrl
+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.ParsedHttpSource
+import okhttp3.OkHttpClient
+import okhttp3.Request
+import org.jsoup.nodes.Document
+import org.jsoup.nodes.Element
+
+class MangaShip : ParsedHttpSource() {
+
+ override val name = "Manga Ship"
+
+ override val baseUrl = "https://www.mangaship.com"
+
+ override val lang = "tr"
+
+ override val supportsLatest = true
+
+ override val client: OkHttpClient = network.cloudflareClient.newBuilder()
+ .addInterceptor(DataImageInterceptor())
+ .build()
+
+ // Popular
+
+ override fun popularMangaRequest(page: Int): Request {
+ return GET("$baseUrl/Tr/PopulerMangalar?page=$page", headers)
+ }
+
+ override fun popularMangaSelector() = "div.movie-item-contents"
+
+ override fun popularMangaFromElement(element: Element): SManga {
+ return SManga.create().apply {
+ element.select("div.movie-item-title a").let {
+ title = it.text()
+ setUrlWithoutDomain(it.attr("href"))
+ }
+ thumbnail_url = element.select("img").attr("abs:src")
+ }
+ }
+
+ override fun popularMangaNextPageSelector() = "li.active + li a:not(.lastpage)"
+
+ // Latest
+
+ override fun latestUpdatesRequest(page: Int): Request {
+ return GET("$baseUrl/Tr/YeniMangalar?page=$page", headers)
+ }
+
+ override fun latestUpdatesSelector() = popularMangaSelector()
+
+ override fun latestUpdatesFromElement(element: Element): SManga = popularMangaFromElement(element)
+
+ override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector()
+
+ // Search
+
+ override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
+ return GET("$baseUrl/Tr/Search?kelime=$query&tur=Manga&page=$page", headers)
+ }
+
+ override fun searchMangaSelector() = popularMangaSelector()
+
+ override fun searchMangaFromElement(element: Element): SManga = popularMangaFromElement(element)
+
+ override fun searchMangaNextPageSelector() = popularMangaNextPageSelector()
+
+ // Details
+
+ override fun mangaDetailsParse(document: Document): SManga {
+ return SManga.create().apply {
+ thumbnail_url = document.select("div.dec-review-img img").attr("abs:src")
+ genre = document.select("div.col-md-10 li:contains(Kategori) div a").joinToString { it.text() }
+ author = document.select("div.col-md-10 li:contains(Yazar) div a").text()
+ description = document.select("div.details-dectiontion p").joinToString("\n") { it.text() }
+ }
+ }
+
+ // Chapters
+
+ override fun chapterListSelector() = "div.item > div"
+
+ override fun chapterFromElement(element: Element): SChapter {
+ return SChapter.create().apply {
+ element.select("div.plylist-single-content > a[title]").let {
+ name = it.text()
+ setUrlWithoutDomain(it.attr("href"))
+ }
+ }
+ }
+
+ // Pages
+
+ override fun pageListParse(document: Document): List {
+ return document.select("div.reading-content img").mapIndexed { i, img ->
+ Page(i, "", img.dataImageAsUrl("src"))
+ }
+ }
+
+ override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not used")
+}