parent
4397c6186e
commit
9f0a40f822
@ -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<Videolatest> videolatestlist; |
||||
@SerializedName(value = "video", alternate = {"video_soruce"}) |
||||
private Video video; |
||||
@SerializedName("video_fragment_list") |
||||
private List<VideoFragmentList> 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<Videolatest> getVideolatest() { |
||||
return videolatestlist == null ? Collections.emptyList() : videolatestlist; |
||||
} |
||||
|
||||
public Video getVideo() { |
||||
return video == null ? new Video() : video; |
||||
} |
||||
|
||||
public List<VideoFragmentList> 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; |
||||
} |
||||
|
||||
} |
||||
|
||||
|
||||
} |
||||
@ -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<String, String> 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<String, String> 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<String, String> playHeader() { |
||||
Map<String, String> 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<Class> classes = new ArrayList<>(); |
||||
List<String> typeIds = Arrays.asList("101", "100", "106", "102", "103", "104", "105"); |
||||
List<String> 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<Vod> 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<String, String> 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<Vod> 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<String> 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<Data.VideoFragmentList> 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<Vod> 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<String> 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(); |
||||
} |
||||
|
||||
} |
||||
@ -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(); |
||||
} |
||||
|
||||
} |
||||
@ -1 +1 @@ |
||||
343fe4d32b861b765cadbab3e28b618a |
||||
150642bf400eaca2938193dc2d092663 |
||||
|
||||
Loading…
Reference in new issue