feat: 添加lequgo(片吧影院)源,支持搜索/详情/播放
This commit is contained in:
@@ -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(
|
||||
|
||||
159
app/src/main/java/com/videoapp/tv/engine/lequgo/LequgoHandler.kt
Normal file
159
app/src/main/java/com/videoapp/tv/engine/lequgo/LequgoHandler.kt
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user