From 7bcd245e5de2292be8f2836687969929e59da0d9 Mon Sep 17 00:00:00 2001 From: cluntop <85211716+cluntop@users.noreply.github.com> Date: Thu, 26 Feb 2026 23:24:02 +0800 Subject: [PATCH] Update Up --- .github/workflows/test.yml | 80 +++++++++++++++++++++++++++ .github/workflows/toos.yml | 4 +- fun.json | 16 +++--- lib/{18 => }/dxzb.m3u | 0 lib/{18 => }/dymzb.m3u | 0 lib/{18 => }/hsck_gc.m3u | 0 lib/{18 => }/hsck_rh.m3u | 0 lib/{18 => }/mdsp.m3u | 0 lib/{18 => }/omzb.m3u | 0 lib/{18 => }/sbjh.m3u | 0 lib/{18 => }/twcr.m3u | 0 py/sinparty.py | 110 +++++++++++++++++++++++++++++++++++++ 12 files changed, 200 insertions(+), 10 deletions(-) create mode 100644 .github/workflows/test.yml rename lib/{18 => }/dxzb.m3u (100%) rename lib/{18 => }/dymzb.m3u (100%) rename lib/{18 => }/hsck_gc.m3u (100%) rename lib/{18 => }/hsck_rh.m3u (100%) rename lib/{18 => }/mdsp.m3u (100%) rename lib/{18 => }/omzb.m3u (100%) rename lib/{18 => }/sbjh.m3u (100%) rename lib/{18 => }/twcr.m3u (100%) create mode 100644 py/sinparty.py diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 000000000..0ce9dc110 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,80 @@ +name: Update Test + +permissions: + contents: write + actions: write + +on: + schedule: + - cron: '30 2 * * *' + workflow_dispatch: + +env: + TZ: Asia/Shanghai + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + Update: + runs-on: self-hosted + timeout-minutes: 15 + + steps: + - name: Checkout repository + uses: actions/checkout@v6 + with: + repository: cluntop/tvbox + token: ${{ secrets.GIT_TOKEN }} + fetch-depth: 1 + + - name: Delete old workflow runs + uses: Mattraks/delete-workflow-runs@v2 + with: + token: ${{ github.token }} + repository: ${{ github.repository }} + retain_days: 1 + keep_minimum_runs: 3 + + #- name: Set up Python + # uses: actions/setup-python@v6 + # with: + # python-version: '3.10' + # cache: 'pip' + + - name: Install dependencies + run: | + python -m venv .venv + source .venv/bin/activate + python -m pip install --upgrade pip + # if [ -f .github/requirements.txt ]; then pip install -r .github/requirements.txt; fi + pip install aiohttp playwright + + - name: Initialize chromium + run: | + source .venv/bin/activate + playwright install chromium + + - name: Run M3U script + run: | + source .venv/bin/activate + python py/sinparty.py + + - name: Commit and Push changes + run: | + export HOME=${GITHUB_WORKSPACE} + + git config --global user.name "GitHub Actions" + git config --global user.email "actions@github.com" + + if [ -n "$(git status --porcelain)" ]; then + echo "Changes detected, committing..." + git add . + git commit -m "Update Test" + + git pull --rebase origin main + git push origin main + else + echo "No changes detected, skipping push." + fi diff --git a/.github/workflows/toos.yml b/.github/workflows/toos.yml index 08c94f6e3..f7076fce4 100644 --- a/.github/workflows/toos.yml +++ b/.github/workflows/toos.yml @@ -48,8 +48,8 @@ jobs: python -m venv .venv source .venv/bin/activate python -m pip install --upgrade pip - if [ -f .github/requirements.txt ]; then pip install -r .github/requirements.txt; fi - # pip install pandas requests + # if [ -f .github/requirements.txt ]; then pip install -r .github/requirements.txt; fi + pip install aiohttp - name: Run M3U script run: | diff --git a/fun.json b/fun.json index 67cc9772f..e01db6534 100755 --- a/fun.json +++ b/fun.json @@ -779,49 +779,49 @@ { "name": "大洋马直播", "type": 0, - "url": "./lib/18/deyzb.m3u", + "url": "./lib/deyzb.m3u", "ua": "" }, { "name": "大秀直播", "type": 0, - "url": "./lib/18/dxzb.m3u", + "url": "./lib/dxzb.m3u", "ua": "" }, { "name": "欧美直播", "type": 0, - "url": "./lib/18/omzb.m3u", + "url": "./lib/omzb.m3u", "ua": "" }, { "name": "国产系列", "type": 0, - "url": "./lib/18/hsck_gc.m3u", + "url": "./lib/hsck_gc.m3u", "ua": "" }, { "name": "日韩系列", "type": 0, - "url": "./lib/18/hsck_rh.m3u", + "url": "./lib/hsck_rh.m3u", "ua": "" }, { "name": "色播聚合", "type": 0, - "url": "./lib/18/sbjh.m3u", + "url": "./lib/sbjh.m3u", "ua": "" }, { "name": "麻豆视频", "type": 0, - "url": "./lib/18/mdsp.m3u", + "url": "./lib/mdsp.m3u", "ua": "" }, { "name": "台湾成人", "type": 0, - "url": "./lib/18/rwcr.m3u", + "url": "./lib/rwcr.m3u", "ua": "" }, { diff --git a/lib/18/dxzb.m3u b/lib/dxzb.m3u similarity index 100% rename from lib/18/dxzb.m3u rename to lib/dxzb.m3u diff --git a/lib/18/dymzb.m3u b/lib/dymzb.m3u similarity index 100% rename from lib/18/dymzb.m3u rename to lib/dymzb.m3u diff --git a/lib/18/hsck_gc.m3u b/lib/hsck_gc.m3u similarity index 100% rename from lib/18/hsck_gc.m3u rename to lib/hsck_gc.m3u diff --git a/lib/18/hsck_rh.m3u b/lib/hsck_rh.m3u similarity index 100% rename from lib/18/hsck_rh.m3u rename to lib/hsck_rh.m3u diff --git a/lib/18/mdsp.m3u b/lib/mdsp.m3u similarity index 100% rename from lib/18/mdsp.m3u rename to lib/mdsp.m3u diff --git a/lib/18/omzb.m3u b/lib/omzb.m3u similarity index 100% rename from lib/18/omzb.m3u rename to lib/omzb.m3u diff --git a/lib/18/sbjh.m3u b/lib/sbjh.m3u similarity index 100% rename from lib/18/sbjh.m3u rename to lib/sbjh.m3u diff --git a/lib/18/twcr.m3u b/lib/twcr.m3u similarity index 100% rename from lib/18/twcr.m3u rename to lib/twcr.m3u diff --git a/py/sinparty.py b/py/sinparty.py new file mode 100644 index 000000000..dcd912321 --- /dev/null +++ b/py/sinparty.py @@ -0,0 +1,110 @@ +import asyncio +import aiohttp +import re +from playwright.async_api import async_playwright + +async def fetch_m3u8(session: aiohttp.ClientSession, name: str, link: str): + """ + 使用 aiohttp 高并发拉取单个直播间源码,并使用正则嗅探底层 .m3u8 流媒体链接 + 优化逻辑:保持底层并发稳定,避免 GitHub Actions 中 OOM + """ + headers = { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" + } + try: + async with session.get(link, headers=headers, timeout=15) as response: + if response.status == 200: + text = await response.text() + match = re.search(r'(https?:[\\/]+[^"\'\s]+\.m3u8[^"\'\s]*)', text) + if match: + m3u8_url = match.group(1).replace('\\/', '/') + return name, m3u8_url, link + except Exception: + pass + + return name, None, link + +async def main(): + results = [] + + # === 阶段 1:Playwright 全站分页抓取 === + async with async_playwright() as p: + browser = await p.chromium.launch(headless=True) + page = await browser.new_page() + + page_num = 1 + while True: + print(f"正在加载并抓取第 {page_num} 页数据...") + # 动态改变 page=&& 参数 + url = f"https://sinparty.com/zh?page={page_num}" + await page.goto(url, wait_until="networkidle") + + # 定位目标:跳过 skeleton 骨架屏,直接锁定在线主播节点 + # 兼容 a.cam-tile.cam-tile--online 作为独立跳转链接提取 + elements = await page.locator("a.cam-tile.cam-tile--online").all() + + if not elements: + print(f"第 {page_num} 页未检测到有效在线主播数据,翻页结束。\n") + break + + for element in elements: + # 抓取标题与名字:兼容 .cam-tile__title 或 .cam-tile__info + title_loc = element.locator(".cam-tile__title, .cam-tile__info") + if await title_loc.count() > 0: + title = await title_loc.first.inner_text() + else: + title = "未知用户" + + # 抓取 href 跳转链接 + href = await element.get_attribute("href") + + if href: + if href.startswith("/"): + href = f"https://sinparty.com{href}" + + results.append({ + "name": title.strip(), + "link": href + }) + + page_num += 1 + + await browser.close() + + # === 阶段 2:AIOHTTP 高性能并发抓取 m3u8 流 === + print(f"全站遍历完毕,共提取 {len(results)} 个直播间链接。开始高并发底层嗅探...") + + connector = aiohttp.TCPConnector(limit=100) + async with aiohttp.ClientSession(connector=connector) as session: + tasks = [fetch_m3u8(session, res["name"], res["link"]) for res in results] + m3u8_results = await asyncio.gather(*tasks) + + # === 阶段 3:转换并格式化输出 M3U === + m3u_lines = ["#EXTM3U"] + success_count = 0 + + for name, m3u8_url, room_link in m3u8_results: + # M3U 标准:需要直接填入可播放的流媒体链接 (.m3u8) + # 如果底层抓不到 m3u8,则使用原始跳转链接作为 fallback + final_link = m3u8_url if m3u8_url else room_link + + # 按照要求输出 group-title="女生" 及名称 + m3u_lines.append(f'#EXTINF:-1 group-title="女生",{name}') + m3u_lines.append(final_link) + + if m3u8_url: + success_count += 1 + + m3u_content = "\n".join(m3u_lines) + + print("\n=== 转换格式 M3U 输出 ===") + print(m3u_content) + + with open("lib/party.m3u", "w", encoding="utf-8") as f: + f.write(m3u_content) + + print(f"\n并发处理完成!成功解析 {success_count} 个底层流,总计写入 {len(results)} 条数据。") + +if __name__ == "__main__": + asyncio.run(main()) +