From 92c50f9780457fb9d39590ca9cfffec313f03b15 Mon Sep 17 00:00:00 2001 From: FongMi Date: Sat, 15 Apr 2023 02:46:23 +0800 Subject: [PATCH] Support WebDAV - part 1 --- app/build.gradle | 9 +- app/proguard-rules.pro | 24 ++- .../java/com/github/catvod/bean/Result.java | 4 +- .../main/java/com/github/catvod/bean/Vod.java | 4 +- .../com/github/catvod/bean/alist/Item.java | 8 +- .../com/github/catvod/bean/webdav/Drive.java | 107 ++++++++++ .../com/github/catvod/bean/webdav/Sorter.java | 37 ++++ .../java/com/github/catvod/spider/Proxy.java | 5 +- .../java/com/github/catvod/spider/WebDAV.java | 197 ++++++++++++++++++ .../java/com/github/catvod/utils/Utils.java | 2 +- gradle.properties | 3 +- settings.gradle | 1 + 12 files changed, 373 insertions(+), 28 deletions(-) create mode 100644 app/src/main/java/com/github/catvod/bean/webdav/Drive.java create mode 100644 app/src/main/java/com/github/catvod/bean/webdav/Sorter.java create mode 100644 app/src/main/java/com/github/catvod/spider/WebDAV.java diff --git a/app/build.gradle b/app/build.gradle index fd3b12e8..10b439d2 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -8,7 +8,7 @@ android { defaultConfig { applicationId "com.github.catvod.demo" - minSdk 17 + minSdk 21 targetSdk 29 ndk { abiFilters "armeabi-v7a" } buildConfigField("String", "CLIENT_ID", "\"${clientId}\"") @@ -36,11 +36,12 @@ android { dependencies { //Debug For HTTP/3 debugImplementation 'org.chromium.net:cronet-embedded:101.4951.41' + implementation 'com.github.thegrizzlylabs:sardine-android:0.8' implementation 'com.google.net.cronet:cronet-okhttp:0.1.0' + implementation 'com.squareup.okhttp3:okhttp:5.0.0-alpha.10' implementation 'androidx.annotation:annotation:1.5.0' - implementation 'com.squareup.okhttp3:okhttp:3.12.13' - implementation 'com.google.code.gson:gson:2.8.6' + implementation 'com.google.code.gson:gson:2.10.1' implementation 'cn.wanghaomiao:JsoupXpath:2.5.1' - implementation 'com.google.zxing:core:3.3.0' + implementation 'com.google.zxing:core:3.4.1' implementation 'org.jsoup:jsoup:1.15.3' } \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 481d17d5..660f5b68 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -11,7 +11,7 @@ -keepattributes Signature -keepattributes *Annotation* -dontwarn sun.misc.** --keep class com.google.gson.**{*;} +-keep class com.google.gson.** { *; } -keep class * extends com.google.gson.TypeAdapter -keep class * implements com.google.gson.TypeAdapterFactory -keep class * implements com.google.gson.JsonSerializer @@ -20,16 +20,20 @@ -keep,allowobfuscation,allowshrinking class com.google.gson.reflect.TypeToken -keep,allowobfuscation,allowshrinking class * extends com.google.gson.reflect.TypeToken +# Cronet +-keep class org.chromium.net.** { *; } +-keep class com.google.net.cronet.** { *; } + +# OkHttp +-dontwarn okhttp3.** +-keep class okio.** { *; } +-keep class okhttp3.** { *; } + +# Sardine +-keep class com.thegrizzlylabs.sardineandroid.** { *; } + # Zxing -keepclassmembers enum * { public static **[] values(); public static ** valueOf(java.lang.String); -} - -# OkHttp --keep class okio.**{*;} --keep class okhttp3.**{*;} - -# Cronet --keep class org.chromium.net.**{*;} --keep class com.google.net.cronet.**{*;} \ No newline at end of file +} \ No newline at end of file diff --git a/app/src/main/java/com/github/catvod/bean/Result.java b/app/src/main/java/com/github/catvod/bean/Result.java index 0962ad12..5bdbd6cf 100644 --- a/app/src/main/java/com/github/catvod/bean/Result.java +++ b/app/src/main/java/com/github/catvod/bean/Result.java @@ -11,9 +11,9 @@ import org.json.JSONObject; import java.lang.reflect.Type; import java.util.Arrays; import java.util.Collections; -import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; public class Result { @@ -105,7 +105,7 @@ public class Result { return this; } - public Result header(HashMap header) { + public Result header(Map header) { if (header.isEmpty()) return this; this.header = new Gson().toJson(header); return this; diff --git a/app/src/main/java/com/github/catvod/bean/Vod.java b/app/src/main/java/com/github/catvod/bean/Vod.java index 314f4da2..17d4c54a 100644 --- a/app/src/main/java/com/github/catvod/bean/Vod.java +++ b/app/src/main/java/com/github/catvod/bean/Vod.java @@ -47,12 +47,12 @@ public class Vod { setVodRemarks(vodRemarks); } - public Vod(String vodId, String vodName, String vodPic, String vodRemarks, String vodTag) { + public Vod(String vodId, String vodName, String vodPic, String vodRemarks, boolean folder) { setVodId(vodId); setVodName(vodName); setVodPic(vodPic); setVodRemarks(vodRemarks); - setVodTag(vodTag); + setVodTag(folder ? "folder" : "file"); } public void setTypeName(String typeName) { diff --git a/app/src/main/java/com/github/catvod/bean/alist/Item.java b/app/src/main/java/com/github/catvod/bean/alist/Item.java index ff026deb..8a0c166a 100644 --- a/app/src/main/java/com/github/catvod/bean/alist/Item.java +++ b/app/src/main/java/com/github/catvod/bean/alist/Item.java @@ -120,15 +120,11 @@ public class Item { return Utils.getSize(getSize()); } - public String getVodTag() { - return isFolder() ? "folder" : "file"; - } - public Vod getVod(String id, String pic) { - return new Vod(getVodId(id), getName(), getPic(pic), getRemark(), getVodTag()); + return new Vod(getVodId(id), getName(), getPic(pic), getRemark(), isFolder()); } public Vod getVod(Drive drive, String pic) { - return new Vod(getVodId(drive.getName()), getName(), getPic(pic), drive.getName(), getVodTag()); + return new Vod(getVodId(drive.getName()), getName(), getPic(pic), drive.getName(), isFolder()); } } diff --git a/app/src/main/java/com/github/catvod/bean/webdav/Drive.java b/app/src/main/java/com/github/catvod/bean/webdav/Drive.java new file mode 100644 index 00000000..8da1a4c6 --- /dev/null +++ b/app/src/main/java/com/github/catvod/bean/webdav/Drive.java @@ -0,0 +1,107 @@ +package com.github.catvod.bean.webdav; + +import android.net.Uri; +import android.text.TextUtils; + +import com.github.catvod.bean.Class; +import com.github.catvod.bean.Vod; +import com.github.catvod.utils.Utils; +import com.google.gson.Gson; +import com.google.gson.annotations.SerializedName; +import com.thegrizzlylabs.sardineandroid.DavResource; +import com.thegrizzlylabs.sardineandroid.Sardine; +import com.thegrizzlylabs.sardineandroid.impl.OkHttpSardine; + +import java.util.ArrayList; +import java.util.List; + +public class Drive { + + @SerializedName("vodPic") + private String vodPic; + @SerializedName("drives") + private List drives; + @SerializedName("name") + private String name; + @SerializedName("server") + private String server; + @SerializedName("user") + private String user; + @SerializedName("pass") + private String pass; + @SerializedName("path") + private String path; + @SerializedName("webdav") + private Sardine webdav; + + public static Drive objectFrom(String str) { + return new Gson().fromJson(str, Drive.class); + } + + public Drive(String name) { + this.name = name; + } + + public List getDrives() { + return drives == null ? new ArrayList<>() : drives; + } + + public String getVodPic() { + return TextUtils.isEmpty(vodPic) ? "" : vodPic; + } + + public String getName() { + return TextUtils.isEmpty(name) ? "" : name; + } + + public String getServer() { + return TextUtils.isEmpty(server) ? "" : server; + } + + public String getUser() { + return TextUtils.isEmpty(user) ? "" : user; + } + + public String getPass() { + return TextUtils.isEmpty(pass) ? "" : pass; + } + + public String getPath() { + return TextUtils.isEmpty(path) ? "" : path; + } + + public void setPath(String path) { + this.path = TextUtils.isEmpty(path) ? "" : path; + } + + public String getHost() { + return getServer().replace(getPath(), ""); + } + + public Sardine getWebdav() { + return webdav; + } + + public Class toType() { + return new Class(getName(), getName(), "1"); + } + + public Drive init() { + webdav = new OkHttpSardine(); + webdav.setCredentials(getUser(), getPass()); + setPath(Uri.parse(getServer()).getPath()); + return this; + } + + public Vod vod(DavResource item, String vodPic) { + return new Vod(getName() + item.getPath(), item.getName(), vodPic, Utils.getSize(item.getContentLength()), item.isDirectory()); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (!(obj instanceof Drive)) return false; + Drive it = (Drive) obj; + return getName().equals(it.getName()); + } +} diff --git a/app/src/main/java/com/github/catvod/bean/webdav/Sorter.java b/app/src/main/java/com/github/catvod/bean/webdav/Sorter.java new file mode 100644 index 00000000..8a201717 --- /dev/null +++ b/app/src/main/java/com/github/catvod/bean/webdav/Sorter.java @@ -0,0 +1,37 @@ +package com.github.catvod.bean.webdav; + +import com.thegrizzlylabs.sardineandroid.DavResource; + +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +public class Sorter implements Comparator { + + private final String type; + private final String order; + + public static void sort(String type, String order, List items) { + Collections.sort(items, new Sorter(type, order)); + } + + public Sorter(String type, String order) { + this.type = type; + this.order = order; + } + + @Override + public int compare(DavResource o1, DavResource o2) { + boolean asc = order.equals("asc"); + switch (type) { + case "name": + return asc ? o1.getName().compareTo(o2.getName()) : o2.getName().compareTo(o1.getName()); + case "size": + return asc ? Long.compare(o1.getContentLength(), o2.getContentLength()) : Long.compare(o2.getContentLength(), o1.getContentLength()); + case "date": + return asc ? o1.getModified().compareTo(o2.getModified()) : o2.getModified().compareTo(o1.getModified()); + default: + return -1; + } + } +} diff --git a/app/src/main/java/com/github/catvod/spider/Proxy.java b/app/src/main/java/com/github/catvod/spider/Proxy.java index 123b639a..41a7426f 100644 --- a/app/src/main/java/com/github/catvod/spider/Proxy.java +++ b/app/src/main/java/com/github/catvod/spider/Proxy.java @@ -5,7 +5,6 @@ import com.github.catvod.crawler.SpiderDebug; import com.github.catvod.net.OkHttp; import java.io.ByteArrayInputStream; -import java.io.UnsupportedEncodingException; import java.util.Map; import java.util.Objects; @@ -13,12 +12,14 @@ public class Proxy extends Spider { private static int port = -1; - public static Object[] proxy(Map params) throws UnsupportedEncodingException { + public static Object[] proxy(Map params) throws Exception { switch (Objects.requireNonNull(params.get("do"))) { case "ck": return new Object[]{200, "text/plain; charset=utf-8", new ByteArrayInputStream("ok".getBytes("UTF-8"))}; case "ali": return Ali.vod(params); + case "webdav": + return WebDAV.vod(params); default: return null; } diff --git a/app/src/main/java/com/github/catvod/spider/WebDAV.java b/app/src/main/java/com/github/catvod/spider/WebDAV.java new file mode 100644 index 00000000..9bdfc534 --- /dev/null +++ b/app/src/main/java/com/github/catvod/spider/WebDAV.java @@ -0,0 +1,197 @@ +package com.github.catvod.spider; + +import android.content.Context; +import android.text.TextUtils; + +import com.github.catvod.bean.Class; +import com.github.catvod.bean.Filter; +import com.github.catvod.bean.Result; +import com.github.catvod.bean.Sub; +import com.github.catvod.bean.Vod; +import com.github.catvod.bean.webdav.Drive; +import com.github.catvod.bean.webdav.Sorter; +import com.github.catvod.crawler.Spider; +import com.github.catvod.net.OkHttp; +import com.github.catvod.utils.Utils; +import com.thegrizzlylabs.sardineandroid.DavResource; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +public class WebDAV extends Spider { + + private static List drives; + private List playExt; + private List allExt; + private String vodPic; + private String ext; + + private List getFilter() { + List items = new ArrayList<>(); + items.add(new Filter("type", "排序類型", Arrays.asList(new Filter.Value("預設", ""), new Filter.Value("名稱", "name"), new Filter.Value("大小", "size"), new Filter.Value("修改時間", "date")))); + items.add(new Filter("order", "排序方式", Arrays.asList(new Filter.Value("預設", ""), new Filter.Value("⬆", "asc"), new Filter.Value("⬇", "desc")))); + return items; + } + + private void fetchRule() { + if (drives != null && !drives.isEmpty()) return; + if (ext.startsWith("http")) ext = OkHttp.string(ext); + Drive drive = Drive.objectFrom(ext); + drives = drive.getDrives(); + vodPic = drive.getVodPic(); + } + + private String getExt(DavResource item) { + return item.getName().substring(item.getName().lastIndexOf(".") + 1); + } + + private String removeExt(DavResource item) { + return item.getName().indexOf(".") > 0 ? item.getName().substring(0, item.getName().lastIndexOf(".")) : item.getName(); + } + + private static Drive getDrive(String name) { + return drives.get(drives.indexOf(new Drive(name))); + } + + @Override + public void init(Context context, String extend) { + playExt = Arrays.asList("mp4", "mkv", "flv", "avi", "mp3", "aac", "flac", "m4a"); + allExt = new ArrayList<>(Arrays.asList("ass", "ssa", "srt")); + allExt.addAll(playExt); + ext = extend; + fetchRule(); + } + + @Override + public String homeContent(boolean filter) throws Exception { + fetchRule(); + List classes = new ArrayList<>(); + LinkedHashMap> filters = new LinkedHashMap<>(); + for (Drive drive : drives) classes.add(drive.init().toType()); + for (Class item : classes) filters.put(item.getTypeId(), getFilter()); + return Result.string(classes, filters); + } + + @Override + public String categoryContent(String tid, String pg, boolean filter, HashMap extend) throws Exception { + String key = tid.contains("/") ? tid.substring(0, tid.indexOf("/")) : tid; + String path = tid.contains("/") ? tid.substring(tid.indexOf("/")) : ""; + String order = extend.containsKey("order") ? extend.get("order") : ""; + String type = extend.containsKey("type") ? extend.get("type") : ""; + List folders = new ArrayList<>(); + List files = new ArrayList<>(); + List list = new ArrayList<>(); + Drive drive = getDrive(key); + for (DavResource item : getList(drive, path, playExt)) { + if (item.isDirectory()) folders.add(item); + else files.add(item); + } + if (!TextUtils.isEmpty(type) && !TextUtils.isEmpty(order)) { + Sorter.sort(type, order, folders); + Sorter.sort(type, order, files); + } + for (DavResource item : folders) list.add(drive.vod(item, vodPic)); + for (DavResource item : files) list.add(drive.vod(item, vodPic)); + return Result.get().vod(list).page().string(); + } + + @Override + public String detailContent(List ids) throws Exception { + String id = ids.get(0); + String key = id.contains("/") ? id.substring(0, id.indexOf("/")) : id; + String name = id.substring(id.lastIndexOf("/") + 1); + String parent = id.substring(0, id.lastIndexOf("/")); + String path = parent.contains("/") ? parent.substring(parent.indexOf("/")) + "/" : ""; + Drive drive = getDrive(key); + List parents = getList(drive, path, allExt); + List subs = getSubs(parents); + Sorter.sort("name", "asc", parents); + List playUrls = new ArrayList<>(); + for (DavResource item : parents) { + if (playExt.contains(getExt(item))) { + playUrls.add(item.getName() + "$" + drive.getName() + item.getPath() + findSubs(drive, item, subs)); + } + } + Vod vod = new Vod(); + vod.setVodId(id); + //TODO 資料夾名稱 + vod.setVodName(name); + vod.setVodPic(vodPic); + vod.setVodPlayFrom(key); + vod.setVodPlayUrl(TextUtils.join("#", playUrls)); + return Result.string(vod); + } + + @Override + public String playerContent(String flag, String id, List vipFlags) throws Exception { + String[] ids = id.split("~~~"); + String key = ids[0].contains("/") ? ids[0].substring(0, ids[0].indexOf("/")) : ids[0]; + return Result.get().url(getProxyUrl(key, ids[0])).subs(getSub(ids)).string(); + } + + private List getList(Drive drive, String path, List ext) throws Exception { + path = drive.getHost() + (path.startsWith(drive.getPath()) ? path : drive.getPath() + path); + List items = drive.getWebdav().list(path); + items.remove(0); //Remove parent + Iterator iterator = items.iterator(); + while (iterator.hasNext()) { + DavResource item = iterator.next(); + if (!item.isDirectory() && !item.getName().contains(".")) iterator.remove(); + if (!item.isDirectory() && !ext.contains(getExt(item))) iterator.remove(); + } + return items; + } + + private List getSubs(List items) { + List subs = new ArrayList<>(); + for (DavResource item : items) if (Utils.isSub(getExt(item))) subs.add(item); + return subs; + } + + private String findSubs(Drive drive, DavResource res, List items) { + StringBuilder sb = new StringBuilder(); + for (DavResource item : items) if (removeExt(item).equals(removeExt(res))) sb.append("~~~").append(item.getName()).append("@@@").append(getExt(item)).append("@@@").append(drive.getName() + item.getPath()); + return sb.length() > 0 ? sb.toString() : findSubs(drive, items); + } + + private String findSubs(Drive drive, List items) { + StringBuilder sb = new StringBuilder(); + for (DavResource item : items) sb.append("~~~").append(item.getName()).append("@@@").append(getExt(item)).append("@@@").append(drive.getName() + item.getPath()); + return sb.toString(); + } + + private List getSub(String[] ids) { + List sub = new ArrayList<>(); + for (String text : ids) { + if (!text.contains("@@@")) continue; + String[] split = text.split("@@@"); + String name = split[0]; + String ext = split[1]; + String key = split[2].contains("/") ? split[2].substring(0, split[2].indexOf("/")) : split[2]; + String url = getProxyUrl(key, split[2]); + sub.add(Sub.create().name(name).ext(ext).url(url)); + } + return sub; + } + + private String getProxyUrl(String key, String path) { + return Proxy.getUrl() + "?do=webdav" + "&key=" + key + "&url=" + getDrive(key).getHost() + path.replace(key, ""); + } + + public static Object[] vod(Map params) throws IOException { + String key = params.get("key"); + String url = params.get("url"); + Drive drive = getDrive(key); + Object[] result = new Object[3]; + result[0] = 200; + result[1] = "application/octet-stream"; + result[2] = drive.getWebdav().get(url); + return result; + } +} diff --git a/app/src/main/java/com/github/catvod/utils/Utils.java b/app/src/main/java/com/github/catvod/utils/Utils.java index 05884b54..9eac47d9 100644 --- a/app/src/main/java/com/github/catvod/utils/Utils.java +++ b/app/src/main/java/com/github/catvod/utils/Utils.java @@ -39,7 +39,7 @@ public class Utils { } public static String getSize(double size) { - if (size == 0) return ""; + if (size <= 0) return ""; if (size > 1024 * 1024 * 1024 * 1024.0) { size /= (1024 * 1024 * 1024 * 1024.0); return String.format(Locale.getDefault(), "%.2f%s", size, "TB"); diff --git a/gradle.properties b/gradle.properties index f48eeb14..7ace3705 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,7 +6,7 @@ # http://www.gradle.org/docs/current/userguide/build_environment.html # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. -org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 +org.gradle.jvmargs=-Xmx4096m -Dfile.encoding=UTF-8 # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects @@ -15,6 +15,7 @@ org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 # Android operating system, and which are packaged with your app"s APK # https://developer.android.com/topic/libraries/support-library/androidx-rn android.useAndroidX=true +android.enableJetifier=true # Enables namespacing of each library's R class so that its R class includes only the # resources declared in the library itself and none from the library's dependencies, # thereby reducing the size of the R class for that library diff --git a/settings.gradle b/settings.gradle index 70a86c32..b9c12608 100644 --- a/settings.gradle +++ b/settings.gradle @@ -11,6 +11,7 @@ dependencyResolutionManagement { repositories { google() mavenCentral() + maven { url 'https://jitpack.io' } } } rootProject.name = "CatVodSpider"