diff --git a/app/src/main/java/com/github/catvod/spider/YangShi.java b/app/src/main/java/com/github/catvod/spider/YangShi.java new file mode 100644 index 0000000..57f5ea5 --- /dev/null +++ b/app/src/main/java/com/github/catvod/spider/YangShi.java @@ -0,0 +1,346 @@ +package com.github.catvod.spider; + +import android.content.Context; +import android.content.SharedPreferences; +import android.text.TextUtils; + +import com.github.catvod.bean.Class; +import com.github.catvod.bean.Result; +import com.github.catvod.bean.Vod; +import com.github.catvod.crawler.Spider; +import com.github.catvod.crawler.SpiderDebug; +import com.google.gson.Gson; +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +/** + * 央视直播爬虫 + * + * 功能: + * 1. homeContent - 显示直播列表 + * 2. categoryContent - 显示栏目列表 + 设置 + * 3. 支持切换内核(系统WebView/X5) + * 4. 预留API接口 + * + * @author xMydev + * @date 2025-01-22 + */ +public class YangShi extends Spider { + + private static final String TAG = "YangShi"; + + // 配置存储 + private static final String PREF_NAME = "yangshi_config"; + private static final String KEY_USE_X5 = "use_x5_webview"; + private static final String KEY_API_URL = "api_url"; + + // 预留API地址(暂未使用) + private String apiUrl = ""; + + // 默认使用系统WebView + private boolean useX5 = false; + + private SharedPreferences prefs; + + // ========== 写死的直播频道列表 ========== + private static final String[][] LIVE_CHANNELS = { + {"1", "CCTV-1 综合", "https://tv.cctv.com/live/cctv1/"}, + {"2", "CCTV-2 财经", "https://tv.cctv.com/live/cctv2/"}, + {"3", "CCTV-3 综艺", "https://tv.cctv.com/live/cctv3/"}, + {"4", "CCTV-4 中文国际", "https://tv.cctv.com/live/cctv4/"}, + {"5", "CCTV-5 体育", "https://tv.cctv.com/live/cctv5/"}, + {"6", "CCTV-6 电影", "https://tv.cctv.com/live/cctv6/"}, + {"7", "CCTV-7 国防军事", "https://tv.cctv.com/live/cctv7/"}, + {"8", "CCTV-8 电视剧", "https://tv.cctv.com/live/cctv8/"}, + {"9", "CCTV-9 纪录", "https://tv.cctv.com/live/cctvjilu"}, + {"10", "CCTV-10 科教", "https://tv.cctv.com/live/cctv10/"}, + {"11", "CCTV-11 戏曲", "https://tv.cctv.com/live/cctv11/"}, + {"12", "CCTV-12 社会与法", "https://tv.cctv.com/live/cctv12/"}, + {"13", "CCTV-13 新闻", "https://tv.cctv.com/live/cctv13/"}, + {"14", "CCTV-14 少儿", "https://tv.cctv.com/live/cctvchild"}, + {"15", "CCTV-15 音乐", "https://tv.cctv.com/live/cctv15/"}, + {"16", "CCTV-16 奥林匹克", "https://tv.cctv.com/live/cctv16/"}, + {"17", "CCTV-17 农业农村", "https://tv.cctv.com/live/cctv17/"}, + {"18", "CCTV-5+ 体育赛事", "https://tv.cctv.com/live/cctv5plus/"} + }; + + // ========== 写死的栏目分类列表 ========== + private static final String[][] CATEGORIES = { + {"news", "新闻"}, + {"tv", "电视剧"}, + {"variety", "综艺"}, + {"documentary", "纪录片"}, + {"sports", "体育"}, + {"settings", "设置"} + }; + + @Override + public void init(Context context, String extend) throws Exception { + super.init(context, extend); + + // 初始化SharedPreferences + prefs = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE); + + // 读取配置 + useX5 = prefs.getBoolean(KEY_USE_X5, false); + apiUrl = prefs.getString(KEY_API_URL, ""); + + SpiderDebug.log(TAG + " 初始化成功"); + SpiderDebug.log("当前内核: " + (useX5 ? "X5 WebView" : "系统 WebView")); + + // TODO: 从extend参数解析API地址(预留) + if (!TextUtils.isEmpty(extend)) { + try { + JsonObject config = new Gson().fromJson(extend, JsonObject.class); + if (config.has("api")) { + apiUrl = config.get("api").getAsString(); + saveConfig(KEY_API_URL, apiUrl); + } + } catch (Exception e) { + SpiderDebug.log(TAG + " 解析extend失败: " + e.getMessage()); + } + } + } + + /** + * 首页内容 - 显示直播列表 + */ + @Override + public String homeContent(boolean filter) throws Exception { + // TODO: 未来从API获取数据 + // if (!TextUtils.isEmpty(apiUrl)) { + // return fetchFromApi(apiUrl + "/live/list"); + // } + + // 当前使用写死的数据 + List classes = new ArrayList<>(); + classes.add(new Class("live", "��视直播")); + + List list = new ArrayList<>(); + for (String[] channel : LIVE_CHANNELS) { + Vod vod = new Vod(); + vod.setVodId(channel[0]); + vod.setVodName(channel[1]); + vod.setVodPic("https://wtv.tools.yigechengzi.pro/prod-api/app/img/cctv" + channel[0] + ".png"); + vod.setVodRemarks("直播中"); + list.add(vod); + } + + return Result.string(classes, list); + } + + /** + * 分类内容 - 显示栏目列表或设置 + */ + @Override + public String categoryContent(String tid, String pg, boolean filter, HashMap extend) throws Exception { + List list = new ArrayList<>(); + + // 特殊处理:设置分类 + if ("settings".equals(tid)) { + return getSettingsContent(); + } + + // TODO: 未来从API获取栏目数据 + // if (!TextUtils.isEmpty(apiUrl)) { + // return fetchFromApi(apiUrl + "/category/" + tid + "?page=" + pg); + // } + + // 当前返回写死的示例数据 + for (int i = 0; i < 10; i++) { + Vod vod = new Vod(); + vod.setVodId(tid + "_" + i); + vod.setVodName(getCategoryName(tid) + " 节目 " + i); + vod.setVodPic("https://via.placeholder.com/300x400"); + vod.setVodRemarks("更新至第" + i + "期"); + list.add(vod); + } + + return Result.string(list); + } + + /** + * 获取设置内容 + */ + private String getSettingsContent() { + List list = new ArrayList<>(); + + // 设置项1:切换内核 + Vod kernelSetting = new Vod(); + kernelSetting.setVodId("setting_kernel"); + kernelSetting.setVodName("切换内核"); + kernelSetting.setVodPic("https://via.placeholder.com/300x400?text=Kernel"); + kernelSetting.setVodRemarks("当前: " + (useX5 ? "X5 WebView" : "系统 WebView")); + kernelSetting.setVodContent("点击切换播放内核\n\n" + + "系统 WebView: 轻量级,兼容性好\n" + + "X5 WebView: 功能强大,适合复杂页面\n\n" + + "当前使用: " + (useX5 ? "X5 WebView" : "系统 WebView")); + list.add(kernelSetting); + + // 设置项2:API地址(预留) + Vod apiSetting = new Vod(); + apiSetting.setVodId("setting_api"); + apiSetting.setVodName("API设置"); + apiSetting.setVodPic("https://via.placeholder.com/300x400?text=API"); + apiSetting.setVodRemarks(TextUtils.isEmpty(apiUrl) ? "未配置" : "已配置"); + apiSetting.setVodContent("设置数据源API地址\n\n当前: " + + (TextUtils.isEmpty(apiUrl) ? "使用内置数据" : apiUrl)); + list.add(apiSetting); + + return Result.string(list); + } + + /** + * 详情内容 + */ + @Override + public String detailContent(List ids) throws Exception { + String vodId = ids.get(0); + + // 处理设置项的点击 + if (vodId.startsWith("setting_")) { + return handleSettingClick(vodId); + } + + // TODO: 未来从API获取详情 + // if (!TextUtils.isEmpty(apiUrl)) { + // return fetchFromApi(apiUrl + "/detail/" + vodId); + // } + + // 查找对应的频道 + for (String[] channel : LIVE_CHANNELS) { + if (channel[0].equals(vodId)) { + Vod vod = new Vod(); + vod.setVodId(channel[0]); + vod.setVodName(channel[1]); + vod.setVodPic("https://wtv.tools.yigechengzi.pro/prod-api/app/img/cctv" + channel[0] + ".png"); + vod.setVodYear("2024"); + vod.setVodArea("中国"); + vod.setVodRemarks("24小时直播"); + vod.setVodContent("中央广播电视总台 " + channel[1] + " 官方直播频道"); + + // 播放信息 + String playFrom = useX5 ? "X5-WebView" : "System-WebView"; + vod.setVodPlayFrom(playFrom); + vod.setVodPlayUrl("播放$" + channel[2]); + + return Result.string(vod); + } + } + + // 未找到,返回空 + return Result.string(new Vod()); + } + + /** + * 处理设置项点击 + */ + private String handleSettingClick(String settingId) { + if ("setting_kernel".equals(settingId)) { + // 切换内核 + useX5 = !useX5; + saveConfig(KEY_USE_X5, useX5); + + SpiderDebug.log("内核已切换为: " + (useX5 ? "X5 WebView" : "系统 WebView")); + + // 返回更新后的设置项 + return getSettingsContent(); + } + + return Result.string(new Vod()); + } + + /** + * 搜索内容 + */ + @Override + public String searchContent(String key, boolean quick) throws Exception { + // TODO: 未来从API搜索 + // if (!TextUtils.isEmpty(apiUrl)) { + // return fetchFromApi(apiUrl + "/search?keyword=" + key); + // } + + List list = new ArrayList<>(); + + // 简单的本地搜索 + for (String[] channel : LIVE_CHANNELS) { + if (channel[1].toLowerCase().contains(key.toLowerCase())) { + Vod vod = new Vod(); + vod.setVodId(channel[0]); + vod.setVodName(channel[1]); + vod.setVodPic("https://wtv.tools.yigechengzi.pro/prod-api/app/img/cctv" + channel[0] + ".png"); + vod.setVodRemarks("直播中"); + list.add(vod); + } + } + + return Result.string(list); + } + + /** + * 播放器内容 + */ + @Override + public String playerContent(String flag, String id, List vipFlags) throws Exception { + HashMap headers = new HashMap<>(); + headers.put("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"); + headers.put("Referer", "https://tv.cctv.com/"); + + // parse=1 表示需要WebView解析 + return Result.get() + .url(id) + .parse(1) + .header(headers) + .string(); + } + + // ========== 工具方法 ========== + + /** + * 保存配置(布尔值) + */ + private void saveConfig(String key, boolean value) { + prefs.edit().putBoolean(key, value).apply(); + } + + /** + * 保存配置(字符串) + */ + private void saveConfig(String key, String value) { + prefs.edit().putString(key, value).apply(); + } + + /** + * 获取分类名称 + */ + private String getCategoryName(String tid) { + for (String[] category : CATEGORIES) { + if (category[0].equals(tid)) { + return category[1]; + } + } + return "未知分类"; + } + + /** + * 从API获取数据(预留方法) + */ + private String fetchFromApi(String url) throws Exception { + // TODO: 实现API请求逻辑 + // String response = OkHttp.string(url); + // return parseApiResponse(response); + throw new Exception("API功能暂未实现"); + } + + /** + * 判断是否使用X5内核 + */ + public static boolean isUseX5(Context context) { + SharedPreferences prefs = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE); + return prefs.getBoolean(KEY_USE_X5, false); + } +} \ No newline at end of file