package com.github.catvod.api; import com.github.catvod.net.OkHttp; import com.github.catvod.net.OkResult; import com.github.catvod.utils.Json; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import org.apache.commons.codec.binary.Base64; import javax.crypto.Cipher; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import java.io.IOException; import java.nio.charset.Charset; import java.security.GeneralSecurityException; import java.security.SecureRandom; import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; public class YunDrive { private final Pattern regex = Pattern.compile("https://yun\\.139\\.com/shareweb/#/w/i/([^&]+)"); private final SecretKeySpec secretKey; private final String baseUrl = "https://share-kd-njs.yun.139.com/yun-share/richlifeApp/devapp/IOutLink/"; private final Map baseHeaders = new HashMap<>(); private final Map cache = new HashMap<>(); private static class Loader { static volatile YunDrive INSTANCE = new YunDrive(); } public static YunDrive get() { return Loader.INSTANCE; } public YunDrive() { this.secretKey = new SecretKeySpec("PVGDwmcvfs1uV3d1".getBytes(Charset.defaultCharset()), "AES"); baseHeaders.put("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"); baseHeaders.put("Content-Type", "application/json"); baseHeaders.put("hcy-cool-flag", "1"); baseHeaders.put("x-deviceinfo", "||3|12.27.0|chrome|131.0.0.0|5c7c68368f048245e1ce47f1c0f8f2d0||windows 10|1536X695|zh-CN|||"); } private String encrypt(String data) throws GeneralSecurityException { Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding"); byte[] ivBytes = new byte[16]; new SecureRandom().nextBytes(ivBytes); cipher.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(ivBytes)); byte[] encrypted = cipher.doFinal(data.getBytes(Charset.defaultCharset())); byte[] combined = new byte[ivBytes.length + encrypted.length]; System.arraycopy(ivBytes, 0, combined, 0, ivBytes.length); System.arraycopy(encrypted, 0, combined, ivBytes.length, encrypted.length); return Base64.encodeBase64String(combined); } private String decrypt(String data) throws GeneralSecurityException { byte[] combined = Base64.decodeBase64(data); byte[] ivBytes = Arrays.copyOfRange(combined, 0, 16); byte[] encrypted = Arrays.copyOfRange(combined, 16, combined.length); Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding"); cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(ivBytes)); return new String(cipher.doFinal(encrypted), Charset.defaultCharset()); } public String extractLinkID(String url) { String linkID = ""; Matcher matcher = regex.matcher(url); boolean finded = matcher.find(); if (!finded) { matcher = Pattern.compile("https://caiyun\\.139\\.com/m/i\\?([^&]+)").matcher(url); finded = matcher.find(); } if (finded) linkID = matcher.group(1); return linkID; } public JsonObject fetchShareInfo(String pCaID, String linkID) throws IOException, GeneralSecurityException { if (linkID.isEmpty()) throw new IllegalStateException("linkID not initialized"); String cacheKey = linkID + "-" + pCaID; if (cache.containsKey(cacheKey)) return cache.get(cacheKey); Map requestBody = Map.of("getOutLinkInfoReq", Map.of("account", "", "linkID", linkID, "passwd", "", "caSrt", 1, "coSrt", 1, "srtDr", 0, "bNum", 1, "pCaID", pCaID, "eNum", 200), "commonAccountInfo", Map.of("account", "", "accountType", 1)); OkResult okResult = OkHttp.post(baseUrl + "getOutLinkInfoV6", encrypt(Json.toJson(requestBody)), baseHeaders); JsonObject result = Json.safeObject(decrypt(okResult.getBody())).getAsJsonObject("data"); cache.put(cacheKey, result); return result; } public Map>> processShareData(String url) throws Exception { if (url == null || url.isEmpty()) return Collections.emptyMap(); boolean isUrl = url.startsWith("http"); String pCaID = isUrl ? "root" : url; String linkID = ""; if (isUrl) linkID = extractLinkID(url); List> fileList = fetchFileList(pCaID, linkID); Map>> result = new LinkedHashMap<>(); for (Map item : fileList) { String name = item.get("name"); List> subItems = fetchUrlList(item.get("path"), linkID); if (!subItems.isEmpty()) { List> list = result.get(name); if (list == null) { list = new ArrayList<>(); result.put(name, list); } list.addAll(subItems); } } if (result.isEmpty()) { List> rootItems = fetchFileList(url, linkID); List> filteredList = new ArrayList<>(); for (Map m : rootItems) { if (!m.isEmpty()) { filteredList.add(m); } } result.put("root", filteredList); } return result; } private List> fetchFileList(String pCaID, String linkID) throws Exception { if (pCaID == null) return Collections.emptyList(); String actualID = pCaID.startsWith("http") ? "root" : pCaID; JsonObject response = fetchShareInfo(actualID, linkID); if (!response.has("caLst")) return Collections.emptyList(); List> items = new ArrayList<>(); Pattern filter = Pattern.compile("App|活动中心|免费|1T空间|免流"); JsonElement array = response.get("caLst"); if (!array.isJsonNull()) { for (JsonElement element : array.getAsJsonArray()) { JsonObject entry = element.getAsJsonObject(); String name = entry.get("caName").getAsString(); String path = entry.get("path").getAsString(); if (!filter.matcher(name).find()) { items.add(Map.of("name", name, "path", path)); items.addAll(fetchFileList(path, linkID)); } } } return items; } private List> fetchUrlList(String pCaID, String linkID) throws Exception { JsonObject response = fetchShareInfo(pCaID, linkID); List> items = new ArrayList<>(); if (response.has("coLst")) { for (JsonElement element : response.getAsJsonArray("coLst")) { JsonObject entry = element.getAsJsonObject(); if (entry.get("coType").getAsInt() == 3) { items.add(Map.of("name", entry.get("coName").getAsString(), "contentId", entry.get("coID").getAsString(), "linkID", linkID)); } } } else if (response.has("caLst")) { for (JsonElement element : response.getAsJsonArray("caLst")) { items.addAll(fetchUrlList(element.getAsJsonObject().get("path").getAsString(), linkID)); } } return items; } public String fetchPlayUrl(String contentId, String linkID) throws Exception { Map requestBody = Map.of("getContentInfoFromOutLinkReq", Map.of("contentId", contentId, "linkID", linkID, "account", ""), "commonAccountInfo", Map.of("account", "", "accountType", 1)); OkResult okResult = OkHttp.post(baseUrl + "getContentInfoFromOutLink", Json.toJson(requestBody), Map.of("Accept-Encoding", "gzip, deflate, br, zstd", "User-Agent", baseHeaders.get("User-Agent"))); return Json.safeObject(okResult.getBody()).getAsJsonObject("data").getAsJsonObject("contentInfo").get("presentURL").getAsString(); } }