feat: add TVBox JSON monitor

This commit is contained in:
Xi Xu committed 2026-05-13 17:03:42 +00:00
commit 37f4ebf1fd
6 files changed
+1040

No files matched your search

+60
View File
@@ -0,0 +1,60 @@
name: Monitor TVBox JSON
on:
schedule:
# Run every 5 minutes. GitHub-hosted schedules may be delayed under load.
- cron: "*/5 * * * *"
workflow_dispatch:
permissions:
contents: write
concurrency:
group: tvbox-json-monitor
cancel-in-progress: false
jobs:
update-tvbox-json:
runs-on: ubuntu-latest
env:
TVBOX_URL: ${{ vars.TVBOX_URL }}
TVBOX_UA: "okhttp/3.15"
OUT_JSON: "data/tvbox.json"
OUT_META: "data/tvbox.meta.json"
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Validate repository variables
run: |
if [ -z "${TVBOX_URL}" ]; then
echo "Repository variable TVBOX_URL is not set."
exit 1
fi
- name: Extract and decode TVBox JSON
run: python3 scripts/extract_tvbox.py
- name: Validate generated files
run: |
python3 -m json.tool "${OUT_JSON}" > /dev/null
python3 -m json.tool "${OUT_META}" > /dev/null
- name: Commit and push changes
run: |
if git diff --quiet -- "${OUT_JSON}" "${OUT_META}"; then
echo "No TVBox JSON update detected."
exit 0
fi
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add "${OUT_JSON}" "${OUT_META}"
COMMIT_TIME="$(date -u +'%Y-%m-%dT%H:%M:%SZ')"
git commit -m "chore(tvbox): update decoded JSON ${COMMIT_TIME}"
git push
+3
View File
@@ -0,0 +1,3 @@
.DS_Store
__pycache__/
*.pyc
+158
View File
@@ -0,0 +1,158 @@
# TVBox JSON Monitor
This repository automatically monitors a TVBox-compatible endpoint, extracts the hidden configuration payload, decodes it, normalizes it as strict JSON, and commits updates when changes are detected.
The update process is handled by GitHub Actions on a scheduled basis.
## What this repository does
The monitored endpoint returns a JPEG-like payload for TVBox-style clients.
The actual TVBox configuration is embedded after the JPEG end marker:
1. Request the endpoint with a TVBox-like `User-Agent`.
2. Locate the JPEG EOI marker: `FF D9`.
3. Read the appended payload after the JPEG data.
4. Locate the `**` separator.
5. Base64-decode the content after the separator.
6. Normalize the decoded configuration into strict JSON.
7. Commit the updated JSON files when changes are detected.
## Output files
The workflow maintains the following generated files:
```text
data/tvbox.json
data/tvbox.meta.json
```
### `data/tvbox.json`
This is the decoded TVBox configuration, normalized as strict JSON.
The upstream configuration may contain JSONC-style full-line comments. These comments are removed so that the stored file can be parsed by standard JSON tools.
### `data/tvbox.meta.json`
This file contains metadata about the latest decoded payload, including:
```text
source_url
final_url
user_agent
raw_size
raw_sha256
decoded_size
decoded_sha256
json_sha256
jpeg_eoi_offset
appended_size
base64_size
etag
last_modified
cache_control
content_type
```
## GitHub Actions workflow
The workflow runs automatically every 5 minutes:
```yaml
- cron: "*/5 * * * *"
```
It can also be triggered manually from the GitHub Actions UI using `workflow_dispatch`.
GitHub-hosted scheduled workflows are not guaranteed to run exactly on time. They may be delayed depending on GitHub Actions load.
## Repository variable
The monitored endpoint URL is configured through a GitHub Actions repository variable.
Create the following repository variable:
```text
Name: TVBOX_URL
Value: https://www.xn--sss604efuw.com/tv/
```
The URL should preferably include the trailing slash to avoid unnecessary redirects.
To set it:
```text
Repository
→ Settings
→ Secrets and variables
→ Actions
→ Variables
→ New repository variable
```
## Required repository settings
The workflow commits generated files back to the repository.
Make sure GitHub Actions has write permission:
```text
Repository
→ Settings
→ Actions
→ General
→ Workflow permissions
→ Read and write permissions
```
## Commit behavior
If the decoded JSON has not changed, the workflow exits without creating a commit.
If changes are detected, the workflow commits the updated files with a message like:
```text
chore(tvbox): update decoded JSON 2026-05-13T16:00:00Z
```
## File normalization
The generated `data/tvbox.json` is not a byte-for-byte copy of the decoded upstream payload.
It is normalized for public repository use:
- Full-line `//` comments are removed.
- JSON is parsed and re-serialized.
- UTF-8 characters are preserved.
- Output formatting uses 2-space indentation.
- Object key order is preserved as parsed by Python.
This makes the file easier to diff, inspect, and consume with standard JSON tooling.
## Security and privacy
The endpoint URL is stored as a repository variable, not a secret.
This is intentional because the URL is not sensitive and this repository is designed for public use.
No credentials are required beyond the default `GITHUB_TOKEN` provided by GitHub Actions.
## Local extraction
The same extraction logic can be run locally with Python:
```bash
export TVBOX_URL="https://www.xn--sss604efuw.com/tv/"
export TVBOX_UA="okhttp/3.15"
export OUT_JSON="data/tvbox.json"
export OUT_META="data/tvbox.meta.json"
python3 scripts/extract_tvbox.py
```
## Disclaimer
This repository only monitors, decodes, normalizes, and stores configuration data from the configured endpoint.
It does not host video content, proxy media streams, or provide any playback service.
+707
View File
@@ -0,0 +1,707 @@
{
"spider": "https://oss4liview.moji.com/thd_file/2026/05/08/b216ded4a854a190ce9f6bd280aff779.jpg;md5;448a9f26f33109f6aa148971c3adab46",
"wallpaper": "https://深色壁纸.xxooo.cf/",
"sites": [
{
"key": "点我切源",
"name": "新增嘟嘟盘,到【太太太硬了】领免费容量",
"type": 3,
"api": "csp_DouDouGuard",
"indexs": 1,
"searchable": 0,
"quickSearch": 0,
"changeable": 0
},
{
"key": "MDrive",
"name": "🗂我的云盘┃我配置",
"type": 3,
"api": "csp_MyDriveGuard",
"changeable": 0,
"indexs": 0,
"searchable": 1,
"style": {
"type": "oval"
},
"ext": {
"Cloud-drive": "tvfan/Cloud-drive.txt"
}
},
{
"key": "玩偶",
"name": "👽玩偶哥哥┃4K弹幕",
"type": 3,
"api": "csp_WoGGGuard",
"timeout": 30,
"searchable": 1,
"quickSearch": 1,
"changeable": 0,
"ext": {
"Cloud-drive": "tvfan/Cloud-drive.txt"
}
},
{
"key": "YGP",
"name": "🚀叨观荐影┃预告片",
"type": 3,
"api": "csp_YGPGuard",
"searchable": 0,
"quickSearch": 0,
"changeable": 0
},
{
"key": "MTV1",
"name": "🎙️易听音乐┃带歌词",
"type": 3,
"api": "csp_MusicGuard",
"style": {
"type": "rect",
"ratio": 1
},
"playerType": 2,
"searchable": 1,
"quickSearch": 0,
"changeable": 0
},
{
"key": "seed",
"name": "💡聚剧剧┃四盘",
"type": 3,
"api": "csp_SeedhubGuard",
"ext": "5++kwLhNYm9UrO9wh7Dl7eKamTee4s/5",
"searchable": 1,
"quickSearch": 1,
"changeable": 0,
"indexs": 0,
"style": {
"type": "list"
}
},
{
"key": "ZPan",
"name": "🎈聚盘搜┃四盘",
"type": 3,
"api": "csp_S_zpsGuard",
"searchable": 1,
"quickSearch": 0,
"changeable": 0,
"ext": {
"siteUrl": "http://107.173.211.148/"
},
"indexs": 0,
"style": {
"type": "list"
}
},
{
"key": "云播",
"name": "⛅️云播┃无搜索",
"type": 3,
"api": "csp_T4Guard",
"searchable": 1,
"quickSearch": 1,
"changeable": 0,
"ext": "rfOIzPkSUkANv6AT2prC8en3+Trbx4j10CIoZMv3Ag4bdEYQqTMqu/Z3YPtC2NJv6n6YeZdgyWlo4WJjBL5gUt6B7LvCEDT4CKrShL75AiGhjhdkfX0cfnI/kIpThjDhEZ0lc8r7irE4mRMelQz4llfmVMCtjIFNIHP1T3XqwSmYZd34DfAtnkbgrpiMBiYW36CCI5rTkC3Rtg1wQQjazUZyVHx4Zz5bWbmPv4G8q7PfHWYStVeDannVT2lmLzKbGEDahyxvLhNZRjXO5rPlruB0ydDcR/e/g5nx8N3JhG0oucfv6xVDl0XxYy0K2bY3plgqx32kfnS63xa8sQDbGlYoPtdHQCi2cAvEOaqMb5a6r2D3yvcLXYLjk37CJgQObzK0bCggZPm3B3YenAuunxl5OQmXrzLHjys6I2isL5AwTS2YAjD/R3f4FYWBp+4RdwMBAChatxjEHcaFhCUb0nfTgYKQ4a1pZ4j6+d4YHa0GaMYHO5NZQ0tDRZfAZPcfYaWLSW8iIUx6G2e0JnI6bLAN1TR0AVer8lxfU0zAzAODbKVrt8Oafq0IQ6MeGFcFRGMu4QWrzaublQ=="
},
{
"key": "立播",
"name": "🌟立播┃秒播",
"type": 3,
"api": "csp_LibvioGuard",
"timeout": 10,
"searchable": 1,
"quickSearch": 1,
"changeable": 1,
"ext": {
"Cloud-drive": "tvfan/Cloud-drive.txt"
}
},
{
"key": "奶酪",
"name": "🧀奶酪┃秒播",
"type": 3,
"api": "csp_T4Guard",
"searchable": 1,
"quickSearch": 1,
"changeable": 0,
"ext": "rfOIzPkSUkANv6AT2prC8en3+TzKx9TnlT8vaY37HhtYfAQe6C5xqrVuJPhQwYV6r3eRdMBGm3Qm6Th+BushR86B6KqJGXDsHazHw7alBG/7zUxkN1tK/NypRxnpBNoeUtpw4jcCGhytI75yO4g4zG6SOPA0RSwhksM0IF2friAkrHCWoW3v+0mdw6sjz4t4XB1Df7yL/R5cfaA/5LQYq3I8OkvMrJMU9Q1P7JXwx7NSF2zTyH/ANVmZ4u5m567DW1KVG7OuQjXPjZiOXTYk0+wjpfBRTf19yIq6q/C76k2Fs80joAMPw0ueDR+QHxtuDcTom2rmHkI1Fonkzi6BotbpUcbSi4PiIgmfdbvVwhG6Z+i4nvt+IYa48l5aLA7PLgDiERpuOs31aHaXlgFswT87XyTb8QaF4CuzKWJuXptwNTjvXAS9KHdxH49Ay+hfBAB2bCvUf4CMoldF2wZUv0mI2qY966erFpOFg+FOc7t88EUH8j8ACXQtHJiKC9RQ+SaLIF0="
},
{
"key": "厂长",
"name": "📔厂长┃秒播",
"type": 3,
"api": "csp_NewCzGuard",
"timeout": 10,
"playerType": 2,
"searchable": 1,
"quickSearch": 1,
"changeable": 1
},
{
"key": "文采",
"name": "💮文采┃秒播",
"type": 3,
"api": "csp_JpysGuard",
"timeout": 10,
"playerType": 2,
"searchable": 1,
"quickSearch": 1,
"changeable": 1
},
{
"key": "原创",
"name": "☀原创┃秒播",
"type": 3,
"api": "csp_YCyzGuard",
"timeout": 15,
"playerType": 1,
"searchable": 1,
"quickSearch": 1,
"changeable": 1
},
{
"key": "比特",
"name": "🍄比特┃秒播",
"type": 3,
"api": "csp_BttwooGuard",
"timeout": 10,
"searchable": 1,
"quickSearch": 1,
"changeable": 1
},
{
"key": "糯米",
"name": "🍓糯米┃多线",
"type": 3,
"api": "csp_NmyswvGuard",
"timeout": 15,
"searchable": 1,
"quickSearch": 1,
"changeable": 1
},
{
"key": "热播",
"name": "📺热播┃多线",
"type": 3,
"api": "csp_AppTTGuard",
"timeout": 10,
"playerType": 2,
"searchable": 1,
"quickSearch": 1,
"changeable": 1,
"ext": "uqGL1bNENExT7/hGxpSE5qU="
},
{
"key": "剧圈",
"name": "🐻剧圈┃多线",
"type": 3,
"api": "csp_AppSxGuard",
"timeout": 10,
"searchable": 1,
"quickSearch": 1,
"changeable": 1,
"ext": "rfOX1voDIQhH8epBwpmIsuSluiuZl4+/lm1iJsy3HwNWMxpf9CY77fshI+kByoxy7DyPatUZ1jk0ty1p"
},
{
"key": "视界",
"name": "🍙视界┃多线",
"type": 3,
"api": "csp_AppSxGuard",
"timeout": 10,
"searchable": 1,
"quickSearch": 1,
"changeable": 1,
"ext": "rfOX1voDIQhH8epBwpmIsuSjpyyamoq83jM9L4r3AlwEM1kc4mV87aNKLeF/iNNL7ne1LNZlk3Bz8A=="
},
{
"key": "花花",
"name": "🥝花花┃多线",
"type": 3,
"api": "csp_AppSxGuard",
"timeout": 10,
"searchable": 1,
"quickSearch": 1,
"changeable": 1,
"ext": "rfOb1uAWbkRHp7hdxprG9un3+TbEn4q2l2kibom2DB9cJ0IB72Vu7apib64Gl9Ew6WDTJcIVzWsluDxqEaBqWIyM77GBXCPqCPmCyOr3RGfwjlsjchkArMHzTFT1VY9QUooz8m8BXA2mSa/whkG7SpZP100qUm6hMmTjm8YQQ51m6mxEE7hhEK1eNQ=="
},
{
"key": "播客",
"name": "🦊播客┃多线",
"type": 3,
"api": "csp_AppSxGuard",
"timeout": 10,
"searchable": 1,
"quickSearch": 1,
"changeable": 1,
"ext": "rfOb1uAWbkRHp7hdxprG9un3+SPC3Nbv1SI/b5LtAUhbYkFU/DFvsLBvd/oV3cA4uGiCZNFRz3ln6nh5Q+AgDZiM5KrCAiO7S7SVzv31EG78jVB4JEMNrMPzTgWqRZdMTdR1smBWWwru"
},
{
"key": "爱影",
"name": "👀爱影┃多线",
"type": 3,
"api": "csp_AppSxGuard",
"timeout": 10,
"searchable": 1,
"quickSearch": 1,
"changeable": 1,
"ext": "rfOb1uAWbkRHp7hdxprG9un3+TbK3c790iI/b5LtEhxNfAQK7DRvrLcqbf1Q0MQntXLZfMhK0Xsn6nwmF+shR8eZ76qXW3y9XKbblunmCSPux0YtJlscp9zpDmW7Ju4ufOtv+BcyBX7VWrSzyUWoVcxGhUtv"
},
{
"key": "猫猫",
"name": "🐟猫猫┃多线",
"type": 3,
"api": "csp_AppSxGuard",
"timeout": 10,
"searchable": 1,
"quickSearch": 1,
"changeable": 1,
"ext": "rfOb1uAWbkRHp7hdxprG9un3+TDC2t/rlTwlcMr+ChdbeV8Q9y9xsKxqfbtO0M05tGWcacFVm2c45jhyH6t1Rt6A6PjICGqxV+uN1uOqS2/x0Vp5J0Vfo8usQADpHg=="
},
{
"key": "奥特",
"name": "🏝奥特┃多线",
"type": 3,
"api": "csp_AueteGuard",
"timeout": 10,
"searchable": 1,
"quickSearch": 1,
"changeable": 1
},
{
"key": "荐片",
"name": "🐭荐片┃P2P",
"type": 3,
"api": "csp_JPJGuard",
"timeout": 10,
"playerType": 2,
"searchable": 1,
"quickSearch": 1,
"changeable": 0
},
{
"key": "新6V",
"name": "🧲新6V┃磁力",
"type": 3,
"api": "csp_SixVGuard",
"timeout": 10,
"searchable": 1,
"quickSearch": 1,
"changeable": 0,
"ext": "https://www.xb6v.com/"
},
{
"key": "咕咕",
"name": "🦉咕咕┃动漫",
"type": 3,
"api": "csp_AppSxGuard",
"timeout": 10,
"searchable": 1,
"quickSearch": 1,
"changeable": 1,
"ext": "rfOX1voDIQhH8epBwpmIsuS/sSCZxc/l0mxkeoroQVYOelMAvX1kuYptSLB36YxEjVawS9p37Cgo"
},
{
"key": "Dm84",
"name": "🚌巴士┃动漫",
"type": 3,
"api": "csp_Dm84Guard",
"timeout": 10,
"searchable": 1,
"quickSearch": 1,
"changeable": 1
},
{
"key": "Anime1",
"name": "🐾日本┃动漫",
"type": 3,
"api": "csp_Anime1Guard",
"timeout": 10,
"searchable": 1,
"quickSearch": 1,
"changeable": 1
},
{
"key": "88",
"name": "⚽八八┃看球",
"type": 3,
"api": "csp_Sir88Guard",
"timeout": 10,
"searchable": 0,
"changeable": 0,
"style": {
"type": "list"
}
},
{
"key": "超全",
"name": "⚽吃瓜┃看球",
"type": 3,
"api": "csp_LiveGzGuard",
"searchable": 0,
"quickSearch": 0,
"changeable": 0,
"style": {
"type": "list"
}
},
{
"key": "看球",
"name": "⚽手机┃看球",
"type": 3,
"api": "csp_KanqiuGuard",
"timeout": 10,
"searchable": 0,
"changeable": 0,
"style": {
"type": "list"
}
},
{
"key": "MTV",
"name": "🎶明星┃MV",
"type": 3,
"api": "csp_BiliGuard",
"style": {
"type": "rect",
"ratio": 1.597
},
"searchable": 0,
"quickSearch": 0,
"changeable": 0,
"ext": {
"json": "https://nos.netease.com/ysf/5af5fbe12a88b7c45aa1c21e6551826c.txt"
}
},
{
"key": "有声小说",
"name": "🎧有声┃小说",
"type": 3,
"api": "csp_Tingshu275Guard",
"style": {
"type": "rect",
"ratio": 1
},
"searchable": 0,
"quickSearch": 0,
"changeable": 0
},
{
"key": "Aid",
"name": "🚑急救┃教学",
"type": 3,
"api": "csp_FirstAidGuard",
"searchable": 0,
"quickSearch": 0,
"changeable": 0,
"style": {
"type": "rect",
"ratio": 3.8
}
},
{
"key": "虎牙js",
"name": "🐯虎牙┃直播",
"type": 3,
"api": "https://gh-proxy.com/https://raw.githubusercontent.com/fantaiying7/EXT/refs/heads/main/drpy2.min.js",
"ext": "https://gh-proxy.com/https://raw.githubusercontent.com/fantaiying7/EXT/refs/heads/main/虎牙.js",
"style": {
"type": "rect",
"ratio": 1.755
},
"timeout": 10,
"playerType": "2",
"searchable": 1,
"quickSearch": 0,
"changeable": 0
},
{
"key": "斗鱼js",
"name": "🐟斗鱼┃直播",
"type": 3,
"api": "https://git.yylx.win/https://raw.githubusercontent.com/fantaiying7/EXT/refs/heads/main/drpy2.min.js",
"ext": "https://git.yylx.win/https://raw.githubusercontent.com/fantaiying7/EXT/refs/heads/main/斗鱼直播.js",
"style": {
"type": "rect",
"ratio": 1.755
},
"timeout": 10,
"playerType": "2",
"searchable": 1,
"quickSearch": 0,
"changeable": 0
},
{
"key": "抠搜",
"name": "🍄抠抠┃搜搜",
"type": 3,
"api": "csp_KkSsGuard",
"searchable": 1,
"quickSearch": 1,
"changeable": 0,
"ext": {
"Cloud-drive": "tvfan/Cloud-drive.txt"
}
},
{
"key": "UC",
"name": "🌈优汐┃搜搜",
"type": 3,
"api": "csp_UuSsGuard",
"searchable": 1,
"quickSearch": 1,
"changeable": 0,
"ext": {
"Cloud-drive": "tvfan/Cloud-drive.txt"
}
},
{
"key": "米搜",
"name": "🦋米搜┃夸嘟",
"type": 3,
"api": "csp_MIPanSoGuard",
"searchable": 1,
"quickSearch": 1,
"changeable": 0,
"ext": {
"Cloud-drive": "tvfan/Cloud-drive.txt"
}
},
{
"key": "YpanSo",
"name": "🐟盘她┃夸父",
"type": 3,
"api": "csp_YpanSoGuard",
"searchable": 1,
"quickSearch": 1,
"changeable": 0,
"ext": {
"Cloud-drive": "tvfan/Cloud-drive.txt"
}
},
{
"key": "BpanSo",
"name": "🐞盘他┃嘟嘟",
"type": 3,
"api": "csp_BpanSoGuard",
"searchable": 1,
"quickSearch": 1,
"changeable": 0,
"ext": {
"Cloud-drive": "tvfan/Cloud-drive.txt"
}
},
{
"key": "push_agent",
"name": "🛴手机┃推送",
"type": 3,
"api": "csp_PushGuard",
"searchable": 0,
"quickSearch": 0,
"ext": {
"Cloud-drive": "tvfan/Cloud-drive.txt"
}
},
{
"key": "Bili",
"name": "🅱哔哔合集┃弹幕",
"type": 3,
"api": "csp_BiliGuard",
"style": {
"type": "rect",
"ratio": 1.597
},
"searchable": 1,
"quickSearch": 0,
"changeable": 0,
"ext": {
"json": "https://nos.netease.com/ysf/0075389dca9afadd4614e9713765ff17.txt"
}
},
{
"key": "Biliych",
"name": "🅱哔哔演唱会┃弹幕",
"type": 3,
"api": "csp_BiliGuard",
"style": {
"type": "rect",
"ratio": 1.597
},
"searchable": 0,
"quickSearch": 0,
"changeable": 0,
"ext": {
"json": "https://nos.netease.com/ysf/6496356286589c68f52c2f99c0c674c7.txt"
}
},
{
"key": "dr_兔小贝",
"name": "📚儿童┃启蒙",
"type": 3,
"api": "https://git.yylx.win/https://raw.githubusercontent.com/fantaiying7/EXT/refs/heads/main/drpy2.min.js",
"ext": "https://git.yylx.win/https://raw.githubusercontent.com/fantaiying7/EXT/refs/heads/main/%E5%85%94%E5%B0%8F%E8%B4%9D.js",
"style": {
"type": "rect",
"ratio": 1.597
},
"searchable": 0,
"quickSearch": 0,
"changeable": 0
},
{
"key": "少儿教育",
"name": "📚少儿┃教育",
"type": 3,
"api": "csp_BiliGuard",
"style": {
"type": "rect",
"ratio": 1.597
},
"searchable": 0,
"quickSearch": 0,
"changeable": 0,
"ext": {
"json": "https://nos.netease.com/ysf/89370c8ddf36b5e1beb4d71adb921bda.txt"
}
},
{
"key": "小学课堂",
"name": "📚小学┃课堂",
"type": 3,
"api": "csp_BiliGuard",
"style": {
"type": "rect",
"ratio": 1.597
},
"searchable": 0,
"quickSearch": 0,
"changeable": 0,
"ext": {
"json": "https://nos.netease.com/ysf/d7a21cf34ede56f5c686ecfba5fc7e3f.txt"
}
},
{
"key": "初中课堂",
"name": "📚初中┃课堂",
"type": 3,
"api": "csp_BiliGuard",
"style": {
"type": "rect",
"ratio": 1.597
},
"searchable": 0,
"quickSearch": 0,
"changeable": 0,
"ext": {
"json": "https://nos.netease.com/ysf/8f55d520f8d70056695740ef151744a7.txt"
}
},
{
"key": "高中教育",
"name": "📚高中┃课堂",
"type": 3,
"api": "csp_BiliGuard",
"style": {
"type": "rect",
"ratio": 1.597
},
"searchable": 0,
"quickSearch": 0,
"changeable": 0,
"ext": {
"json": "https://nos.netease.com/ysf/c66a4b5356141c49fd45ec51568017b4.txt"
}
},
{
"key": "fan",
"name": "导航 www.饭太硬.com",
"type": 3,
"api": "csp_XPathGuard",
"searchable": 1,
"quickSearch": 0,
"changeable": 0
},
{
"key": "cc",
"name": "请勿相信视频中广告",
"type": 3,
"api": "csp_XPathGuard",
"searchable": 1,
"quickSearch": 0,
"changeable": 0
}
],
"rules": [
{
"name": "cdn.ryplay",
"hosts": [
"cdn.ryplay"
],
"regex": [
"5.480000",
"#EXT-X-DISCONTINUITY\\r*\\n*#EXTINF:5.480000,[\\s\\S]*?#EXT-X-DISCONTINUITY",
"#EXT-X-DISCONTINUITY\\s*\\r?\\n#EXTINF:5.320000,\\s*\\r?\\n.+\\.ts\\s*\\r?\\n(?:#EXTINF:[\\d.]+,\\s*\\r?\\n.+\\.ts\\s*\\r?\\n)*?#EXTINF:3.360000,\\s*\\r?\\n.+\\.ts\\s*\\r?\\n#EXT-X-DISCONTINUITY",
"#EXT-X-DISCONTINUITY\\s*\\r?\\n#EXTINF:4.000000,\\s*\\r?\\n.+\\.ts\\s*\\r?\\n(?:#EXTINF:[\\d.]+,\\s*\\r?\\n.+\\.ts\\s*\\r?\\n)*?#EXTINF:0.560000,\\s*\\r?\\n.+\\.ts\\s*\\r?\\n#EXT-X-DISCONTINUITY",
"#EXTINF.*?\\s+.*?1o.*?\\.ts\\s+"
]
}
],
"logo": "https://bdcache1-f1.v3mh.com/image/25-12-16/6941612717007.gif",
"hosts": [
"img1.wsyzy.org=danmu.888484.xyz",
"fiizvfck.top=danmu.888484.xyz"
],
"lives": [
{
"name": "develop202",
"type": 0,
"url": "https://gh.927223.xyz/https://raw.githubusercontent.com/develop202/migu_video/refs/heads/main/interface.txt",
"playerType": 2,
"epg": "http://diyp5.112114.xyz/?ch={name}&date={date}",
"logo": "https://epg.112114.xyz/logo/{name}.png"
},
{
"name": "Kimentanm",
"type": 0,
"url": "https://gh.927223.xyz/https://raw.githubusercontent.com/Kimentanm/aptv/master/m3u/iptv.m3u",
"playerType": 2
},
{
"name": "范明明(需开启V6网络)",
"type": 0,
"url": "https://nos.netease.com/ysf/3d75a78a0fc7ede372c03598d6d10367.m3u",
"playerType": 2
},
{
"name": "虎牙一起看",
"type": 0,
"url": "https://sub.ottiptv.cc/huyayqk.m3u",
"playerType": 2,
"timeout": 10,
"ua": "okHttp/Mod-1.5.0.0"
},
{
"name": "斗鱼一起看",
"type": 0,
"url": "https://sub.ottiptv.cc/douyuyqk.m3u",
"playerType": 2,
"timeout": 10,
"ua": "okHttp/Mod-1.5.0.0"
},
{
"name": "B站直播",
"type": 0,
"url": "https://sub.ottiptv.cc/bililive.m3u",
"playerType": 2,
"timeout": 10,
"ua": "okHttp/Mod-1.5.0.0"
},
{
"name": "YY轮播",
"type": 0,
"url": "https://sub.ottiptv.cc/yylunbo.m3u",
"playerType": 2,
"timeout": 10,
"ua": "okHttp/Mod-1.5.0.0"
}
]
}
+17
View File
@@ -0,0 +1,17 @@
{
"source_url": "https://www.xn--sss604efuw.com/tv/",
"final_url": "https://www.xn--sss604efuw.com/tv/",
"user_agent": "okhttp/3.15",
"raw_size": 20711,
"raw_sha256": "b993e136f723dd1fa87b413a21e0ef24b505550325d93d87d9e9d6f4da54a0e7",
"decoded_size": 13724,
"decoded_sha256": "05b5451b0935ba2d6a89e5f681e94b0b4541103a8badf115cc90577cba52339c",
"json_sha256": "5e1e3aabfdc9f138c86883749a255b1367cc50b94244d70a5066a081214f302b",
"jpeg_eoi_offset": 2399,
"appended_size": 18310,
"base64_size": 18300,
"etag": "\"69fe04c3-50e7\"",
"last_modified": "Fri, 08 May 2026 15:44:03 GMT",
"cache_control": "max-age=300",
"content_type": "text/html"
}
+95
View File
@@ -0,0 +1,95 @@
#!/usr/bin/env python3
"""Extract, decode, and normalize a TVBox JSON configuration payload."""
from __future__ import annotations
import base64
import hashlib
import json
import os
import urllib.request
from pathlib import Path
def main() -> None:
source_url = os.environ["TVBOX_URL"]
user_agent = os.environ.get("TVBOX_UA", "okhttp/3.15")
output_json = Path(os.environ.get("OUT_JSON", "data/tvbox.json"))
output_meta = Path(os.environ.get("OUT_META", "data/tvbox.meta.json"))
request = urllib.request.Request(
source_url,
headers={
"User-Agent": user_agent,
"Accept": "*/*",
},
)
with urllib.request.urlopen(request, timeout=30) as response:
raw_payload = response.read()
final_url = response.geturl()
response_headers = dict(response.headers.items())
# The endpoint returns a JPEG-like payload to TVBox clients. The actual
# configuration is appended after the JPEG EOI marker.
jpeg_eoi_offset = raw_payload.find(b"\xff\xd9")
if jpeg_eoi_offset < 0:
raise SystemExit("JPEG EOI marker FF D9 was not found.")
appended_payload = raw_payload[jpeg_eoi_offset + 2 :]
# The appended section uses a short prefix followed by the "**" separator.
separator_offset = appended_payload.find(b"**")
if separator_offset < 0:
raise SystemExit('Payload separator "**" was not found after JPEG EOI.')
base64_payload = appended_payload[separator_offset + 2 :]
decoded_bytes = base64.b64decode(base64_payload, validate=True)
decoded_text = decoded_bytes.decode("utf-8-sig")
# The upstream configuration may contain JSONC-style full-line comments.
# This repository stores strict JSON for compatibility with standard tools.
strict_json_text = "\n".join(
line
for line in decoded_text.splitlines()
if not line.lstrip().startswith("//")
)
parsed_json = json.loads(strict_json_text)
output_json.parent.mkdir(parents=True, exist_ok=True)
output_meta.parent.mkdir(parents=True, exist_ok=True)
output_json.write_text(
json.dumps(parsed_json, ensure_ascii=False, indent=2, sort_keys=False) + "\n",
encoding="utf-8",
)
metadata = {
"source_url": source_url,
"final_url": final_url,
"user_agent": user_agent,
"raw_size": len(raw_payload),
"raw_sha256": hashlib.sha256(raw_payload).hexdigest(),
"decoded_size": len(decoded_bytes),
"decoded_sha256": hashlib.sha256(decoded_bytes).hexdigest(),
"json_sha256": hashlib.sha256(output_json.read_bytes()).hexdigest(),
"jpeg_eoi_offset": jpeg_eoi_offset,
"appended_size": len(appended_payload),
"base64_size": len(base64_payload),
"etag": response_headers.get("ETag"),
"last_modified": response_headers.get("Last-Modified"),
"cache_control": response_headers.get("Cache-Control"),
"content_type": response_headers.get("Content-Type"),
}
output_meta.write_text(
json.dumps(metadata, ensure_ascii=False, indent=2) + "\n",
encoding="utf-8",
)
print(json.dumps(metadata, ensure_ascii=False, indent=2))
if __name__ == "__main__":
main()