This commit is contained in:
lushunming 2025-03-13 08:38:36 +08:00
parent 61fdb1e569
commit 3444e1c5d2
12 changed files with 404 additions and 12 deletions

View File

@ -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<String, Object> platformStates = new HashMap<>();
private Map<String, String> addition = new HashMap<>();
private Map<String, String> 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<String, String> 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<String, String> 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<String, String>() {{
put("query_token", queryToken);
put("request_id", reqId);
}});
showQRCode(qrCode);
Init.execute(() -> startService());
/*Map<String, Object> result = new HashMap<>();
result.put("qrcode", "data:image/png;base64," + qrCode);
result.put("status", "NEW");*/
return qrCode;
}
public Map<String, Object> checkUC_TOKENStatus() throws UnsupportedEncodingException {
Map<String, String> state = (Map<String, String>) 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<String, String> 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<String, String> 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<String, Object> 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<String, Object> 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<String, String> 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<String, String> 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);
}
}

View File

@ -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<String, Map<String, Object>> 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<String, String> 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<String, Object> 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<String, Object> 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<Map<String, Object>>) down.get("data")).get(0).get("download_url").toString();
}
}*/
return null;
}

View File

@ -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;

View File

@ -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;
}
}
}

View File

@ -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;

View File

@ -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);
}
}

Binary file not shown.

View File

@ -1 +1 @@
cb3d867389c8616647f4dcd1263c498d
3b08198a806ecc12e7211453f314fb37

View File

@ -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": [
{

View File

@ -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",

View File

@ -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",

View File

@ -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": "直播",