394 lines
16 KiB
Java
394 lines
16 KiB
Java
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<String, JsonObject> 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<String, String> getHeaders() {
|
||
Map<String, String> 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<Item> files = new ArrayList<>();
|
||
List<Item> 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<String> playFromtmp = new ArrayList<>();
|
||
playFromtmp.add("天意");
|
||
|
||
|
||
List<String> playUrl = new ArrayList<>();
|
||
|
||
if (files.isEmpty()) {
|
||
return null;
|
||
}
|
||
|
||
for (int index = 0; index < playFromtmp.size(); index++) {
|
||
List<String> 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<String, String> 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<String, String> 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<String, String> params, Map<String, Object> 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<Item> videos, List<Item> 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<String, Object> findBestLCS(Item mainItem, List<Item> targetItems) {
|
||
List<Map<String, Object>> 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<String, Object> 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<String, Object> bestMatch = results.get(bestMatchIndex);
|
||
Map<String, Object> 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<Item> videos, List<Item> 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<String, Object> matchSubtitle = findBestLCS(video, subtitles);
|
||
if (matchSubtitle.get("bestMatch") != null) {
|
||
video.setSubtitle((String) ((Map<String, Object>) matchSubtitle.get("bestMatch")).get("target"));
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
private String getDownload(String shareId, String fileId) throws Exception {
|
||
Map<String, String> 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 "";
|
||
}
|
||
|
||
|
||
}
|
||
|