diff --git a/src/fr/fmteam/AndroidManifest.xml b/src/fr/fmteam/AndroidManifest.xml
new file mode 100644
index 000000000..30deb7f79
--- /dev/null
+++ b/src/fr/fmteam/AndroidManifest.xml
@@ -0,0 +1,2 @@
+
+
diff --git a/src/fr/fmteam/build.gradle b/src/fr/fmteam/build.gradle
new file mode 100644
index 000000000..a4f1c5133
--- /dev/null
+++ b/src/fr/fmteam/build.gradle
@@ -0,0 +1,11 @@
+apply plugin: 'com.android.application'
+apply plugin: 'kotlin-android'
+
+ext {
+ extName = 'FMTEAM'
+ pkgNameSuffix = 'fr.fmteam'
+ extClass = '.FMTEAM'
+ extVersionCode = 1
+}
+
+apply from: "$rootDir/common.gradle"
diff --git a/src/fr/fmteam/res/mipmap-hdpi/ic_launcher.png b/src/fr/fmteam/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 000000000..354a1e9c2
Binary files /dev/null and b/src/fr/fmteam/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/src/fr/fmteam/res/mipmap-mdpi/ic_launcher.png b/src/fr/fmteam/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 000000000..d4b623456
Binary files /dev/null and b/src/fr/fmteam/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/src/fr/fmteam/res/mipmap-xhdpi/ic_launcher.png b/src/fr/fmteam/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 000000000..1d197ea3b
Binary files /dev/null and b/src/fr/fmteam/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/src/fr/fmteam/res/mipmap-xxhdpi/ic_launcher.png b/src/fr/fmteam/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 000000000..7b578bcff
Binary files /dev/null and b/src/fr/fmteam/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/src/fr/fmteam/res/mipmap-xxxhdpi/ic_launcher.png b/src/fr/fmteam/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 000000000..d998d473f
Binary files /dev/null and b/src/fr/fmteam/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/src/fr/fmteam/res/web_hi_res_512.png b/src/fr/fmteam/res/web_hi_res_512.png
new file mode 100644
index 000000000..75bcd78b6
Binary files /dev/null and b/src/fr/fmteam/res/web_hi_res_512.png differ
diff --git a/src/fr/fmteam/src/eu/kanade/tachiyomi/extension/fr/fmteam/FMTEAM.kt b/src/fr/fmteam/src/eu/kanade/tachiyomi/extension/fr/fmteam/FMTEAM.kt
new file mode 100644
index 000000000..f496fc8a2
--- /dev/null
+++ b/src/fr/fmteam/src/eu/kanade/tachiyomi/extension/fr/fmteam/FMTEAM.kt
@@ -0,0 +1,119 @@
+package eu.kanade.tachiyomi.extension.fr.fmteam
+
+import eu.kanade.tachiyomi.network.GET
+import eu.kanade.tachiyomi.source.model.FilterList
+import eu.kanade.tachiyomi.source.model.MangasPage
+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 eu.kanade.tachiyomi.util.asJsoup
+import okhttp3.Request
+import okhttp3.Response
+import org.jsoup.nodes.Document
+import org.jsoup.nodes.Element
+import java.text.ParseException
+import java.text.SimpleDateFormat
+import java.util.Date
+import java.util.Locale
+
+class FMTEAM : ParsedHttpSource() {
+ override val name = "FMTEAM"
+ override val baseUrl = "https://fmteam.fr"
+ override val lang = "fr"
+ override val supportsLatest = true
+
+
+
+ companion object {
+ private val dateFormat = SimpleDateFormat("yyyy.MM.dd", Locale.FRENCH)
+ private val allPagesRegex = "\"url\":\"(.*?)\"".toRegex()
+ private val authorRegex = "Author: *(.*) Synopsis".toRegex()
+ private val descriptionRegex = "Synopsis: *(.*)".toRegex()
+ }
+
+ // All manga
+ override fun popularMangaRequest(page: Int) = GET("$baseUrl/directory/")
+ override fun popularMangaSelector() = "#content .panel .list.series .group"
+ override fun popularMangaNextPageSelector(): String? = null
+ override fun popularMangaFromElement(element: Element): SManga = SManga.create().apply {
+ title = element.selectFirst(".title a")!!.text().trim()
+ setUrlWithoutDomain(element.selectFirst("a")!!.attr("href"))
+ thumbnail_url = element.select(".preview").attr("src")
+ }
+
+ // Latest
+ override fun latestUpdatesRequest(page: Int) = GET(baseUrl)
+ override fun latestUpdatesSelector() = ".panel .list .group"
+ override fun latestUpdatesNextPageSelector() = ".prevnext .next .gbutton.fright:last-child a"
+ override fun latestUpdatesFromElement(element: Element): SManga = SManga.create().apply {
+ title = element.selectFirst(".title a")!!.text().trim()
+ setUrlWithoutDomain(element.select(".title a").attr("href"))
+ }
+ override fun latestUpdatesParse(response: Response): MangasPage {
+ val document = response.asJsoup()
+ val mangas = document.select(latestUpdatesSelector()).map { element ->
+ latestUpdatesFromElement(element)
+ }.distinctBy { it.title }
+ return MangasPage(mangas, false)
+ }
+
+ // Search
+ override fun searchMangaParse(response: Response): MangasPage {
+ val document = response.asJsoup()
+ var mangas = document.select(popularMangaSelector()).map { popularMangaFromElement(it) }
+ val query = response.request.headers["query"]
+ if (query != null) {
+ mangas = mangas.filter { it.title.contains(query, true) }
+ }
+ return MangasPage(mangas, false)
+ }
+ override fun searchMangaFromElement(element: Element): SManga = popularMangaFromElement(element)
+ override fun searchMangaNextPageSelector(): String? = null
+ override fun searchMangaSelector(): String = popularMangaSelector()
+ override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
+ val headers = headersBuilder()
+ .add("query", query)
+ .build()
+ return GET("$baseUrl/directory/", headers)
+ }
+
+ // Manga details
+ override fun mangaDetailsParse(document: Document): SManga = SManga.create().apply {
+ thumbnail_url = document.select(".comic.info .thumbnail img").attr("src")
+ val authorSynopsis = document.select(".large.comic .info").text().trim()
+ if (authorSynopsis.contains("Author")) {
+ author = authorRegex.find(authorSynopsis)!!.groups[1]!!.value.trim()
+ }
+ description = descriptionRegex.find(authorSynopsis)!!.groups[1]!!.value.trim()
+ }
+
+ // Chapter list
+ override fun chapterListSelector() = ".list .group .element"
+ override fun chapterFromElement(element: Element): SChapter = SChapter.create().apply {
+ setUrlWithoutDomain(element.select(".title a").attr("href"))
+ name = element.select(".title a").attr("title").trim()
+ val date = element.select(".meta_r").text().trim().split(", ")[1]
+ date_upload = if (date === "Aujourd'hui") {
+ dateFormat.format(Date()).toLong()
+ } else {
+ try {
+ dateFormat.parse(date)!!.time
+ } catch (e: ParseException) {
+ 0L
+ }
+ }
+ }
+
+ // Pages
+ override fun pageListParse(document: Document): List {
+ val pages = mutableListOf()
+ val script = document.selectFirst("script:containsData(pages =)")!!.data()
+ val allPages = allPagesRegex.findAll(script)
+ allPages.asIterable().mapIndexed { i, it ->
+ pages.add(Page(i, "", it.groupValues[1].replace("\\/", "/")))
+ }
+ return pages
+ }
+ override fun imageUrlParse(document: Document): String = throw Exception("Not used")
+}