diff --git a/app/src/main/java/com/github/catvod/api/QRCodeHandler.java b/app/src/main/java/com/github/catvod/api/QRCodeHandler.java new file mode 100644 index 00000000..93b8ab8f --- /dev/null +++ b/app/src/main/java/com/github/catvod/api/QRCodeHandler.java @@ -0,0 +1,306 @@ +package com.github.catvod.api; + +import android.app.AlertDialog; +import android.content.DialogInterface; +import android.graphics.Color; +import android.graphics.drawable.ColorDrawable; +import android.view.Gravity; +import android.widget.FrameLayout; +import android.widget.ImageView; +import com.github.catvod.crawler.SpiderDebug; +import com.github.catvod.net.OkHttp; +import com.github.catvod.net.OkResult; +import com.github.catvod.spider.Init; +import com.github.catvod.utils.*; +import com.google.gson.JsonObject; +import org.apache.commons.lang3.StringUtils; + +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +public class QRCodeHandler { + private static final String CLIENT_ID = "5acf882d27b74502b7040b0c65519aa7"; + private static final String SIGN_KEY = "l3srvtd7p42l0d0x1u8d7yc8ye9kki4d"; + private static final String API_URL = "https://open-api-drive.uc.cn"; + private static final String CODE_API_URL = "http://api.extscreen.com/ucdrive"; + + + private Map platformStates = new HashMap<>(); + private Map addition = new HashMap<>(); + private Map conf = new HashMap<>(); + + + private ScheduledExecutorService service; + private AlertDialog dialog; + + public QRCodeHandler() { + addition.put("DeviceID", "07b48aaba8a739356ab8107b5e230ad4"); + conf.put("api", API_URL); + conf.put("clientID", CLIENT_ID); + conf.put("signKey", SIGN_KEY); + conf.put("appVer", "1.6.8"); + conf.put("channel", "UCTVOFFICIALWEB"); + conf.put("codeApi", CODE_API_URL); + } + + private String generateUUID() { + return UUID.randomUUID().toString(); + } + + private String generateDeviceID(String timestamp) { + + return Util.MD5(timestamp).substring(0, 16); + } + + private String generateReqId(String deviceID, String timestamp) { + + return Util.MD5(deviceID + timestamp).substring(0, 16); + } + + private String generateXPanToken(String method, String pathname, String timestamp, String key) { + + return Util.sha256Hex(method + "&" + pathname + "&" + timestamp + "&" + key); + } + + public String startUC_TOKENScan() throws Exception { + String pathname = "/oauth/authorize"; + String timestamp = String.valueOf(new Date().getTime() / 1000 + 1) + "000"; + String deviceID = StringUtils.isNoneBlank((String) addition.get("DeviceID")) ? (String) addition.get("DeviceID") : generateDeviceID(timestamp); + String reqId = generateReqId(deviceID, timestamp); + String token = generateXPanToken("GET", pathname, timestamp, (String) conf.get("signKey")); + + Map headers = new HashMap<>(); + headers.put("Accept", "application/json, text/plain, */*"); + headers.put("User-Agent", "Mozilla/5.0 (Linux; U; Android 13; zh-cn; M2004J7AC Build/UKQ1.231108.001) AppleWebKit/533.1 (KHTML, like Gecko) Mobile Safari/533.1"); + headers.put("x-pan-tm", timestamp); + headers.put("x-pan-token", token); + headers.put("x-pan-client-id", (String) conf.get("clientID")); + headers.put("Host", "open-api-drive.uc.cn"); + Map params = new HashMap<>(); + params.put("req_id", reqId); + params.put("access_token", StringUtils.isNoneBlank((String) addition.get("AccessToken")) ? (String) addition.get("AccessToken") : ""); + params.put("app_ver", (String) conf.get("appVer")); + params.put("device_id", deviceID); + params.put("device_brand", "Xiaomi"); + params.put("platform", "tv"); + params.put("device_name", "M2004J7AC"); + params.put("device_model", "M2004J7AC"); + params.put("build_device", "M2004J7AC"); + params.put("build_product", "M2004J7AC"); + params.put("device_gpu", "Adreno (TM) 550"); + params.put("activity_rect", URLEncoder.encode("{}", "UTF-8")); + params.put("channel", (String) conf.get("channel")); + params.put("auth_type", "code"); + params.put("client_id", (String) conf.get("clientID")); + params.put("scope", "netdisk"); + params.put("qrcode", "1"); + params.put("qr_width", "460"); + params.put("qr_height", "460"); + OkResult okResult = OkHttp.get(API_URL + pathname, params, headers); + + + JsonObject resData = Json.safeObject(okResult.getBody()); + String queryToken = resData.get("query_token").getAsString(); + String qrCode = resData.get("qr_data").getAsString(); + + platformStates.put("UC_TOKEN", new HashMap() {{ + put("query_token", queryToken); + put("request_id", reqId); + }}); + + showQRCode(qrCode); + Init.execute(() -> startService()); + /*Map result = new HashMap<>(); + result.put("qrcode", "data:image/png;base64," + qrCode); + result.put("status", "NEW");*/ + return qrCode; + + } + + + public Map checkUC_TOKENStatus() throws UnsupportedEncodingException { + Map state = (Map) platformStates.get("UC_TOKEN"); + if (state == null) { + return Map.of("status", "EXPIRED"); + } + + String pathname = "/oauth/code"; + String timestamp = String.valueOf(new Date().getTime() / 1000) + "000"; + String deviceID = StringUtils.isAllBlank((String) addition.get("DeviceID")) ? (String) addition.get("DeviceID") : generateDeviceID(timestamp); + String reqId = generateReqId(deviceID, timestamp); + String xPanToken = generateXPanToken("GET", pathname, timestamp, (String) conf.get("signKey")); + + Map headers = new HashMap<>(); + headers.put("Accept", "application/json, text/plain, */*"); + headers.put("User-Agent", "Mozilla/5.0 (Linux; U; Android 13; zh-cn; M2004J7AC Build/UKQ1.231108.001) AppleWebKit/533.1 (KHTML, like Gecko) Mobile Safari/533.1"); + headers.put("x-pan-tm", timestamp); + headers.put("x-pan-token", xPanToken); + headers.put("x-pan-client-id", (String) conf.get("clientID")); + + Map params = new HashMap<>(); + params.put("req_id", reqId); + params.put("access_token", (String) addition.get("AccessToken")); + params.put("app_ver", (String) conf.get("appVer")); + params.put("device_id", deviceID); + params.put("device_brand", "Xiaomi"); + params.put("platform", "tv"); + params.put("device_name", "M2004J7AC"); + params.put("device_model", "M2004J7AC"); + params.put("build_device", "M2004J7AC"); + params.put("build_product", "M2004J7AC"); + params.put("device_gpu", "Adreno (TM) 550"); + params.put("activity_rect", URLEncoder.encode("{}", "UTF-8")); + params.put("channel", (String) conf.get("channel")); + params.put("client_id", (String) conf.get("clientID")); + params.put("scope", "netdisk"); + params.put("query_token", state.get("query_token")); + + OkResult okResult = OkHttp.get(API_URL + pathname, params, headers); + + + if (okResult.getCode() == 200) { + JsonObject resData = Json.safeObject(okResult.getBody()); + String code = resData.get("code").getAsString(); + + pathname = "/token"; + reqId = generateReqId(deviceID, timestamp); + + Map postData = new HashMap<>(); + postData.put("req_id", reqId); + postData.put("app_ver", (String) conf.get("appVer")); + postData.put("device_id", deviceID); + postData.put("device_brand", "Xiaomi"); + postData.put("platform", "tv"); + postData.put("device_name", "M2004J7AC"); + postData.put("device_model", "M2004J7AC"); + postData.put("build_device", "M2004J7AC"); + postData.put("build_product", "M2004J7AC"); + postData.put("device_gpu", "Adreno (TM) 550"); + postData.put("activity_rect", URLEncoder.encode("{}", "UTF-8")); + postData.put("channel", (String) conf.get("channel")); + postData.put("code", code); + + OkResult okResult1 = OkHttp.post(API_URL + pathname, params, headers); + + + if (okResult1.getCode() == 200) { + JsonObject tokenResData = Json.safeObject(okResult1.getBody()); + platformStates.remove("UC_TOKEN"); + Map result = new HashMap<>(); + result.put("status", "CONFIRMED"); + result.put("cookie", tokenResData.get("access_token").getAsString()); + //停止检验线程,关闭弹窗 + stopService(); + return result; + } + + } else if (okResult.getCode() == 400) { + return Map.of("status", "NEW"); + } + + + platformStates.remove("UC_TOKEN"); + return Map.of("status", "EXPIRED"); + } + + public void download(String token, String saveFileId) throws Exception { + String pathname = "/file"; + String timestamp = Long.toString(System.currentTimeMillis() / 1000); + String deviceID = StringUtils.isAllBlank((String) addition.get("DeviceID")) ? (String) addition.get("DeviceID") : generateDeviceID(timestamp); + String reqId = generateReqId(deviceID, timestamp); + String xPanToken = generateXPanToken("GET", pathname, timestamp, (String) conf.get("signKey")); + + Map headers = new HashMap<>(); + headers.put("Accept-Encoding", "gzip"); + headers.put("User-Agent", "Mozilla/5.0 (Linux; U; Android 13; zh-cn; M2004J7AC Build/UKQ1.231108.001) AppleWebKit/533.1 (KHTML, like Gecko) Mobile Safari/533.1"); + headers.put("x-pan-tm", timestamp); + headers.put("x-pan-token", xPanToken); + headers.put("x-pan-client-id", (String) conf.get("clientID")); + + + Map params = new HashMap<>(); + params.put("req_id", reqId); + params.put("access_token", token); + params.put("app_ver", (String) conf.get("appVer")); + params.put("device_id", deviceID); + params.put("device_brand", "Xiaomi"); + params.put("platform", "tv"); + params.put("device_name", "M2004J7AC"); + params.put("device_model", "M2004J7AC"); + params.put("build_device", "M2004J7AC"); + params.put("build_product", "M2004J7AC"); + params.put("device_gpu", "Adreno (TM) 550"); + params.put("activity_rect", URLEncoder.encode("{}", "UTF-8")); + params.put("channel", (String) conf.get("channel")); + params.put("method", "streaming"); + params.put("group_by", "source"); + params.put("fid", saveFileId); + params.put("resolution", "low,normal,high,super,2k,4k"); + params.put("support", "dolby_vision"); + + OkResult okResult1 = OkHttp.post(API_URL + pathname, params, headers); + SpiderDebug.log("uc TV 下载文件内容:" + okResult1.getBody()); + + } + + /** + * 显示qrcode + * + * @param base64Str + */ + public void showQRCode(String base64Str) { + try { + int size = ResUtil.dp2px(240); + FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(size, size); + ImageView image = new ImageView(Init.context()); + image.setScaleType(ImageView.ScaleType.CENTER_CROP); + image.setImageBitmap(QRCode.base64StringToImage(base64Str)); + FrameLayout frame = new FrameLayout(Init.context()); + params.gravity = Gravity.CENTER; + frame.addView(image, params); + dialog = new AlertDialog.Builder(Init.getActivity()).setView(frame).setOnCancelListener(this::dismiss).setOnDismissListener(this::dismiss).show(); + dialog.getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); + Notify.show("请使用uc网盘App扫描二维码"); + } catch (Exception ignored) { + } + } + + private void dismiss() { + try { + if (dialog != null) dialog.dismiss(); + } catch (Exception ignored) { + } + } + + private void dismiss(DialogInterface dialog) { + stopService(); + } + + private void stopService() { + if (service != null) service.shutdownNow(); + Init.run(this::dismiss); + } + + public void startService() { + SpiderDebug.log("----start UC token service"); + + service = Executors.newScheduledThreadPool(1); + + service.scheduleWithFixedDelay(() -> { + try { + checkUC_TOKENStatus(); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + + }, 1, 3, TimeUnit.SECONDS); + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/github/catvod/api/UCApi.java b/app/src/main/java/com/github/catvod/api/UCApi.java index 223a57ae..87d3ab45 100644 --- a/app/src/main/java/com/github/catvod/api/UCApi.java +++ b/app/src/main/java/com/github/catvod/api/UCApi.java @@ -12,7 +12,6 @@ import android.view.ViewGroup; import android.widget.EditText; import android.widget.FrameLayout; import android.widget.ImageView; - import com.github.catvod.bean.Result; import com.github.catvod.bean.Vod; import com.github.catvod.bean.uc.Cache; @@ -26,25 +25,23 @@ import com.github.catvod.spider.Init; import com.github.catvod.spider.Proxy; import com.github.catvod.utils.*; import com.google.gson.Gson; - import org.apache.commons.lang3.RandomUtils; import org.apache.commons.lang3.StringUtils; import java.io.ByteArrayInputStream; import java.io.File; import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; import java.util.*; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; import java.util.regex.Pattern; -import java.util.stream.Collectors; public class UCApi { private String apiUrl = "https://pc-api.uc.cn/1/clouddrive/"; private String cookie = ""; + private String cookieToken = ""; private String ckey = ""; private Map> shareTokenCache = new HashMap<>(); private String pr = "pr=UCBrowser&fr=pc"; @@ -59,6 +56,7 @@ public class UCApi { private AlertDialog dialog; private String serviceTicket; + private QRCodeHandler qrCodeHandler; public Object[] proxyVideo(Map params) throws Exception { String url = Util.base64Decode(params.get("url")); @@ -163,6 +161,8 @@ public class UCApi { Init.checkPermission(); cache = Cache.objectFrom(Path.read(getCache())); + qrCodeHandler = new QRCodeHandler(); + this.cookieToken = cache.getUser().getToken(); } public File getCache() { @@ -681,10 +681,20 @@ public class UCApi { if (saveFileId == null) return null; this.saveFileIdCaches.put(fileId, saveFileId); } - Map down = Json.parseSafe(api("file/download?" + this.pr + "&uc_param_str=", Collections.emptyMap(), Map.of("fids", List.of(this.saveFileIdCaches.get(fileId))), 0, "POST"), Map.class); + + //token 为空,扫码登录 + if (StringUtils.isBlank(cookieToken)) { + cookieToken = qrCodeHandler.startUC_TOKENScan(); + SpiderDebug.log("扫码登录获取到的cookieToken: " + cookieToken); + } + SpiderDebug.log("cookieToken不为空: " + cookieToken + ";开始下载"); + qrCodeHandler.download(cookieToken, this.saveFileIdCaches.get(fileId)); + + + /* Map down = Json.parseSafe(api("file/download?" + this.pr + "&uc_param_str=", Collections.emptyMap(), Map.of("fids", List.of(this.saveFileIdCaches.get(fileId))), 0, "POST"), Map.class); if (down.get("data") != null) { return ((List>) down.get("data")).get(0).get("download_url").toString(); - } + }*/ return null; } diff --git a/app/src/main/java/com/github/catvod/bean/uc/User.java b/app/src/main/java/com/github/catvod/bean/uc/User.java index 714f3739..0fdb0781 100644 --- a/app/src/main/java/com/github/catvod/bean/uc/User.java +++ b/app/src/main/java/com/github/catvod/bean/uc/User.java @@ -9,6 +9,16 @@ public class User { @SerializedName("cookie") private String cookie; + @SerializedName("token") + private String token; + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } public String getCookie() { return cookie; diff --git a/app/src/main/java/com/github/catvod/utils/QRCode.java b/app/src/main/java/com/github/catvod/utils/QRCode.java index 7f2fc916..20bdaf70 100644 --- a/app/src/main/java/com/github/catvod/utils/QRCode.java +++ b/app/src/main/java/com/github/catvod/utils/QRCode.java @@ -1,7 +1,8 @@ package com.github.catvod.utils; import android.graphics.Bitmap; - +import android.graphics.BitmapFactory; +import android.util.Base64; import com.google.zxing.BarcodeFormat; import com.google.zxing.EncodeHintType; import com.google.zxing.MultiFormatWriter; @@ -41,4 +42,15 @@ public class QRCode { return null; } } + + public static Bitmap base64StringToImage(String strBase64) { + try { + byte[] arr = android.util.Base64.decode(strBase64, Base64.NO_WRAP); + Bitmap bmp = BitmapFactory.decodeByteArray(arr, 0, arr.length); + return bmp; + } catch (Exception ex) { + return null; + } + } + } diff --git a/app/src/main/java/com/github/catvod/utils/Util.java b/app/src/main/java/com/github/catvod/utils/Util.java index 8f5b8fac..f8ec0452 100644 --- a/app/src/main/java/com/github/catvod/utils/Util.java +++ b/app/src/main/java/com/github/catvod/utils/Util.java @@ -351,6 +351,16 @@ public class Util { return hexString.toString(); } + public static String sha256Hex(String input) { + try { + MessageDigest md = MessageDigest.getInstance("SHA-256"); + byte[] messageDigest = md.digest(input.getBytes(Charset.defaultCharset())); + return bytesToHex(messageDigest); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + } + public static class LCSResult { public int length; diff --git a/app/src/test/java/com/github/catvod/api/QRCodeHandlerTest.java b/app/src/test/java/com/github/catvod/api/QRCodeHandlerTest.java new file mode 100644 index 00000000..5a391e8f --- /dev/null +++ b/app/src/test/java/com/github/catvod/api/QRCodeHandlerTest.java @@ -0,0 +1,44 @@ +package com.github.catvod.api; + +import com.github.catvod.net.OkHttp; +import com.github.catvod.net.OkResult; +import com.google.gson.JsonObject; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.robolectric.RobolectricTestRunner; + +import java.util.HashMap; +import java.util.Map; + +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.anyMap; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +@RunWith(RobolectricTestRunner.class) +public class QRCodeHandlerTest { + + private QRCodeHandler qrCodeHandler; + + + @Before + public void setUp() { + qrCodeHandler = new QRCodeHandler(); + + } + + @Test + public void testStartUC_TOKENScan() throws Exception { + // Mock the OkHttp.get method to return a predefined OkResult + + + // Execute the method under test + String result = qrCodeHandler.startUC_TOKENScan(); + System.out.println(result); + + + + } +} \ No newline at end of file diff --git a/jar/custom_spider.jar b/jar/custom_spider.jar index e68bffba..1b583e2f 100644 Binary files a/jar/custom_spider.jar and b/jar/custom_spider.jar differ diff --git a/jar/custom_spider.jar.md5 b/jar/custom_spider.jar.md5 index 2e8850fe..f737f139 100644 --- a/jar/custom_spider.jar.md5 +++ b/jar/custom_spider.jar.md5 @@ -1 +1 @@ -cb3d867389c8616647f4dcd1263c498d +3b08198a806ecc12e7211453f314fb37 diff --git a/json/index.json b/json/index.json index 0c54f06a..7e27bedc 100644 --- a/json/index.json +++ b/json/index.json @@ -1,5 +1,5 @@ { - "spider": "https://androidcatvodspider.netlify.app/jar/custom_spider.jar;md5;cb3d867389c8616647f4dcd1263c498d", + "spider": "https://androidcatvodspider.netlify.app/jar/custom_spider.jar;md5;3b08198a806ecc12e7211453f314fb37", "lives": [ { diff --git a/json/index1.json b/json/index1.json index e8e3c08e..db6dac34 100644 --- a/json/index1.json +++ b/json/index1.json @@ -1,5 +1,5 @@ { - "spider": "https://androidcatvodspider.netlify.app/jar/custom_spider.jar;md5;cb3d867389c8616647f4dcd1263c498d", + "spider": "https://androidcatvodspider.netlify.app/jar/custom_spider.jar;md5;3b08198a806ecc12e7211453f314fb37", "lives": [ { "name": "直播ipv6", diff --git a/json/index2.json b/json/index2.json index 2320d400..3120dc68 100644 --- a/json/index2.json +++ b/json/index2.json @@ -1,5 +1,5 @@ { - "spider": "https://androidcatvodspider.netlify.app/jar/custom_spider.jar;md5;cb3d867389c8616647f4dcd1263c498d", + "spider": "https://androidcatvodspider.netlify.app/jar/custom_spider.jar;md5;3b08198a806ecc12e7211453f314fb37", "lives": [ { "name": "直播ipv6", diff --git a/json/test.json b/json/test.json index b297a4eb..0f7db35d 100644 --- a/json/test.json +++ b/json/test.json @@ -1,5 +1,5 @@ { - "spider": "https://androidcatvodspider.netlify.app/jar/custom_spider.jar;md5;cb3d867389c8616647f4dcd1263c498d", + "spider": "https://androidcatvodspider.netlify.app/jar/custom_spider.jar;md5;3b08198a806ecc12e7211453f314fb37", "lives": [ { "name": "直播",