package com.github.catvod.api; import android.text.TextUtils; import com.github.catvod.bean.Result; import com.github.catvod.bean.Vod; import com.github.catvod.bean.tianyi.Item; import com.github.catvod.bean.tianyi.ShareData; import com.github.catvod.crawler.SpiderDebug; import com.github.catvod.net.OkHttpWithCookie; import com.github.catvod.net.OkResult; import com.github.catvod.spider.Init; import com.github.catvod.spider.Proxy; import com.github.catvod.utils.*; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import org.apache.commons.lang3.StringUtils; import java.io.File; import java.nio.charset.Charset; import java.util.*; import java.util.concurrent.ScheduledExecutorService; import java.util.regex.Matcher; import java.util.regex.Pattern; public class TianyiApi { private String apiUrl = "https://cloud.189.cn/api/open/share/"; public static final String URL_START = "https://cloud.189.cn/"; private Map shareTokenCache = new HashMap<>(); private ScheduledExecutorService service; private String sessionKey = ""; private TianYiHandler tianYiHandler; private SimpleCookieJar cookieJar; public String[] getPlayFormatList() { return new String[]{"天意"}; } private static class Loader { static volatile TianyiApi INSTANCE = new TianyiApi(); } public static TianyiApi get() { return TianyiApi.Loader.INSTANCE; } public void setCookie(String token) throws Exception { if (StringUtils.isNoneBlank(token)) { JsonObject obj = Json.safeObject(token); //初始化CookieJar if (Objects.nonNull(obj)) { tianYiHandler.setCookie(obj); } } getUserSizeInfo(); this.sessionKey = getUserBriefInfo(); } private Map getHeaders() { Map headers = new HashMap<>(); headers.put("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) quark-cloud-drive/2.5.20 Chrome/100.0.4896.160 Electron/18.3.5.4-b478491100 Safari/537.36 Channel/pckk_other_ch"); headers.put("Content-Type", "application/x-www-form-urlencoded"); headers.put("accept", "application/json;charset=UTF-8"); if (StringUtils.isNotBlank(sessionKey)) { headers.put("sessionKey", sessionKey); } return headers; } private TianyiApi() { Init.checkPermission(); tianYiHandler = new TianYiHandler(); cookieJar = tianYiHandler.getCookieJar(); } public File getCache() { return Path.tv("tianyi"); } public Vod getVod(ShareData shareData) throws Exception { getShareToken(shareData); List files = new ArrayList<>(); List subs = new ArrayList<>(); try { JsonArray listData = listFile(1, shareData, files, subs, shareData.getShareId(), shareData.getFolderId(), 1); } catch (Exception e) { SpiderDebug.log("资源已取消:" + e.getMessage()); Notify.show("资源已取消"); throw new RuntimeException(e); } List playFromtmp = new ArrayList<>(); playFromtmp.add("天意"); List playUrl = new ArrayList<>(); if (files.isEmpty()) { return null; } for (int index = 0; index < playFromtmp.size(); index++) { List vodItems = new ArrayList<>(); for (Item video_item : files) { vodItems.add(video_item.getEpisodeUrl("电影"));// + findSubs(video_item.getName(), subs)); } playUrl.add(TextUtils.join("#", vodItems)); } Vod vod = new Vod(); vod.setVodId(""); vod.setVodContent(""); vod.setVodPic(""); vod.setVodName(""); vod.setVodPlayUrl(TextUtils.join("$$$", playUrl)); vod.setVodPlayFrom(TextUtils.join("$$$", playFromtmp)); vod.setTypeName("天意云盘"); return vod; } public String playerContent(String[] split, String flag) throws Exception { String fileId = split[0], shareId = split[1]; String playUrl = ""; playUrl = this.getDownload(shareId, fileId); Map header = getHeaders(); header.remove("Host"); header.remove("Content-Type"); return Result.get().url(ProxyVideo.buildCommonProxyUrl(playUrl, header)).octet().header(header).string(); } private String proxyVideoUrl(String url, Map header) { return String.format(Proxy.getUrl() + "?do=quark&type=video&url=%s&header=%s", Util.base64Encode(url.getBytes(Charset.defaultCharset())), Util.base64Encode(Json.toJson(header).getBytes(Charset.defaultCharset()))); } /** * @param url * @param params get 参数 * @param data post json * @param retry * @param method * @return * @throws Exception */ private String api(String url, Map params, Map data, Integer retry, String method) throws Exception { int leftRetry = retry != null ? retry : 3; OkResult okResult; if ("GET".equals(method)) { okResult = OkHttpWithCookie.get(this.apiUrl + url, params, getHeaders(), cookieJar); } else { okResult = OkHttpWithCookie.post(this.apiUrl + url, Json.toJson(data), getHeaders(), cookieJar); } /* if (okResult.getResp().get("Set-Cookie") != null) { Matcher matcher = Pattern.compile("__puus=([^;]+)").matcher(StringUtils.join(okResult.getResp().get("Set-Cookie"), ";;;")); if (matcher.find()) { Matcher cookieMatcher = Pattern.compile("__puus=([^;]+)").matcher(this.cookie); if (cookieMatcher.find() && !cookieMatcher.group(1).equals(matcher.group(1))) { this.cookie = this.cookie.replaceAll("__puus=[^;]+", "__puus=" + matcher.group(1)); } else { this.cookie = this.cookie + ";__puus=" + matcher.group(1); } } } */ if (okResult.getCode() != 200 && leftRetry > 0) { SpiderDebug.log("api error code:" + okResult.getCode()); Thread.sleep(1000); return api(url, params, data, leftRetry - 1, method); } return okResult.getBody(); } public ShareData getShareData(String url, String accessCode) { String shareCode = ""; // 第一种匹配规则:使用预编译的 regex Matcher matcher = Pattern.compile("https:\\/\\/cloud\\.189\\.cn\\/web\\/share\\?code=([^&]+)").matcher(url); if (matcher.find() && matcher.group(1) != null) { shareCode = matcher.group(1); // 从shareCode中提取访问码 Matcher accessMatcher = Pattern.compile("访问码:([a-zA-Z0-9]+)").matcher(shareCode); if (accessMatcher.find()) { accessCode = accessMatcher.group(1); } else { accessCode = ""; } } else { // 第二种匹配规则:直接匹配 cloud.189.cn/t/ 格式 Matcher fallbackMatcher = Pattern.compile("https://cloud\\.189\\.cn/t/([^&]+)").matcher(url); if (fallbackMatcher.find()) { shareCode = fallbackMatcher.group(1); } else { shareCode = null; } // 再次尝试从shareCode提取访问码 if (shareCode != null) { Matcher accessMatcher = Pattern.compile("访问码:([a-zA-Z0-9]+)").matcher(shareCode); accessCode = accessMatcher.find() ? accessMatcher.group(1) : ""; } else { accessCode = ""; } } shareCode = shareCode.split("(访问码")[0].trim(); ShareData shareData = new ShareData(shareCode, "0"); shareData.setSharePwd(accessCode); return shareData; } private String getUserBriefInfo() throws Exception { OkResult result = OkHttpWithCookie.get("https://cloud.189.cn/api/portal/v2/getUserBriefInfo.action", new HashMap<>(), getHeaders(), cookieJar); JsonObject obj = Json.safeObject(result.getBody()); return obj.get("sessionKey").getAsString(); } private String getUserSizeInfo() throws Exception { OkResult result = OkHttpWithCookie.get("https://cloud.189.cn/api/portal/getUserSizeInfo.action", new HashMap<>(), getHeaders(), cookieJar); JsonObject res = Json.safeObject(result.getBody()); if (StringUtils.isAllBlank(result.getBody()) || (Objects.nonNull(res.get("errorCode")) && res.get("errorCode").getAsString().equals("InvalidSessionKey"))) { // tianYiHandler.startScan(); tianYiHandler.refreshCookie(); //tianYiHandler.startScan(); } return ""; } private void getShareToken(ShareData shareData) throws Exception { if (!this.shareTokenCache.containsKey(shareData.getShareId())) { this.shareTokenCache.remove(shareData.getShareId()); JsonObject shareToken = Json.safeObject(api("getShareInfoByCodeV2.action?noCache=0.8886566349412803&shareCode=" + shareData.getShareId(), new HashMap<>(), new HashMap<>(), 0, "GET")); /** * { * "res_code" : 0.0, * "res_message" : "成功", * "accessCode" : "", * "creator" : { * "iconURL" : "", * "oper" : false, * "ownerAccount" : "185****1601@189.cn", * "superVip" : 33.0, * "vip" : 0.0 * }, * "expireTime" : 6.0, * "expireType" : 1.0, * "fileCreateDate" : "2025-03-20 13:49:18", * "fileId" : "12350115314094", * "fileLastOpTime" : "2025-03-20 13:49:19", * "fileName" : "05_如何制作动感影集.mp4等", * "fileSize" : 0.0, * "fileType" : "batchShare", * "isFolder" : true, * "needAccessCode" : 1.0, * "reviewStatus" : 1.0, * "shareDate" : 1.742449758E12, * "shareMode" : 1.0, * "shareType" : 1.0 * } */ if (Objects.nonNull(shareToken.get("res_code")) && shareToken.get("res_code").getAsInt() == 0) { shareData.setShareId(shareToken.get("shareId").getAsString()); shareData.setShareMode(shareToken.get("shareMode").getAsInt()); shareData.setFolder(shareToken.get("isFolder").getAsBoolean()); shareData.setFileId(shareToken.get("fileId").getAsString()); shareData.setFolderId(shareToken.get("fileId").getAsString()); this.shareTokenCache.put(shareData.getShareId(), shareToken); } } } private JsonArray listFile(int shareIndex, ShareData shareData, List videos, List subtitles, String shareId, String folderId, Integer page) throws Exception { int prePage = 200; page = page != null ? page : 1; String url = "listShareDir.action?" + "pageNum=" + page + "&pageSize=" + prePage + "&fileId=" + folderId + "&shareDirFileId=" + folderId + "&isFolder=" + shareData.getFolder() + "&shareId=" + shareId + "&shareMode=" + shareData.getShareMode() + "&iconOption=5" + "&orderBy=filename" + "&descending=false" + "&accessCode=" + shareData.getSharePwd(); JsonObject listData = Json.safeObject(api(url, Collections.emptyMap(), Collections.emptyMap(), 0, "GET")); if (listData.get("res_code").getAsInt() != 0) return new JsonArray(); if (listData.get("fileListAO").getAsJsonObject().get("count").getAsInt() == 0 && listData.get("fileListAO").getAsJsonObject().get("fileListSize").getAsInt() == 0) return new JsonArray(); JsonArray items = listData.get("fileListAO").getAsJsonObject().get("fileList").getAsJsonArray(); JsonArray subDir = listData.get("fileListAO").getAsJsonObject().get("folderList").getAsJsonArray(); for (JsonElement item : items) { if (item.getAsJsonObject().get("mediaType").getAsInt() == 3) { if (item.getAsJsonObject().get("size").getAsInt() < 1024 * 1024 * 5) continue; videos.add(Item.objectFrom(item.getAsJsonObject(), shareData.getShareId(), shareIndex)); } /*else if ("file".equals(item.get("type")) && this.subtitleExts.contains("." + Util.getExt((String) item.get("file_name")))) { subtitles.add(Item.objectFrom(item, shareData.getShareId(), shareIndex)); }*/ } if (listData.get("fileListAO").getAsJsonObject().get("count").getAsInt() > (items.size() + subDir.size())) { JsonArray nextItems = listFile(shareIndex, shareData, videos, subtitles, shareId, folderId, page + 1); items.addAll(nextItems); } for (JsonElement dir : subDir) { String subfolderId = dir.getAsJsonObject().get("id").getAsString(); JsonArray subItems = listFile(shareIndex, shareData, videos, subtitles, shareId, subfolderId, null); items.addAll(subItems); } return items; } private Map findBestLCS(Item mainItem, List targetItems) { List> results = new ArrayList<>(); int bestMatchIndex = 0; for (int i = 0; i < targetItems.size(); i++) { Util.LCSResult currentLCS = Util.lcs(mainItem.getName(), targetItems.get(i).getName()); Map result = new HashMap<>(); result.put("target", targetItems.get(i)); result.put("lcs", currentLCS); results.add(result); if (currentLCS.length > results.get(bestMatchIndex).get("lcs").toString().length()) { bestMatchIndex = i; } } Map bestMatch = results.get(bestMatchIndex); Map finalResult = new HashMap<>(); finalResult.put("allLCS", results); finalResult.put("bestMatch", bestMatch); finalResult.put("bestMatchIndex", bestMatchIndex); return finalResult; } public void getFilesByShareUrl(int shareIndex, String shareInfo, List videos, List subtitles) throws Exception { ShareData shareData = getShareData((String) shareInfo, ""); if (shareData == null) return; getShareToken(shareData); if (!this.shareTokenCache.containsKey(shareData.getShareId())) return; listFile(shareIndex, shareData, videos, subtitles, shareData.getShareId(), shareData.getFolderId(), 1); if (!subtitles.isEmpty()) { for (Item video : videos) { Map matchSubtitle = findBestLCS(video, subtitles); if (matchSubtitle.get("bestMatch") != null) { video.setSubtitle((String) ((Map) matchSubtitle.get("bestMatch")).get("target")); } } } } private String getDownload(String shareId, String fileId) throws Exception { Map headers = getHeaders(); //headers.remove("sessionKey"); OkResult result = OkHttpWithCookie.get("https://cloud.189.cn/api/portal/getNewVlcVideoPlayUrl.action?shareId=" + shareId + "&dt=1&fileId=" + fileId + "&type=4&key=noCache", new HashMap<>(), headers, cookieJar); JsonObject res = Json.safeObject(result.getBody()); if (Objects.nonNull(res.get("res_code")) && res.get("res_code").getAsInt() == 0) { if (res.get("normal") != null) { String normal = res.get("normal").getAsJsonObject().get("url").getAsString(); //String downloadUrl = OkHttp.getLocation(normal, headers); SpiderDebug.log("获取天翼下载地址成功:" + normal); return normal; } } else if (res.get("errorCode") != null && res.get("errorCode").getAsString().equals("InvalidSessionKey")) { //刷新cookie SpiderDebug.log("cookie 过期,刷新cookie。。。。"); tianYiHandler.refreshCookie(); //重试下载 SpiderDebug.log("重试下载。。。。"); getDownload(shareId, fileId); } return ""; } }