diff --git a/src/zh/iyf/build.gradle b/src/zh/iyf/build.gradle
new file mode 100644
index 00000000..b5dc7aca
--- /dev/null
+++ b/src/zh/iyf/build.gradle
@@ -0,0 +1,7 @@
+ext {
+    extName = 'iyf'
+    extClass = '.Iyf'
+    extVersionCode = 1
+}
+
+apply from: "$rootDir/common.gradle"
diff --git a/src/zh/iyf/res/mipmap-hdpi/ic_launcher.png b/src/zh/iyf/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 00000000..ec3d9955
Binary files /dev/null and b/src/zh/iyf/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/src/zh/iyf/res/mipmap-mdpi/ic_launcher.png b/src/zh/iyf/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 00000000..eecbea0d
Binary files /dev/null and b/src/zh/iyf/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/src/zh/iyf/res/mipmap-xhdpi/ic_launcher.png b/src/zh/iyf/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 00000000..57f6536b
Binary files /dev/null and b/src/zh/iyf/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/src/zh/iyf/res/mipmap-xxhdpi/ic_launcher.png b/src/zh/iyf/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 00000000..8617de5b
Binary files /dev/null and b/src/zh/iyf/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/src/zh/iyf/res/mipmap-xxxhdpi/ic_launcher.png b/src/zh/iyf/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 00000000..8fd0c30b
Binary files /dev/null and b/src/zh/iyf/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/src/zh/iyf/src/eu/kanade/tachiyomi/animeextension/zh/iyf/Filters.kt b/src/zh/iyf/src/eu/kanade/tachiyomi/animeextension/zh/iyf/Filters.kt
new file mode 100644
index 00000000..8a8884db
--- /dev/null
+++ b/src/zh/iyf/src/eu/kanade/tachiyomi/animeextension/zh/iyf/Filters.kt
@@ -0,0 +1,197 @@
+package eu.kanade.tachiyomi.animeextension.zh.iyf
+
+import eu.kanade.tachiyomi.animesource.model.AnimeFilter
+
+open class PairSelectFilter(
+    name: String,
+    private val options: List<Pair<String, String>>,
+    val key: String,
+) : AnimeFilter.Select<String>(name, options.map { it.first }.toTypedArray()) {
+    val selected
+        get() = options[state].second
+}
+
+class TypeFilter(options: List<Pair<String, String>> = DEFAULT_TYPES) :
+    PairSelectFilter("类型", options, "cid")
+
+class RegionFilter(options: List<Pair<String, String>> = DEFAULT_REGIONS) :
+    PairSelectFilter("地区", options, "region")
+
+class LangFilter(options: List<Pair<String, String>> = DEFAULT_LANG) :
+    PairSelectFilter("语言", options, "language")
+
+class YearFilter(options: List<Pair<String, String>> = DEFAULT_YEAR) :
+    PairSelectFilter("年份", options, "year")
+
+class QualityFilter(options: List<Pair<String, String>> = DEFAULT_QUALITY) :
+    PairSelectFilter("画质", options, "vipResource")
+
+class StatusFilter(options: List<Pair<String, String>> = DEFAULT_STATUS) :
+    PairSelectFilter("状态", options, "isserial")
+
+class SortFilter(private val options: List<Pair<String, String>> = DEFAULT_SORT) :
+    AnimeFilter.Sort("排序", options.map { it.first }.toTypedArray(), Selection(0, false)) {
+    val desc
+        get() = if (state?.ascending == true) {
+            0
+        } else {
+            1
+        }
+    val orderBy
+        get() = options[state?.index ?: 0].second
+}
+
+//region default filter config
+private val DEFAULT_TYPES = listOf(
+    "全部板块" to "0,1",
+    "动漫" to "0,1,6",
+    "电影" to "0,1,3",
+    "电视剧" to "0,1,4",
+    "综艺" to "0,1,5",
+    "体育" to "0,1,95",
+    "纪录片" to "0,1,7",
+    "动漫-热血" to "0,1,6,46",
+    "动漫-格斗" to "0,1,6,47",
+    "动漫-机战" to "0,1,6,48",
+    "动漫-少女" to "0,1,6,49",
+    "动漫-竞技" to "0,1,6,51",
+    "动漫-科幻" to "0,1,6,52",
+    "动漫-魔幻" to "0,1,6,53",
+    "动漫-爆笑" to "0,1,6,54",
+    "动漫-推理" to "0,1,6,55",
+    "动漫-冒险" to "0,1,6,121",
+    "动漫-恋爱" to "0,1,6,120",
+    "动漫-校园" to "0,1,6,119",
+    "动漫-治愈" to "0,1,6,118",
+    "动漫-泡面" to "0,1,6,117",
+    "动漫-穿越" to "0,1,6,116",
+    "动漫-灵异" to "0,1,6,56",
+    "动漫-耽美" to "0,1,6,122",
+    "动漫-剧场版" to "0,1,6,57",
+    "动漫-其它" to "0,1,6,58",
+    "电影-喜剧" to "0,1,3,19",
+    "电影-爱情" to "0,1,3,20",
+    "电影-动作" to "0,1,3,21",
+    "电影-犯罪" to "0,1,3,22",
+    "电影-科幻" to "0,1,3,23",
+    "电影-奇幻" to "0,1,3,24",
+    "电影-冒险" to "0,1,3,25",
+    "电影-灾难" to "0,1,3,26",
+    "电影-恐怖" to "0,1,3,123",
+    "电影-惊悚" to "0,1,3,27",
+    "电影-剧情" to "0,1,3,28",
+    "电影-战争" to "0,1,3,29",
+    "电影-歌舞" to "0,1,3,30",
+    "电影-经典" to "0,1,3,31",
+    "电影-悬疑" to "0,1,3,32",
+    "电影-动画" to "0,1,3,113",
+    "电影-同性" to "0,1,3,124",
+    "电影-网络电影" to "0,1,3,125",
+    "电视剧-偶像" to "0,1,4,129",
+    "电视剧-爱情" to "0,1,4,146",
+    "电视剧-言情" to "0,1,4,127",
+    "电视剧-古装" to "0,1,4,126",
+    "电视剧-历史" to "0,1,4,141",
+    "电视剧-玄幻" to "0,1,4,142",
+    "电视剧-谍战" to "0,1,4,136",
+    "电视剧-历险" to "0,1,4,143",
+    "电视剧-都市" to "0,1,4,132",
+    "电视剧-科幻" to "0,1,4,144",
+    "电视剧-军旅" to "0,1,4,135",
+    "电视剧-喜剧" to "0,1,4,133",
+    "电视剧-武侠" to "0,1,4,128",
+    "电视剧-江湖" to "0,1,4,145",
+    "电视剧-罪案" to "0,1,4,138",
+    "电视剧-青春" to "0,1,4,131",
+    "电视剧-家庭" to "0,1,4,130",
+    "电视剧-战争" to "0,1,4,134",
+    "电视剧-悬疑" to "0,1,4,137",
+    "电视剧-穿越" to "0,1,4,139",
+    "电视剧-宫廷" to "0,1,4,140",
+    "电视剧-神话" to "0,1,4,147",
+    "电视剧-商战" to "0,1,4,148",
+    "电视剧-警匪" to "0,1,4,149",
+    "电视剧-动作" to "0,1,4,150",
+    "电视剧-惊悚" to "0,1,4,151",
+    "电视剧-剧情" to "0,1,4,152",
+    "电视剧-同性" to "0,1,4,153",
+    "电视剧-奇幻" to "0,1,4,154",
+    "综艺-真人秀" to "0,1,5,39",
+    "综艺-选秀" to "0,1,5,38",
+    "综艺-网综" to "0,1,5,94",
+    "综艺-脱口秀" to "0,1,5,43",
+    "综艺-搞笑" to "0,1,5,40",
+    "综艺-竞技" to "0,1,5,91",
+    "综艺-情感" to "0,1,5,33",
+    "综艺-访谈" to "0,1,5,34",
+    "综艺-演唱会" to "0,1,5,44",
+    "综艺-晚会" to "0,1,5,92",
+    "综艺-其它" to "0,1,5,45",
+    "体育-奥运" to "0,1,95,99",
+    "体育-综合" to "0,1,95,98",
+    "体育-篮球" to "0,1,95,97",
+    "体育-足球" to "0,1,95,96",
+    "纪录片-文化" to "0,1,7,50",
+    "纪录片-探索" to "0,1,7,59",
+    "纪录片-军事" to "0,1,7,60",
+    "纪录片-解密" to "0,1,7,61",
+    "纪录片-科技" to "0,1,7,62",
+    "纪录片-历史" to "0,1,7,63",
+    "纪录片-人物" to "0,1,7,64",
+    "纪录片-自然" to "0,1,7,66",
+    "纪录片-其它" to "0,1,7,67",
+)
+private val DEFAULT_REGIONS = listOf(
+    "全部地区" to "",
+    "大陆" to "大陆",
+    "香港" to "香港",
+    "台湾" to "台湾",
+    "日本" to "日本",
+    "韩国" to "韩国",
+    "欧美" to "欧美",
+    "英国" to "英国",
+    "泰国" to "泰国",
+    "其它" to "其它",
+)
+private val DEFAULT_LANG = listOf(
+    "全部语言" to "",
+    "国语" to "国语",
+    "粤语" to "粤语",
+    "英语" to "英语",
+    "韩语" to "韩语",
+    "日语" to "日语",
+    "西班牙语" to "西班牙语",
+    "法语" to "法语",
+    "德语" to "德语",
+    "意大利语" to "意大利语",
+    "泰国语" to "泰国语",
+    "其它" to "其它",
+)
+private val DEFAULT_YEAR = listOf(
+    "全部年份" to "",
+    "今年" to "今年",
+    "去年" to "去年",
+    "更早" to "更早",
+    "90年代" to "90年代",
+    "80年代" to "80年代",
+    "怀旧" to "怀旧",
+)
+private val DEFAULT_STATUS = listOf(
+    "全部" to "-1",
+    "全集" to "0",
+    "连载中" to "1",
+)
+private val DEFAULT_QUALITY = listOf(
+    "全部画质" to "",
+    "4K" to "4K",
+    "1080P" to "1080P",
+    "900P" to "900P",
+    "720P" to "720P",
+)
+private val DEFAULT_SORT = listOf(
+    "添加时间" to "0",
+    "更新时间" to "1",
+    "人气高低" to "2",
+    "评分高低" to "3",
+)
+//endregion
diff --git a/src/zh/iyf/src/eu/kanade/tachiyomi/animeextension/zh/iyf/Iyf.kt b/src/zh/iyf/src/eu/kanade/tachiyomi/animeextension/zh/iyf/Iyf.kt
new file mode 100644
index 00000000..28cdcb99
--- /dev/null
+++ b/src/zh/iyf/src/eu/kanade/tachiyomi/animeextension/zh/iyf/Iyf.kt
@@ -0,0 +1,265 @@
+package eu.kanade.tachiyomi.animeextension.zh.iyf
+
+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.AnimeHttpSource
+import eu.kanade.tachiyomi.network.GET
+import eu.kanade.tachiyomi.network.POST
+import eu.kanade.tachiyomi.util.asJsoup
+import eu.kanade.tachiyomi.util.parseAs
+import kotlinx.serialization.json.Json
+import kotlinx.serialization.json.JsonElement
+import kotlinx.serialization.json.jsonArray
+import kotlinx.serialization.json.jsonObject
+import kotlinx.serialization.json.jsonPrimitive
+import okhttp3.FormBody
+import okhttp3.Headers
+import okhttp3.HttpUrl
+import okhttp3.HttpUrl.Companion.toHttpUrl
+import okhttp3.Request
+import okhttp3.Response
+import uy.kohesive.injekt.injectLazy
+import java.security.MessageDigest
+import java.text.SimpleDateFormat
+import java.util.Locale
+
+class Iyf : AnimeHttpSource() {
+    override val baseUrl: String
+        get() = "https://www.iyf.tv"
+    override val lang: String
+        get() = "zh"
+    override val name: String
+        get() = "爱壹帆"
+    override val supportsLatest: Boolean
+        get() = true
+
+    override fun headersBuilder(): Headers.Builder {
+        // Force the use of desktop user agent.
+        return super.headersBuilder().set(
+            "User-Agent",
+            "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36",
+        )
+    }
+
+    private val json by injectLazy<Json>()
+    private val pConfig: PConfig by lazy {
+        val doc = client.newCall(GET("$baseUrl/list", headers)).execute().asJsoup()
+        val script = doc.selectFirst("script:containsData(injectJson)")!!.data()
+        val pConfigStr =
+            script.substringAfter("\"pConfig\":").let { it.substring(0, it.indexOf("}") + 1) }
+        val pConfig = json.decodeFromString<JsonElement>(pConfigStr).jsonObject
+        PConfig(
+            publicKey = pConfig["publicKey"]!!.jsonPrimitive.content,
+            privateKey = pConfig["privateKey"]!!.jsonArray[0].jsonPrimitive.content,
+        )
+    }
+    private val popularAnimeFilterList = AnimeFilterList(
+        TypeFilter(listOf("动漫" to "0,1,6")),
+        SortFilter(listOf("人气高低" to "2")),
+    )
+    private val latestAnimeFilterList = AnimeFilterList(
+        TypeFilter(listOf("动漫" to "0,1,6")),
+        SortFilter(listOf("更新时间" to "1")),
+    )
+    private val episodeDateFormat by lazy {
+        SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.getDefault())
+    }
+    private val searchSortFilter by lazy {
+        SortFilter(listOf("匹配度" to "4"))
+    }
+
+    override fun animeDetailsRequest(anime: SAnime): Request {
+        val vid = "$baseUrl${anime.url}".toHttpUrl().pathSegments.last()
+        val detailUrl = "https://m10.iyf.tv/v3/video/detail".toHttpUrl().newBuilder()
+        detailUrl.addQueryParameter("cinema", "1")
+            .addQueryParameter("device", "1")
+            .addQueryParameter("player", "CkPlayer")
+            .addQueryParameter("tech", "HLS")
+            .addQueryParameter("country", "HU")
+            .addQueryParameter("lang", "cns")
+            .addQueryParameter("v", "1")
+            .addQueryParameter("id", vid)
+            .addQueryParameter("region", "SG")
+            .appendSignature()
+        return GET(detailUrl.build(), headers)
+    }
+
+    override fun animeDetailsParse(response: Response): SAnime {
+        val resp = response.parseAs<CommonResponse<VideoDetail>>()
+        val detail = resp.data.info[0]
+        return SAnime.create().apply {
+            title = detail.title
+            thumbnail_url = detail.imgPath
+            author = detail.directors.joinToString()
+            artist = detail.stars.joinToString()
+            genre = detail.keyWord.split(",").joinToString()
+            status = if (detail.serialCount == 0) {
+                SAnime.COMPLETED
+            } else {
+                SAnime.ONGOING
+            }
+            description = """
+                添加:${detail.addDate}
+                更新:${detail.updateWeekly}
+                简介:${detail.context}
+            """.trimIndent()
+        }
+    }
+
+    override fun episodeListRequest(anime: SAnime): Request {
+        val httpUrl = "$baseUrl${anime.url}".toHttpUrl()
+        val vid = httpUrl.pathSegments.last()
+        val cid = httpUrl.fragment
+        val playlistUrl = "https://m10.iyf.tv/v3/video/languagesplaylist".toHttpUrl().newBuilder()
+        playlistUrl.addQueryParameter("cinema", "1")
+            .addQueryParameter("vid", vid)
+            .addQueryParameter("lsk", "1")
+            .addQueryParameter("taxis", "0")
+            .addQueryParameter("cid", cid)
+            .appendSignature()
+        return GET(playlistUrl.build(), headers)
+    }
+
+    override fun episodeListParse(response: Response): List<SEpisode> {
+        val resp = response.parseAs<CommonResponse<PlayList>>()
+        val playList = resp.data.info[0].playList
+        return playList.map {
+            SEpisode.create().apply {
+                url = "/${it.key}"
+                name = it.name
+                date_upload =
+                    runCatching { episodeDateFormat.parse(it.updateDate)?.time }.getOrNull() ?: 0L
+            }
+        }.asReversed()
+    }
+
+    override fun videoListRequest(episode: SEpisode): Request {
+        val vid = "$baseUrl${episode.url}".toHttpUrl().pathSegments.last()
+        val playUrl = "https://m10.iyf.tv/v3/video/play".toHttpUrl().newBuilder()
+        playUrl.addQueryParameter("cinema", "1")
+            .addQueryParameter("id", vid)
+            .addQueryParameter("a", "0")
+            .addQueryParameter("lang", "none")
+            .addQueryParameter("usersign", "1")
+            .addQueryParameter("region", "SG")
+            .addQueryParameter("device", "1")
+            .addQueryParameter("isMasterSupport", "1")
+            .appendSignature()
+        return GET(playUrl.build(), headers)
+    }
+
+    override fun videoListParse(response: Response): List<Video> {
+        val resp = response.parseAs<CommonResponse<Play>>()
+        val play = resp.data.info[0]
+        return play.clarity.filter { it.path != null }.map {
+            val url = it.path!!.rtmp.toHttpUrl().newBuilder().removeAllQueryParameters("us").build()
+                .toString()
+            Video(url, it.title, url)
+        }
+    }
+
+    override fun latestUpdatesParse(response: Response): AnimesPage = searchAnimeParse(response)
+
+    override fun latestUpdatesRequest(page: Int): Request =
+        searchAnimeRequest(page, "", latestAnimeFilterList)
+
+    override fun popularAnimeParse(response: Response): AnimesPage = searchAnimeParse(response)
+
+    override fun popularAnimeRequest(page: Int): Request =
+        searchAnimeRequest(page, "", popularAnimeFilterList)
+
+    override fun searchAnimeParse(response: Response): AnimesPage {
+        val resp = response.parseAs<CommonResponse<SearchResult>>()
+        val result = resp.data.info[0].result
+        return AnimesPage(
+            result.map {
+                SAnime.create().apply {
+                    url = "/play/${it.vid}#${it.videoClassID}"
+                    title = it.title
+                    thumbnail_url = it.image
+                }
+            },
+            result.size >= PAGE_SIZE,
+        )
+    }
+
+    private fun keywordSearchRequest(page: Int, query: String): Request {
+        val searchUrlBuilder = "https://rankv21.iyf.tv/v3/list/briefsearch".toHttpUrl().newBuilder()
+        searchUrlBuilder.addQueryParameter("tags", query)
+        val sortFilter = searchSortFilter
+        searchUrlBuilder.addQueryParameter("orderby", sortFilter.orderBy)
+            .addQueryParameter("page", "$page")
+            .addQueryParameter("size", "$PAGE_SIZE")
+            .addQueryParameter("desc", "${sortFilter.desc}")
+            .addQueryParameter("isserial", "-1")
+        val searchUrl = searchUrlBuilder.build()
+        return POST(
+            searchUrl.toString(),
+            headers,
+            FormBody.Builder().add("tags", query)
+                .addEncoded("vv", signature(searchUrl.query ?: "", pConfig))
+                .addEncoded("pub", pConfig.publicKey)
+                .build(),
+        )
+    }
+
+    override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
+        if (query.isNotEmpty()) {
+            return keywordSearchRequest(page, query)
+        }
+        val searchUrl = "https://m10.iyf.tv/api/list/Search".toHttpUrl().newBuilder()
+        searchUrl.addQueryParameter("cinema", "1")
+            .addQueryParameter("page", "$page")
+            .addQueryParameter("size", "$PAGE_SIZE")
+        filters.list.filterIsInstance<SortFilter>().firstOrNull()?.let {
+            searchUrl.addQueryParameter("orderby", it.orderBy)
+                .addQueryParameter("desc", "${it.desc}")
+        }
+        filters.list.filterIsInstance<PairSelectFilter>().forEach {
+            if (it.selected.isNotEmpty()) {
+                searchUrl.addQueryParameter(it.key, it.selected)
+            }
+        }
+        searchUrl.addQueryParameter("isIndex", "-1")
+            .addQueryParameter("isfree", "-1")
+        // add signature
+        searchUrl.appendSignature()
+        return GET(searchUrl.build(), headers)
+    }
+
+    private fun HttpUrl.Builder.appendSignature(): HttpUrl.Builder {
+        addQueryParameter("vv", signature(build().query ?: "", pConfig))
+        addQueryParameter("pub", pConfig.publicKey)
+        return this
+    }
+
+    private fun signature(query: String, pConfig: PConfig): String {
+        val s = "${pConfig.publicKey}&${query.lowercase()}&${pConfig.privateKey}"
+        return MessageDigest.getInstance("MD5").digest(s.toByteArray())
+            .joinToString(separator = "") { "%02x".format(it) }
+    }
+
+    override fun getFilterList(): AnimeFilterList {
+        return AnimeFilterList(
+            TypeFilter(),
+            SortFilter(),
+            RegionFilter(),
+            LangFilter(),
+            YearFilter(),
+            QualityFilter(),
+            StatusFilter(),
+        )
+    }
+
+    private class PConfig(
+        val publicKey: String,
+        val privateKey: String,
+    )
+
+    companion object {
+        private const val PAGE_SIZE = 32
+    }
+}
diff --git a/src/zh/iyf/src/eu/kanade/tachiyomi/animeextension/zh/iyf/IyfDTO.kt b/src/zh/iyf/src/eu/kanade/tachiyomi/animeextension/zh/iyf/IyfDTO.kt
new file mode 100644
index 00000000..b62093d1
--- /dev/null
+++ b/src/zh/iyf/src/eu/kanade/tachiyomi/animeextension/zh/iyf/IyfDTO.kt
@@ -0,0 +1,77 @@
+package eu.kanade.tachiyomi.animeextension.zh.iyf
+
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+import kotlinx.serialization.json.JsonNames
+
+@Serializable
+class CommonResponse<T>(
+    val data: Data<T>,
+) {
+    @Serializable
+    class Data<T>(
+        val info: List<T>,
+    )
+}
+
+@Serializable
+class SearchResult(
+    val result: List<SearchResultItem>,
+)
+
+@Serializable
+class SearchResultItem(
+    @JsonNames("imgPath")
+    val image: String,
+    val key: String?,
+    val title: String,
+    val videoClassID: String,
+    val contxt: String,
+) {
+    val vid: String
+        get() = key ?: contxt
+}
+
+@Serializable
+class VideoDetail(
+    @SerialName("add_date")
+    val addDate: String,
+    @SerialName("contxt")
+    val context: String,
+    @SerialName("updateweekly")
+    val updateWeekly: String,
+    val imgPath: String,
+    val title: String,
+    val directors: List<String>,
+    val stars: List<String>,
+    val keyWord: String,
+    val serialCount: Int,
+)
+
+@Serializable
+class PlayList(
+    val playList: List<PlayListItem>,
+)
+
+@Serializable
+class PlayListItem(
+    val key: String,
+    val name: String,
+    val updateDate: String,
+)
+
+@Serializable
+class Play(
+    val clarity: List<PlayClarity>,
+)
+
+@Serializable
+class PlayClarity(
+    val title: String,
+    val path: Path?,
+) {
+    @Serializable
+    class Path(
+        val rtmp: String,
+    )
+}