Skill: wuchubuzai2018/baidu-milan-winter-olympics-2026 (version 0.0.2)

--- name: baidu-milan-winter-olympics-2026 description: 获取2026年米兰冬奥会数据技能，包括奖牌榜排名、现场新闻报道和赛程安排。从百度体育网页抓取实时的奖牌排行榜信息、最新新闻资讯和比赛赛程。当用户需要获取米兰冬奥会需求，需要查询冬奥会奖牌榜、了解各国奖牌数量、获取现场新闻、查看赛程安排时使用此技能。能够根据指定时间(今天、明天、yyyy-MM-dd日期格式)或指定运动项目获取赛程安排。A skill for retrieving 2026 Milan Winter Olympics data, including medal standings, live news reports, and competition schedules. Scrapes real-time medal rankings, latest news, and match schedules from Baidu Sports. Use this skill when users need to query Winter Olympics medal standings, check medal counts by country, get live news, or view competition schedules. --- # 2026年米兰冬奥会数据获取 ## 技能概述 此技能用于获取2026年米兰冬奥会的以下数据： ### 1. 奖牌榜数据 - 各国/地区奖牌排名 - 金牌、银牌、铜牌数量 - 奖牌总数统计 - 国旗图片链接 - 详情页面链接 ### 2. 现场新闻报道 - 最新赛事新闻 - 精彩瞬间 - 赛后采访 - 视频资讯 - 赛事集锦 ### 3. 赛程数据 - 全部赛程安排 - 中国相关赛程 - 金牌赛赛程 - 热门赛程 - 比赛时间、状态、项目信息 ### 4. 中国队获奖名单数据 - 中国队所有获奖运动员名单 - 奖牌类型（金牌/银牌/铜牌） - 运动员姓名 - 比赛项目（大项和小项） - 获奖时间 - 视频集锦链接 - 奖牌统计信息 数据来源：百度体育 (tiyu.baidu.com) ## 获取奖牌榜数据 ### 获取奖牌榜TOP30 当用户需要查看奖牌榜前30名时： ```bash node scripts/milan-olympics.js top ``` ### 获取奖牌榜TOP N 获取指定数量的排名： ```bash node scripts/milan-olympics.js top 10 ``` ### 获取完整奖牌榜 ```bash node scripts/milan-olympics.js all ``` ### 奖牌榜返回数据字段说明 | 字段 | 类型 | 说明 | |------|------|------| | rank | number | 排名 | | country | string | 国家/地区名称（中文） | | countryEn | string | 国家/地区名称（英文） | | gold | number | 金牌数 | | silver | number | 银牌数 | | bronze | number | 铜牌数 | | total | number | 奖牌总数 | | flagUrl | string | 国旗图片URL | | detailUrl | string | 详情页面URL | ## 获取现场新闻数据 ### 获取最新新闻列表 当用户需要查看冬奥会现场新闻时： ```bash node scripts/milan-news.js list ``` ### 获取指定数量的新闻 获取20条最新新闻： ```bash node scripts/milan-news.js list 20 ``` ### 按类型筛选新闻 获取"赛事集锦"类型的新闻： ```bash node scripts/milan-news.js list 10 赛事集锦 ``` ### 获取可用的内容类型 ```bash node scripts/milan-news.js types ``` 可用类型包括： - 全部 - 热门内容 - 赛事集锦 - 精彩瞬间 - 选手集锦 - 赛后采访 - 赛前采访 - 项目介绍 - 专栏节目 - 其他 ## 新闻数据字段说明 | 字段 | 类型 | 说明 | |------|------|------| | id | string | 新闻唯一标识 | | title | string | 新闻标题 | | type | string | 内容类型：article（文章）、video（视频）、post（动态） | | subType | string | 内容子类型 | | source | string | 新闻来源 | | url | string | 详情页面URL | | images | array | 图片URL数组 | | videoDuration | string | 视频时长（仅视频类型） | | videoUrl | string | 视频播放链接（仅视频类型） | | matchId | array | 关联的赛事ID | ## 获取中国队获奖名单数据 ### 获取全部获奖名单 当用户需要查看中国队所有获奖运动员时： ```bash node scripts/milan-china-medals.js list ``` ### 按奖牌类型筛选 获取中国队的金牌获奖名单： ```bash node scripts/milan-china-medals.js list gold ``` 获取中国队的银牌获奖名单： ```bash node scripts/milan-china-medals.js list silver ``` 获取中国队的铜牌获奖名单： ```bash node scripts/milan-china-medals.js list bronze ``` ### 获取奖牌统计 获取中国队奖牌统计信息（按项目和类型统计）： ```bash node scripts/milan-china-medals.js stats ``` ### 中国队获奖名单数据字段说明 **代表团信息（delegationInfo）：** | 字段 | 类型 | 说明 | |------|------|------| | country | string | 国家名称（中文） | | countryEn | string | 国家名称（英文） | | rank | string | 当前排名 | | gold | string | 金牌数 | | silver | string | 银牌数 | | bronze | string | 铜牌数 | | delegationId | string | 代表团ID | **获奖记录（medals）：** | 字段 | 类型 | 说明 | |------|------|------| | playerName | string | 运动员姓名 | | medal | string | 奖牌名称（如"第1银"） | | medalType | string | 奖牌类型：gold/silver/bronze | | medalRank | number | 奖牌序号 | | bigMatch | string | 大项（如"自由式滑雪"） | | smallMatch | string | 小项（如"自由式滑雪女子坡面障碍技巧"） | | date | string | 日期（如"02月09日"） | | time | string | 时间（如"21:00"） | | medalTime | string | 时间戳 | | rank | number | 比赛排名 | | detailUrl | string | 详情页面URL | | loc | string | 本地链接 | | videoInfo | object | 视频信息（含播放链接） | | playIconArr | array | 播放图标数组 | | country | string | 国家 | | olympicEventId | string | 赛事ID | ## 获取赛程数据 ### 获取全部赛程 ```bash node scripts/milan-schedule.js all ``` ### 获取特定日期的赛程 ```bash node scripts/milan-schedule.js all 2026-02-08 ``` ### 获取中国相关赛程 ```bash node scripts/milan-schedule.js china ``` 获取特定日期的中国赛程： ```bash node scripts/milan-schedule.js china 2026-02-08 ``` ### 获取金牌赛赛程 ```bash node scripts/milan-schedule.js gold ``` 获取特定日期的金牌赛： ```bash node scripts/milan-schedule.js gold 2026-02-08 ``` ### 获取热门赛程 ```bash node scripts/milan-schedule.js hot ``` ### 获取今天的赛程（综合TAB） 自动获取今天日期的全部赛程，无需手动指定日期： ```bash node scripts/milan-schedule.js today ``` ### 获取明天的赛程（综合TAB） 自动获取明天日期的全部赛程，无需手动指定日期： ```bash node scripts/milan-schedule.js tomorrow ``` ### 获取可用的日期列表 ```bash node scripts/milan-schedule.js dates ``` ### 赛程数据字段说明 | 字段 | 类型 | 说明 | |------|------|------| | matchId | string | 比赛唯一标识 | | matchName | string | 比赛名称 | | sportName | string | 项目大类名称 | | eventName | string | 具体小项名称 | | startTime | string | 开始时间（HH:mm） | | startDate | string | 开始日期（YYYY-MM-DD） | | startDateTime | string | 完整开始时间 | | status | string | 比赛状态（未开赛、进行中、已结束等） | | statusId | string | 状态ID | | desc | string | 比赛描述/备注 | | isChina | boolean | 是否中国相关赛程 | | isGold | boolean | 是否金牌赛 | | isHot | boolean | 是否热门赛程 | | isMedal | boolean | 是否奖牌赛 | | hasLive | boolean | 是否有直播 | | participant | string | 参赛类型（单人/团体） | | detailUrl | string | 详情页面URL | | iconArr | array | 图标标签数组 | ### 获取运动项目列表 查看所有可用的运动项目及其ID： ```bash node scripts/milan-schedule.js sports ``` 返回数据结构： - **hot**: 热门项目列表（包含热度值） - **other**: 其他项目列表 常见运动项目ID对照： | 项目名称 | ID | |---------|-----| | 短道速滑 | 302 | | 花样滑冰 | 217 | | 速度滑冰 | 103 | | 单板滑雪 | 222 | | 自由式滑雪 | 221 | | 冰壶 | 212 | | 冰球 | 113 | | 高山滑雪 | 115 | | 雪车 | 213 | | 雪橇 | 214 | | 钢架雪车 | 307 | | 跳台滑雪 | 215 | | 越野滑雪 | 220 | | 滑雪登山 | 615 | | 北欧两项 | 216 | | 冬季两项 | 218 | ### 获取指定运动项目的赛程 查询特定运动项目的赛程安排： ```bash # 获取短道速滑所有赛程 node scripts/milan-schedule.js sport 302 # 获取特定日期的短道速滑赛程 node scripts/milan-schedule.js sport 302 2026-02-10 ``` ### 获取中国指定运动项目的赛程 查询中国队在特定运动项目的赛程： ```bash # 获取中国短道速滑赛程 node scripts/milan-schedule.js china-sport 302 # 获取特定日期中国短道速滑赛程 node scripts/milan-schedule.js china-sport 302 2026-02-10 ``` ## 作者介绍 - 爱海贼的无处不在 - 我的微信公众号：无处不在的技术 ## 注意事项 - 数据从百度体育网页实时抓取，可能存在短暂延迟 - 奖牌榜数据会随着比赛进行不断更新 - 排名规则遵循国际奥委会标准（先按金牌数，再按银牌数，再按铜牌数） - 新闻内容实时更新，包含文字报道、图片和视频 - 赛程数据包含比赛时间、项目、状态等信息

path: scripts/milan-china-medals.js
content: #!/usr/bin/env node

/**
 * 2026年米兰冬奥会中国队获奖名单获取工具
 * 从百度体育网页抓取中国队的获奖名单数据
 */

const https = require('https');

// 可配置 User-Agent 池（固定 20 个），每次请求随机选一个，避免固定 UA
const USER_AGENTS = [
  'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36',
  'Mozilla/5.0 (Macintosh; Intel Mac OS X 14_2_1) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Safari/605.1.15',
  'Mozilla/5.0 (Macintosh; Intel Mac OS X 13_6_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36',
  'Mozilla/5.0 (Macintosh; Intel Mac OS X 14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36',
  'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36',
  'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36',
  'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Edg/123.0.0.0 Chrome/123.0.0.0 Safari/537.36',
  'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Edg/122.0.0.0 Chrome/122.0.0.0 Safari/537.36',
  'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36',
  'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36',
  'Mozilla/5.0 (X11; Linux x86_64; rv:123.0) Gecko/20100101 Firefox/123.0',
  'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:123.0) Gecko/20100101 Firefox/123.0',
  'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:123.0) Gecko/20100101 Firefox/123.0',
  'Mozilla/5.0 (iPhone; CPU iPhone OS 17_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Mobile/15E148 Safari/604.1',
  'Mozilla/5.0 (iPhone; CPU iPhone OS 16_7 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.6 Mobile/15E148 Safari/604.1',
  'Mozilla/5.0 (iPad; CPU OS 17_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Mobile/15E148 Safari/604.1',
  'Mozilla/5.0 (Linux; Android 14; Pixel 8 Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Mobile Safari/537.36',
  'Mozilla/5.0 (Linux; Android 13; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Mobile Safari/537.36',
  'Mozilla/5.0 (Linux; Android 14; SM-S918B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Mobile Safari/537.36',
  'Mozilla/5.0 (Linux; Android 13; Mi 11) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Mobile Safari/537.36',
];

function getRandomUserAgent() {
  return USER_AGENTS[Math.floor(Math.random() * USER_AGENTS.length)];
}

const HEADERS = {
  'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
  'Accept-Encoding': 'identity',
  'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
  'Referer': 'https://tiyu.baidu.com/',
  'Origin': 'https://tiyu.baidu.com'
};

// 中国队获奖名单页面URL（id=26为中国队）
const CHINA_MEDAL_URL = 'https://tiyu.baidu.com/al/major/delegation?id=26&match=2026年米兰冬奥会&tab=获奖名单';

/**
 * 发起HTTP GET请求
 * @param {string} url - 请求URL
 * @returns {Promise<string>} 响应HTML内容
 */
function httpGet(url) {
  return new Promise((resolve, reject) => {
    const urlObj = new URL(url);
    const options = {
      hostname: urlObj.hostname,
      path: urlObj.pathname + urlObj.search,
      method: 'GET',
      headers: { ...HEADERS, 'User-Agent': getRandomUserAgent() }
    };

    const req = https.request(options, (res) => {
      const chunks = [];
      
      res.on('data', (chunk) => { chunks.push(chunk); });
      
      res.on('end', () => {
        const buffer = Buffer.concat(chunks);
        resolve(buffer.toString('utf-8'));
      });
    });

    req.on('error', reject);
    req.setTimeout(15000, () => {
      req.destroy();
      reject(new Error('Request timeout'));
    });
    req.end();
  });
}

/**
 * 从HTML中提取JSON数据（获奖名单数据）
 * @param {string} html - HTML内容
 * @returns {Object|null} 获奖名单数据对象
 */
function extractJsonFromHtml(html) {
  try {
    // 查找包含页面数据的script标签
    const scriptRegex = /<script id="atom-data-[^"]*" type="application\/json">([\s\S]*?)<\/script>/;
    const match = html.match(scriptRegex);
    
    if (match && match[1]) {
      const parsed = JSON.parse(match[1]);
      
      // 数据在 data.data.data.tabsList 中
      const pageData = parsed.data && parsed.data.data ? parsed.data.data : null;
      
      if (pageData && pageData.tabsList) {
        // 查找获奖名单标签页 (rootTab === 'medalDetail')
        const medalTab = pageData.tabsList.find(tab => tab.rootTab === 'medalDetail');
        
        if (medalTab && medalTab.data) {
          return {
            delegationInfo: {
              country: pageData.header?.title || '中国',
              countryEn: pageData.header?.subtitle || 'China（CHN）',
              rank: pageData.header?.rankInfo?.rank || '',
              gold: pageData.header?.medalInfo?.gold || '0',
              silver: pageData.header?.medalInfo?.silver || '0',
              bronze: pageData.header?.medalInfo?.bronze || '0',
              delegationId: pageData.header?.delegationId || '26'
            },
            medalList: medalTab.data
          };
        }
      }
    }
  } catch (e) {
    console.error('解析JSON数据失败:', e.message);
  }
  return null;
}

/**
 * 解析获奖名单数据
 * @param {Object} data - 从JSON中提取的原始数据
 * @returns {Array} 结构化的获奖名单数组
 */
function parseMedalList(data) {
  if (!data || !data.medalList || !Array.isArray(data.medalList)) {
    return [];
  }

  const medals = [];
  
  // data.medalList 是一个数组，每个元素代表一个筛选标签（如"全部"、"金牌"、"银牌"等）
  // 我们取第一个元素（通常是"全部"）
  const allMedalsTab = data.medalList[0];
  
  if (allMedalsTab && allMedalsTab.tabData) {
    // tabData 是按日期分组的数据
    allMedalsTab.tabData.forEach(dateGroup => {
      const date = dateGroup.date || '';
      
      if (dateGroup.dateList && Array.isArray(dateGroup.dateList)) {
        dateGroup.dateList.forEach(item => {
          medals.push({
            // 运动员信息
            playerName: item.playerName || '',
            
            // 奖牌信息
            medal: item.medal || '',           // 如"第1银"
            medalType: item.medalType || '',   // gold/silver/bronze
            medalRank: item.medal ? parseInt(item.medal.replace(/[^0-9]/g, '')) || 0 : 0,
            
            // 赛事信息
            bigMatch: item.bigMatch || '',     // 大项，如"自由式滑雪"
            smallMatch: item.smallMatch || '', // 小项，如"自由式滑雪女子坡面障碍技巧"
            
            // 时间和地点
            date: date,                        // 日期，如"02月09日"
            time: item.time || '',             // 时间，如"21:00"
            medalTime: item.medalTime || '',   // 时间戳
            
            // 排名信息
            rank: item.rank || 0,              // 比赛排名
            
            // 链接信息
            detailUrl: item.link ? `https://tiyu.baidu.com${item.link}` : '',
            loc: item.loc || '',               // 本地链接
            
            // 媒体信息
            videoInfo: item.videoInfo || null, // 视频信息
            playIconArr: item.playIconArr || [], // 播放图标
            
            // 其他信息
            country: item.country || '中国',
            olympicEventId: item.olympicEventId || '',
            backgroundColor: item.backgroundColor || '',
            color: item.color || '',
            iconType: item.iconType || ''
          });
        });
      }
    });
  }
  
  return medals;
}

/**
 * 获取中国队获奖名单
 * @param {string} medalType - 奖牌类型过滤（可选）：gold（金牌）、silver（银牌）、bronze（铜牌）、all（全部）
 * @returns {Promise<Object>} 获奖名单数据对象
 */
async function getChinaMedals(medalType = 'all') {
  try {
    const html = await httpGet(CHINA_MEDAL_URL);
    const data = extractJsonFromHtml(html);
    
    if (!data) {
      throw new Error('未能从页面解析出获奖名单数据');
    }
    
    // 解析获奖名单
    let medals = parseMedalList(data);
    
    // 按奖牌类型过滤
    if (medalType && medalType !== 'all') {
      medals = medals.filter(item => item.medalType === medalType);
    }
    
    // 按奖牌时间排序（降序，最新的在前）
    medals.sort((a, b) => {
      const timeA = parseInt(a.medalTime) || 0;
      const timeB = parseInt(b.medalTime) || 0;
      return timeB - timeA;
    });
    
    return {
      delegationInfo: data.delegationInfo,
      medals: medals,
      total: medals.length
    };
  } catch (error) {
    throw new Error(`获取中国队获奖名单失败: ${error.message}`);
  }
}

/**
 * 获取奖牌统计数据
 * @returns {Promise<Object>} 奖牌统计数据
 */
async function getMedalStats() {
  try {
    const html = await httpGet(CHINA_MEDAL_URL);
    const data = extractJsonFromHtml(html);
    
    if (!data || !data.delegationInfo) {
      throw new Error('未能获取奖牌统计数据');
    }
    
    const info = data.delegationInfo;
    const medals = parseMedalList(data);
    
    // 按类型统计
    const goldCount = medals.filter(m => m.medalType === 'gold').length;
    const silverCount = medals.filter(m => m.medalType === 'silver').length;
    const bronzeCount = medals.filter(m => m.medalType === 'bronze').length;
    
    // 按大项统计
    const sportStats = {};
    medals.forEach(m => {
      const sport = m.bigMatch || '其他';
      if (!sportStats[sport]) {
        sportStats[sport] = { gold: 0, silver: 0, bronze: 0, total: 0 };
      }
      sportStats[sport][m.medalType]++;
      sportStats[sport].total++;
    });
    
    return {
      delegationInfo: info,
      summary: {
        gold: goldCount,
        silver: silverCount,
        bronze: bronzeCount,
        total: medals.length
      },
      bySport: sportStats
    };
  } catch (error) {
    throw new Error(`获取奖牌统计失败: ${error.message}`);
  }
}

/**
 * 主函数 - 处理命令行参数
 */
async function main() {
  const args = process.argv.slice(2);
  const command = args[0];

  try {
    switch (command) {
      case 'list':
      case '--list':
      case '-l': {
        const medalType = args[1] || 'all';
        const result = await getChinaMedals(medalType);
        console.log(JSON.stringify(result, null, 2));
        break;
      }
      
      case 'stats':
      case '--stats':
      case '-s': {
        const stats = await getMedalStats();
        console.log(JSON.stringify(stats, null, 2));
        break;
      }
      
      default:
        console.log(`
2026年米兰冬奥会中国队获奖名单获取工具

用法:
  node milan-china-medals.js <command> [options]

命令:
  list, -l, --list [type]    获取中国队获奖名单
  stats, -s, --stats         获取奖牌统计数据

参数:
  type    奖牌类型过滤，可选值：
          all（全部，默认）、gold（金牌）、silver（银牌）、bronze（铜牌）

示例:
  # 获取全部获奖名单
  node milan-china-medals.js list

  # 获取金牌获奖名单
  node milan-china-medals.js list gold

  # 获取银牌获奖名单
  node milan-china-medals.js list silver

  # 获取铜牌获奖名单
  node milan-china-medals.js list bronze

  # 获取奖牌统计
  node milan-china-medals.js stats

数据字段说明:
  playerName    运动员姓名
  medal         奖牌名称（如"第1银"）
  medalType     奖牌类型（gold/silver/bronze）
  bigMatch      大项（如"自由式滑雪"）
  smallMatch    小项（如"自由式滑雪女子坡面障碍技巧"）
  date          日期
  time          时间
  detailUrl     详情页面URL
  videoInfo     视频信息
`);
        process.exit(0);
    }
  } catch (error) {
    console.error(`Error: ${error.message}`);
    process.exit(1);
  }
}

// 导出模块供其他脚本使用
module.exports = { getChinaMedals, getMedalStats };

// 如果直接运行此脚本
if (require.main === module) {
  main();
}


path: scripts/milan-news.js
content: #!/usr/bin/env node

/**
 * 2026年米兰冬奥会现场新闻获取工具
 * 从百度体育网页抓取最新的现场新闻报道
 */

const https = require('https');

// 可配置 User-Agent 池（固定 20 个），每次请求随机选一个，避免固定 UA
const USER_AGENTS = [
  'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36',
  'Mozilla/5.0 (Macintosh; Intel Mac OS X 14_2_1) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Safari/605.1.15',
  'Mozilla/5.0 (Macintosh; Intel Mac OS X 13_6_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36',
  'Mozilla/5.0 (Macintosh; Intel Mac OS X 14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36',
  'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36',
  'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36',
  'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Edg/123.0.0.0 Chrome/123.0.0.0 Safari/537.36',
  'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Edg/122.0.0.0 Chrome/122.0.0.0 Safari/537.36',
  'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36',
  'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36',
  'Mozilla/5.0 (X11; Linux x86_64; rv:123.0) Gecko/20100101 Firefox/123.0',
  'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:123.0) Gecko/20100101 Firefox/123.0',
  'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:123.0) Gecko/20100101 Firefox/123.0',
  'Mozilla/5.0 (iPhone; CPU iPhone OS 17_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Mobile/15E148 Safari/604.1',
  'Mozilla/5.0 (iPhone; CPU iPhone OS 16_7 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.6 Mobile/15E148 Safari/604.1',
  'Mozilla/5.0 (iPad; CPU OS 17_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Mobile/15E148 Safari/604.1',
  'Mozilla/5.0 (Linux; Android 14; Pixel 8 Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Mobile Safari/537.36',
  'Mozilla/5.0 (Linux; Android 13; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Mobile Safari/537.36',
  'Mozilla/5.0 (Linux; Android 14; SM-S918B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Mobile Safari/537.36',
  'Mozilla/5.0 (Linux; Android 13; Mi 11) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Mobile Safari/537.36',
];

function getRandomUserAgent() {
  return USER_AGENTS[Math.floor(Math.random() * USER_AGENTS.length)];
}

const HEADERS = {
  'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
  'Accept-Encoding': 'identity',
  'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
  'Referer': 'https://tiyu.baidu.com/',
  'Origin': 'https://tiyu.baidu.com'
};

const NEWS_URL = 'https://tiyu.baidu.com/al/major/home?match=2026年米兰冬奥会&tab=直击现场';

/**
 * 发起HTTP GET请求
 * @param {string} url - 请求URL
 * @returns {Promise<string>} 响应HTML内容
 */
function httpGet(url) {
  return new Promise((resolve, reject) => {
    const urlObj = new URL(url);
    const options = {
      hostname: urlObj.hostname,
      path: urlObj.pathname + urlObj.search,
      method: 'GET',
      headers: { ...HEADERS, 'User-Agent': getRandomUserAgent() }
    };

    const req = https.request(options, (res) => {
      const chunks = [];
      
      res.on('data', (chunk) => { chunks.push(chunk); });
      
      res.on('end', () => {
        const buffer = Buffer.concat(chunks);
        resolve(buffer.toString('utf-8'));
      });
    });

    req.on('error', reject);
    req.setTimeout(15000, () => {
      req.destroy();
      reject(new Error('Request timeout'));
    });
    req.end();
  });
}

/**
 * 从HTML中提取JSON数据
 * @param {string} html - HTML内容
 * @returns {Array|null} 新闻数据数组
 */
function extractNewsFromHtml(html) {
  try {
    // 查找包含页面数据的script标签
    const scriptRegex = /<script id="atom-data-[^"]*" type="application\/json">([\s\S]*?)<\/script>/;
    const match = html.match(scriptRegex);
    
    if (match && match[1]) {
      const parsed = JSON.parse(match[1]);
      
      // 数据在 data.data.data.tabsList 中
      const pageData = parsed.data && parsed.data.data ? parsed.data.data : null;
      
      if (pageData && pageData.tabsList) {
        // 查找"直击现场"标签页的数据 (rootTab === 'video')
        const liveTab = pageData.tabsList.find(tab => tab.rootTab === 'video');
        
        if (liveTab && liveTab.data && liveTab.data.list) {
          return liveTab.data.list.map(item => ({
            id: item.dataId || '',
            title: item.title || '',
            type: item.type || 'article', // article, video, post
            subType: item.subType || '',
            source: item.provider || '',
            url: item.jumpUrl || '',
            images: item.imgs || (item.img ? [item.img] : []),
            videoDuration: item.durationText || '',
            videoUrl: item.playUrl || '',
            matchId: item.matchId || []
          }));
        }
      }
    }
  } catch (e) {
    console.error('解析JSON数据失败:', e.message);
  }
  return null;
}

/**
 * 通过正则表达式解析新闻数据（备用方案）
 * @param {string} html - HTML内容
 * @returns {Array} 新闻数据数组
 */
function parseNewsFromHtml(html) {
  const news = [];
  
  // 匹配新闻项
  const itemRegex = /<a[^>]*data-index="(\d+)"[^>]*data-key="([^"]+)"[^>]*href="([^"]+)"[^>]*>[\s\S]*?<\/a>/g;
  let match;
  
  while ((match = itemRegex.exec(html)) !== null) {
    try {
      const index = match[1];
      const key = match[2];
      const href = match[3];
      const itemHtml = match[0];
      
      // 提取标题
      const titleMatch = itemHtml.match(/class="title[^"]*"[^>]*>([^<]+)<\/div>/);
      const title = titleMatch ? titleMatch[1].trim() : '';
      
      // 提取来源
      const sourceMatch = itemHtml.match(/class="source[^"]*"[^>]*>([^<]+)<\/div>/);
      const source = sourceMatch ? sourceMatch[1].trim() : '';
      
      // 提取图片
      const imgMatches = itemHtml.match(/style="background-image:url\(([^)]+)\)/g);
      const images = imgMatches ? imgMatches.map(m => {
        const urlMatch = m.match(/url\(([^)]+)\)/);
        return urlMatch ? urlMatch[1].replace(/&amp;/g, '&') : '';
      }).filter(url => url) : [];
      
      // 提取视频时长
      const durationMatch = itemHtml.match(/class="time[^"]*"[^>]*>([^<]+)<\/span>/);
      const videoDuration = durationMatch ? durationMatch[1].trim() : '';
      
      // 判断类型
      const type = itemHtml.includes('video') ? 'video' : 'article';
      
      if (title) {
        news.push({
          id: key,
          title,
          type,
          subType: '',
          source,
          url: href.startsWith('http') ? href : `https://tiyu.baidu.com${href}`,
          images,
          videoDuration,
          videoUrl: '',
          matchId: []
        });
      }
    } catch (e) {
      continue;
    }
  }
  
  return news;
}

/**
 * 获取现场新闻列表
 * @param {number} limit - 返回数量限制，默认10
 * @param {string} subType - 子类型过滤（可选）：全部、热门内容、赛事集锦、精彩瞬间、选手集锦、赛后采访、赛前采访、项目介绍、专栏节目、其他
 * @returns {Promise<Array>} 新闻数组
 */
async function getLiveNews(limit = 10, subType = '') {
  try {
    const html = await httpGet(NEWS_URL);
    
    // 首先尝试从JSON提取
    let news = extractNewsFromHtml(html);
    
    // 如果JSON提取失败，使用正则解析
    if (!news || news.length === 0) {
      news = parseNewsFromHtml(html);
    }
    
    if (news.length === 0) {
      throw new Error('未能从页面解析出新闻数据');
    }
    
    // 按子类型过滤
    if (subType && subType !== '全部') {
      news = news.filter(item => item.subType === subType);
    }
    
    // 限制数量
    return news.slice(0, limit);
  } catch (error) {
    throw new Error(`获取现场新闻失败: ${error.message}`);
  }
}

/**
 * 获取可用的子类型列表
 * @returns {Promise<Array>} 子类型数组
 */
async function getSubTypes() {
  try {
    const html = await httpGet(NEWS_URL);
    const data = extractNewsFromHtml(html);
    
    if (data && data.subTabs) {
      return data.subTabs;
    }
    
    // 默认子类型
    return [
      '全部',
      '热门内容',
      '赛事集锦',
      '精彩瞬间',
      '选手集锦',
      '赛后采访',
      '赛前采访',
      '项目介绍',
      '专栏节目',
      '其他'
    ];
  } catch (error) {
    return ['全部'];
  }
}

/**
 * 主函数 - 处理命令行参数
 */
async function main() {
  const args = process.argv.slice(2);
  const command = args[0];

  try {
    switch (command) {
      case 'list':
      case '--list':
      case '-l': {
        const limit = parseInt(args[1]) || 10;
        const subType = args[2] || '';
        const news = await getLiveNews(limit, subType);
        console.log(JSON.stringify(news, null, 2));
        break;
      }
      
      case 'types':
      case '--types':
      case '-t': {
        const types = await getSubTypes();
        console.log(JSON.stringify(types, null, 2));
        break;
      }
      
      default:
        console.log(`
2026年米兰冬奥会现场新闻获取工具

用法:
  node milan-news.js <command> [options]

命令:
  list, -l, --list [n] [subtype]  获取现场新闻列表（默认10条）
  types, -t, --types              获取可用的内容类型列表

参数:
  n          返回的新闻数量（默认10）
  subtype    内容类型过滤，可选值：
             全部、热门内容、赛事集锦、精彩瞬间、选手集锦、
             赛后采访、赛前采访、项目介绍、专栏节目、其他

示例:
  # 获取最新的10条新闻
  node milan-news.js list

  # 获取最新的20条新闻
  node milan-news.js list 20

  # 获取赛事集锦类新闻
  node milan-news.js list 10 赛事集锦

  # 查看所有可用的内容类型
  node milan-news.js types
`);
        process.exit(0);
    }
  } catch (error) {
    console.error(`Error: ${error.message}`);
    process.exit(1);
  }
}

// 导出模块供其他脚本使用
module.exports = { getLiveNews, getSubTypes };

// 如果直接运行此脚本
if (require.main === module) {
  main();
}


path: scripts/milan-olympics.js
content: #!/usr/bin/env node

/**
 * 2026年米兰冬奥会奖牌榜获取工具
 * 从百度体育网页抓取奖牌榜数据
 */

const https = require('https');

// 可配置 User-Agent 池（固定 20 个），每次请求随机选一个，避免固定 UA
const USER_AGENTS = [
  'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36',
  'Mozilla/5.0 (Macintosh; Intel Mac OS X 14_2_1) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Safari/605.1.15',
  'Mozilla/5.0 (Macintosh; Intel Mac OS X 13_6_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36',
  'Mozilla/5.0 (Macintosh; Intel Mac OS X 14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36',
  'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36',
  'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36',
  'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Edg/123.0.0.0 Chrome/123.0.0.0 Safari/537.36',
  'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Edg/122.0.0.0 Chrome/122.0.0.0 Safari/537.36',
  'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36',
  'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36',
  'Mozilla/5.0 (X11; Linux x86_64; rv:123.0) Gecko/20100101 Firefox/123.0',
  'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:123.0) Gecko/20100101 Firefox/123.0',
  'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:123.0) Gecko/20100101 Firefox/123.0',
  'Mozilla/5.0 (iPhone; CPU iPhone OS 17_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Mobile/15E148 Safari/604.1',
  'Mozilla/5.0 (iPhone; CPU iPhone OS 16_7 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.6 Mobile/15E148 Safari/604.1',
  'Mozilla/5.0 (iPad; CPU OS 17_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Mobile/15E148 Safari/604.1',
  'Mozilla/5.0 (Linux; Android 14; Pixel 8 Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Mobile Safari/537.36',
  'Mozilla/5.0 (Linux; Android 13; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Mobile Safari/537.36',
  'Mozilla/5.0 (Linux; Android 14; SM-S918B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Mobile Safari/537.36',
  'Mozilla/5.0 (Linux; Android 13; Mi 11) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Mobile Safari/537.36',
];

function getRandomUserAgent() {
  return USER_AGENTS[Math.floor(Math.random() * USER_AGENTS.length)];
}

const HEADERS = {
  'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
  // 不要 gzip/br/deflate 等压缩处理：请求服务器返回明文
  'Accept-Encoding': 'identity',
  'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
  'Referer': 'https://tiyu.baidu.com/',
  'Origin': 'https://tiyu.baidu.com'
};

const MEDAL_URL = 'https://tiyu.baidu.com/al/major/home?match=2026年米兰冬奥会&tab=奖牌榜';

/**
 * 发起HTTP GET请求
 * @param {string} url - 请求URL
 * @returns {Promise<string>} 响应HTML内容
 */
function httpGet(url) {
  return new Promise((resolve, reject) => {
    const urlObj = new URL(url);
    const options = {
      hostname: urlObj.hostname,
      path: urlObj.pathname + urlObj.search,
      method: 'GET',
      headers: { ...HEADERS, 'User-Agent': getRandomUserAgent() }
    };

    const req = https.request(options, (res) => {
      const chunks = [];
      
      res.on('data', (chunk) => { chunks.push(chunk); });
      
      res.on('end', () => {
        const buffer = Buffer.concat(chunks);
        resolve(buffer.toString('utf-8'));
      });
    });

    req.on('error', reject);
    req.setTimeout(15000, () => {
      req.destroy();
      reject(new Error('Request timeout'));
    });
    req.end();
  });
}

/**
 * 解析奖牌榜HTML，提取结构化数据
 * @param {string} html - HTML内容
 * @returns {Array} 奖牌榜数据数组
 */
function parseMedalRankings(html) {
  const rankings = [];
  
  // 提取排名项的正则表达式
  // 匹配模式: class="rankContainer rankTable" 开头的<a>标签内的数据
  const rankItemRegex = /<a[^>]*class="rankContainer[^"]*"[^>]*>[\s\S]*?<\/a>/g;
  const items = html.match(rankItemRegex) || [];
  
  for (const item of items) {
    try {
      // 提取排名
      const rankMatch = item.match(/class="rankHeaderRanking[^"]*"[^>]*>.*?<span[^>]*>(\d+)<\/span>/s);
      const rank = rankMatch ? parseInt(rankMatch[1]) : 0;
      
      // 提取国家/地区名称
      const countryMatch = item.match(/class="rankHeaderAreaName[^"]*"[^>]*>([^<]+)<\/span>/);
      const country = countryMatch ? countryMatch[1].trim() : '';
      
      // 提取奖牌数 - 匹配 gold/silver/copper 类的 div
      const goldMatch = item.match(/class="medalImg gold"[^>]*>(\d+)<\/div>/);
      const silverMatch = item.match(/class="medalImg silver"[^>]*>(\d+)<\/div>/);
      const copperMatch = item.match(/class="medalImg copper"[^>]*>(\d+)<\/div>/);
      
      const gold = goldMatch ? parseInt(goldMatch[1]) : 0;
      const silver = silverMatch ? parseInt(silverMatch[1]) : 0;
      const bronze = copperMatch ? parseInt(copperMatch[1]) : 0;
      
      // 提取总数
      const totalMatch = item.match(/class="rankHeaderSum[^"]*"[^>]*>(\d+)<\/div>/);
      const total = totalMatch ? parseInt(totalMatch[1]) : (gold + silver + bronze);
      
      // 提取国旗URL
      const flagMatch = item.match(/class="rankHeaderFlag"[^>]*style="background-image:url\('([^']+)'/);
      const flagUrl = flagMatch ? flagMatch[1] : '';
      
      // 提取详情链接
      const linkMatch = item.match(/href="([^"]+)"/);
      const detailUrl = linkMatch ? `https://tiyu.baidu.com${linkMatch[1]}` : '';
      
      if (country && rank > 0) {
        rankings.push({
          rank,
          country,
          countryEn: '', // 从HTML中无法直接获取英文名
          gold,
          silver,
          bronze,
          total,
          flagUrl,
          detailUrl
        });
      }
    } catch (e) {
      // 解析单个项目失败，继续处理下一个
      continue;
    }
  }
  
  // 如果正则匹配失败，尝试从JSON数据中提取
  if (rankings.length === 0) {
    const jsonData = extractJsonFromHtml(html);
    if (jsonData && jsonData.length > 0) {
      return jsonData;
    }
  }
  
  return rankings;
}

/**
 * 尝试从HTML中提取JSON格式的奖牌榜数据
 * @param {string} html - HTML内容
 * @returns {Array|null} 奖牌榜数据数组
 */
function extractJsonFromHtml(html) {
  try {
    // 查找包含奖牌榜数据的script标签或JSON数据
    const jsonMatch = html.match(/<script[^>]*type="application\/json"[^>]*>([\s\S]*?)<\/script>/);
    if (jsonMatch) {
      const data = JSON.parse(jsonMatch[1]);
      // 尝试找到奖牌榜数据
      if (data && data.medalRankings) {
        return data.medalRankings.map(item => ({
          rank: item.rank || 0,
          country: item.country || item.name || '',
          countryEn: item.countryEn || '',
          gold: item.gold || 0,
          silver: item.silver || 0,
          bronze: item.bronze || 0,
          total: item.total || 0,
          flagUrl: item.flagUrl || '',
          detailUrl: item.detailUrl || ''
        }));
      }
    }
  } catch (e) {
    // JSON解析失败
  }
  return null;
}

/**
 * 获取奖牌榜TOP N
 * @param {number} limit - 返回数量限制，默认30
 * @returns {Promise<Array>} 奖牌榜数组
 */
async function getTopMedals(limit = 30) {
  try {
    const html = await httpGet(MEDAL_URL);
    const rankings = parseMedalRankings(html);
    
    if (rankings.length === 0) {
      throw new Error('未能从页面解析出奖牌榜数据');
    }
    
    // 按排名排序并限制数量
    return rankings
      .sort((a, b) => a.rank - b.rank)
      .slice(0, limit);
  } catch (error) {
    throw new Error(`获取奖牌榜失败: ${error.message}`);
  }
}

/**
 * 获取完整奖牌榜
 * @returns {Promise<Array>} 完整奖牌榜数组
 */
async function getAllMedals() {
  return getTopMedals(100);
}

/**
 * 主函数 - 处理命令行参数
 */
async function main() {
  const args = process.argv.slice(2);
  const command = args[0];

  try {
    switch (command) {
      case 'top':
      case '--top':
      case '-t': {
        const limit = parseInt(args[1]) || 30;
        const rankings = await getTopMedals(limit);
        console.log(JSON.stringify(rankings, null, 2));
        break;
      }
      
      case 'all':
      case '--all':
      case '-a': {
        const rankings = await getAllMedals();
        console.log(JSON.stringify(rankings, null, 2));
        break;
      }
      
      default:
        console.log(`
2026年米兰冬奥会奖牌榜获取工具

用法:
  node milan-olympics.js <command> [options]

命令:
  top, -t, --top [n]    获取奖牌榜前N名（默认30）
  all, -a, --all        获取完整奖牌榜

示例:
  # 获取奖牌榜前30名
  node milan-olympics.js top

  # 获取奖牌榜前10名
  node milan-olympics.js top 10

  # 获取完整奖牌榜
  node milan-olympics.js all
`);
        process.exit(0);
    }
  } catch (error) {
    console.error(`Error: ${error.message}`);
    process.exit(1);
  }
}

// 导出模块供其他脚本使用
module.exports = { getTopMedals, getAllMedals };

// 如果直接运行此脚本
if (require.main === module) {
  main();
}


path: scripts/milan-schedule.js
content: #!/usr/bin/env node

/**
 * 2026年米兰冬奥会赛程获取工具
 * 从百度体育异步API获取赛程安排数据
 */

const https = require('https');

// 可配置 User-Agent 池（固定 20 个），每次请求随机选一个，避免固定 UA
const USER_AGENTS = [
  'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36',
  'Mozilla/5.0 (Macintosh; Intel Mac OS X 14_2_1) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Safari/605.1.15',
  'Mozilla/5.0 (Macintosh; Intel Mac OS X 13_6_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36',
  'Mozilla/5.0 (Macintosh; Intel Mac OS X 14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36',
  'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36',
  'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36',
  'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Edg/123.0.0.0 Chrome/123.0.0.0 Safari/537.36',
  'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Edg/122.0.0.0 Chrome/122.0.0.0 Safari/537.36',
  'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36',
  'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36',
  'Mozilla/5.0 (X11; Linux x86_64; rv:123.0) Gecko/20100101 Firefox/123.0',
  'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:123.0) Gecko/20100101 Firefox/123.0',
  'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:123.0) Gecko/20100101 Firefox/123.0',
  'Mozilla/5.0 (iPhone; CPU iPhone OS 17_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Mobile/15E148 Safari/604.1',
  'Mozilla/5.0 (iPhone; CPU iPhone OS 16_7 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.6 Mobile/15E148 Safari/604.1',
  'Mozilla/5.0 (iPad; CPU OS 17_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Mobile/15E148 Safari/604.1',
  'Mozilla/5.0 (Linux; Android 14; Pixel 8 Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Mobile Safari/537.36',
  'Mozilla/5.0 (Linux; Android 13; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Mobile Safari/537.36',
  'Mozilla/5.0 (Linux; Android 14; SM-S918B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Mobile Safari/537.36',
  'Mozilla/5.0 (Linux; Android 13; Mi 11) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Mobile Safari/537.36',
];

function getRandomUserAgent() {
  return USER_AGENTS[Math.floor(Math.random() * USER_AGENTS.length)];
}

const HEADERS = {
  'Accept': 'application/json, text/plain, */*',
  'Accept-Encoding': 'identity',
  'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
  'Referer': 'https://tiyu.baidu.com/',
  'Origin': 'https://tiyu.baidu.com'
};

// API基础URL
const API_BASE_URL = 'https://tiyu.baidu.com/al/major/schedule/list';

// 赛程类型映射
const SCHEDULE_TYPES = {
  all: 'all',      // 综合
  hot: 'hot',      // 热门
  china: 'china',  // 中国
  gold: 'gold'     // 金牌
};

// 2026年米兰冬奥会日期范围 (2月6日 - 2月22日)
const OLYMPICS_START_DATE = '2026-02-06';
const OLYMPICS_END_DATE = '2026-02-22';

/**
 * 发起HTTP GET请求
 * @param {string} url - 请求URL
 * @returns {Promise<Object>} 响应JSON数据
 */
function httpGet(url) {
  return new Promise((resolve, reject) => {
    const urlObj = new URL(url);
    const options = {
      hostname: urlObj.hostname,
      path: urlObj.pathname + urlObj.search,
      method: 'GET',
      headers: { ...HEADERS, 'User-Agent': getRandomUserAgent() }
    };

    const req = https.request(options, (res) => {
      const chunks = [];
      
      res.on('data', (chunk) => { chunks.push(chunk); });
      
      res.on('end', () => {
        try {
          const buffer = Buffer.concat(chunks);
          const text = buffer.toString('utf-8');
          const data = JSON.parse(text);
          resolve(data);
        } catch (e) {
          reject(new Error(`解析JSON失败: ${e.message}`));
        }
      });
    });

    req.on('error', reject);
    req.setTimeout(15000, () => {
      req.destroy();
      reject(new Error('Request timeout'));
    });
    req.end();
  });
}

/**
 * 构建API URL
 * @param {string} date - 日期 (YYYY-MM-DD)
 * @param {string} scheduleType - 赛程类型: all, hot, china, gold
 * @param {string} sportId - 运动项目ID，默认为'all'
 * @returns {string} 完整的API URL
 */
function buildApiUrl(date, scheduleType = 'all', sportId = 'all') {
  const type = SCHEDULE_TYPES[scheduleType] || 'all';
  return `${API_BASE_URL}?date=${date}&scheduleType=${type}&sportId=${sportId}&page=home&from=landing&isAsync=1`;
}

/**
 * 从API响应中提取赛程数据
 * @param {Object} response - API响应数据
 * @returns {Array|null} 赛程数据数组
 */
function extractScheduleFromResponse(response) {
  try {
    if (!response || response.status !== '0' || !response.data) {
      return null;
    }

    const data = response.data;
    
    if (!data.dateList || !Array.isArray(data.dateList)) {
      return null;
    }

    // 转换数据结构
    const schedules = data.dateList.map(dateItem => ({
      date: dateItem.date,
      dateFmt: dateItem.dateFmt,
      week: dateItem.week,
      countText: dateItem.countText,
      display: dateItem.display,
      matches: dateItem.scheduleList ? dateItem.scheduleList.map(match => ({
        matchId: match.matchId || '',
        matchName: match.matchName || '',
        sportName: match.discipline ? match.discipline.sportName : '',
        eventName: match.discipline ? match.discipline.eventName : '',
        subSportName: match.discipline ? match.discipline.subSportName : '',
        startTime: match.startTime || '',
        startDate: match.startDate || '',
        startDateTime: match.startDateTime || '',
        startTimestamp: match.startTimestamp || null,
        status: match.eventStatusName || '',
        statusId: match.eventStatusId || '',
        desc: match.desc || '',
        isChina: match.isChina === '1',
        isGold: match.isGold === '1',
        isHot: match.isHot === '1',
        isMedal: match.isMedal === '1',
        isPk: match.isPk === '1',
        hasLive: match.hasLive || false,
        participant: match.participant || '',
        detailUrl: match.fullLink || '',
        iconArr: match.iconArr || [],
        result: match.result || null,
        dataSource: match.dataSource || null
      })) : []
    }));

    return schedules;
  } catch (e) {
    console.error('解析赛程数据失败:', e.message);
    return null;
  }
}

/**
 * 获取指定日期的赛程数据
 * @param {string} date - 日期 (YYYY-MM-DD)
 * @param {string} scheduleType - 赛程类型: all, hot, china, gold
 * @returns {Promise<Array>} 该日期的赛程数据数组
 */
async function getScheduleByDate(date, scheduleType = 'all') {
  try {
    const url = buildApiUrl(date, scheduleType);
    const response = await httpGet(url);
    const schedules = extractScheduleFromResponse(response);
    
    return schedules || [];
  } catch (error) {
    console.warn(`获取 ${date} 的赛程失败: ${error.message}`);
    return [];
  }
}

/**
 * 生成日期范围内的所有日期
 * @param {string} startDate - 开始日期 (YYYY-MM-DD)
 * @param {string} endDate - 结束日期 (YYYY-MM-DD)
 * @returns {Array<string>} 日期数组
 */
function generateDateRange(startDate, endDate) {
  const dates = [];
  const start = new Date(startDate);
  const end = new Date(endDate);
  
  for (let d = new Date(start); d <= end; d.setDate(d.getDate() + 1)) {
    dates.push(d.toISOString().split('T')[0]);
  }
  
  return dates;
}

/**
 * 获取全部赛程
 * @param {string} date - 日期过滤（可选），格式：2026-02-08。如果为空，则获取所有日期的赛程
 * @param {boolean} fetchAll - 是否获取所有日期的数据（默认为true）
 * @returns {Promise<Array>} 赛程数组
 */
async function getAllSchedule(date = '', fetchAll = true) {
  try {
    // 如果指定了具体日期，只获取该日期
    if (date) {
      return await getScheduleByDate(date, 'all');
    }
    
    // 获取所有日期的赛程
    if (fetchAll) {
      const allSchedules = [];
      const dates = generateDateRange(OLYMPICS_START_DATE, OLYMPICS_END_DATE);
      
      // 并发请求所有日期的数据（限制并发数）
      const batchSize = 5;
      for (let i = 0; i < dates.length; i += batchSize) {
        const batch = dates.slice(i, i + batchSize);
        const results = await Promise.all(
          batch.map(date => getScheduleByDate(date, 'all'))
        );
        
        results.forEach(daySchedules => {
          if (daySchedules && daySchedules.length > 0) {
            daySchedules.forEach(day => {
              if (day.matches && day.matches.length > 0) {
                allSchedules.push(day);
              }
            });
          }
        });
      }
      
      return allSchedules;
    }
    
    // 默认只获取今天的数据
    const today = getTodayDate();
    return await getScheduleByDate(today, 'all');
  } catch (error) {
    throw new Error(`获取赛程失败: ${error.message}`);
  }
}

/**
 * 获取中国相关赛程
 * @param {string} date - 日期过滤（可选），格式：2026-02-08。如果为空，则获取所有日期的中国赛程
 * @param {boolean} fetchAll - 是否获取所有日期的数据（默认为true）
 * @returns {Promise<Array>} 中国相关赛程数组
 */
async function getChinaSchedule(date = '', fetchAll = true) {
  try {
    // 如果指定了具体日期，只获取该日期
    if (date) {
      const schedules = await getScheduleByDate(date, 'china');
      // 过滤出中国相关的比赛
      return schedules.map(day => ({
        ...day,
        matches: day.matches.filter(match => match.isChina)
      })).filter(day => day.matches.length > 0);
    }
    
    // 获取所有日期的中国赛程
    if (fetchAll) {
      const allSchedules = [];
      const dates = generateDateRange(OLYMPICS_START_DATE, OLYMPICS_END_DATE);
      
      // 并发请求所有日期的数据（限制并发数）
      const batchSize = 5;
      for (let i = 0; i < dates.length; i += batchSize) {
        const batch = dates.slice(i, i + batchSize);
        const results = await Promise.all(
          batch.map(date => getScheduleByDate(date, 'china'))
        );
        
        results.forEach(daySchedules => {
          if (daySchedules && daySchedules.length > 0) {
            daySchedules.forEach(day => {
              if (day.matches && day.matches.length > 0) {
                // API返回的china类型数据已经是过滤过的，但再过滤一次确保安全
                const chinaMatches = day.matches.filter(match => match.isChina);
                if (chinaMatches.length > 0) {
                  allSchedules.push({
                    ...day,
                    matches: chinaMatches
                  });
                }
              }
            });
          }
        });
      }
      
      return allSchedules;
    }
    
    // 默认只获取今天的数据
    const today = getTodayDate();
    const schedules = await getScheduleByDate(today, 'china');
    return schedules.map(day => ({
      ...day,
      matches: day.matches.filter(match => match.isChina)
    })).filter(day => day.matches.length > 0);
  } catch (error) {
    throw new Error(`获取中国赛程失败: ${error.message}`);
  }
}

/**
 * 获取金牌赛赛程
 * @param {string} date - 日期过滤（可选），格式：2026-02-08。如果为空，则获取所有日期的金牌赛
 * @param {boolean} fetchAll - 是否获取所有日期的数据（默认为true）
 * @returns {Promise<Array>} 金牌赛赛程数组
 */
async function getGoldSchedule(date = '', fetchAll = true) {
  try {
    // 如果指定了具体日期，只获取该日期
    if (date) {
      const schedules = await getScheduleByDate(date, 'gold');
      return schedules.map(day => ({
        ...day,
        matches: day.matches.filter(match => match.isGold)
      })).filter(day => day.matches.length > 0);
    }
    
    // 获取所有日期的金牌赛
    if (fetchAll) {
      const allSchedules = [];
      const dates = generateDateRange(OLYMPICS_START_DATE, OLYMPICS_END_DATE);
      
      const batchSize = 5;
      for (let i = 0; i < dates.length; i += batchSize) {
        const batch = dates.slice(i, i + batchSize);
        const results = await Promise.all(
          batch.map(date => getScheduleByDate(date, 'gold'))
        );
        
        results.forEach(daySchedules => {
          if (daySchedules && daySchedules.length > 0) {
            daySchedules.forEach(day => {
              if (day.matches && day.matches.length > 0) {
                const goldMatches = day.matches.filter(match => match.isGold);
                if (goldMatches.length > 0) {
                  allSchedules.push({
                    ...day,
                    matches: goldMatches
                  });
                }
              }
            });
          }
        });
      }
      
      return allSchedules;
    }
    
    // 默认只获取今天的数据
    const today = getTodayDate();
    const schedules = await getScheduleByDate(today, 'gold');
    return schedules.map(day => ({
      ...day,
      matches: day.matches.filter(match => match.isGold)
    })).filter(day => day.matches.length > 0);
  } catch (error) {
    throw new Error(`获取金牌赛赛程失败: ${error.message}`);
  }
}

/**
 * 获取热门赛程
 * @param {string} date - 日期过滤（可选），格式：2026-02-08。如果为空，则获取所有日期的热门赛程
 * @param {boolean} fetchAll - 是否获取所有日期的数据（默认为true）
 * @returns {Promise<Array>} 热门赛程数组
 */
async function getHotSchedule(date = '', fetchAll = true) {
  try {
    // 如果指定了具体日期，只获取该日期
    if (date) {
      const schedules = await getScheduleByDate(date, 'hot');
      return schedules.map(day => ({
        ...day,
        matches: day.matches.filter(match => match.isHot)
      })).filter(day => day.matches.length > 0);
    }

    // 获取所有日期的热门赛程
    if (fetchAll) {
      const allSchedules = [];
      const dates = generateDateRange(OLYMPICS_START_DATE, OLYMPICS_END_DATE);

      const batchSize = 5;
      for (let i = 0; i < dates.length; i += batchSize) {
        const batch = dates.slice(i, i + batchSize);
        const results = await Promise.all(
          batch.map(date => getScheduleByDate(date, 'hot'))
        );

        results.forEach(daySchedules => {
          if (daySchedules && daySchedules.length > 0) {
            daySchedules.forEach(day => {
              if (day.matches && day.matches.length > 0) {
                const hotMatches = day.matches.filter(match => match.isHot);
                if (hotMatches.length > 0) {
                  allSchedules.push({
                    ...day,
                    matches: hotMatches
                  });
                }
              }
            });
          }
        });
      }

      return allSchedules;
    }

    // 默认只获取今天的数据
    const today = getTodayDate();
    const schedules = await getScheduleByDate(today, 'hot');
    return schedules.map(day => ({
      ...day,
      matches: day.matches.filter(match => match.isHot)
    })).filter(day => day.matches.length > 0);
  } catch (error) {
    throw new Error(`获取热门赛程失败: ${error.message}`);
  }
}

/**
 * 获取可用的日期列表
 * @returns {Promise<Array>} 日期数组
 */
async function getAvailableDates() {
  try {
    const today = getTodayDate();
    const url = buildApiUrl(today, 'all');
    const response = await httpGet(url);
    
    if (!response || response.status !== '0' || !response.data) {
      return [];
    }

    const data = response.data;
    
    if (data.select && data.select.labels) {
      return data.select.labels.map(label => ({
        date: label.date,
        suffix: label.suffix,
        desc: label.desc,
        disabled: label.disabled === '1',
        icon: label.icon
      }));
    }
    
    return [];
  } catch (error) {
    console.warn('获取可用日期失败:', error.message);
    return [];
  }
}

/**
 * 获取所有运动项目列表
 * @returns {Promise<Object>} 包含热门项目和其他项目的对象
 */
async function getAllSports() {
  try {
    const today = getTodayDate();
    const url = buildApiUrl(today, 'all');
    const response = await httpGet(url);
    
    if (!response || response.status !== '0' || !response.data) {
      return { hot: [], other: [] };
    }

    const data = response.data;
    
    if (data.select && data.select.sport) {
      const sports = data.select.sport;
      return {
        hot: (sports.hot || []).map(sport => ({
          name: sport.name,
          value: sport.value,
          selected: sport.selected,
          hot: sport.hot || 0
        })),
        other: (sports.other || []).map(sport => ({
          name: sport.name,
          value: sport.value,
          selected: sport.selected
        }))
      };
    }
    
    return { hot: [], other: [] };
  } catch (error) {
    console.warn('获取运动项目列表失败:', error.message);
    return { hot: [], other: [] };
  }
}

/**
 * 获取指定运动项目的赛程数据
 * @param {string} sportId - 运动项目ID (如 '302' 表示短道速滑，'all' 表示全部项目)
 * @param {string} date - 日期 (YYYY-MM-DD)，如果为空则获取今天
 * @returns {Promise<Array>} 该运动项目的赛程数据数组
 */
async function getScheduleBySport(sportId, date = '') {
  try {
    const targetDate = date || getTodayDate();
    const url = buildApiUrl(targetDate, 'all', sportId);
    const response = await httpGet(url);
    const schedules = extractScheduleFromResponse(response);
    
    return schedules || [];
  } catch (error) {
    console.warn(`获取运动项目 ${sportId} 的赛程失败: ${error.message}`);
    return [];
  }
}

/**
 * 获取中国指定日期的指定运动项目赛程
 * @param {string} sportId - 运动项目ID (如 '302' 表示短道速滑)
 * @param {string} date - 日期 (YYYY-MM-DD)，如果为空则获取今天
 * @returns {Promise<Array>} 中国相关的指定运动项目赛程数组
 */
async function getChinaScheduleBySport(sportId, date = '') {
  try {
    const targetDate = date || getTodayDate();
    const url = buildApiUrl(targetDate, 'china', sportId);
    const response = await httpGet(url);
    const schedules = extractScheduleFromResponse(response);
    
    if (!schedules || schedules.length === 0) {
      return [];
    }
    
    // 过滤出中国相关的比赛
    return schedules.map(day => ({
      ...day,
      matches: day.matches.filter(match => match.isChina)
    })).filter(day => day.matches.length > 0);
  } catch (error) {
    console.warn(`获取中国运动项目 ${sportId} 的赛程失败: ${error.message}`);
    return [];
  }
}

/**
 * 获取今天的日期字符串（YYYY-MM-DD）
 * @returns {string} 今天的日期
 */
function getTodayDate() {
  const now = new Date();
  const year = now.getFullYear();
  const month = String(now.getMonth() + 1).padStart(2, '0');
  const day = String(now.getDate()).padStart(2, '0');
  return `${year}-${month}-${day}`;
}

/**
 * 获取明天的日期字符串（YYYY-MM-DD）
 * @returns {string} 明天的日期
 */
function getTomorrowDate() {
  const now = new Date();
  now.setDate(now.getDate() + 1);
  const year = now.getFullYear();
  const month = String(now.getMonth() + 1).padStart(2, '0');
  const day = String(now.getDate()).padStart(2, '0');
  return `${year}-${month}-${day}`;
}

/**
 * 获取今天的赛程（综合TAB下全部赛程）
 * @returns {Promise<Array>} 今天的赛程数组
 */
async function getTodaySchedule() {
  const today = getTodayDate();
  return getAllSchedule(today);
}

/**
 * 获取明天的赛程（综合TAB下全部赛程）
 * @returns {Promise<Array>} 明天的赛程数组
 */
async function getTomorrowSchedule() {
  const tomorrow = getTomorrowDate();
  return getAllSchedule(tomorrow);
}

/**
 * 主函数 - 处理命令行参数
 */
async function main() {
  const args = process.argv.slice(2);
  const command = args[0];

  try {
    switch (command) {
      case 'all':
      case '--all':
      case '-a': {
        const date = args[1] || '';
        const schedules = await getAllSchedule(date);
        console.log(JSON.stringify(schedules, null, 2));
        break;
      }
      
      case 'china':
      case '--china':
      case '-c': {
        const date = args[1] || '';
        const schedules = await getChinaSchedule(date);
        console.log(JSON.stringify(schedules, null, 2));
        break;
      }
      
      case 'gold':
      case '--gold':
      case '-g': {
        const date = args[1] || '';
        const schedules = await getGoldSchedule(date);
        console.log(JSON.stringify(schedules, null, 2));
        break;
      }
      
      case 'hot':
      case '--hot':
      case '-h': {
        const date = args[1] || '';
        const schedules = await getHotSchedule(date);
        console.log(JSON.stringify(schedules, null, 2));
        break;
      }
      
      case 'dates':
      case '--dates':
      case '-d': {
        const dates = await getAvailableDates();
        console.log(JSON.stringify(dates, null, 2));
        break;
      }

      case 'today':
      case '--today':
      case '-t': {
        const schedules = await getTodaySchedule();
        console.log(JSON.stringify(schedules, null, 2));
        break;
      }

      case 'tomorrow':
      case '--tomorrow':
      case '-m': {
        const schedules = await getTomorrowSchedule();
        console.log(JSON.stringify(schedules, null, 2));
        break;
      }

      case 'sports':
      case '--sports':
      case '-s': {
        const sports = await getAllSports();
        console.log(JSON.stringify(sports, null, 2));
        break;
      }

      case 'sport':
      case '--sport': {
        const sportId = args[1];
        const date = args[2] || '';
        if (!sportId) {
          console.error('Error: 请指定运动项目ID');
          console.log('用法: node milan-schedule.js sport <sportId> [date]');
          console.log('示例: node milan-schedule.js sport 302 2026-02-10');
          process.exit(1);
        }
        const schedules = await getScheduleBySport(sportId, date);
        console.log(JSON.stringify(schedules, null, 2));
        break;
      }

      case 'china-sport':
      case '--china-sport': {
        const sportId = args[1];
        const date = args[2] || '';
        if (!sportId) {
          console.error('Error: 请指定运动项目ID');
          console.log('用法: node milan-schedule.js china-sport <sportId> [date]');
          console.log('示例: node milan-schedule.js china-sport 302 2026-02-10');
          process.exit(1);
        }
        const schedules = await getChinaScheduleBySport(sportId, date);
        console.log(JSON.stringify(schedules, null, 2));
        break;
      }

      default:
        console.log(`
2026年米兰冬奥会赛程获取工具

用法:
  node milan-schedule.js <command> [options]

命令:
  all, -a, --all [date]         获取全部赛程（默认获取所有日期）
  china, -c, --china [date]     获取中国相关赛程（默认获取所有日期）
  gold, -g, --gold [date]       获取金牌赛赛程（默认获取所有日期）
  hot, -h, --hot [date]         获取热门赛程（默认获取所有日期）
  today, -t, --today            获取今天的赛程（无需指定日期）
  tomorrow, -m, --tomorrow      获取明天的赛程（无需指定日期）
  dates, -d, --dates            获取可用的日期列表
  sports, -s, --sports          获取所有运动项目列表
  sport <sportId> [date]        获取指定运动项目的赛程
  china-sport <sportId> [date]  获取中国指定运动项目的赛程

参数:
  date      日期过滤，格式：2026-02-08（可选）。
            不指定date时默认获取2026-02-06至2026-02-22所有日期的数据
  sportId   运动项目ID，可通过 sports 命令查看

示例:
  # 获取全部赛程（所有日期）
  node milan-schedule.js all

  # 获取特定日期的赛程
  node milan-schedule.js all 2026-02-08

  # 获取今天的赛程
  node milan-schedule.js today

  # 获取明天的赛程
  node milan-schedule.js tomorrow

  # 获取中国相关赛程（所有日期）
  node milan-schedule.js china

  # 获取特定日期的中国赛程
  node milan-schedule.js china 2026-02-08

  # 获取金牌赛赛程（所有日期）
  node milan-schedule.js gold

  # 查看所有可用日期
  node milan-schedule.js dates

  # 查看所有运动项目
  node milan-schedule.js sports

  # 获取短道速滑赛程（sportId: 302）
  node milan-schedule.js sport 302

  # 获取特定日期短道速滑赛程
  node milan-schedule.js sport 302 2026-02-10

  # 获取中国短道速滑赛程
  node milan-schedule.js china-sport 302

  # 获取特定日期中国短道速滑赛程
  node milan-schedule.js china-sport 302 2026-02-10
`);
        process.exit(0);
    }
  } catch (error) {
    console.error(`Error: ${error.message}`);
    process.exit(1);
  }
}

// 导出模块供其他脚本使用
module.exports = {
  getAllSchedule,
  getChinaSchedule,
  getGoldSchedule,
  getHotSchedule,
  getAvailableDates,
  getAllSports,
  getScheduleBySport,
  getChinaScheduleBySport,
  getTodaySchedule,
  getTomorrowSchedule,
  getScheduleByDate,
  generateDateRange,
  OLYMPICS_START_DATE,
  OLYMPICS_END_DATE
};

// 如果直接运行此脚本
if (require.main === module) {
  main();
}


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (MEDIUM)