feat: 添加lequgo(片吧影院)源,支持搜索/详情/播放

This commit is contained in:
xiaji
2026-06-10 19:35:40 +08:00
parent 100ecb4aef
commit e2118d8faa
2 changed files with 161 additions and 0 deletions

View File

@@ -3,6 +3,7 @@ package com.videoapp.tv.engine
import android.content.Context
import com.videoapp.tv.data.ConfigRepository
import com.videoapp.tv.data.SiteConfig
import com.videoapp.tv.engine.lequgo.LequgoHandler
import com.videoapp.tv.engine.tvcat.TvcatHandler
import com.videoapp.tv.engine.xb6v.Xb6vHandler
@@ -27,6 +28,7 @@ object SourceRegistry {
val xb6vConfig = xb6vConfig()
register(Xb6vHandler(xb6vConfig))
register(TvcatHandler())
register(LequgoHandler())
}
private fun xb6vConfig() = SiteConfig(

View File

@@ -0,0 +1,159 @@
package com.videoapp.tv.engine.lequgo
import com.videoapp.tv.data.SearchResult
import com.videoapp.tv.data.SiteConfig
import com.videoapp.tv.engine.BaseSourceHandler
import com.videoapp.tv.engine.Episode
import com.videoapp.tv.engine.PlaySource
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.jsoup.Jsoup
import java.net.HttpURLConnection
import java.net.URL
import java.net.URLEncoder
class LequgoHandler : BaseSourceHandler(
id = "lequgo",
displayName = "lequgo (片吧影院)",
baseUrl = "https://www.lequgo.com",
config = SiteConfig(
baseUrl = "https://www.lequgo.com",
searchPath = "/vodsearch/",
searchMethod = "GET",
keywordParam = "wd",
extraParams = emptyMap(),
resultSelector = "ul#searchList > li",
titleSelector = "h4.title a",
coverSelector = "a.myui-vodlist__thumb",
linkSelector = "h4.title a",
categorySelector = ".text-muted:contains(类型)",
dateSelector = "",
episodeSelector = "ul.myui-content__list li a",
sourceSelector = "",
sourceEpisodeGroupSelector = "",
iframeSelector = "iframe",
videoSelector = "video source, video[src]"
)
) {
override suspend fun search(
keyword: String,
onResult: suspend (List<SearchResult>) -> Unit,
onError: suspend (String) -> Unit
) {
try {
val results = withContext(Dispatchers.IO) {
val encoded = URLEncoder.encode(keyword, "UTF-8")
val searchUrl = "https://www.lequgo.com/vodsearch/${encoded}-------------.html"
val conn = URL(searchUrl).openConnection() as HttpURLConnection
conn.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36")
conn.setRequestProperty("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8")
conn.connectTimeout = 15000
conn.readTimeout = 15000
val html = if (conn.responseCode == 200) {
conn.inputStream.bufferedReader(Charsets.UTF_8).use { it.readText() }
} else ""
conn.disconnect()
if (html.isEmpty()) return@withContext emptyList<SearchResult>()
val doc = Jsoup.parseBodyFragment(html)
val items = doc.select("ul#searchList > li")
val resultsList = mutableListOf<SearchResult>()
for (item in items) {
try {
val titleEl = item.selectFirst("h4.title a") ?: continue
val linkEl = item.selectFirst("h4.title a")
val coverEl = item.selectFirst("a.myui-vodlist__thumb")
val categoryEl = item.selectFirst("span.text-muted:contains(类型)")
val title = titleEl.text().trim()
val detailUrl = buildFullUrl(linkEl?.attr("href")?.trim() ?: "")
val coverUrl = coverEl?.attr("data-original")?.trim() ?: ""
val category = categoryEl?.text()?.replace("类型:", "")?.trim() ?: ""
if (title.isNotEmpty() && detailUrl.isNotEmpty()) {
resultsList.add(
SearchResult(
title = title,
coverUrl = coverUrl,
detailUrl = detailUrl,
category = category,
date = ""
)
)
}
} catch (_: Exception) {
}
}
resultsList
}
if (results.isEmpty()) {
onError("未找到结果")
} else {
onResult(results)
}
} catch (e: Exception) {
onError("搜索失败: ${e.message}")
}
}
override suspend fun extractVideos(detailUrl: String): List<PlaySource> =
withContext(Dispatchers.IO) {
val conn = URL(detailUrl).openConnection() as HttpURLConnection
conn.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36")
conn.setRequestProperty("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8")
conn.connectTimeout = 15000
conn.readTimeout = 15000
val html = if (conn.responseCode == 200) {
conn.inputStream.bufferedReader(Charsets.UTF_8).use { it.readText() }
} else ""
conn.disconnect()
if (html.isEmpty()) return@withContext emptyList()
val doc = Jsoup.parseBodyFragment(html)
val episodes = doc.select("ul.myui-content__list li a").mapNotNull { ep ->
val title = ep.text().trim()
val href = ep.attr("href").trim()
if (title.isNotEmpty() && href.isNotEmpty()) {
Episode(title, buildFullUrl(href))
} else null
}
if (episodes.isNotEmpty()) {
listOf(PlaySource("默认来源", episodes))
} else emptyList()
}
override suspend fun resolvePlayUrl(playUrl: String): Pair<String?, String?> =
withContext(Dispatchers.IO) {
try {
val conn = URL(playUrl).openConnection() as HttpURLConnection
conn.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36")
conn.setRequestProperty("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8")
conn.connectTimeout = 15000
conn.readTimeout = 15000
val html = if (conn.responseCode == 200) {
conn.inputStream.bufferedReader(Charsets.UTF_8).use { it.readText() }
} else ""
conn.disconnect()
if (html.isEmpty()) return@withContext Pair(null, null)
val match = Regex("""var\s+player_aaaa\s*=\s*(\{[^}]+\})""").find(html)
if (match != null) {
val jsonStr = match.groupValues[1]
val urlMatch = Regex(""""url"\s*:\s*"([^"]+)"""").find(jsonStr)
if (urlMatch != null) {
val videoUrl = urlMatch.groupValues[1]
.replace("\\/", "/")
.replace("\\u002F", "/")
return@withContext Pair(videoUrl, null)
}
}
Pair(null, null)
} catch (_: Exception) {
Pair(null, null)
}
}
}