From e2118d8faa722a36e9885cc361f608a1d8d10564 Mon Sep 17 00:00:00 2001 From: xiaji Date: Wed, 10 Jun 2026 19:35:40 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0lequgo(=E7=89=87?= =?UTF-8?q?=E5=90=A7=E5=BD=B1=E9=99=A2)=E6=BA=90=EF=BC=8C=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E6=90=9C=E7=B4=A2/=E8=AF=A6=E6=83=85/=E6=92=AD?= =?UTF-8?q?=E6=94=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/videoapp/tv/engine/SourceRegistry.kt | 2 + .../tv/engine/lequgo/LequgoHandler.kt | 159 ++++++++++++++++++ 2 files changed, 161 insertions(+) create mode 100644 app/src/main/java/com/videoapp/tv/engine/lequgo/LequgoHandler.kt diff --git a/app/src/main/java/com/videoapp/tv/engine/SourceRegistry.kt b/app/src/main/java/com/videoapp/tv/engine/SourceRegistry.kt index d6c7390..297d221 100644 --- a/app/src/main/java/com/videoapp/tv/engine/SourceRegistry.kt +++ b/app/src/main/java/com/videoapp/tv/engine/SourceRegistry.kt @@ -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( diff --git a/app/src/main/java/com/videoapp/tv/engine/lequgo/LequgoHandler.kt b/app/src/main/java/com/videoapp/tv/engine/lequgo/LequgoHandler.kt new file mode 100644 index 0000000..418ea40 --- /dev/null +++ b/app/src/main/java/com/videoapp/tv/engine/lequgo/LequgoHandler.kt @@ -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) -> 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() + + val doc = Jsoup.parseBodyFragment(html) + val items = doc.select("ul#searchList > li") + val resultsList = mutableListOf() + + 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 = + 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 = + 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) + } + } +}