diff --git a/app/src/main/java/com/github/catvod/spider/SeedHub.kt b/app/src/main/java/com/github/catvod/spider/SeedHub.kt new file mode 100644 index 00000000..6e680d36 --- /dev/null +++ b/app/src/main/java/com/github/catvod/spider/SeedHub.kt @@ -0,0 +1,211 @@ +package com.github.catvod.spider + + +import android.content.Context +import com.github.catvod.bean.Class +import com.github.catvod.bean.Filter +import com.github.catvod.bean.Result +import com.github.catvod.bean.Vod +import com.github.catvod.net.OkHttp +import com.github.catvod.utils.Util +import com.google.gson.JsonObject +import com.google.gson.JsonParser +import kotlinx.coroutines.* +import okhttp3.HttpUrl +import org.jsoup.Jsoup +import org.jsoup.nodes.Document +import java.net.URLEncoder +import java.util.concurrent.CopyOnWriteArrayList +import java.util.regex.Pattern + +/** + * @author lushunming + */ +class SeedHub : Cloud() { + private val siteUrl = "https://www.seedhub.cc" + private val regexCategory: Pattern = Pattern.compile("/vodtype/(\\w+).html") + private val regexPageTotal: Pattern = Pattern.compile("\\$\\(\"\\.mac_total\"\\)\\.text\\('(\\d+)'\\);") + + private var extend: JsonObject? = null + + + private val header: MutableMap + get() { + val header: MutableMap = HashMap() + header.put("User-Agent", Util.MOBILE) + return header + } + + @Throws(Exception::class) + override fun init(context: Context, extend: String) { + this.extend = JsonParser.parseString(extend).getAsJsonObject() + super.init(context, "") + } + + override fun homeContent(filter: Boolean): String? { + val classes: MutableList = ArrayList() + val filters = LinkedHashMap?>() + + + val html = OkHttp.string(siteUrl, this.header) + val doc = Jsoup.parse(html) + + parseClassFromDoc(doc, classes) + if (filter) { + parseFilterFromDoc(doc, filters) + } + + return Result.string(classes, parseVodListFromDoc(doc), filters) + } + + private fun parseFilterFromDoc( + doc: Document, filters: LinkedHashMap?> + ) { + + val groups = doc.select("div.sidebar-group") + for (group in groups) { + val clazz = group.select("p.sidebar-heading a") + if (clazz.attr("href").startsWith("/")) { + val name = clazz.text() + val url = clazz.attr("href") + val filter = group.select("ul.sidebar-sub-headers>li.sidebar-sub-header>a") + val filterList: MutableList = ArrayList() + val values: MutableList = ArrayList() + for (f in filter) { + val filterName = f.text() + val filterUrl = f.attr("href") + values.add(Filter.Value(filterName, filterUrl)) + } + filterList.add(Filter("0", "分类", values)) + filters[url] = filterList + } + + } + } + + /** + *获取分类 + */ + private fun parseClassFromDoc( + doc: Document, classes: MutableList + ) { + val navs = doc.select("div.nav-item") + for (nav in navs) { + val link = nav.select("a") + if (link.attr("href").startsWith("/")) { + val name = nav.select("a").text() + val url = nav.select("a").attr("href") + classes.add(Class(url, name)) + } + } + } + + override fun categoryContent( + tid: String?, pg: String, filter: Boolean, extend: HashMap? + ): String? { + var urlParams = tid + if (extend != null && extend.size > 0) { + extend.keys.forEach { + urlParams = extend[it] + } + } + val doc = Jsoup.parse( + OkHttp.string( + String.format("%s%s?page=%s", siteUrl, urlParams, pg), this.header + ) + ) + val page = pg.toInt() + val limit = 20 + val total = Int.MAX_VALUE + + + return Result.get().vod(parseVodListFromDoc(doc)).page(page, 0, limit, total).string() + } + + private fun parseVodListFromDoc(doc: Document): MutableList { + val list: MutableList = ArrayList() + val elements = doc.select("div.cover") + for (e in elements) { + val vodId = e.selectFirst(" a")!!.attr("href") + var vodPic = e.selectFirst("img")!!.attr("src") + if (!vodPic.startsWith("http")) { + vodPic = siteUrl + vodPic + } + val vodName = e.selectFirst("h2")!!.text() + val vodRemarks = e.select("ul >li")!!.text() + list.add(Vod(vodId, vodName, vodPic, vodRemarks)) + } + return list + } + + @Throws(Exception::class) + override fun detailContent(ids: MutableList): String? { + + val vodId = ids.get(0) + val doc = Jsoup.parse( + OkHttp.string( + siteUrl + vodId, this.header + ) + ) + val infos = doc.select("div.cover-container >ul > li").text().replace(" ", "") + val item = Vod() + item.setVodId(vodId) + item.setVodName(doc.selectFirst("h1")!!.text()) + item.setVodPic(doc.selectFirst("div.cover-container img")!!.attr("src")) + item.setVodArea(Util.getStrByRegex(Pattern.compile("制片国家/地区:(.*?)语言:"), infos)) + item.setTypeName(Util.getStrByRegex(Pattern.compile("类型:(.*?)制片"), infos)) + item.setVodDirector(Util.getStrByRegex(Pattern.compile("导演:(.*?)制片"), infos)) + item.setVodActor(Util.getStrByRegex(Pattern.compile("主演:(.*?)类型"), infos)) + item.setVodYear(Util.getStrByRegex(Pattern.compile("首播:(.*?)集数"), infos)) + item.setVodRemarks(Util.getStrByRegex(Pattern.compile("豆瓣评分:(.*?)常用标签"), infos)) + item.vodContent = doc.select("div.content > p").text() + + val shareLinks: CopyOnWriteArrayList = CopyOnWriteArrayList() + val jobs = ArrayList() + + runBlocking { + doc.select("ul.pan-links > li > a").forEach { element -> + + jobs += CoroutineScope(Dispatchers.IO).launch { + var link = siteUrl + element.attr("href") + val movieTitle = HttpUrl.parse(link)?.queryParameter("movie_title") + link = HttpUrl.parse(link)?.newBuilder()?.removeAllQueryParameters("movie_title") + ?.addEncodedQueryParameter( + "movie_title", URLEncoder.encode(movieTitle) + )?.build().toString() + val string = OkHttp.string(link, header) + val docEle = Jsoup.parse(string) + docEle.select("a.direct-pan").attr("href").let { + if (it.isNotEmpty()) { + shareLinks.add(it) + } + } + } + } + jobs.joinAll() + item.vodPlayUrl = super.detailContentVodPlayUrl(java.util.ArrayList(shareLinks)) + item.setVodPlayFrom(super.detailContentVodPlayFrom(java.util.ArrayList(shareLinks))) + + + } + return Result.string(item) + } + + @Throws(Exception::class) + override fun searchContent(key: String?, quick: Boolean): String? { + return searchContent(key, "1") + } + + @Throws(Exception::class) + override fun searchContent(key: String?, quick: Boolean, pg: String?): String? { + return searchContent(key, pg) + } + + private fun searchContent(key: String?, pg: String?): String? { + val searchURL = siteUrl + String.format("/s/%s/?page=%s", URLEncoder.encode(key), pg) + val html = OkHttp.string(searchURL, this.header) + val doc = Jsoup.parse(html) + + return Result.string(parseVodListFromDoc(doc)) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/github/catvod/utils/Util.java b/app/src/main/java/com/github/catvod/utils/Util.java index af15868d..6e5be795 100644 --- a/app/src/main/java/com/github/catvod/utils/Util.java +++ b/app/src/main/java/com/github/catvod/utils/Util.java @@ -23,6 +23,8 @@ import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; +import static android.net.Uri.encode; + public class Util { public static final String patternAli = "(https:\\/\\/www\\.aliyundrive\\.com\\/s\\/[^\"]+|https:\\/\\/www\\.alipan\\.com\\/s\\/[^\"]+)"; public static final String patternQuark = "(https:\\/\\/pan\\.quark\\.cn\\/s\\/[^\"]+)"; @@ -30,6 +32,8 @@ public class Util { public static final Pattern RULE = Pattern.compile("http((?!http).){12,}?\\.(m3u8|mp4|mkv|flv|mp3|m4a|aac)\\?.*|http((?!http).){12,}\\.(m3u8|mp4|mkv|flv|mp3|m4a|aac)|http((?!http).)*?video/tos*"); public static final Pattern THUNDER = Pattern.compile("(magnet|thunder|ed2k):.*"); public static final String CHROME = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36"; + public static final String MOBILE = "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Mobile Safari/537.36 Edg/142.0.0.0"; + public static final String ACCEPT = "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7"; public static final List MEDIA = Arrays.asList("mp4", "mkv", "wmv", "flv", "avi", "iso", "mpg", "ts", "mp3", "aac", "flac", "m4a", "ape", "ogg"); public static final List SUB = Arrays.asList("srt", "ass", "ssa", "vtt"); @@ -257,6 +261,12 @@ public class Util { return ""; } } + public static String getStrByRegex(Pattern pattern, String str) { + Matcher matcher = pattern.matcher(str); + if (matcher.find()) return matcher.group(1).trim(); + return ""; + } + public static String base64Decode(String s) { return new String(android.util.Base64.decode(s, Base64.NO_WRAP), Charset.defaultCharset()); diff --git a/app/src/test/java/SeedHubTest.java b/app/src/test/java/SeedHubTest.java new file mode 100644 index 00000000..f5e55560 --- /dev/null +++ b/app/src/test/java/SeedHubTest.java @@ -0,0 +1,100 @@ +import android.app.Application; +import com.github.catvod.server.Server; +import com.github.catvod.spider.Init; +import com.github.catvod.spider.SeedHub; +import com.github.catvod.spider.Wogg; +import com.github.catvod.utils.Json; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonObject; +import org.junit.Assert; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; + +import java.util.ArrayList; +import java.util.Arrays; + +@RunWith(RobolectricTestRunner.class) +public class SeedHubTest { + + private Application mockContext; + + private SeedHub spider; + + @org.junit.Before + public void setUp() throws Exception { + mockContext = RuntimeEnvironment.application; + Init.init(mockContext); + spider = new SeedHub(); + Server.get().start(); + // spider.init(mockContext, "{\"cookie\":\"b-user-id=89ede34e-0efc-e1dd-c997-f16aaa792d0c; _UP_A4A_11_=wb9661c6dfb642f88f73d8e0c7edd398; b-user-id=89ede34e-0efc-e1dd-c997-f16aaa792d0c; ctoken=wla6p3EUOLyn1FSB8IKp1SEW; grey-id=5583e32b-39df-4bf0-f39f-1adf83f604a2; grey-id.sig=p8ReBIMG2BeZu1sYvsuOAZxYbx-MVrsfKEiCv87MsTM; isQuark=true; isQuark.sig=hUgqObykqFom5Y09bll94T1sS9abT1X-4Df_lzgl8nM; _UP_F7E_8D_=ZkyvVHnrBLp1A1NFJIjWi0PwKLOVbxJPcg0RzQPI6KmBtV6ZMgPh38l93pgubgHDQqhaZ2Sfc0qv%2BRantbfg1mWGAUpRMP4RqXP78Wvu%2FCfvkWWGc5NhCTV71tGOIGgDBR3%2Bu6%2Fjj44KlE5biSNDOWW7Bigcz27lvOTidzNw8s%2FWtKAIxWbnCzZn4%2FJMBUub1SIMcW89g57k4mfPmDlCgpZKzxwl6beSfdtZ4RUWXmZOn5v5NkxVKhU4wR0Pq7NklczEGdRq2nIAcu7v22Uw2o%2FxMY0xBdeC9Korm5%2FNHnxl6K%2Bd6FXSoT9a3XIMQO359auZPiZWzrNlZe%2BqnOahXcx7KAhQIRqSOapSmL4ygJor4r5isJhRuDoXy7vJAVuH%2FRDtEJJ8rZTq0BdC23Bz%2B0MrsdgbK%2BiW; _UP_D_=pc; __wpkreporterwid_=3d3f74a7-99b7-4916-3f78-911fc2eb9d87; tfstk=fIoZNxjnbhKwPOu0TWZ4LsaRqirTcudSSmNbnxD0C5VgClMm8xMyB-GsnSu4tjpOflAOmSD-9PNiGl120XrgkVNb1SrqHbJBN3tSBAEYoQOWVUUg9qZ8n1bGGkD3CqGYINKSBABhjnXgp3_Vywz6gSc0Syj3BWf0mr2DLW24eZfiiovEKWefj1q0swq3E82iNEMinMy7SLrcpA4Fh3z_ZAViCfih3PbtdW5N_DuU77AaTijmYRkL2Wq54ENoy5a7ZXxCbok33XzS7QSZgxD-oyoVsdGotql0p2dVu7umC4nLStbiLmParc4FELHrI-c0u2dPVRrs8zoZWKCnIbNZrlHfUCMUz2z8KyXVSlgSFmUojh58OzeqTzgwaGll4YCYKwctDV5coP2LL79eKHxpNTXHmre1kZU32JPWCR_AkP2LL79eLZQY-WeUNdw1.; __pus=2051c82285199d8be553be41dd5a2100AAQ+mmv35G4FDDZ5x+3Mhe2OMbNgweQ1ODbW8zDt9YuP1LQVqHUuAAz9KWLsPjpNtim0AVGHusN4MCosTmbq/khM; __kp=e6604120-6051-11ef-bfe4-c31b6cdd0766; __kps=AATcZArVgS76EPn0FMaV4HEj; __ktd=sii/iz4ePzEaoVirXul7QQ==; __uid=AATcZArVgS76EPn0FMaV4HEj; __itrace_wid=5829b95d-dac1-48d3-bfd5-f60cd9462786; __puus=7da0b96cb710fa1b376934485f977e05AATp/q8/QupT7IiBR1GWqZhxlIRT677smMvoHlLxQA0Lk6CkP0YJBOTl+p9DZgzlMz6w4hPXPgWsokukk8PW7ZfhFfPmv8tKMgLpCGLW+tk57luhNghmSdTeVPkAF59STtyCPBEtiNzNAd/zZJ6qILJDi5ywEBAAAg+gOyWHoLHNUR+QxeHRuQa8g5WWA95J8jebIlrr8rCvI1vjTbtiYktT\",\"token\":\"26fc6787afff43e78b78992e782502f1\"}"); + spider.init(mockContext, "{\"site\": [\"https://www.wogg.net/\",\"https://wogg.xxooo.cf/\"],\"uccookie\":\"_UP_28A_52_=381;_UP_BT_=html5;_UP_F7E_8D_=ZkyvVHnrBLp1A1NFJIjWi0PwKLOVbxJPcg0RzQPI6Kl8ttcYB1X9Nkx0DnGMyJVLgv0M%2FCztZQaIhZhKaI%2F0Fa%2F5Fqe1t%2BDWF1o9sO71vnupc%2Fvxa%2B78J%2B%2BRZYZzk2EJNXvW0Y4gaAMFHf67r%2BOPjtggEPU7aNnlZbsGKBzPbuW85OJ3M3Dyz9a0oAjFZucLNmfj8kwFS5su6ugGZUgH1RU4wR0Pq7NklczEGdRq2nIAcu7v22Uw2o%2FxMY0xBdeC9Korm5%2FNHnxl6K%2Bd6FXSoT9a3XIMQO359auZPiZWzrNlZe%2BqnOahXcx7KAhQIRqSHYYCfquwtPWx%2FgYBqTnLfzoXy7vJAVuH%2FRDtEJJ8rZTq0BdC23Bz%2B0MrsdgbK%2BiW;_UP_6D1_64_=069;_UP_A4A_11_=wb96b12a16f941809f5af993726ba192;_UP_D_=mobilectoken=oxJV13ITm7aa7_rsplIXC-_v;__pus=cef2cc2dfd5d8af70df36bcedf83995cAAT3ZYhqlLos+yCaVQYYC944c4HEQnHz8uEpdQner0OcqISOpBObxl2kck65MGceRIDBd+MLtDxsNqwXvgDIFpYU;__kp=0d340990-9b39-11ef-ae54-4f733858896a;__kps=AAQXoZxLp9Oe2Ps0d/hNBJl4;__ktd=2gPNadz6Z9c+2+FyQyQZUw==;__uid=AAQXoZxLp9Oe2Ps0d/hNBJl4;UDRIVE_TRANSFER_SESS=UpLXXX2HAXJNW0AHgDcMurpazcqTbU-EQWnKG6RKtkdhdqZgHGTM-BSulf_oo1nmMMjo6hFdByLlm-bEiwjByMbIIEehsxhuuV00b96SSaPExn0wMcQ8SmzJa-YwonEE2MEVWCHcRYuW4Z-ljMOgab7qaGtQUpqjkl-p6OTv23BW-4gM6y7DNKvGeaMv_3NX\"," + + "\"cookie\":\"b-user-id=89ede34e-0efc-e1dd-c997-f16aaa792d0c; _UP_A4A_11_=wb9661c6dfb642f88f73d8e0c7edd398; b-user-id=89ede34e-0efc-e1dd-c997-f16aaa792d0c; ctoken=wla6p3EUOLyn1FSB8IKp1SEW; grey-id=5583e32b-39df-4bf0-f39f-1adf83f604a2; grey-id.sig=p8ReBIMG2BeZu1sYvsuOAZxYbx-MVrsfKEiCv87MsTM; isQuark=true; isQuark.sig=hUgqObykqFom5Y09bll94T1sS9abT1X-4Df_lzgl8nM; _UP_F7E_8D_=ZkyvVHnrBLp1A1NFJIjWi0PwKLOVbxJPcg0RzQPI6KmBtV6ZMgPh38l93pgubgHDQqhaZ2Sfc0qv%2BRantbfg1mWGAUpRMP4RqXP78Wvu%2FCfvkWWGc5NhCTV71tGOIGgDBR3%2Bu6%2Fjj44KlE5biSNDOWW7Bigcz27lvOTidzNw8s%2FWtKAIxWbnCzZn4%2FJMBUub1SIMcW89g57k4mfPmDlCgpZKzxwl6beSfdtZ4RUWXmZOn5v5NkxVKhU4wR0Pq7NklczEGdRq2nIAcu7v22Uw2o%2FxMY0xBdeC9Korm5%2FNHnxl6K%2Bd6FXSoT9a3XIMQO359auZPiZWzrNlZe%2BqnOahXcx7KAhQIRqSOapSmL4ygJor4r5isJhRuDoXy7vJAVuH%2FRDtEJJ8rZTq0BdC23Bz%2B0MrsdgbK%2BiW; _UP_D_=pc; __wpkreporterwid_=3d3f74a7-99b7-4916-3f78-911fc2eb9d87; tfstk=fIoZNxjnbhKwPOu0TWZ4LsaRqirTcudSSmNbnxD0C5VgClMm8xMyB-GsnSu4tjpOflAOmSD-9PNiGl120XrgkVNb1SrqHbJBN3tSBAEYoQOWVUUg9qZ8n1bGGkD3CqGYINKSBABhjnXgp3_Vywz6gSc0Syj3BWf0mr2DLW24eZfiiovEKWefj1q0swq3E82iNEMinMy7SLrcpA4Fh3z_ZAViCfih3PbtdW5N_DuU77AaTijmYRkL2Wq54ENoy5a7ZXxCbok33XzS7QSZgxD-oyoVsdGotql0p2dVu7umC4nLStbiLmParc4FELHrI-c0u2dPVRrs8zoZWKCnIbNZrlHfUCMUz2z8KyXVSlgSFmUojh58OzeqTzgwaGll4YCYKwctDV5coP2LL79eKHxpNTXHmre1kZU32JPWCR_AkP2LL79eLZQY-WeUNdw1.; __pus=2051c82285199d8be553be41dd5a2100AAQ+mmv35G4FDDZ5x+3Mhe2OMbNgweQ1ODbW8zDt9YuP1LQVqHUuAAz9KWLsPjpNtim0AVGHusN4MCosTmbq/khM; __kp=e6604120-6051-11ef-bfe4-c31b6cdd0766; __kps=AATcZArVgS76EPn0FMaV4HEj; __ktd=sii/iz4ePzEaoVirXul7QQ==; __uid=AATcZArVgS76EPn0FMaV4HEj; __itrace_wid=5829b95d-dac1-48d3-bfd5-f60cd9462786; __puus=7da0b96cb710fa1b376934485f977e05AATp/q8/QupT7IiBR1GWqZhxlIRT677smMvoHlLxQA0Lk6CkP0YJBOTl+p9DZgzlMz6w4hPXPgWsokukk8PW7ZfhFfPmv8tKMgLpCGLW+tk57luhNghmSdTeVPkAF59STtyCPBEtiNzNAd/zZJ6qILJDi5ywEBAAAg+gOyWHoLHNUR+QxeHRuQa8g5WWA95J8jebIlrr8rCvI1vjTbtiYktT\",\"token\":\"26fc6787afff43e78b78992e782502f1\"}"); + // spider.init(mockContext, ""); + } + + @org.junit.Test + public void homeContent() throws Exception { + String content = spider.homeContent(true); + JsonObject map = Json.safeObject(content); + Gson gson = new GsonBuilder().setPrettyPrinting().create(); + + System.out.println("homeContent--" + gson.toJson(map)); + + //Assert.assertFalse(map.getAsJsonArray("list").isEmpty()); + } + + @org.junit.Test + public void homeVideoContent() throws Exception { + String content = spider.homeVideoContent(); + JsonObject map = Json.safeObject(content); + Gson gson = new GsonBuilder().setPrettyPrinting().create(); + + System.out.println("homeVideoContent--" + gson.toJson(map)); + + // Assert.assertFalse(map.getAsJsonArray("list").isEmpty()); + } + + @org.junit.Test + public void categoryContent() throws Exception { + String content = spider.categoryContent("2", "2", true, null); + JsonObject map = Json.safeObject(content); + Gson gson = new GsonBuilder().setPrettyPrinting().create(); + System.out.println("categoryContent--" + gson.toJson(map)); + Assert.assertFalse(map.getAsJsonArray("list").isEmpty()); + } + + @org.junit.Test + public void detailContent() throws Exception { + + String content = spider.detailContent(Arrays.asList("/movies/113317/")); + System.out.println("detailContent--" + content); + + JsonObject map = Json.safeObject(content); + Gson gson = new GsonBuilder().setPrettyPrinting().create(); + System.out.println("detailContent--" + gson.toJson(map)); + Assert.assertFalse(map.getAsJsonArray("list").isEmpty()); + } + + @org.junit.Test + public void playerContent() throws Exception { + String content = spider.playerContent("uc普画", "52fd84f0bfd44c93a1fca6c76d0b8d16++28d4a0a00ad96562f96506da49b988a0++5ae2d1c474024++yA4L8FmH06i8nfmmY4MwHNSsyhpVzLhzxqDixCp0xm4=", new ArrayList<>()); + System.out.println("playerContent--" + content); + JsonObject map = Json.safeObject(content); + Gson gson = new GsonBuilder().setPrettyPrinting().create(); + System.out.println("playerContent--" + gson.toJson(map)); + Assert.assertFalse(map.getAsJsonPrimitive("url").getAsString().isEmpty()); + } + + @org.junit.Test + public void searchContent() throws Exception { + String content = spider.searchContent("红海", false); + JsonObject map = Json.safeObject(content); + Gson gson = new GsonBuilder().setPrettyPrinting().create(); + System.out.println("searchContent--" + gson.toJson(map)); + Assert.assertFalse(map.getAsJsonArray("list").isEmpty()); + } + + +} \ No newline at end of file diff --git a/json/index.json b/json/index.json index 2c5b77ee..4637fbd0 100644 --- a/json/index.json +++ b/json/index.json @@ -66,6 +66,15 @@ "changeable": 1, "ext": "{\"site\": [\"https://www.wogg.net/\",\"https://wogg.xxooo.cf/\"]}" }, + { + "key": "SeedHub", + "name": "SeedHub", + "type": 3, + "api": "csp_SeedHub", + "searchable": 1, + "changeable": 1, + "ext": {} + }, { "key": "PanTa", "name": "盘他|139Pan",