diff --git a/app/src/main/java/com/github/catvod/bean/uvod/Data.java b/app/src/main/java/com/github/catvod/bean/uvod/Data.java new file mode 100644 index 00000000..968ec984 --- /dev/null +++ b/app/src/main/java/com/github/catvod/bean/uvod/Data.java @@ -0,0 +1,155 @@ +package com.github.catvod.bean.uvod; + +import android.text.TextUtils; + +import com.github.catvod.bean.Vod; +import com.google.gson.Gson; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.google.gson.annotations.SerializedName; + +import java.util.Collections; +import java.util.List; + +public class Data { + + @SerializedName(value = "video_latest_list", alternate = {"video_list"}) + private List videolatestlist; + @SerializedName(value = "video", alternate = {"video_soruce"}) + private Video video; + @SerializedName("video_fragment_list") + private List videoFragmentList; + + public static Data objectFrom(String str) { + JsonObject jsonObject = JsonParser.parseString(str).getAsJsonObject(); + if (jsonObject.has("data")) { + return new Gson().fromJson(jsonObject.get("data"), Data.class); + } + return new Data(); + } + + public List getVideolatest() { + return videolatestlist == null ? Collections.emptyList() : videolatestlist; + } + + public Video getVideo() { + return video == null ? new Video() : video; + } + + public List getVideoFragmentList() { + return videoFragmentList == null ? Collections.emptyList() : videoFragmentList; + } + + + public static class Videolatest { + @SerializedName("id") + private String id; + @SerializedName("title") + private String title; + @SerializedName("pic") + private String pic; + @SerializedName("state") + private String state; + @SerializedName("last_fragment_symbol") + private String lastfragment; + @SerializedName("year") + private String year; + + public String getId() { + return TextUtils.isEmpty(id) ? "" : id; + } + + public String getTitle() { + return TextUtils.isEmpty(title) ? "" : title; + } + + public String getPic() { + return TextUtils.isEmpty(pic) ? "" : pic; + } + + public String getState() { + return TextUtils.isEmpty(state) ? "" : state; + } + + public String getLastFragment() { + return TextUtils.isEmpty(lastfragment) ? "" : lastfragment; + } + + public String getYear() { + return TextUtils.isEmpty(year) ? "" : year; + } + + public Vod vod() { + return new Vod(getId(), getTitle(), getPic(), getState() + getLastFragment()); + } + } + + public static class Video { + @SerializedName("year") + private String year; + @SerializedName("region") + private String region; + @SerializedName("starring") + private String starring; + @SerializedName("state") + private String state; + @SerializedName("description") + private String description; + @SerializedName("director") + private String director; + @SerializedName("language") + private String language; + @SerializedName("url") + private String url; + + public String getYear() { + return TextUtils.isEmpty(year) ? "" : year; + } + + public String getRegion() { + return TextUtils.isEmpty(region) ? "" : region; + } + + public String getStarring() { + return TextUtils.isEmpty(starring) ? "" : starring; + } + + public String getDescription() { + return TextUtils.isEmpty(description) ? "" : description; + } + + public String getState() { + return TextUtils.isEmpty(state) ? "" : state; + } + + public String getDirector() { + return TextUtils.isEmpty(director) ? "" : director; + } + + public String getLanguage() { + return TextUtils.isEmpty(language) ? "" : language; + } + + public String getUrl() { + return TextUtils.isEmpty(url) ? "" : url; + } + } + + public static class VideoFragmentList { + @SerializedName("id") + private String id; + @SerializedName("symbol") + private String symbol; + + public String getId() { + return TextUtils.isEmpty(id) ? "" : id; + } + + public String getSymbol() { + return TextUtils.isEmpty(symbol) ? "" : symbol; + } + + } + + +} \ No newline at end of file diff --git a/app/src/main/java/com/github/catvod/debug/MainActivity.java b/app/src/main/java/com/github/catvod/debug/MainActivity.java index 6feed4fa..ba9bdffe 100644 --- a/app/src/main/java/com/github/catvod/debug/MainActivity.java +++ b/app/src/main/java/com/github/catvod/debug/MainActivity.java @@ -7,7 +7,7 @@ import android.widget.Button; import com.github.catvod.R; import com.github.catvod.crawler.Spider; import com.github.catvod.spider.Init; -import com.github.catvod.spider.Kanqiu; +import com.github.catvod.spider.Uvod; import com.orhanobut.logger.AndroidLogAdapter; import com.orhanobut.logger.Logger; @@ -46,7 +46,7 @@ public class MainActivity extends Activity { private void initSpider() { try { Init.init(getApplicationContext()); - spider = new Kanqiu(); + spider = new Uvod(); spider.init(this, ""); } catch (Throwable e) { e.printStackTrace(); @@ -82,7 +82,7 @@ public class MainActivity extends Activity { public void detailContent() { try { - Logger.t("detailContent").d(spider.detailContent(Arrays.asList("434686"))); + Logger.t("detailContent").d(spider.detailContent(Arrays.asList("78702"))); } catch (Throwable e) { e.printStackTrace(); } diff --git a/app/src/main/java/com/github/catvod/spider/Uvod.java b/app/src/main/java/com/github/catvod/spider/Uvod.java new file mode 100644 index 00000000..21fabae4 --- /dev/null +++ b/app/src/main/java/com/github/catvod/spider/Uvod.java @@ -0,0 +1,220 @@ +package com.github.catvod.spider; + +import android.content.Context; + +import com.github.catvod.bean.Class; +import com.github.catvod.bean.Result; +import com.github.catvod.bean.Vod; +import com.github.catvod.bean.uvod.Data; +import com.github.catvod.crawler.Spider; +import com.github.catvod.net.OkHttp; +import com.github.catvod.utils.AES; +import com.github.catvod.utils.Util; + +import java.net.URLEncoder; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * @author Qile + * @date 2024/11/15 + */ +public class Uvod extends Spider { + + private static String siteUrl = "https://api-h5.uvod.tv"; + private static final String latest = siteUrl + "/video/latest"; + private static final String list = siteUrl + "/video/list"; + private static final String detail = siteUrl + "/video/info"; + private static final String play = siteUrl + "/video/source"; + + private static final String publicKeyPem = "-----BEGIN PUBLIC KEY-----\n" + + "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCeBQWotWOpsuPn3PAA+bcmM8YD\n" + + "fEOzPz7hb/vItV43vBJV2FcM72Hdcv3DccIFuEV9LQ8vcmuetld98eksja9vQ1Ol\n" + + "8rTnjpTpMbd4HedevSuIhWidJdMAOJKDE3AgGFcQvQePs80uXY2JhTLkRn2ICmDR\n" + + "/fb32OwWY3QGOvLcuQIDAQAB\n" + + "-----END PUBLIC KEY-----"; + private static final String privateKeyPem = + "-----BEGIN PRIVATE KEY-----\n" + + "MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAJ4FBai1Y6my4+fc\n" + + "8AD5tyYzxgN8Q7M/PuFv+8i1Xje8ElXYVwzvYd1y/cNxwgW4RX0tDy9ya562V33x\n" + + "6SyNr29DU6XytOeOlOkxt3gd5169K4iFaJ0l0wA4koMTcCAYVxC9B4+zzS5djYmF\n" + + "MuRGfYgKYNH99vfY7BZjdAY68ty5AgMBAAECgYB1rbvHJj5wVF7Rf4Hk2BMDCi9+\n" + + "zP4F8SW88Y6KrDbcPt1QvOonIea56jb9ZCxf4hkt3W6foRBwg86oZo2FtoZcpCJ+\n" + + "rFqUM2/wyV4CuzlL0+rNNSq7bga7d7UVld4hQYOCffSMifyF5rCFNH1py/4Dvswm\n" + + "pi5qljf+dPLSlxXl2QJBAMzPJ/QPAwcf5K5nngQtbZCD3nqDFpRixXH4aUAIZcDz\n" + + "S1RNsHrT61mEwZ/thQC2BUJTQNpGOfgh5Ecd1MnURwsCQQDFhAFfmvK7svkygoKX\n" + + "t55ARNZy9nmme0StMOfdb4Q2UdJjfw8+zQNtKFOM7VhB7ijHcfFuGsE7UeXBe20n\n" + + "g/XLAkEAv9SoT2hgJaQxxUk4MCF8pgddstJlq8Z3uTA7JMa4x+kZfXTm/6TOo6I8\n" + + "2VbXZLsYYe8op0lvsoHMFvBSBljV0QJBAKhxyoYRa98dZB5qZRskciaXTlge0WJk\n" + + "kA4vvh3/o757izRlQMgrKTfng1GVfIZFqKtnBiIDWTXQw2N9cnqXtH8CQAx+CD5t\n" + + "l1iT0cMdjvlMg2two3SnpOjpo7gALgumIDHAmsUWhocLtcrnJI032VQSUkNnLq9z\n" + + "EIfmHDz0TPVNHBQ=\n" + + "-----END PRIVATE KEY-----"; + + private Map getHeader(String url) { + String[] item = url.split("\\|"); + String URL = item[0]; + String tid = item.length > 1 ? item[1] : ""; + String pg = item.length > 2 ? item[2] : ""; + String hm = String.valueOf(System.currentTimeMillis()); + String text = ""; + if (URL.equals(latest)) { + text = String.format("-parent_category_id=101-%s", hm); + } else if (URL.equals(list)) { + if (pg != null && !pg.isEmpty()) { + text = String.format("-page=%s&pagesize=42&parent_category_id=%s&sort_type=asc-%s", pg, tid, hm); + } else { + text = String.format("-keyword=%s&need_fragment=1&page=1&pagesize=42&sort_type=asc-%s", URLEncoder.encode(tid).toLowerCase(), hm); + } + } else if (URL.equals(detail)) { + text = String.format("-id=%s-%s", tid, hm); + } else if (URL.equals(play)) { + text = String.format("-quality=4&video_fragment_id=%s&video_id=%s-%s", pg, tid, hm); + } + String sign = AES.MD5(text); + Map header = new HashMap<>(); + header.put("User-Agent", Util.CHROME); + header.put("referer", "https://www.uvod.tv/"); + header.put("origin", "https://www.uvod.tv"); + header.put("content-type", "application/json"); + header.put("accept", "*/*"); + header.put("x-signature", sign); + header.put("x-timestamp", hm); + header.put("x-token", ""); + System.out.println(header); + return header; + } + + private Map playHeader() { + Map header = new HashMap<>(); + header.put("User-Agent", Util.CHROME); + header.put("referer", "https://www.uvod.tv/"); + header.put("origin", "https://www.uvod.tv"); + return header; + } + + @Override + public void init(Context context, String extend) throws Exception { + if (!extend.isEmpty()) { + siteUrl = extend; + } + } + + public String encrypt(String data) throws Exception { + String aesKey = AES.randomKey(32); + String aesEncryptedData = AES.aesEncrypt(data, aesKey, "abcdefghijklmnop"); + String rsaEncryptedKey = AES.rsaEncrypt(aesKey, publicKeyPem); + return aesEncryptedData + "." + rsaEncryptedKey; + } + + public String decrypt(String encryptedData) throws Exception { + encryptedData = encryptedData.replaceAll("\\s", ""); + String[] parts = encryptedData.split("\\."); + if (parts.length != 2) { + return null; + } + String rsaEncryptedKey = parts[1]; + String decryptedKey = AES.decryptRSA(rsaEncryptedKey, privateKeyPem); + String aesEncryptedData = parts[0]; + return AES.CBC(aesEncryptedData, decryptedKey, "abcdefghijklmnop"); + } + + @Override + public String homeContent(boolean filter) throws Exception { + List classes = new ArrayList<>(); + List typeIds = Arrays.asList("101", "100", "106", "102", "103", "104", "105"); + List typeNames = Arrays.asList("电视剧", "电影", "粤台专区", "综艺", "动漫", "体育", "纪录片"); + for (int i = 0; i < typeIds.size(); i++) + classes.add(new Class(typeIds.get(i), typeNames.get(i))); + String param = "{\"parent_category_id\":101}"; + String encryptData = encrypt(param); + String content = OkHttp.post(latest, encryptData, getHeader(latest)).getBody(); + String decryptData = decrypt(content); + List list = new ArrayList<>(); + Data data = Data.objectFrom(decryptData); + for (Data.Videolatest video : data.getVideolatest()) { + list.add(video.vod()); + } + return Result.string(classes, list); + } + + @Override + public String categoryContent(String tid, String pg, boolean filter, HashMap extend) throws Exception { + String param = String.format("{\"parent_category_id\":\"%s\",\"category_id\":null,\"language\":null,\"year\":null,\"region\":null,\"state\":null,\"keyword\":\"\",\"paid\":null,\"page\":%s,\"pagesize\":42,\"sort_field\":\"\",\"sort_type\":\"asc\"}", tid, pg); + String encryptData = encrypt(param); + String content = OkHttp.post(list, encryptData, getHeader(list + "|" + tid + "|" + pg)).getBody(); + String decryptData = decrypt(content); + List list = new ArrayList<>(); + Data data = Data.objectFrom(decryptData); + for (Data.Videolatest video : data.getVideolatest()) { + list.add(video.vod()); + } + return Result.string(list); + } + + @Override + public String detailContent(List ids) throws Exception { + String param = String.format("{\"id\":\"%s\"}", ids.get(0)); + String encryptData = encrypt(param); + String content = OkHttp.post(detail, encryptData, getHeader(detail + "|" + ids.get(0))).getBody(); + String decryptData = decrypt(content); + Data data = Data.objectFrom(decryptData); + StringBuilder vod_play_url = new StringBuilder(); + List videoFragmentList = data.getVideoFragmentList(); + for (int j = 0; j < videoFragmentList.size(); j++) { + Data.VideoFragmentList videolist = videoFragmentList.get(j); + String name = videolist.getSymbol(); + String nid = videolist.getId(); + nid = ids.get(0) + "|" + nid; + vod_play_url.append(name).append("$").append(nid); + boolean notLastEpisode = j < videoFragmentList.size() - 1; + vod_play_url.append(notLastEpisode ? "#" : "$$$"); + } + Data.Video video = data.getVideo(); + Vod vod = new Vod(); + vod.setVodId(ids.get(0)); + vod.setVodYear(video.getYear()); + vod.setVodArea(video.getRegion()); + vod.setVodActor(video.getStarring()); + vod.setVodRemarks(video.getState()); + vod.setVodContent(video.getDescription()); + vod.setVodDirector(video.getDirector()); + vod.setTypeName(video.getLanguage()); + vod.setVodPlayFrom("Qile"); + vod.setVodPlayUrl(vod_play_url.toString()); + return Result.string(vod); + } + + @Override + public String searchContent(String key, boolean quick) throws Exception { + String param = String.format("{\"parent_category_id\":null,\"category_id\":null,\"language\":null,\"year\":null,\"region\":null,\"state\":null,\"keyword\":\"%s\",\"paid\":null,\"page\":1,\"pagesize\":42,\"sort_field\":\"\",\"sort_type\":\"asc\",\"need_fragment\":1}", key); + String encryptData = encrypt(param); + String content = OkHttp.post(list, encryptData, getHeader(list + "|" + key)).getBody(); + String decryptData = decrypt(content); + List list = new ArrayList<>(); + Data data = Data.objectFrom(decryptData); + for (Data.Videolatest video : data.getVideolatest()) { + list.add(video.vod()); + } + return Result.string(list); + } + + @Override + public String playerContent(String flag, String id, List vipFlags) throws Exception { + String[] item = id.split("\\|"); + String tid = item[0]; + String nid = item[1]; + String param = String.format("{\"video_id\":\"%s\",\"video_fragment_id\":%s,\"quality\":4,\"seek\":null}", tid, nid); + String encryptData = encrypt(param); + String content = OkHttp.post(play, encryptData, getHeader(play + "|" + tid + "|" + nid)).getBody(); + String decryptData = decrypt(content); + Data data = Data.objectFrom(decryptData); + Data.Video video = data.getVideo(); + String realUrl = video.getUrl(); + return Result.get().url(realUrl).header(playHeader()).string(); + } + +} diff --git a/app/src/main/java/com/github/catvod/utils/AES.java b/app/src/main/java/com/github/catvod/utils/AES.java new file mode 100644 index 00000000..61a9dcfa --- /dev/null +++ b/app/src/main/java/com/github/catvod/utils/AES.java @@ -0,0 +1,115 @@ +package com.github.catvod.utils; + +import android.annotation.SuppressLint; + +import java.net.URLDecoder; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.security.Key; +import java.security.KeyFactory; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.spec.AlgorithmParameterSpec; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; +import java.util.Base64; + +import javax.crypto.Cipher; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.DESKeySpec; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; + +public class AES { + + @SuppressLint("NewApi") + public static String CBC(String src, String KEY, String IV) { + try { + src = src.replace("\\", ""); + Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + SecretKeySpec keySpec = new SecretKeySpec(KEY.getBytes(), "AES"); + AlgorithmParameterSpec paramSpec = new IvParameterSpec(IV.getBytes()); + cipher.init(Cipher.DECRYPT_MODE, keySpec, paramSpec); + byte[] decrypted = cipher.doFinal(Base64.getDecoder().decode(src)); + return new String(decrypted); + } catch (Exception ignored) { + + } + return null; + } + + @SuppressLint("NewApi") + public static String aesEncrypt(String data, String key, String iv) throws Exception { + SecretKeySpec secretKey = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "AES"); + IvParameterSpec ivSpec = new IvParameterSpec(iv.getBytes(StandardCharsets.UTF_8)); + Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivSpec); + byte[] encrypted = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8)); + return Base64.getEncoder().withoutPadding().encodeToString(encrypted); + + } + + @SuppressLint("NewApi") + public static String rsaEncrypt(String data, String publicKeyPem) throws Exception { + String publicKeyPEM = publicKeyPem.replace("-----BEGIN PUBLIC KEY-----", "") + .replace("-----END PUBLIC KEY-----", "") + .replaceAll("\\s+", ""); + byte[] decoded = Base64.getDecoder().decode(publicKeyPEM); + X509EncodedKeySpec spec = new X509EncodedKeySpec(decoded); + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + PublicKey publicKey = keyFactory.generatePublic(spec); + Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding"); + cipher.init(Cipher.ENCRYPT_MODE, publicKey); + byte[] encrypted = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8)); + return Base64.getEncoder().encodeToString(encrypted); + } + + @SuppressLint("NewApi") + public static String decryptRSA(String encryptedKey, String privateKeyPem) throws Exception { + String privateKeyPEM = privateKeyPem.replace("-----BEGIN PRIVATE KEY-----", "") + .replace("-----END PRIVATE KEY-----", "") + .replaceAll("\\s", ""); + byte[] privateKeyBytes = Base64.getDecoder().decode(privateKeyPEM); + PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKeyBytes); + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + PrivateKey privateKey = keyFactory.generatePrivate(keySpec); + + Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding"); + cipher.init(Cipher.DECRYPT_MODE, privateKey); + byte[] decrypted = cipher.doFinal(Base64.getDecoder().decode(encryptedKey)); + return new String(decrypted, StandardCharsets.UTF_8); + } + + public static String MD5(String src) { + try { + MessageDigest md = MessageDigest.getInstance("MD5"); + byte[] bytes = md.digest(src.getBytes(Charset.forName("UTF-8"))); + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < bytes.length; i++) { + int v = bytes[i] & 0xFF; + String hv = Integer.toHexString(v); + if (hv.length() < 2) { + sb.append(0); + } + sb.append(hv); + } + return sb.toString().toLowerCase(); + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + } + return ""; + } + + public static String randomKey(int size) { + String keys = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + StringBuilder key = new StringBuilder(); + for (int i = 0; i < size; i++) { + int pos = (int) Math.floor(Math.random() * keys.length()); + key.append(keys.charAt(pos)); + } + return key.toString(); + } + +} diff --git a/jar/custom_spider.jar b/jar/custom_spider.jar index a4e6cc48..678136f8 100644 Binary files a/jar/custom_spider.jar and b/jar/custom_spider.jar differ diff --git a/jar/custom_spider.jar.md5 b/jar/custom_spider.jar.md5 index af574295..5cf48cc5 100644 --- a/jar/custom_spider.jar.md5 +++ b/jar/custom_spider.jar.md5 @@ -1 +1 @@ -343fe4d32b861b765cadbab3e28b618a +150642bf400eaca2938193dc2d092663