From 8c9cf31df36b7d7b71500fa6276746b770283344 Mon Sep 17 00:00:00 2001 From: okjack <***@gmail.com> Date: Wed, 29 Nov 2023 21:26:39 +0800 Subject: [PATCH 1/2] =?UTF-8?q?=E5=8E=9F=E7=94=BB=E5=A4=9A=E7=BA=BF?= =?UTF-8?q?=E7=A8=8B=EF=BC=8C=E5=88=86=E4=BA=AB=E5=8E=9F=E7=94=BB=E4=B8=8D?= =?UTF-8?q?=E8=BD=AC=E5=AD=98=EF=BC=8C=E5=8E=9F=E7=94=BB=E6=99=AE=E7=94=BB?= =?UTF-8?q?15=E5=88=86=E9=92=9F=E8=BF=87=E6=9C=9F=E9=97=AE=E9=A2=98?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/github/catvod/api/AliYun.java | 165 +++++++++++++++++- .../java/com/github/catvod/spider/Ali.java | 1 + .../com/github/catvod/utils/ProxyVideo.java | 55 ++++++ 3 files changed, 217 insertions(+), 4 deletions(-) create mode 100644 app/src/main/java/com/github/catvod/utils/ProxyVideo.java diff --git a/app/src/main/java/com/github/catvod/api/AliYun.java b/app/src/main/java/com/github/catvod/api/AliYun.java index 81f4c47..cefc32d 100644 --- a/app/src/main/java/com/github/catvod/api/AliYun.java +++ b/app/src/main/java/com/github/catvod/api/AliYun.java @@ -5,6 +5,7 @@ import android.content.DialogInterface; import android.content.Intent; import android.graphics.Color; import android.graphics.drawable.ColorDrawable; +import android.net.UrlQuerySanitizer; import android.os.SystemClock; import android.text.TextUtils; import android.view.Gravity; @@ -34,10 +35,13 @@ import com.github.catvod.net.OkResult; import com.github.catvod.spider.Init; import com.github.catvod.spider.Proxy; import com.github.catvod.utils.MultiThread; +import com.github.catvod.utils.MultiThreadedDownloader; import com.github.catvod.utils.Path; +import com.github.catvod.utils.ProxyVideo; import com.github.catvod.utils.QRCode; import com.github.catvod.utils.Utils; import com.google.gson.JsonObject; +import com.google.gson.JsonParser; import java.io.ByteArrayInputStream; import java.io.File; @@ -47,11 +51,14 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.TreeMap; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import java.util.concurrent.locks.ReentrantLock; +import fi.iki.elonen.NanoHTTPD; import okhttp3.Response; public class AliYun { @@ -62,6 +69,10 @@ public class AliYun { private String refreshToken; private Share share; private Cache cache; + private Map downloadMap; + private Map shareDownloadMap; + private Map> m3u8MediaMap; + private final ReentrantLock rLock; private static class Loader { static volatile AliYun INSTANCE = new AliYun(); @@ -79,6 +90,10 @@ public class AliYun { Init.checkPermission(); tempIds = new ArrayList<>(); cache = Cache.objectFrom(Path.read(getCache())); + downloadMap = new HashMap<>(); + shareDownloadMap = new HashMap<>(); + m3u8MediaMap = new HashMap<>(); + rLock = new ReentrantLock(); } public void setRefreshToken(String token) { @@ -320,8 +335,28 @@ public class AliYun { return sub; } + public String getShareDownloadUrl(String shareId, String fileId) { + try { + if (shareDownloadMap.containsKey(fileId) && shareDownloadMap.get(fileId) != null && !isExpire(shareDownloadMap.get(fileId))) return shareDownloadMap.get(fileId); + refreshShareToken(shareId); + SpiderDebug.log("getShareDownloadUrl..." + fileId); + JsonObject param = new JsonObject(); + param.addProperty("file_id", fileId); + param.addProperty("share_id", shareId); + param.addProperty("expire_sec", 600); + String json = auth("v2/file/get_share_link_download_url", param.toString(), false); + String url = JsonParser.parseString(json).getAsJsonObject().get("download_url").getAsString(); + shareDownloadMap.put(fileId, url); + return url; + } catch (Exception e) { + e.printStackTrace(); + return ""; + } + } + public String getDownloadUrl(String shareId, String fileId) { try { + if (downloadMap.containsKey(fileId) && downloadMap.get(fileId) != null && !isExpire(downloadMap.get(fileId))) return downloadMap.get(fileId); refreshShareToken(shareId); SpiderDebug.log("getDownloadUrl..." + fileId); tempIds.add(0, copy(shareId, fileId)); @@ -329,7 +364,9 @@ public class AliYun { param.addProperty("file_id", tempIds.get(0)); param.addProperty("drive_id", cache.getDrive().getDriveId()); String json = oauth("openFile/getDownloadUrl", param.toString(), true); - return Download.objectFrom(json).getUrl(); + String url = Download.objectFrom(json).getUrl(); + downloadMap.put(fileId, url); + return url; } catch (Exception e) { e.printStackTrace(); return ""; @@ -362,9 +399,9 @@ public class AliYun { if (flag.split("#")[0].equals("普畫")) { return getPreviewContent(ids); } else if (flag.split("#")[0].equals("原畫")) { - return Result.get().url(getDownloadUrl(ids[0], ids[1])).octet().subs(getSubs(ids)).header(getHeader()).string(); - } else if (flag.split("#")[0].equals("極速")) { - return Result.get().url(MultiThread.url(getDownloadUrl(ids[0], ids[1]), 5)).octet().subs(getSubs(ids)).header(getHeader()).string(); + return Result.get().url(proxyVideoUrl("open", ids[0], ids[1])).octet().subs(getSubs(ids)).header(getHeader()).string(); + } else if (flag.split("#")[0].equals("分享原畫")) { + return Result.get().url(proxyVideoUrl("share", ids[0], ids[1])).octet().subs(getSubs(ids)).header(getHeader()).string(); } else { return ""; } @@ -373,6 +410,12 @@ public class AliYun { private String getPreviewContent(String[] ids) { Preview.Info info = getVideoPreviewPlayInfo(ids[0], ids[1]); List url = getPreviewUrl(info); + List proxyUrl = new ArrayList<>(); + for(int i = 0; i < url.size(); i++) { + String item = url.get(i); + if (item.startsWith("http")) item = proxyVideoUrl("preview", ids[0], ids[1], url.get(i-1)); + proxyUrl.add(item); + } List subs = getSubs(ids); subs.addAll(getSubs(info)); return Result.get().url(url).m3u8().subs(subs).header(getHeader()).string(); @@ -419,6 +462,120 @@ public class AliYun { return resp.getResponse().getStatus() == 404; } + private String proxyVideoUrl(String cate, String shareId, String fileId) { + return String.format(Proxy.getUrl() + "?do=ali&type=video&cate=%s&shareId=%s&fileId=%s", cate, shareId, fileId); + } + + private String proxyVideoUrl(String cate, String shareId, String fileId, String templateId) { + return String.format(Proxy.getUrl() + "?do=ali&type=video&cate=%s&shareId=%s&fileId=%s&templateId=%s", cate, shareId, fileId, templateId); + } + + private String proxyVideoUrl(String cate, String shareId, String fileId, String templateId, String mediaId) { + return String.format(Proxy.getUrl() + "?do=ali&type=video&cate=%s&shareId=%s&fileId=%s&templateId=%s&mediaId=%s", cate, shareId, fileId, templateId, mediaId); + } + + private static boolean isExpire(String url) { + String expires = new UrlQuerySanitizer(url).getValue("x-oss-expires"); + if (TextUtils.isEmpty(expires)) return false; + if (new Long(expires) - getTimeStamp() <= 60) return true; + return false; + } + + private static long getTimeStamp() { + return System.currentTimeMillis() / 1000; + } + + public Object[] proxyVideo(Map params) throws Exception { + String fileId = params.get("fileId"); + String shareId = params.get("shareId"); + String cate = params.get("cate"); + String templateId = params.get("templateId"); + String mediaId = params.get("mediaId"); + String downloadUrl = ""; + int thread = 1; + switch (cate) { + case "preview": + return previewProxy(shareId, fileId, templateId); + case "m3u8": + rLock.lock(); + String mediaUrl = m3u8MediaMap.get(fileId).get(mediaId); + if (isExpire(mediaUrl)) { + getM3u8(shareId, fileId, templateId); + mediaUrl = m3u8MediaMap.get(fileId).get(mediaId); + } + rLock.unlock(); + downloadUrl = mediaUrl; + break; + case "open": + downloadUrl = getDownloadUrl(shareId, fileId); + thread = 30; + break; + case "share": + downloadUrl = getShareDownloadUrl(shareId, fileId); + thread = 30; + break; + default: + break; + } + Map headers = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); + for (String key : params.keySet()) headers.put(key, params.get(key)); + headers.remove("do"); + headers.remove("type"); + headers.remove("cate"); + headers.remove("fileId"); + headers.remove("shareId"); + headers.remove("templateId"); + headers.remove("mediaId"); + headers.remove("host"); + headers.remove("remote-addr"); + headers.remove("http-client-ip"); + NanoHTTPD.Response mResponse; + if (thread == 1) { + mResponse = ProxyVideo.proxy(downloadUrl, headers); + } else { + mResponse = new MultiThreadedDownloader(downloadUrl, headers, thread).start(); + } + return new Object[]{mResponse}; + } + + private Object[] previewProxy(String shareId, String fileId, String templateId) { + String m3u8 = getM3u8(shareId, fileId, templateId); + return new Object[]{200, "application/vnd.apple.mpegurl", new ByteArrayInputStream(m3u8.getBytes())}; + } + + private String getM3u8Url(String shareId, String fileId, String templateId) { + Preview.Info info = getVideoPreviewPlayInfo(shareId, fileId); + List url = getPreviewUrl(info); + Map previewMap = new HashMap<>(); + for(int i = 0; i < url.size(); i=i+2) { + previewMap.put(url.get(i), url.get(i+1)); + } + String m3u8Url = previewMap.get(templateId); + return m3u8Url; + } + + private String getM3u8(String shareId, String fileId, String templateId) { + String m3u8Url = getM3u8Url(shareId, fileId, templateId); + String m3u8 = OkHttp.string(m3u8Url, getHeader()); + String[] m3u8Arr = m3u8.split("\\\n"); + List listM3u8 = new ArrayList<>(); + Map media = new HashMap<>(); + String site = m3u8Url.substring(0, m3u8Url.lastIndexOf("/")) + "/"; + int mediaId = 0; + for(String oneLine:m3u8Arr) { + String thisOne = oneLine; + if (oneLine.contains("x-oss-expires")) { + media.put("" + mediaId, site + thisOne); + thisOne = proxyVideoUrl("m3u8", shareId, fileId, templateId, "" + mediaId); + mediaId ++; + } + listM3u8.add(thisOne); + } + m3u8MediaMap.put(fileId, media); + String content = TextUtils.join("\n", listM3u8); + return content; + } + public Object[] proxySub(Map params) throws Exception { String fileId = params.get("fileId"); String shareId = params.get("shareId"); diff --git a/app/src/main/java/com/github/catvod/spider/Ali.java b/app/src/main/java/com/github/catvod/spider/Ali.java index 611cde6..6ffd2dd 100644 --- a/app/src/main/java/com/github/catvod/spider/Ali.java +++ b/app/src/main/java/com/github/catvod/spider/Ali.java @@ -80,6 +80,7 @@ public class Ali extends Spider { public static Object[] proxy(Map params) throws Exception { String type = params.get("type"); + if ("video".equals(type)) return AliYun.get().proxyVideo(params); if ("sub".equals(type)) return AliYun.get().proxySub(params); if ("token".equals(type)) return AliYun.get().getToken(); return null; diff --git a/app/src/main/java/com/github/catvod/utils/ProxyVideo.java b/app/src/main/java/com/github/catvod/utils/ProxyVideo.java new file mode 100644 index 0000000..df4dbdb --- /dev/null +++ b/app/src/main/java/com/github/catvod/utils/ProxyVideo.java @@ -0,0 +1,55 @@ +package com.github.catvod.utils; + +import static fi.iki.elonen.NanoHTTPD.newFixedLengthResponse; +import com.github.catvod.net.OkHttp; +import java.util.Map; +import fi.iki.elonen.NanoHTTPD; +import okhttp3.Response; + +public class ProxyVideo { + + public static NanoHTTPD.Response proxy(String url, Map headers) throws Exception { + NanoHTTPD.Response.Status status = NanoHTTPD.Response.Status.PARTIAL_CONTENT; + if (!headers.containsKey("Range")) { + headers.put("Range", "bytes=0-"); + status = NanoHTTPD.Response.Status.OK; + } + Response dResponse = OkHttp.newCall(url, headers); + String hContentLength = dResponse.headers().get("Content-Length"); + long contentLength = Long.parseLong(hContentLength); + String contentType = dResponse.headers().get("Content-Type"); + String contentDisposition = dResponse.headers().get("Content-Disposition"); + if (contentDisposition != null) { + if (contentDisposition.endsWith(".mp4")) { + contentType = "video/mp4"; + } else if (contentDisposition.endsWith(".webm")) { + contentType = "video/webm"; + } else if (contentDisposition.endsWith(".avi")) { + contentType = "video/x-msvideo"; + } else if (contentDisposition.endsWith(".wmv")) { + contentType = "video/x-ms-wmv"; + } else if (contentDisposition.endsWith(".flv")) { + contentType = "video/x-flv"; + } else if (contentDisposition.endsWith(".mov")) { + contentType = "video/quicktime"; + } else if (contentDisposition.endsWith(".mkv")) { + contentType = "video/x-matroska"; + } else if (contentDisposition.endsWith(".mpeg")) { + contentType = "video/mpeg"; + } else if (contentDisposition.endsWith(".3gp")) { + contentType = "video/3gpp"; + } else if (contentDisposition.endsWith(".ts")) { + contentType = "video/MP2T"; + } else if (contentDisposition.endsWith(".mp3")) { + contentType = "audio/mp3"; + } else if (contentDisposition.endsWith(".wav")) { + contentType = "audio/wav"; + } else if (contentDisposition.endsWith(".aac")) { + contentType = "audio/aac"; + } + } + return newFixedLengthResponse(status, contentType, dResponse.body().byteStream(), contentLength); + } + + +} From cda3b23b11fa5c50b360925742eeab34f1a59460 Mon Sep 17 00:00:00 2001 From: okjack <***@gmail.com> Date: Wed, 29 Nov 2023 21:28:28 +0800 Subject: [PATCH 2/2] =?UTF-8?q?=E5=8E=9F=E7=94=BB=E5=A4=9A=E7=BA=BF?= =?UTF-8?q?=E7=A8=8B=EF=BC=8C=E5=88=86=E4=BA=AB=E5=8E=9F=E7=94=BB=E4=B8=8D?= =?UTF-8?q?=E8=BD=AC=E5=AD=98=EF=BC=8C=E5=8E=9F=E7=94=BB=E6=99=AE=E7=94=BB?= =?UTF-8?q?15=E5=88=86=E9=92=9F=E8=BF=87=E6=9C=9F=E9=97=AE=E9=A2=98?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/github/catvod/utils/MultiThreadedDownloader.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/src/main/java/com/github/catvod/utils/MultiThreadedDownloader.java b/app/src/main/java/com/github/catvod/utils/MultiThreadedDownloader.java index d0874b9..b604542 100644 --- a/app/src/main/java/com/github/catvod/utils/MultiThreadedDownloader.java +++ b/app/src/main/java/com/github/catvod/utils/MultiThreadedDownloader.java @@ -113,6 +113,14 @@ public class MultiThreadedDownloader { contentType = "video/mpeg"; } else if (contentDisposition.endsWith(".3gp")) { contentType = "video/3gpp"; + } else if (contentDisposition.endsWith(".ts")) { + contentType = "video/MP2T"; + } else if (contentDisposition.endsWith(".mp3")) { + contentType = "audio/mp3"; + } else if (contentDisposition.endsWith(".wav")) { + contentType = "audio/wav"; + } else if (contentDisposition.endsWith(".aac")) { + contentType = "audio/aac"; } } if (contentType == null) {