港澳台直播 v8.3.0 源码快照(含全部历史功能)

This commit is contained in:
Hermes Agent committed 2026-10-08 12:56:16 +08:00
commit 1d2449ec44
72 files changed
+7365

No files matched your search

+5
View File
@@ -0,0 +1,5 @@
build/
.gradle/
local.properties
*.apk
.idea/
+42
View File
@@ -0,0 +1,42 @@
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
}
android {
namespace = "com.iptv807.tv"
compileSdk = 35
defaultConfig {
applicationId = "com.iptv807.tv"
minSdk = 21
targetSdk = 35
versionCode = 830
versionName = "8.3.0"
}
buildTypes {
release {
isMinifyEnabled = false
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = "17"
}
}
dependencies {
implementation("androidx.core:core-ktx:1.12.0")
implementation("androidx.appcompat:appcompat:1.6.1")
implementation("androidx.webkit:webkit:1.9.0")
// ExoPlayer (Media3) 播放 FLV/HLS 直播流
implementation("androidx.media3:media3-exoplayer:1.3.1")
implementation("androidx.media3:media3-exoplayer-hls:1.3.1")
implementation("androidx.media3:media3-ui:1.3.1")
implementation("androidx.media3:media3-datasource:1.3.1")
}
View File
Whitespace-only changes.
+45
View File
@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-feature android:name="android.software.leanback" android:required="false" />
<uses-feature android:name="android.hardware.touchscreen" android:required="false" />
<application android:name=".IptvApp"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:networkSecurityConfig="@xml/network_security_config"
android:usesCleartextTraffic="true"
android:theme="@style/Theme.IPTVTV"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:banner="@drawable/banner"
android:exported="true"
android:screenOrientation="landscape"
android:configChanges="orientation|screenSize|keyboardHidden"
android:theme="@style/Theme.IPTVTV">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".LoginActivity" android:exported="false" />
<activity android:name=".TrialExpiredActivity" android:exported="false" />
<activity android:name=".ShopActivity" android:exported="false" />
<receiver android:name=".BootReceiver" android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
<activity android:name=".SettingsActivity" android:exported="false" />
<activity android:name=".AiSettingsActivity" android:exported="false" />
</application>
</manifest>
@@ -0,0 +1,201 @@
package com.iptv807.tv
import android.annotation.SuppressLint
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.graphics.Color
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.view.Gravity
import android.view.KeyEvent
import android.view.ViewGroup
import android.widget.Button
import android.widget.EditText
import android.widget.LinearLayout
import android.widget.ScrollView
import android.widget.TextView
/** AI 助手 —— 超保守实现:静态布局,异常完全不崩 */
class AiChatActivity : Activity() {
private lateinit var scroll: ScrollView
private lateinit var chatBox: LinearLayout
private lateinit var input: EditText
private lateinit var statusText: TextView
private val handler = Handler(Looper.getMainLooper())
private var channelsText = ""
@SuppressLint("SetTextI18n")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
try { init() } catch (e: Exception) {
val r = LinearLayout(this)
r.orientation = LinearLayout.VERTICAL
val tv = TextView(this)
tv.text = "AI 加载失败(已恢复)。回按下返回重启"
tv.setTextColor(Color.RED); tv.textSize = 16f
r.addView(tv)
setContentView(r)
}
}
@SuppressLint("SetTextI18n")
private fun init() {
val root = LinearLayout(this)
root.orientation = LinearLayout.VERTICAL
root.setBackgroundColor(Color.parseColor("#12141A"))
root.setPadding(48, 40, 48, 40)
setContentView(root)
val title = TextView(this)
title.text = "🤖 AI 助手"
title.textSize = 24f; title.setTextColor(Color.WHITE); title.gravity = Gravity.CENTER
root.addView(title, LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT).apply { bottomMargin = 20 })
// 检测配置
if (!AiClient.isConfigured(this)) {
val hint = TextView(this)
hint.text = "尚未配置 AI。\n请先到 设置 → AI 配置 填写 API 地址 / Key / 模型名。"
hint.textSize = 15f; hint.setTextColor(Color.LTGRAY); hint.gravity = Gravity.CENTER
root.addView(hint, LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT).apply { topMargin = 200 })
val go = Button(this)
go.text = "去配置 AI"; go.textSize = 16f
go.setOnClickListener {
try { startActivity(Intent(this, AiSettingsActivity::class.java)) } catch (e: Exception) { }
}
root.addView(go, LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT).apply { topMargin = 40 })
return
}
// 拼频道清单
try {
channelsText = ChannelData.load().take(160).joinToString("\n") { c -> "${c.num}. ${c.name} [${c.group}]" }
} catch (e: Exception) { channelsText = "" }
scroll = ScrollView(this)
scroll.setBackgroundColor(Color.parseColor("#171A20"))
chatBox = LinearLayout(this)
chatBox.orientation = LinearLayout.VERTICAL
chatBox.setPadding(24, 24, 24, 24)
scroll.addView(chatBox)
root.addView(scroll, LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, 0, 1f
).apply { bottomMargin = 16 })
// 快捷键按钮(功能入口)
val quickActions = listOf(
"📺 当前在播什么" to "@now",
"🎯 推荐值得看" to "@rec",
"📊 我的收视报告" to "@report",
)
val row = LinearLayout(this)
row.orientation = LinearLayout.HORIZONTAL
for ((label, action) in quickActions) {
val b = Button(this)
b.text = label.take(3); b.textSize = 11f
b.setBackgroundColor(Color.parseColor("#2A2F3A"))
b.setOnClickListener {
try { ask(action, label) } catch (e: Exception) { statusText.text = "动作失败" }
}
row.addView(b, LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f).apply { rightMargin = 8 })
}
root.addView(row, LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT
).apply { bottomMargin = 16 })
input = EditText(this)
input.hint = "问点啥…"
input.textSize = 16f
input.setTextColor(Color.WHITE); input.setHintTextColor(Color.GRAY)
input.setBackgroundColor(Color.parseColor("#23262F"))
input.setPadding(24, 20, 24, 20)
input.setSingleLine(true)
root.addView(input, LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT
).apply { bottomMargin = 16 })
statusText = TextView(this)
statusText.textSize = 13f; statusText.setTextColor(Color.LTGRAY); statusText.gravity = Gravity.CENTER
root.addView(statusText, LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT
).apply { bottomMargin = 12 })
val send = Button(this)
send.text = "发送"; send.textSize = 17f
send.setOnClickListener {
try {
val q = input.text?.toString()?.trim().orEmpty()
if (q.isNotEmpty()) ask(q, null)
input.setText("")
} catch (e: Exception) { statusText.text = "发送失败" }
}
root.addView(send, LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT))
addBubble("AI 已就绪。可以直接说话,或用上面的快捷键。", Color.parseColor("#2A3038"))
}
private fun addBubble(text: String, bg: Int) {
try {
val v = TextView(this)
v.text = text
v.textSize = 15f; v.setTextColor(Color.WHITE)
v.setBackgroundColor(bg)
v.setPadding(20, 16, 20, 16)
chatBox.addView(v, LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT
).apply { bottomMargin = 12 })
scroll.post { scroll.fullScroll(android.view.View.FOCUS_DOWN) }
} catch (e: Exception) { }
}
private fun ask(action: String, label: String?) {
val q = when (action) {
"@now" -> "当前时段值得看的频道有哪些节目"
"@rec" -> "推荐我现在时期值得看的3-5个频道"
"@report" -> "请基于我的收视记录给报告"
else -> action
}
addBubble(label ?: q, Color.parseColor("#1B3A5C"))
statusText.text = "思考中…"
val ctx = this
Thread {
var reply = try {
val sys = "你是Android TV IPTV应用的AI助手。频道清单(编号. 名称 [组]):\n$channelsText\n简洁回复,直接给频道编号和名字。"
AiClient.chat(ctx, sys, q, 600)
} catch (e: Exception) { "AI 调用失败: ${e.message}" }
handler.post {
try { statusText.text = "" } catch (e: Exception) { }
try {
// SWITCH:台号 切台
if (reply.contains("SWITCH:")) {
val target = reply.substringAfter("SWITCH:").substringBefore("\n").trim()
val found = ChannelData.load().firstOrNull { ch -> ch.name.contains(target, true) }
if (found != null) {
MainActivity.playRequested = found.num
addBubble("已切到:${found.num} ${found.name}", Color.parseColor("#2A4030"))
handler.postDelayed({ try { finish() } catch (e: Exception) { } }, 800)
return@post
}
}
} catch (e: Exception) { }
addBubble(reply, Color.parseColor("#2A3038"))
}
}.start()
}
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
if (keyCode == KeyEvent.KEYCODE_BACK) {
try { finish() } catch (e: Exception) { }
return true
}
return super.onKeyDown(keyCode, event)
}
}
private fun String?.orFallback(): String = this ?: ""
@@ -0,0 +1,62 @@
package com.iptv807.tv
import android.content.Context
import org.json.JSONArray
import org.json.JSONObject
import java.io.BufferedReader
import java.io.InputStreamReader
import java.net.HttpURLConnection
import java.net.URL
import java.nio.charset.StandardCharsets
/**
* 通用 AI 客户端 —— 兼容 OpenAI /api/chat/completions 格式
* 用户在设置里填: BASE_URL / API_KEY / MODEL(任何一个 OpenAI 兼容服务都可用,
* 如 new-api 一站式、DeepSeek、通义、Kimi、OpenAI、本地Ollama等)
*/
object AiClient {
private const val PREF = "iptv_ai"
fun baseUrl(ctx: Context) = ctx.getSharedPreferences(PREF, Context.MODE_PRIVATE).getString("base_url", "https://ai.88531.cn/v1") ?: "https://ai.88531.cn/v1"
fun apiKey(ctx: Context) = ctx.getSharedPreferences(PREF, Context.MODE_PRIVATE).getString("api_key", "") ?: ""
fun model(ctx: Context) = ctx.getSharedPreferences(PREF, Context.MODE_PRIVATE).getString("model", "deepseek-v4.1-flash") ?: "deepseek-v4.1-flash"
fun isConfigured(ctx: Context): Boolean =
baseUrl(ctx).isNotEmpty() && apiKey(ctx).isNotEmpty() && model(ctx).isNotEmpty()
fun save(ctx: Context, baseUrl: String, apiKey: String, model: String) {
ctx.getSharedPreferences(PREF, Context.MODE_PRIVATE).edit()
.putString("base_url", baseUrl.trim().removeSuffix("/"))
.putString("api_key", apiKey.trim())
.putString("model", model.trim()).apply()
}
/** chat 请求(同步,调用方自行放子线程)返回回复文本,失败抛异常 */
fun chat(ctx: Context, systemPrompt: String, userPrompt: String, maxTokens: Int = 500): String {
val url = URL(baseUrl(ctx) + "/chat/completions")
val body = JSONObject().apply {
put("model", model(ctx))
put("max_tokens", maxTokens)
put("temperature", 0.6)
put("messages", JSONArray()
.put(JSONObject().put("role", "system").put("content", systemPrompt))
.put(JSONObject().put("role", "user").put("content", userPrompt)))
}
val conn = (url.openConnection() as HttpURLConnection).apply {
requestMethod = "POST"
connectTimeout = 10000
readTimeout = 60000
setRequestProperty("Content-Type", "application/json")
setRequestProperty("Authorization", "Bearer ${apiKey(ctx)}")
doOutput = true
outputStream.use { it.write(body.toString().toByteArray(StandardCharsets.UTF_8)) }
}
val code = conn.responseCode
val text = (if (code in 200..299) conn.inputStream else conn.errorStream)
?.let { BufferedReader(InputStreamReader(it, StandardCharsets.UTF_8)).readText() } ?: ""
conn.disconnect()
if (code !in 200..299) throw RuntimeException("HTTP $code: ${text.take(180)}")
val j = JSONObject(text)
return j.optJSONArray("choices")?.optJSONObject(0)?.optJSONObject("message")?.optString("content") ?: ""
}
}
@@ -0,0 +1,99 @@
package com.iptv807.tv
import android.annotation.SuppressLint
import android.app.Activity
import android.graphics.Color
import android.os.Bundle
import android.view.ViewGroup
import android.widget.Button
import android.widget.EditText
import android.widget.LinearLayout
import android.widget.TextView
/** 用户自定义 AI 服务配置(OpenAI兼容格式) */
class AiSettingsActivity : Activity() {
private lateinit var baseEdit: EditText
private lateinit var keyEdit: EditText
private lateinit var modelEdit: EditText
private lateinit var statusText: TextView
@SuppressLint("SetTextI18n")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val root = LinearLayout(this).apply {
orientation = LinearLayout.VERTICAL
setBackgroundColor(Color.parseColor("#12141A"))
setPadding(80, 56, 80, 56)
}
setContentView(root)
root.addView(TextView(this).apply {
text = "AI 功能配置"
textSize = 26f; setTextColor(Color.WHITE); gravity = android.view.Gravity.CENTER
setPadding(0, 0, 0, 12)
})
root.addView(TextView(this).apply {
text = "兼容 OpenAI 格式。支持:DeepSeek、通义、Kimi、OpenAI、本地Ollama、NewAPI 中转等。填你的 key 即可。"
textSize = 13f; setTextColor(Color.LTGRAY); gravity = android.view.Gravity.CENTER
setPadding(0, 0, 0, 32)
})
fun mkEdit(hint: String, v: String = "") = EditText(this).apply {
this.hint = hint
setText(v)
setSingleLine(true)
textSize = 15f
setTextColor(Color.WHITE); setHintTextColor(Color.GRAY)
setBackgroundColor(Color.parseColor("#23262F"))
setPadding(24, 20, 24, 20)
}
baseEdit = mkEdit("https://ai.88531.cn/v1(可改自己的)", AiClient.baseUrl(this))
keyEdit = mkEdit("API Key(sk-…)", AiClient.apiKey(this))
modelEdit = mkEdit("deepseek-v4.1-flash(可改)", AiClient.model(this))
listOf(baseEdit, keyEdit, modelEdit).forEach {
root.addView(it, LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT
).apply { bottomMargin = 24 })
}
val saveBtn = Button(this).apply { text = "保存"; textSize = 17f }
val testBtn = Button(this).apply { text = "测试连接"; textSize = 17f }
val row = LinearLayout(this).apply { orientation = LinearLayout.HORIZONTAL }
row.addView(saveBtn, LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f).apply { rightMargin = 16 })
row.addView(testBtn, LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f))
root.addView(row)
statusText = TextView(this).apply {
textSize = 14f; setTextColor(Color.LTGRAY); gravity = android.view.Gravity.CENTER
setPadding(0, 32, 0, 0)
text = "AI 功能:语音搜台 · 节目解读 · 故障诊断 · 智能推荐 · 找片 · 收视报告"
}
root.addView(statusText)
saveBtn.setOnClickListener {
if (baseEdit.text.trim().isEmpty() || keyEdit.text.trim().isEmpty() || modelEdit.text.trim().isEmpty()) {
statusText.text = "三项都要填"; return@setOnClickListener
}
AiClient.save(this, baseEdit.text.toString(), keyEdit.text.toString(), modelEdit.text.toString())
statusText.setTextColor(Color.parseColor("#4CAF50"))
statusText.text = "✓ 已保存"
}
testBtn.setOnClickListener {
if (!AiClient.isConfigured(this)) {
statusText.setTextColor(Color.RED); statusText.text = "先填写并保存三项"; return@setOnClickListener
}
statusText.setTextColor(Color.LTGRAY)
statusText.text = "测试中…"
Thread {
try {
val r = AiClient.chat(this, "你是测试助手", "回复:OK")
runOnUiThread { statusText.setTextColor(Color.parseColor("#4CAF50")); statusText.text = "✓ 连通:$r" }
} catch (e: Exception) {
runOnUiThread { statusText.setTextColor(Color.RED); statusText.text = "✗ ${e.message}" }
}
}.start()
}
}
}
@@ -0,0 +1,16 @@
package com.iptv807.tv
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
/** 开机自动启动(用户在设置里打开开关才启用) */
class BootReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (intent.action == Intent.ACTION_BOOT_COMPLETED) {
val i = Intent(context, LoginActivity::class.java)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
context.startActivity(i)
}
}
}
@@ -0,0 +1,38 @@
package com.iptv807.tv
import org.json.JSONObject
import java.io.BufferedReader
import java.io.InputStreamReader
import java.net.HttpURLConnection
import java.net.URL
import java.nio.charset.StandardCharsets
/** CardAuth 公开API的轻量封装 */
object CardAuthApi {
private const val BASE = "http://app.88531.cn:8881"
private const val API_KEY = "ak_563f4768d5d411d381e58bc3f76ba447"
fun get(path: String): JSONObject = request("GET", path, null)
fun post(path: String, body: JSONObject): JSONObject = request("POST", path, body.toString())
private fun request(method: String, path: String, body: String?): JSONObject {
val conn = (URL(BASE + path).openConnection() as HttpURLConnection).apply {
requestMethod = method
connectTimeout = 8000; readTimeout = 10000
setRequestProperty("Content-Type", "application/json")
setRequestProperty("X-API-Key", API_KEY)
if (body != null && method == "POST") {
doOutput = true
outputStream.use { it.write(body.toByteArray(StandardCharsets.UTF_8)) }
}
}
val code = conn.responseCode
val text = (if (code in 200..299) conn.inputStream else conn.errorStream)
?.let { BufferedReader(InputStreamReader(it, StandardCharsets.UTF_8)).readText() } ?: ""
conn.disconnect()
if (text.trimStart().startsWith("<")) {
throw RuntimeException("服务端返回了HTML(路径错误/网关拦截),HTTP $code")
}
return JSONObject(text)
}
}
@@ -0,0 +1,75 @@
package com.iptv807.tv
/**
* 港澳台专项清单(55台)—— 3大子分组(香港 / 澳门 / 台湾)
* 存 tid+id,播放Url每次实时解密(token 每次都换)
*/
object ChannelData {
data class Channel(val num: Int, val name: String, val group: String, val tid: String, val id: String)
data class Group(val name: String, val startNum: Int, val count: Int)
fun load(): List<Channel> = listOf(
Channel(1, "HOY TV", "🌏 香港", "tv", "43"),
Channel(2, "HOY国际财经台", "🌏 香港", "tv", "41"),
Channel(3, "HOY资讯台", "🌏 香港", "tv", "42"),
Channel(4, "NOW新闻台", "🌏 香港", "tv", "36"),
Channel(5, "NOW财经台", "🌏 香港", "tv", "37"),
Channel(6, "TVB Plus", "🌏 香港", "tv", "26"),
Channel(7, "TVB无线新闻台", "🌏 香港", "tv", "25"),
Channel(8, "TVB明珠台", "🌏 香港", "tv", "27"),
Channel(9, "TVB翡翠台", "🌏 香港", "tv", "24"),
Channel(10, "ViuTV", "🌏 香港", "tv", "38"),
Channel(11, "ViuTV6", "🌏 香港", "tv", "39"),
Channel(12, "凤凰卫视中文台", "🌏 香港", "tv", "20"),
Channel(13, "凤凰卫视资讯台", "🌏 香港", "tv", "21"),
Channel(14, "凤凰卫视香港台", "🌏 香港", "tv", "22"),
Channel(15, "港台电视31", "🌏 香港", "tv", "45"),
Channel(16, "港台电视32", "🌏 香港", "gt", "27"),
Channel(17, "香港卫视HKS", "🌏 香港", "gt", "30"),
Channel(18, "香港有线18台", "🌏 香港", "gt", "12"),
Channel(19, "香港有线18台(备)", "🌏 香港", "gt", "13"),
Channel(20, "香港有线新闻台", "🌏 香港", "tv", "29"),
Channel(21, "澳視澳門", "🌏 澳门", "gt", "38"),
Channel(22, "澳视综艺", "🌏 澳门", "gt", "40"),
Channel(23, "澳视资讯", "🌏 澳门", "gt", "37"),
Channel(24, "澳門Macau", "🌏 澳门", "gt", "39"),
Channel(25, "澳門卫视", "🌏 澳门", "gt", "36"),
Channel(26, "澳门莲花", "🌏 澳门", "gt", "35"),
Channel(27, "DAZN 1", "🌏 台湾", "tv", "80"),
Channel(28, "DAZN 2", "🌏 台湾", "tv", "81"),
Channel(29, "ELEVEN 1(DAZN 1)", "🌏 台湾", "ty", "24"),
Channel(30, "ELEVEN 2(DAZN 2)", "🌏 台湾", "ty", "25"),
Channel(31, "TVBS HD", "🌏 台湾", "tv", "65"),
Channel(32, "TVBS新闻台", "🌏 台湾", "tv", "63"),
Channel(33, "三立新闻台", "🌏 台湾", "tv", "54"),
Channel(34, "东森新闻台", "🌏 台湾", "tv", "50"),
Channel(35, "东森财经新闻台", "🌏 台湾", "gt", "45"),
Channel(36, "东森超视", "🌏 台湾", "tv", "98"),
Channel(37, "中天新闻台", "🌏 台湾", "tv", "49"),
Channel(38, "中视HD", "🌏 台湾", "tv", "68"),
Channel(39, "中视新闻台", "🌏 台湾", "tv", "52"),
Channel(40, "华视HD", "🌏 台湾", "tv", "71"),
Channel(41, "华视新闻台", "🌏 台湾", "tv", "57"),
Channel(42, "台视HD", "🌏 台湾", "tv", "69"),
Channel(43, "台视新闻台", "🌏 台湾", "tv", "53"),
Channel(44, "寰宇新闻台", "🌏 台湾", "tv", "59"),
Channel(45, "寰宇新闻台湾台", "🌏 台湾", "tv", "60"),
Channel(46, "民视HD", "🌏 台湾", "tv", "70"),
Channel(47, "民视新闻台", "🌏 台湾", "tv", "56"),
Channel(48, "爱尔达体育1台", "🌏 台湾", "tv", "83"),
Channel(49, "爱尔达体育2台", "🌏 台湾", "tv", "84"),
Channel(50, "緯來體育台", "🌏 台湾", "tv", "77"),
Channel(51, "纬来日本台", "🌏 台湾", "tv", "78"),
Channel(52, "纬来育乐台", "🌏 台湾", "tv", "79"),
Channel(53, "镜电视新闻台", "🌏 台湾", "tv", "58"),
Channel(54, "非凡新闻", "🌏 台湾", "tv", "62"),
Channel(55, "华丽翡翠台", "🌏 其他", "tv", "28")
)
fun groups(): List<Group> = listOf(
Group("🌏 香港", 1, 20),
Group("🌏 澳门", 21, 6),
Group("🌏 台湾", 27, 28),
Group("🌏 其他", 55, 1)
)
}
@@ -0,0 +1,36 @@
package com.iptv807.tv
import android.content.Context
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
/** 收视历史(供 AI 推荐/报告用) */
object HistoryRecorder {
private fun prefs(ctx: Context) = ctx.getSharedPreferences("iptv_history", Context.MODE_PRIVATE)
private val fmt = SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.CHINA)
fun log(ctx: Context, chanNum: Int, name: String) {
val now = fmt.format(Date())
val p = prefs(ctx)
var count = p.getInt("count_${chanNum}", 0)
count++
p.edit().putInt("count_${chanNum}", count)
.putString("last_" + chanNum, "$now $name").apply()
// 只保留最近 90 days(清理老数据)
if (p.all.size > 200) {
val sorted = p.all.keys.sorted()
p.edit().remove(sorted.take(50).toString()).apply()
}
}
fun topChannels(ctx: Context, n: Int = 5): List<Pair<Int, String>> {
val p = prefs(ctx)
return p.all.entries
.filter { it.key.startsWith("count_") }
.sortedByDescending { it.value as Int }
.take(n)
.map { it.key.removePrefix("count_").toInt() }
.mapNotNull { num -> ChannelData.load().find { it.num == num }?.let { num to it.name } }
}
}
@@ -0,0 +1,19 @@
package com.iptv807.tv
import android.app.Application
/** 应用级 crash 防护:把异常写标记后照常崩溃(不吞什),下次启动 reset license 缓存 */
class IptvApp : Application() {
override fun onCreate() {
super.onCreate()
val default = Thread.getDefaultUncaughtExceptionHandler()
Thread.setDefaultUncaughtExceptionHandler { t, e ->
try {
getSharedPreferences("iptv_crash", MODE_PRIVATE)
.edit().putBoolean("crashed", true)
.putString("msg", "${e.javaClass.simpleName}: ${e.message?.take(200)}").commit()
} catch (ex: Exception) { }
default?.uncaughtException(t, e)
}
}
}
@@ -0,0 +1,116 @@
package com.iptv807.tv
import android.content.Context
import android.os.Build
import org.json.JSONObject
import java.io.BufferedReader
import java.io.InputStreamReader
import java.net.HttpURLConnection
import java.net.URL
import java.nio.charset.StandardCharsets
import java.security.MessageDigest
/**
* CardAuth 授权客户端(app.88531.cn:8881)
*
* 协议(已实测):
* POST /api/public/verify
* Header: X-API-Key: ak_...(项目API密钥)
* Body: {"card_key":"...","machine_id":"TV-XXXX","device_info":"AndroidTV"}
* 未用卡 → 自动激活绑定;已绑 → 校验machine_id一致 + 未过期
* 响应 data: {valid, type, duration_days, expire_time, is_permanent, remaining_days}
* 免费用户:无卡/无效卡 → 每天最多1小时(本地计时 + 服务器日期同步)
*/
object LicenseClient {
private const val BASE = "http://app.88531.cn:8881"
private const val API_KEY = "ak_563f4768d5d411d381e58bc3f76ba447"
data class Result(
val ok: Boolean, // 通过授权(VIP)
val message: String,
val expireTime: String? = null,
val isPermanent: Boolean = false,
val remainingDays: Int? = null,
val trial: Boolean = false // 走免费1小时
)
/** 卡密验证/激活 */
fun verify(cardKey: String, machineId: String): Result {
return try {
val body = JSONObject().apply {
put("card_key", cardKey)
put("machine_id", machineId)
put("device_info", "AndroidTV ${Build.MODEL}")
}
val r = post("/api/public/verify", body)
val data = r.optJSONObject("data") ?: JSONObject()
when {
r.optInt("code") == 200 && data.optBoolean("valid") ->
Result(true, data.optString("message", "授权有效"),
data.optString("expire_time", ""), data.optBoolean("is_permanent"),
if (data.has("remaining_days") && !data.isNull("remaining_days")) data.optInt("remaining_days") else null)
else -> Result(false, data.optString("message", r.optString("message", "授权无效")).ifEmpty { "卡密无效" })
}
} catch (e: Exception) {
Result(false, "网络错误: ${e.message ?: e.javaClass.simpleName}")
}
}
private fun post(path: String, body: JSONObject): JSONObject {
val url = URL(BASE + path)
val conn = (url.openConnection() as HttpURLConnection).apply {
requestMethod = "POST"
connectTimeout = 8000
readTimeout = 10000
setRequestProperty("Content-Type", "application/json")
setRequestProperty("X-API-Key", API_KEY)
doOutput = true
outputStream.use { it.write(body.toString().toByteArray(StandardCharsets.UTF_8)) }
}
val code = conn.responseCode
val stream = if (code in 200..299) conn.inputStream else conn.errorStream
val text = stream?.let { BufferedReader(InputStreamReader(it, StandardCharsets.UTF_8)).readText() } ?: ""
conn.disconnect()
return JSONObject(text)
}
/** 稳定机器码(安装期持久化+硬件融合) */
fun machineCode(ctx: Context): String {
val prefs = ctx.getSharedPreferences("iptv_license", Context.MODE_PRIVATE)
prefs.getString("machine_code", null)?.let { return it }
val raw = listOf(Build.BOARD, Build.BRAND, Build.DEVICE, Build.MODEL, Build.PRODUCT).joinToString("|")
val md = MessageDigest.getInstance("SHA-256")
val code = "TV-" + md.digest(raw.toByteArray(StandardCharsets.UTF_8))
.joinToString("") { "%02X".format(it) }.take(12)
prefs.edit().putString("machine_code", code).apply()
return code
}
// ===== 免费时长控制(每天1小时)=====
const val FREE_DAILY_MS = 60 * 60 * 1000L
/** 今天已用免费毫秒数 */
fun freeUsedTodayMs(ctx: Context): Long {
val prefs = ctx.getSharedPreferences("iptv_license", Context.MODE_PRIVATE)
val today = todayKey()
if (prefs.getString("free_date", "") != today) return 0
return prefs.getLong("free_used_ms", 0)
}
fun addFreeUsedMs(ctx: Context, delta: Long) {
val prefs = ctx.getSharedPreferences("iptv_license", Context.MODE_PRIVATE)
val today = todayKey()
val used = if (prefs.getString("free_date", "") == today) prefs.getLong("free_used_ms", 0) else 0
prefs.edit().putString("free_date", today).putLong("free_used_ms", used + delta).apply()
}
fun freeRemainingMs(ctx: Context): Long =
(FREE_DAILY_MS - freeUsedTodayMs(ctx)).coerceAtLeast(0)
/** 日期键北京时间 */
private fun todayKey(): String {
val f = java.text.SimpleDateFormat("yyyyMMdd", java.util.Locale.CHINA)
f.timeZone = java.util.TimeZone.getTimeZone("GMT+8")
return f.format(java.util.Date())
}
}
@@ -0,0 +1,143 @@
package com.iptv807.tv
import android.annotation.SuppressLint
import android.app.Activity
import android.content.Intent
import android.graphics.Color
import android.os.Bundle
import android.view.KeyEvent
import android.view.ViewGroup
import android.view.inputmethod.EditorInfo
import android.widget.Button
import android.widget.EditText
import android.widget.LinearLayout
import android.widget.TextView
import android.widget.Toast
/**
* CardAuth 授权登录页 —— 应用第一屏:
* 有效卡本地缓存 → 直接进播放;否则显示卡密输入
* CardAuth 无用户体系:一卡一机绑定,验证即在服务端注册到期时间
*/
class LoginActivity : Activity() {
private lateinit var cardEdit: EditText
private lateinit var statusText: TextView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
try {
// crashGuard: 若上次有崩溃标记,先清before检查
getSharedPreferences("iptv_crash", MODE_PRIVATE).let {
if (it.getBoolean("crashed", false)) {
it.edit().putBoolean("crashed", false).apply()
// 清理license cache以防被污染
getSharedPreferences("iptv_license", MODE_PRIVATE).edit().remove("token").remove("card_key").apply()
}
}
} catch (e: Exception) { }
val prefs = getSharedPreferences("iptv_license", MODE_PRIVATE)
val machine = LicenseClient.machineCode(this)
// 本地已授权缓存 → 快速放行(在线重验在主界面做,避免卡启动)
if (prefs.getBoolean("is_vip", false)) {
startMain(); return
}
val root = LinearLayout(this).apply {
orientation = LinearLayout.VERTICAL
gravity = android.view.Gravity.CENTER
setBackgroundColor(Color.parseColor("#12141A"))
setPadding(96, 64, 96, 64)
}
setContentView(root)
root.addView(TextView(this).apply {
text = "港澳台直播"
textSize = 30f; setTextColor(Color.WHITE)
gravity = android.view.Gravity.CENTER; setPadding(0, 0, 0, 16)
})
root.addView(TextView(this).apply {
text = "免费用户每天可看 1 小时 · 输入卡密解锁全天观看"
textSize = 15f; setTextColor(Color.LTGRAY)
gravity = android.view.Gravity.CENTER; setPadding(0, 0, 0, 48)
})
cardEdit = EditText(this).apply {
hint = "卡密(如 8914-XXXX-XXXX)"
setSingleLine(true)
textSize = 18f
setTextColor(Color.WHITE); setHintTextColor(Color.GRAY)
setBackgroundColor(Color.parseColor("#23262F"))
setPadding(24, 24, 24, 24)
imeOptions = EditorInfo.IME_ACTION_DONE
}
root.addView(cardEdit, LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT).apply { bottomMargin = 32 })
val btn = Button(this).apply {
text = "激活 / 验证"; textSize = 19f
setOnClickListener { doVerify() }
}
root.addView(btn, LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT))
statusText = TextView(this).apply {
textSize = 14f; setTextColor(Color.LTGRAY)
gravity = android.view.Gravity.CENTER; setPadding(0, 40, 0, 0)
text = "已有卡密可直接激活;或购买套餐自动绑定本机"
}
root.addView(statusText)
val shopBtn = Button(this).apply {
text = "购买套餐(支付宝/微信)"; textSize = 15f
setBackgroundColor(Color.parseColor("#1B6EF3"))
setOnClickListener { startActivity(android.content.Intent(this@LoginActivity, ShopActivity::class.java)) }
}
root.addView(shopBtn, LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT).apply { topMargin = 24 })
val freeBtn = Button(this).apply {
text = "先看一会(免费)"; textSize = 14f
setBackgroundColor(Color.parseColor("#2A3344"))
setOnClickListener {
getSharedPreferences("iptv_license", MODE_PRIVATE).edit().putBoolean("is_vip", false).apply()
startMain()
}
}
root.addView(freeBtn, LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT).apply { topMargin = 24 })
cardEdit.setOnEditorActionListener { _,_,_ -> doVerify(); true }
}
private fun doVerify() {
val card = cardEdit.text.toString().trim()
if (card.isEmpty()) { statusText.text = "请输入卡密"; return }
statusText.text = "验证中…"
val machine = LicenseClient.machineCode(this)
Thread {
val r = LicenseClient.verify(card, machine)
runOnUiThread {
if (r.ok) {
getSharedPreferences("iptv_license", MODE_PRIVATE).edit()
.putBoolean("is_vip", true)
.putString("card_key", card)
.putString("vip_expire", r.expireTime)
.putBoolean("is_permanent", r.isPermanent)
.apply()
Toast.makeText(this, "✓ ${r.message}${if (!r.isPermanent && r.expireTime != null) "\n到期: ${r.expireTime}" else ""}", Toast.LENGTH_LONG).show()
startMain()
} else {
statusText.text = r.message
}
}
}.start()
}
private fun startMain() {
startActivity(Intent(this, MainActivity::class.java))
finish()
}
}
@@ -0,0 +1,742 @@
package com.iptv807.tv
import android.annotation.SuppressLint
import android.app.Activity
import android.content.Intent
import android.graphics.Color
import android.net.Uri
import android.os.Bundle
import android.os.CountDownTimer
import android.os.Handler
import android.os.Looper
import android.view.Gravity
import android.view.KeyEvent
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.widget.AbsListView
import android.widget.AdapterView
import android.widget.ArrayAdapter
import android.widget.FrameLayout
import android.widget.LinearLayout
import android.widget.ListView
import android.widget.TextView
import android.widget.Toast
import androidx.media3.common.MediaItem
import androidx.media3.common.MimeTypes
import androidx.media3.common.Player
import androidx.media3.datasource.DefaultDataSource
import androidx.media3.datasource.DefaultHttpDataSource
import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory
import androidx.media3.ui.PlayerView
/**
* 港澳台直播 v7.0 —— 纯原生播放(无网页兜底)
*
* · App 本地解密获取新鲜播放源 → ExoPlayer 直接播放(带 Referer/302)
* (纯原生播放,无网页兜底)
*/
class MainActivity : Activity() {
private val channels: List<ChannelData.Channel> = ChannelData.load()
private val groups: List<ChannelData.Group> = ChannelData.groups()
private val favGroup = ChannelData.Group("❤ 收藏", 0, 0)
private var player: ExoPlayer? = null
private lateinit var playerView: PlayerView
private lateinit var statusText: TextView
private lateinit var trialText: TextView
private lateinit var digitText: TextView
private lateinit var loadingText: TextView
private lateinit var loadingMask: FrameLayout
private lateinit var root: FrameLayout
private var panelLayout: LinearLayout? = null
private var groupList: ListView? = null
private var channelList: ListView? = null
private var curGroupPos = 0
private var currentChannel = -1
private var currentLine = 0
private var mode = MODE_NATIVE
private val handler = Handler(Looper.getMainLooper())
private val hidePanelTask = Runnable { hidePanel() }
private val uiTick = Runnable { onUiTick() }
private var loadStart = 0L
private val loadTick = object : Runnable {
override fun run() {
if (loadingMask.visibility != View.VISIBLE) return
val s = ((System.currentTimeMillis() - loadStart) / 1000 + 1).toInt()
loadingText.text = "正在加载 $s 秒,精彩继续…"
handler.postDelayed(this, 1000)
}
}
private var digitBuf = ""
private val digitTask = object : CountDownTimer(2000, 2000) {
override fun onTick(m: Long) {}
override fun onFinish() { if (digitBuf.isNotEmpty()) { commitDigit(); digitBuf = "" } }
}
private var isVip = false
private var lastPlayError = ""
private var lastSelfTest = ""
private var failureReport = ""
private var wasStopped = false
private var resumeBtn: TextView? = null
// 连续滑动两次退出
private var lastSwipeTs = 0L
private var downX = 0f
private var downY = 0f
private var downT = 0L
private var exitHint: TextView? = null
companion object {
const val MODE_NATIVE = 0
@JvmStatic var playRequested: Int = -1
}
@SuppressLint("SetJavaScriptEnabled", "SetTextI18n", "ClickableViewAccessibility")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
try { init(savedInstanceState) } catch (e: Exception) {
// 保守恢复:显示错误不闪退
val r = FrameLayout(this)
r.setBackgroundColor(Color.BLACK)
val tv = TextView(this)
tv.text = "启动异常已恢复 (${e.javaClass.simpleName})\n请按返回键重启"
tv.setTextColor(Color.WHITE); tv.textSize = 18f; tv.gravity = Gravity.CENTER
r.addView(tv)
setContentView(r)
}
}
@SuppressLint("SetJavaScriptEnabled", "ClickableViewAccessibility")
private fun init(savedInstanceState: Bundle?) {
hideSystemUi()
root = FrameLayout(this)
setContentView(root)
// 1) 原生播放层
playerView = PlayerView(this).apply { useController = false }
root.addView(playerView, FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT))
// 3) 左上状态
statusText = TextView(this).apply {
textSize = 18f; setTextColor(Color.WHITE)
setShadowLayer(4f, 2f, 2f, Color.BLACK)
setPadding(32, 24, 32, 24)
}
root.addView(statusText, FrameLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT))
// 4) 右上:免费时长 + 模式
val rightCol = LinearLayout(this).apply { orientation = LinearLayout.VERTICAL }
trialText = TextView(this).apply {
textSize = 14f; setTextColor(Color.WHITE)
setBackgroundColor(0x88000000u.toInt())
setPadding(20, 10, 20, 10)
}
rightCol.addView(trialText)
val rcLP = FrameLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.TOP or Gravity.END)
root.addView(rightCol, rcLP)
// 5) 数字键大字
digitText = TextView(this).apply {
textSize = 56f; setTextColor(Color.WHITE)
setShadowLayer(6f, 3f, 3f, Color.BLACK)
setPadding(24, 64, 48, 24)
visibility = View.GONE
}
root.addView(digitText, FrameLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.TOP or Gravity.END))
// 6) 加载罩
loadingMask = FrameLayout(this).apply { setBackgroundColor(0xB2000000u.toInt()); visibility = View.GONE }
loadingText = TextView(this).apply {
textSize = 28f; setTextColor(Color.WHITE); gravity = Gravity.CENTER
setShadowLayer(6f, 3f, 3f, Color.BLACK)
}
loadingMask.addView(loadingText, FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT))
root.addView(loadingMask, FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT))
// 8) 「继续播放」按钮(从桌面回来画面卡住时点它重连)
resumeBtn = TextView(this).apply {
text = "▶ 继续播放"
textSize = 22f
setTextColor(Color.WHITE)
setBackgroundColor(0xE61B6EF3u.toInt())
setPadding(48, 24, 48, 24)
gravity = Gravity.CENTER
isFocusable = true
visibility = View.GONE
setOnClickListener { resumePlayback() }
}
root.addView(resumeBtn, FrameLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.CENTER))
// 7) ExoPlayer 初始化
val loadControl = androidx.media3.exoplayer.DefaultLoadControl.Builder()
.setBufferDurationsMs(1500, 12000, 500, 1000)
.build()
player = ExoPlayer.Builder(this).setLoadControl(loadControl).setMediaSourceFactory(
DefaultMediaSourceFactory(
DefaultDataSource.Factory(this,
DefaultHttpDataSource.Factory()
.setUserAgent("Mozilla/5.0 (Linux; Android 12; SM-S901B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Mobile Safari/537.36")
.setDefaultRequestProperties(mapOf(
"Referer" to "https://m.iptv807.com/",
"Origin" to "https://m.iptv807.com"
))
.setAllowCrossProtocolRedirects(true)
.setConnectTimeoutMs(8000)
.setReadTimeoutMs(12000)
)
)).build()
playerView.player = player
player?.addListener(object : Player.Listener {
override fun onPlayerError(err: androidx.media3.common.PlaybackException) {
lastPlayError = "播放错误 code=${err.errorCode}: ${err.message?.take(60) ?: ""}"
autoSwitchLine()
}
override fun onPlaybackStateChanged(state: Int) {
when (state) {
Player.STATE_READY -> { setMode(MODE_NATIVE); hideLoading() }
Player.STATE_BUFFERING -> { /* 缓冲中,保持计时继续 */ }
}
}
override fun onIsPlayingChanged(p: Boolean) {
if (p) { hideLoading(); resumeBtn?.visibility = View.GONE }
}
})
// 触屏/遥控 OK 弹面板
playerView.setOnClickListener { if (!isPanelVisible()) showPanel() else hidePanel() }
SourceResolver.init(this)
// 播放期间保持屏幕常亮
window.addFlags(android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
checkLicense()
if (savedInstanceState == null && channels.isNotEmpty()) {
val defIdx = channels.indexOfFirst { it.name.contains("TVB翡翠台") }.let { if (it >= 0) it else 0 }
playChannel(defIdx)
// 启动后台预取默认频道所在分组,之后切台秒开
val g = channels.getOrNull(defIdx)?.group
val gi = ChannelData.groups().indexOfFirst { it.name == g }
if (gi >= 0) handler.postDelayed({ prefetchGroup(gi) }, 2500)
}
}
private fun setMode(m: Int) { mode = m }
/** 启动自检:解析源 + 实测第一线路连通性,结果在状态栏显示 10 秒 */
private fun runSelfTest(idx: Int) {
val ch = channels.getOrNull(idx) ?: return
Thread {
val t0 = System.currentTimeMillis()
val urls = try { SourceResolver.resolve(ch.tid, ch.id) } catch (e: Exception) { null }
val resolveMs = System.currentTimeMillis() - t0
val info = StringBuilder()
if (urls.isNullOrEmpty()) {
info.append("自检: 解析失败(${SourceResolver.lastError})")
} else {
info.append("自检: 解析${urls.size}线/${resolveMs}ms")
// 实测第一条能否连上
info.append(" | ").append(SourceResolver.verifyChain(ch.tid, ch.id))
}
runOnUiThread {
statusText.text = info.toString()
handler.postDelayed({
// 10 秒后恢复正常显示
val c = channels.getOrNull(currentChannel)
if (c != null) statusText.text = "▶ ${c.num} ${c.name}"
}, 10000)
}
}.start()
}
private fun freeRemainingMs(): Long = LicenseClient.freeRemainingMs(this)
private fun checkLicense() {
val prefs = getSharedPreferences("iptv_license", MODE_PRIVATE)
val card = prefs.getString("card_key", "") ?: ""
Thread {
val r = if (card.isNotEmpty()) LicenseClient.verify(card, LicenseClient.machineCode(this)) else LicenseClient.Result(false, "免费")
runOnUiThread {
isVip = r.ok
prefs.edit().putBoolean("is_vip", r.ok).apply()
if (r.ok) { trialText.visibility = View.GONE; trialText.text = "VIP" } else enterFreeMode()
}
}.start()
}
private fun enterFreeMode() {
trialText.visibility = View.VISIBLE
handler.removeCallbacks(uiTick)
handler.post(uiTick)
}
private fun onUiTick() {
if (isVip) return
// 每秒累计一次免费用量,这样右上角倒计时才会真正走动
if (player?.isPlaying == true) LicenseClient.addFreeUsedMs(this, 1000)
val r = freeRemainingMs()
trialText.visibility = View.VISIBLE
trialText.text = if (r <= 0) "⏳ 今日免費已用完"
else "⏳ 今日免費 ${r / 60000}分${((r % 60000) / 1000).toString().padStart(2, '0')}秒"
handler.postDelayed(uiTick, 1000)
}
private fun showTrialExpired() {
player?.pause()
trialText.visibility = View.GONE
statusText.text = "今日免费时长已用完"
startActivity(Intent(this, TrialExpiredActivity::class.java))
finish()
}
private fun isPanelVisible(): Boolean = panelLayout?.visibility == View.VISIBLE
private fun panelGroups(): List<ChannelData.Group> =
if (favSet().isNotEmpty()) listOf(favGroup) + groups else groups
private fun favSet(): MutableSet<String> =
getSharedPreferences("iptv_fav", MODE_PRIVATE).getStringSet("favs", HashSet<String>())!!.toMutableSet()
private fun toggleFav(ch: ChannelData.Channel) {
val s = favSet()
val n = ch.num.toString()
if (n in s) s.remove(n) else s.add(n)
getSharedPreferences("iptv_fav", MODE_PRIVATE).edit().putStringSet("favs", s).apply()
}
private fun channelsOfGroup(gpos: Int): List<ChannelData.Channel> {
val g = panelGroups()[gpos]
return if (g.name == favGroup.name) {
favSet().mapNotNull { n -> channels.find { it.num == n.toInt() } }.sortedBy { it.num }
} else channels.filter { it.group == g.name }
}
@SuppressLint("ClickableViewAccessibility")
private fun showPanel() {
if (panelLayout == null) {
panelLayout = buildPanel()
root.addView(panelLayout, FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT))
}
panelLayout?.visibility = View.VISIBLE
val gpos = panelGroups().indexOfFirst { g ->
val num = channels.getOrNull(currentChannel)?.num
if (g.name == favGroup.name) num?.toString() in favSet()
else g.name == channels.getOrNull(currentChannel)?.group
}
if (gpos >= 0) { curGroupPos = gpos; groupList?.setSelection(gpos + 1); updateChannelListOfGroup(gpos) }
resetHideTimer()
}
@SuppressLint("ClickableViewAccessibility")
private fun buildPanel(): LinearLayout = LinearLayout(this).apply {
orientation = LinearLayout.HORIZONTAL
setBackgroundColor(0xE8141418u.toInt())
setOnClickListener { hidePanel() }
groupList = ListView(this@MainActivity).apply {
setBackgroundColor(Color.TRANSPARENT)
adapter = ArrayAdapter(this@MainActivity, android.R.layout.simple_list_item_1,
listOf("⚙ 设置") + panelGroups().map { "${it.name} (${it.count})" })
onItemClickListener = AdapterView.OnItemClickListener { _, _, pos, _ ->
if (pos == 0) {
startActivity(Intent(this@MainActivity, SettingsActivity::class.java))
hidePanel(); return@OnItemClickListener
}
curGroupPos = pos - 1
groupList?.setSelection(pos)
updateChannelListOfGroup(curGroupPos)
}
setPadding(8, 24, 8, 24)
setOnTouchListener { v, ev ->
if (ev.action == MotionEvent.ACTION_DOWN || ev.action == MotionEvent.ACTION_MOVE) resetHideTimer()
v.onTouchEvent(ev)
}
}
addView(groupList, LinearLayout.LayoutParams(
(resources.displayMetrics.widthPixels * 0.2f).toInt(), ViewGroup.LayoutParams.MATCH_PARENT))
channelList = ListView(this@MainActivity).apply {
setBackgroundColor(Color.TRANSPARENT)
onItemClickListener = AdapterView.OnItemClickListener { _, _, pos, _ ->
val list = channelsOfGroup(curGroupPos)
if (pos < list.size) { playChannel(channels.indexOf(list[pos])); hidePanel() }
}
setPadding(8, 24, 8, 24)
setOnTouchListener { v, ev ->
if (ev.action == MotionEvent.ACTION_DOWN || ev.action == MotionEvent.ACTION_MOVE) resetHideTimer()
v.onTouchEvent(ev)
}
setOnScrollListener(object : AbsListView.OnScrollListener {
override fun onScrollStateChanged(v: AbsListView, s: Int) { resetHideTimer() }
override fun onScroll(v: AbsListView, a: Int, b: Int, c: Int) { resetHideTimer() }
})
}
addView(channelList, LinearLayout.LayoutParams(
(resources.displayMetrics.widthPixels * 0.38f).toInt(), ViewGroup.LayoutParams.MATCH_PARENT))
}
private fun updateChannelListOfGroup(pos: Int) {
prefetchGroup(pos)
val list = channelsOfGroup(pos)
channelList?.adapter = ArrayAdapter(this, android.R.layout.simple_list_item_1,
list.map { "%3d %s".format(it.num, it.name) })
}
private fun hidePanel() { handler.removeCallbacks(hidePanelTask); panelLayout?.visibility = View.GONE }
/** 后台预取某分组所有频道的源,让切台变秒切(每次运行只做一轮) */
private var prefetchedGroups = HashSet<Int>()
private fun prefetchGroup(gpos: Int) {
if (prefetchedGroups.contains(gpos)) return
prefetchedGroups.add(gpos)
val list = channelsOfGroup(gpos)
Thread {
for (ch in list) {
try { SourceResolver.resolveLines(ch.tid, ch.id) } catch (e: Exception) { }
try { Thread.sleep(250) } catch (ie: InterruptedException) { }
}
}.start()
}
private fun resetHideTimer() {
handler.removeCallbacks(hidePanelTask)
handler.postDelayed(hidePanelTask, 5000)
}
private fun hideLoading() { loadingMask.visibility = View.GONE; handler.removeCallbacks(loadTick) }
/** 开始一次新的换台计时(只在切台时调用) */
private fun beginLoading() {
loadStart = System.currentTimeMillis()
loadingText.text = "正在加载 1 秒,精彩继续…"
loadingMask.visibility = View.VISIBLE
handler.removeCallbacks(loadTick)
handler.postDelayed(loadTick, 1000)
}
private fun playChannel(idx: Int, line: Int = 0) {
if (idx >= channels.size) return
val isSameChannel = (idx == currentChannel)
currentChannel = idx; currentLine = line
val ch = channels[idx]
if (!isSameChannel || loadingMask.visibility != View.VISIBLE) {
beginLoading() // 只有换台才重置计时;同台换线路时继续计时
}
Thread {
val urls = try { SourceResolver.resolve(ch.tid, ch.id) } catch (e: Exception) { null }
if (urls.isNullOrEmpty()) {
// 自动重试 3 次(间隔 4 秒),期间给友好提示
var got: List<String>? = null
for (r in 1..3) {
runOnUiThread { statusText.text = "网络波动,正在重试 ($r/3)…"; loadingText.text = "正在加载,精彩继续…" }
Thread.sleep(4000)
got = try { SourceResolver.resolveLines(ch.tid, ch.id) } catch (e: Exception) { null }
if (!got.isNullOrEmpty()) break
}
if (got.isNullOrEmpty()) {
runOnUiThread {
statusText.text = "⚠ ${ch.num} ${ch.name} 源解析失败\n解析: ${SourceResolver.lastError}"
failureReport = "解析失败 ${SourceResolver.lastError}"
hideLoading()
}
return@Thread
}
val u2 = got!!
runOnUiThread { playUrl(u2[0], ch) }
return@Thread
}
// 不预探测(那是切台慢的主因):直接播对应线路,失败由 onPlayerError 自动换下一条
var chosen = line % urls.size
val probe = StringBuilder("skip")
val tok = Regex("token=([^&]+)").find(urls[0])?.groupValues?.get(1)?.take(14) ?: "?"
failureReport = "探测: $probe\ntoken: $tok"
lastSelfTest = failureReport
if (chosen < 0) {
// 用的是缓存线路且全失效 → 强制刷新一次再试
runOnUiThread { statusText.text = "线路失效,正在刷新源…" }
Thread.sleep(2500)
val fresh = try { SourceResolver.resolveFresh(ch.tid, ch.id) } catch (e: Exception) { null }
if (!fresh.isNullOrEmpty() && fresh != urls) {
for (i in fresh.indices) {
val st = probeLine(fresh[i])
if (st == "200" || st == "206" || st == "302") { chosen = i; break }
}
if (chosen >= 0) {
runOnUiThread { playUrl(fresh[chosen], ch) }
return@Thread
}
}
}
runOnUiThread {
if (chosen < 0) {
statusText.text = "⚠ ${ch.num} ${ch.name} 源站拒绝(全部线路)\n$failureReport"
hideLoading()
} else {
playUrl(urls[chosen], ch)
}
}
}.start()
}
/** 轻量探测一条线路:跟 302 后取 HTTP 状态码 */
private fun probeLine(url: String): String = try {
val conn = (java.net.URL(url).openConnection() as java.net.HttpURLConnection).apply {
requestMethod = "GET"; connectTimeout = 4000; readTimeout = 4000
setRequestProperty("User-Agent", "Mozilla/5.0 (Linux; Android 12; SM-S901B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Mobile Safari/537.36")
setRequestProperty("Referer", "https://m.iptv807.com/")
setRequestProperty("Origin", "https://m.iptv807.com")
setRequestProperty("Range", "bytes=0-1")
instanceFollowRedirects = true
}
val code = conn.responseCode
runCatching { conn.inputStream?.close() }
conn.disconnect()
code.toString()
} catch (e: Exception) { "E:${e.javaClass.simpleName}" }
private fun playUrl(url: String, ch: ChannelData.Channel) {
val isFlv = url.contains("type=.flv") || url.endsWith(".flv")
val item = MediaItem.Builder().setUri(Uri.parse(url))
.setMimeType(if (isFlv) MimeTypes.VIDEO_FLV else MimeTypes.APPLICATION_M3U8)
.build()
player?.setMediaItem(item)
player?.prepare()
player?.playWhenReady = true
statusText.text = "… ${ch.num} ${ch.name}"
HistoryRecorder.log(this, ch.num, ch.name)
}
@SuppressLint("SetTextI18n")
private fun autoSwitchLine() {
val ch = channels.getOrNull(currentChannel) ?: return
currentLine++
if (currentLine > 12) {
val vn = try { packageManager.getPackageInfo(packageName, 0).versionName } catch (e: Exception) { "?" }
statusText.text = "⚠ ${ch.num} ${ch.name} 全部线路失败\n$lastPlayError\n解析: ${SourceResolver.lastError}\n$failureReport\n版本: v$vn"
hideLoading()
return
}
playChannel(currentChannel, currentLine)
}
private fun onDigit(d: Int) {
digitBuf += d.toString()
digitText.text = digitBuf
digitText.visibility = View.VISIBLE
val t = digitBuf.toIntOrNull() ?: return
val idx = channels.indexOfFirst { it.num == t }
if (idx >= 0) {
playChannel(idx); digitBuf = ""; digitText.visibility = View.GONE; digitTask.cancel()
} else { digitTask.cancel(); digitTask.start() }
}
private fun commitDigit() {
val t = digitBuf.toIntOrNull() ?: return
val idx = channels.indexOfFirst { it.num == t }
if (idx >= 0) playChannel(idx)
digitText.visibility = View.GONE
}
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
// 「继续播放」按钮可见时,遥控 OK/确认 直接触发
if (resumeBtn?.visibility == View.VISIBLE &&
(keyCode == KeyEvent.KEYCODE_DPAD_CENTER || keyCode == KeyEvent.KEYCODE_ENTER ||
keyCode == KeyEvent.KEYCODE_NUMPAD_ENTER || keyCode == KeyEvent.KEYCODE_SPACE)) {
resumePlayback(); return true
}
if (isPanelVisible()) resetHideTimer()
if (keyCode in KeyEvent.KEYCODE_0..KeyEvent.KEYCODE_9) { onDigit(keyCode - KeyEvent.KEYCODE_0); return true }
if (keyCode == KeyEvent.KEYCODE_NUMPAD_0) { onDigit(0); return true }
if (keyCode in KeyEvent.KEYCODE_NUMPAD_1..KeyEvent.KEYCODE_NUMPAD_9) { onDigit(keyCode - KeyEvent.KEYCODE_NUMPAD_0); return true }
when (keyCode) {
KeyEvent.KEYCODE_MENU -> {
if (isPanelVisible()) {
val list = channelsOfGroup(curGroupPos)
val pos = channelList?.checkedItemPosition ?: -1
if (pos in 0 until list.size) {
toggleFav(list[pos])
updateChannelListOfGroup(curGroupPos)
Toast.makeText(this, if (favSet().contains(list[pos].num.toString())) "★ 已收藏 ${list[pos].name}" else "☆ 已取消 ${list[pos].name}", Toast.LENGTH_SHORT).show()
}
return true
} else { autoSwitchLine(); return true }
}
KeyEvent.KEYCODE_CHANNEL_UP -> { playChannel((currentChannel - 1 + channels.size) % channels.size); return true }
KeyEvent.KEYCODE_CHANNEL_DOWN -> { playChannel((currentChannel + 1) % channels.size); return true }
KeyEvent.KEYCODE_DPAD_LEFT -> {
if (isPanelVisible()) {
curGroupPos = (curGroupPos - 1 + groups.size) % groups.size
groupList?.setSelection(curGroupPos + 1); updateChannelListOfGroup(curGroupPos)
return true
}
}
KeyEvent.KEYCODE_DPAD_RIGHT -> {
if (isPanelVisible()) {
curGroupPos = (curGroupPos + 1) % groups.size
groupList?.setSelection(curGroupPos + 1); updateChannelListOfGroup(curGroupPos)
return true
}
}
KeyEvent.KEYCODE_DPAD_UP, KeyEvent.KEYCODE_DPAD_DOWN -> {
if (isPanelVisible()) return super.onKeyDown(keyCode, event)
playChannel((currentChannel + (if (keyCode == KeyEvent.KEYCODE_DPAD_UP) -1 else 1) + channels.size) % channels.size); return true
}
KeyEvent.KEYCODE_DPAD_CENTER, KeyEvent.KEYCODE_ENTER -> {
if (isPanelVisible()) {
val list = channelsOfGroup(curGroupPos)
val pos = channelList?.checkedItemPosition ?: -1
if (pos >= 0 && pos < list.size) { playChannel(channels.indexOf(list[pos])); hidePanel(); return true }
}
showPanel(); return true
}
KeyEvent.KEYCODE_BACK -> { if (isPanelVisible()) { hidePanel(); return true } }
}
return super.onKeyDown(keyCode, event)
}
/** 全局手势:连续滑动两次 → 完全退出 App */
override fun dispatchTouchEvent(ev: android.view.MotionEvent?): Boolean {
try {
if (ev != null) {
when (ev.actionMasked) {
android.view.MotionEvent.ACTION_DOWN -> {
downX = ev.rawX; downY = ev.rawY; downT = System.currentTimeMillis()
}
android.view.MotionEvent.ACTION_UP -> {
val dx = ev.rawX - downX
val dy = ev.rawY - downY
val dist = kotlin.math.hypot(dx.toDouble(), dy.toDouble()).toFloat()
val dt = System.currentTimeMillis() - downT
// 一次有效滑动:位移够大、时间够短
if (dist > 120f && dt < 800) {
val now = System.currentTimeMillis()
if (now - lastSwipeTs < 1500) { exitApp(); return true }
lastSwipeTs = now
showExitHint()
}
}
}
}
} catch (e: Exception) { }
return super.dispatchTouchEvent(ev)
}
/** 第一次滑动后提示"再滑一次退出" */
private fun showExitHint() {
runOnUiThread {
if (exitHint == null) {
exitHint = TextView(this).apply {
textSize = 18f; setTextColor(Color.WHITE)
setBackgroundColor(0xB3000000u.toInt())
setPadding(40, 20, 40, 20); gravity = Gravity.CENTER
}
(window.decorView as android.view.ViewGroup).addView(exitHint,
android.view.ViewGroup.LayoutParams(
android.view.ViewGroup.LayoutParams.WRAP_CONTENT,
android.view.ViewGroup.LayoutParams.WRAP_CONTENT).also {
(it as android.widget.FrameLayout.LayoutParams).gravity = Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL
it.bottomMargin = 80
})
}
exitHint?.text = "再滑动一次退出应用"
exitHint?.visibility = View.VISIBLE
handler.removeCallbacks(hideExitHintTask)
handler.postDelayed(hideExitHintTask, 2000)
}
}
private val hideExitHintTask = Runnable { exitHint?.visibility = View.GONE }
/** 退出前先弹窗确认 */
private fun exitApp() {
runOnUiThread {
try {
exitHint?.visibility = View.GONE
android.app.AlertDialog.Builder(this)
.setTitle("退出应用")
.setMessage("确定要退出「港澳台直播」吗?")
.setPositiveButton("退出") { _, _ -> doExit() }
.setNegativeButton("取消", null)
.setCancelable(true)
.show()
} catch (e: Exception) {
doExit() // 弹窗异常时直接退出,别卡住
}
}
}
/** 真正完全退出 App */
private fun doExit() {
try { handler.removeCallbacksAndMessages(null) } catch (e: Exception) { }
try { player?.release() } catch (e: Exception) { }
try { finishAndRemoveTask() } catch (e: Exception) { finishAffinity() }
// 彻底结束进程(电视盒子上保证真的退出)
handler.postDelayed({ android.os.Process.killProcess(android.os.Process.myPid()) }, 250)
}
override fun onResume() {
super.onResume()
// 从桌面/设置返回:先尝试续播,失败则显示「继续播放」按钮
if (wasStopped) {
wasStopped = false
try { player?.play() } catch (e: Exception) { }
handler.postDelayed({
// 直播流暂停后常常定格无法续播 → 给用户一个明确的按钮
if (player?.isPlaying != true) resumeBtn?.visibility = View.VISIBLE
else resumeBtn?.visibility = View.GONE
}, 1800)
}
if (playRequested > 0) {
val idx = channels.indexOfFirst { it.num == playRequested }
playRequested = -1
if (idx >= 0) playChannel(idx)
}
}
/** 点「继续播放」→ 重新连接当前频道(直播流最稳的恢复方式) */
private fun resumePlayback() {
resumeBtn?.visibility = View.GONE
val idx = if (currentChannel in channels.indices) currentChannel else 0
try { player?.play() } catch (e: Exception) { }
// 等 1.2 秒看是否已恢复,没恢复就重连
handler.postDelayed({
if (player?.isPlaying != true) playChannel(idx)
}, 1200)
}
override fun onWindowFocusChanged(hasFocus: Boolean) {
super.onWindowFocusChanged(hasFocus)
if (hasFocus) hideSystemUi()
}
private fun hideSystemUi() {
window.decorView.systemUiVisibility =
View.SYSTEM_UI_FLAG_FULLSCREEN or
View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY or
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or
View.SYSTEM_UI_FLAG_LAYOUT_STABLE
}
override fun onStop() {
wasStopped = true
super.onStop()
player?.pause()
}
override fun onDestroy() {
digitTask.cancel()
handler.removeCallbacksAndMessages(null)
playerView.player = null
player?.release()
super.onDestroy()
}
}
@@ -0,0 +1,152 @@
package com.iptv807.tv
import android.annotation.SuppressLint
import android.app.Activity
import android.content.ComponentName
import android.content.Intent
import android.content.pm.PackageManager
import android.graphics.Color
import android.os.Bundle
import android.view.Gravity
import android.view.KeyEvent
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.LinearLayout
import android.widget.Switch
import android.widget.TextView
/**
* 设置页:
* - 版本号显示
* - 开机自启动开关(BOOT_COMPLETED receiver)
* - 会员状态显示(已是会员显示有效期;非会员显示成为会员按钮→跳购买)
*/
class SettingsActivity : Activity() {
private lateinit var statusText: TextView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
try {
// crashGuard: 若上次有崩溃标记,先清before检查
getSharedPreferences("iptv_crash", MODE_PRIVATE).let {
if (it.getBoolean("crashed", false)) {
it.edit().putBoolean("crashed", false).apply()
// 清理license cache以防被污染
getSharedPreferences("iptv_license", MODE_PRIVATE).edit().remove("token").remove("card_key").apply()
}
}
} catch (e: Exception) { }
val root = LinearLayout(this).apply {
orientation = LinearLayout.VERTICAL
setBackgroundColor(Color.parseColor("#12141A"))
setPadding(96, 64, 96, 64)
}
setContentView(root)
root.addView(TextView(this).apply {
text = "设置"
textSize = 28f; setTextColor(Color.WHITE)
gravity = Gravity.CENTER; setPadding(0, 0, 0, 48)
})
// ===== 版本信息 =====
val versionName = try {
packageManager.getPackageInfo(packageName, 0).versionName ?: "1.0"
} catch (e: Exception) { "1.0" }
val prefs = getSharedPreferences("iptv_license", MODE_PRIVATE)
val isVip = prefs.getBoolean("is_vip", false)
val expire = prefs.getString("vip_expire", "")
val permanent = prefs.getBoolean("is_permanent", false)
val cardKey = prefs.getString("card_key", "")
root.addView(TextView(this).apply {
text = "版本信息"
textSize = 15f; setTextColor(Color.parseColor("#7AA7D9"))
setPadding(0, 0, 0, 8)
})
root.addView(TextView(this).apply {
text = "App 版本:$versionName\n授权系统:CardAuth(app.88531.cn)\n机器码:${LicenseClient.machineCode(this@SettingsActivity)}"
textSize = 15f; setTextColor(Color.WHITE)
setLineSpacing(0f, 1.15f)
setBackgroundColor(Color.parseColor("#1C2028"))
setPadding(24, 20, 24, 20)
}, LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT).apply { bottomMargin = 40 })
// ===== 开机自启动 =====
root.addView(TextView(this).apply {
text = "通用设置"
textSize = 15f; setTextColor(Color.parseColor("#7AA7D9"))
setPadding(0, 0, 0, 8)
})
val bootSwitch = Switch(this).apply {
text = "开机自动启动"; textSize = 17f
setTextColor(Color.WHITE)
isChecked = isBootReceiverEnabled()
val pad = (24 * resources.displayMetrics.density).toInt()
setPadding(pad, pad / 2, pad, pad / 2)
setBackgroundColor(Color.parseColor("#1C2028"))
}
bootSwitch.setOnCheckedChangeListener { _, checked ->
val pm = packageManager
val comp = ComponentName(this, BootReceiver::class.java)
pm.setComponentEnabledSetting(
comp,
if (checked) PackageManager.COMPONENT_ENABLED_STATE_ENABLED
else PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
PackageManager.DONT_KILL_APP
)
}
root.addView(bootSwitch, LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT).apply { bottomMargin = 40 })
// ===== 会员状态 =====
root.addView(TextView(this).apply {
text = "会员状态"
textSize = 15f; setTextColor(Color.parseColor("#7AA7D9"))
setPadding(0, 0, 0, 8)
})
statusText = TextView(this).apply {
textSize = 15f; setLineSpacing(0f, 1.15f)
setTextColor(Color.WHITE)
setBackgroundColor(Color.parseColor("#1C2028"))
setPadding(24, 20, 24, 20)
text = if (isVip) {
"✓ 已是会员\n${if (permanent) "永久有效" else "有效期至:$expire"}${if (cardKey?.isNotEmpty() == true) "\n卡号:$cardKey" else ""}"
} else {
"当前为免费用户(每天 1 小时)\n尚未成为会员"
}
}
root.addView(statusText, LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT).apply { bottomMargin = 24 })
if (isVip) {
root.addView(TextView(this).apply {
text = "✓ 已经是会员"
textSize = 18f; setTextColor(Color.parseColor("#4CAF50"))
gravity = Gravity.CENTER; setPadding(0, 24, 0, 24)
})
} else {
val btn = Button(this).apply {
text = "支付成为会员(月/季/年)"
textSize = 17f
setBackgroundColor(Color.parseColor("#1B6EF3"))
setOnClickListener { startActivity(Intent(this@SettingsActivity, ShopActivity::class.java)) }
}
root.addView(btn, LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT))
}
}
private fun isBootReceiverEnabled(): Boolean =
packageManager.getComponentEnabledSetting(ComponentName(this, BootReceiver::class.java)) ==
PackageManager.COMPONENT_ENABLED_STATE_ENABLED
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
if (keyCode == KeyEvent.KEYCODE_BACK) { finish(); return true }
return super.onKeyDown(keyCode, event)
}
}
@@ -0,0 +1,246 @@
package com.iptv807.tv
import android.annotation.SuppressLint
import android.app.Activity
import android.content.Intent
import android.graphics.Color
import android.net.Uri
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.view.Gravity
import android.view.KeyEvent
import android.view.View
import android.view.ViewGroup
import android.widget.AdapterView
import android.widget.ArrayAdapter
import android.widget.Button
import android.widget.LinearLayout
import android.widget.ListView
import android.widget.TextView
import org.json.JSONArray
/**
* 套餐商店 —— App 内直接购买
* 流程: GET card-types → 用户选定套餐+支付方式 → POST /api/public/orders
* (contact_info=MACHINE:TV-xxx) → 拉起浏览器 pay_url 扫码
* → 轮询 /api/public/orders/query?order_no= → paid → 查询卡号 → 自动verify → 进主界面
*/
class ShopActivity : Activity() {
private val plans = mutableListOf<Plan>()
data class Plan(val id: Int, val name: String, val price: String, val days: Int)
private lateinit var list: ListView
private lateinit var statusText: TextView
private var payType = "alipay"
private var orderNo = ""
private var machineId = ""
private val handler = Handler(Looper.getMainLooper())
private var pollTask: Runnable? = null
private var pollCount = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
try {
// crashGuard: 若上次有崩溃标记,先清before检查
getSharedPreferences("iptv_crash", MODE_PRIVATE).let {
if (it.getBoolean("crashed", false)) {
it.edit().putBoolean("crashed", false).apply()
// 清理license cache以防被污染
getSharedPreferences("iptv_license", MODE_PRIVATE).edit().remove("token").remove("card_key").apply()
}
}
} catch (e: Exception) { }
val root = LinearLayout(this).apply {
orientation = LinearLayout.VERTICAL
setBackgroundColor(Color.parseColor("#12141A"))
setPadding(64, 48, 64, 48)
}
setContentView(root)
root.gravity = Gravity.CENTER
machineId = LicenseClient.machineCode(this)
root.addView(TextView(this).apply {
text = "IPTV · 选择套餐"
textSize = 26f; setTextColor(Color.WHITE)
gravity = Gravity.CENTER; setPadding(0, 0, 0, 12)
})
root.addView(TextView(this).apply {
text = "支付成功自动激活本机(无需卡密)"
textSize = 14f; setTextColor(Color.LTGRAY)
gravity = Gravity.CENTER; setPadding(0, 0, 0, 32)
})
list = ListView(this).apply {
setBackgroundColor(Color.parseColor("#1C1F26"))
}
root.addView(list, LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, 0, 1f
).apply { bottomMargin = 24 })
statusText = TextView(this).apply {
textSize = 14f; setTextColor(Color.LTGRAY)
gravity = Gravity.CENTER; setPadding(0, 0, 0, 24)
}
root.addView(statusText)
val row = LinearLayout(this).apply { orientation = LinearLayout.HORIZONTAL }
val alipay = Button(this).apply { text = "支付宝"; textSize = 15f }
val wxpay = Button(this).apply { text = "微信"; textSize = 15f }
fun sel(b: Button, t: String) {
val active = payType == t
b.setBackgroundColor(if (active) Color.parseColor("#1B6EF3") else Color.parseColor("#2A2F3A"))
}
alipay.setOnClickListener { payType = "alipay"; sel(alipay, "alipay"); sel(wxpay, "wxpay") }
wxpay.setOnClickListener { payType = "wxpay"; sel(alipay, "alipay"); sel(wxpay, "wxpay") }
sel(alipay, "alipay"); sel(wxpay, "wxpay")
row.addView(alipay, LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f).apply { rightMargin = 16 })
row.addView(wxpay, LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f))
root.addView(row)
list.onItemClickListener = AdapterView.OnItemClickListener { _, _, pos, _ ->
if (pos < plans.size) createOrder(plans[pos])
}
loadPlans()
}
@SuppressLint("SetTextI18n")
private fun loadPlans() {
statusText.text = "加载套餐…"
Thread {
try {
val r = CardAuthApi.get("/api/public/projects/2/card-types")
val arr = r.optJSONArray("data") ?: JSONArray()
plans.clear()
for (i in 0 until arr.length()) {
val p = arr.getJSONObject(i)
// 接口不返回 status 字段时视为启用(默认 1),只有显式 status=0 才跳过
val st = if (p.has("status")) p.optInt("status", 1) else 1
if (st == 1) {
val days = p.optInt("duration_days", 0)
plans.add(Plan(p.optInt("id"), p.optString("name"), p.optString("price"), days))
}
}
if (plans.isEmpty()) {
runOnUiThread { statusText.text = "暂无可用套餐(请稍后重试)" }
}
runOnUiThread {
plans.sortBy { it.days }
list.adapter = ArrayAdapter(this, android.R.layout.simple_list_item_1, plans.map {
val d = it.days
"📱 ${it.name} ¥${it.price} ${if (d >= 999) "永久" else "${d}天"}"
})
statusText.text = "选择套餐后按 OK 购买"
}
} catch (e: Exception) {
runOnUiThread { statusText.text = "加载失败: ${e.javaClass.simpleName}: ${e.message?.take(150)}" }
}
}.start()
}
private fun createOrder(plan: Plan) {
statusText.text = "创建订单…"
Thread {
try {
val body = org.json.JSONObject().apply {
put("project_id", 2)
put("card_type_id", plan.id)
put("pay_type", payType)
put("contact_info", "MACHINE:$machineId")
}
val r = CardAuthApi.post("/api/public/orders", body)
val d = r.optJSONObject("data")
val url = d?.optString("pay_url") ?: ""
runOnUiThread {
if (url.isNotEmpty()) {
orderNo = d!!.optString("order_no", "")
statusText.text = "订单已创建,正在打开支付页…支付成功后自动激活"
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url)))
startPolling()
} else {
statusText.text = "下单失败: ${r.optString("message")}"
}
}
} catch (e: Exception) {
runOnUiThread { statusText.text = "下单错误: ${e.message}" }
}
}.start()
}
private fun startPolling() {
pollCount = 0
pollTask = object : Runnable {
override fun run() {
pollCount++
if (pollCount > 60) { // 最多3分钟
handler.post { statusText.text = "等待支付超时,可重新下单" }
return
}
Thread {
var paid = false
var cardKey = ""
try {
val r = CardAuthApi.get("/api/public/orders/query?order_no=$orderNo")
val d = r.optJSONObject("data") ?: org.json.JSONObject()
paid = d.optString("status") == "paid"
cardKey = d.optString("card_key", "")
} catch (e: Exception) { }
runOnUiThread {
if (paid) {
statusText.text = "✓ 支付成功!正在激活本机…"
if (cardKey.isNotEmpty()) activateAndEnter(cardKey)
else { // 兜底: 直接用verify免卡路径
handler.postDelayed({
getSharedPreferences("iptv_license", MODE_PRIVATE)
.edit().putBoolean("is_vip", true).apply()
enterMain()
}, 500)
}
} else {
statusText.text = "等待支付…(${pollCount * 3}秒)"
handler.postDelayed(this, 3000)
}
}
}.start()
}
}
handler.postDelayed(pollTask!!, 3000)
}
private fun activateAndEnter(cardKey: String) {
Thread {
val r = LicenseClient.verify(cardKey, machineId)
runOnUiThread {
getSharedPreferences("iptv_license", MODE_PRIVATE).edit()
.putBoolean("is_vip", r.ok)
.putString("card_key", if (r.ok) cardKey else "")
.putString("vip_expire", r.expireTime)
.putBoolean("is_permanent", r.isPermanent)
.apply()
if (r.ok) {
statusText.text = "✓ 激活成功,开始播放"
enterMain()
} else {
statusText.text = "激活校验失败: ${r.message}(可直接输入卡密重试)"
}
}
}.start()
}
private fun enterMain() {
startActivity(Intent(this, MainActivity::class.java))
finish()
}
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
if (keyCode == KeyEvent.KEYCODE_BACK && pollTask != null) {
// 支付轮询中返回 → 取消轮询
handler.removeCallbacks(pollTask!!); pollTask = null
}
return super.onKeyDown(keyCode, event)
}
}
@@ -0,0 +1,266 @@
package com.iptv807.tv
import android.util.Base64
import java.io.BufferedReader
import java.io.InputStreamReader
import java.net.HttpURLConnection
import java.net.URL
import java.nio.charset.StandardCharsets
import java.util.concurrent.ConcurrentHashMap
/**
* iptv807 播放源本地解密 —— App 内直接解密,完全不经过 VPS
*
* 解密步骤(Python mirror 端到端 verified):
* 1. GET https://m.iptv807.com/?act=play&tid=X&id=Y (UA=Android mobile, Referer=m.iptv807.com)
* 2. 找 <script>.split("") 混淆块, 逐语句 (quote-aware ; 分割)
* 3. evalConcat: 双引号对 - 其后紧跟 .split("").reverse().join("") → 取反向值,并跳过整个 suffix(含 "" 内嵌引号不再被当作独立literal)
* 4. 从 assignments 找:
* - 首个 32hex = xKey (加密 key)
* - 另一个 32hex = oldToken
* - 含 %XX 的长串 = newToken
* 5. xorConst = html 里最早的 "16hex" 字面量
* 6. decrypt option:
* reversed → base64 → XOR(key=xKey+xorConst) → base64 → eat token 替换 → xKey 剥离 → URLDecode UTF-8
*/
object SourceResolver {
@Volatile private var cache: MutableMap<String, Cached>? = null
private data class Cached(val lines: List<String>, val ts: Long)
private const val TTL = 8 * 60 * 1000L
private const val BASE = "https://m.iptv807.com/"
private const val UA = "Mozilla/5.0 (Linux; Android 12; SM-S901B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Mobile Safari/537.36"
@Volatile var lastError: String = ""
/** 磁盘兜底缓存(App 重启后仍可用上次成功的线路) */
private var appCtx: android.content.Context? = null
fun init(ctx: android.content.Context) { appCtx = ctx.applicationContext }
private fun saveDisk(key: String, lines: List<String>) {
runCatching {
appCtx?.getSharedPreferences("iptv_lines", android.content.Context.MODE_PRIVATE)?.edit()
?.putString(key, org.json.JSONArray(lines).toString())
?.putLong("ts_$key", System.currentTimeMillis())
?.apply()
}
}
private fun loadDisk(key: String): List<String>? = runCatching {
val j = appCtx?.getSharedPreferences("iptv_lines", android.content.Context.MODE_PRIVATE)?.getString(key, null)
?: return null
val arr = org.json.JSONArray(j)
(0 until arr.length()).map { arr.getString(it) }.takeIf { it.isNotEmpty() }
}.getOrNull()
/** 兼容之前的 method signature */
fun resolve(tid: String, id: String): List<String>? = resolveLines(tid, id)
/** 强制丢弃缓存重新抓源(用于"缓存线路全失效"时) */
fun resolveFresh(tid: String, id: String): List<String>? {
cache?.remove("$tid-$id")
return resolveLines(tid, id)
}
fun resolveLines(tid: String, id: String): List<String>? {
val key = "$tid-$id"
cache?.get(key)?.let { if (System.currentTimeMillis() - it.ts < TTL) return it.lines }
synchronized(this) {
// double-check
cache?.get(key)?.let { if (System.currentTimeMillis() - it.ts < TTL) return it.lines }
try {
val url = "$BASE?act=play&tid=$tid&id=$id"
val html = httpGet(url, BASE)
// 1) script blocks - find mix
val blocks = Regex("<script>([\\s\\S]*?)</script>").findAll(html).map { it.groupValues[1] }.toList()
val mixBlock = blocks.firstOrNull { it.contains(".split(\"\")") } ?: run {
lastError = "no-mix-block"; return null
}
// 2) quote-aware split by ;
val stmts = StringBuilder()
var inStr = false
for (c in mixBlock) {
when {
c == '"' -> { inStr = !inStr; stmts.append(c) }
c == ';' && !inStr -> { stmts.append('\n'); }
else -> stmts.append(c)
}
}
val statements = stmts.toString().split('\n').map { it.trim() }.filter { it.isNotEmpty() }
// 3) extensions
val REV = ".split(\"\").reverse().join(\"\")"
fun evalConcat(s: String): String = buildString {
var i = 0
while (i < s.length) {
if (s[i] == '"') {
var j = i + 1; var lit = ""
while (j < s.length && s[j] != '"') { lit += s[j]; j++ }
if (j >= s.length) { append(lit); i = s.length; break }
val tail = s.substring((j + 1).coerceAtMost(s.length))
if (tail.startsWith(REV)) {
append(lit.reversed())
i = j + 1 + REV.length
} else {
append(lit)
i = j + 1
}
while (i < s.length && (s[i] == '+' || s[i] == ' ')) i++
} else i++
}
}
// 关键:rhs 原样交给 evalConcat(不要剥前导引号/尾部括号,那会破坏字面量配对)
val assign = LinkedHashMap<String, String>()
for (st in statements) {
if (st.isEmpty()) continue
val mVar = Regex("^var\\s+(\\w+)\\s*=\\s*(.*)$").find(st)
if (mVar != null) {
val name = mVar.groupValues[1]
val rhs = mVar.groupValues[2].trim()
assign[name] = when {
rhs.isEmpty() || rhs == "\"\"" -> ""
Regex("^\"[a-f0-9]{32}\"$").matches(rhs) -> rhs.substring(1, rhs.length - 1)
else -> evalConcat(rhs)
}
continue
}
val mCopy = Regex("^(\\w+)\\s*=\\s*(\\w+)$").find(st)
if (mCopy != null) {
assign[mCopy.groupValues[1]] = assign[mCopy.groupValues[2]] ?: ""
continue
}
val mHex = Regex("^(\\w+)\\s*=\\s*\"([a-f0-9]{32})\"$").find(st)
if (mHex != null) { assign[mHex.groupValues[1]] = mHex.groupValues[2]; continue }
}
val vals = assign.values.filter { it.isNotEmpty() }
val hex32 = vals.filter { Regex("^[a-f0-9]{32}$").matches(it) }
if (hex32.isEmpty()) { lastError = "no-hex32"; return null }
val xKey = hex32[0]
// ★★ 核心修复2:混淆块里常有 "xx = yy" 复制语句 → hex32[1] 可能 == xKey,
// 那样 replace("token=$oldToken") 就匹配不到 → URL 保留过期旧 token → 全部 404。
// 必须取"第一个不等于 xKey"的 32hex 才是真正的 oldToken。
val oldToken = hex32.firstOrNull { it != xKey } ?: xKey
val newToken = vals.firstOrNull {
it.contains(Regex("%[0-9A-Fa-f]{2}")) &&
!Regex("^[a-f0-9]{32}$").matches(it) &&
!Regex("^[a-f0-9]{16}$").matches(it) &&
it != xKey
} ?: run { lastError = "no-newToken"; return null }
val xorConst = Regex("\"([a-f0-9]{16})\"").findAll(html).map { it.groupValues[1] }.firstOrNull() ?: run {
lastError = "no-xorConst"; return null
}
// 4) decrypt options
fun decrypt(encrypted: String): String? = try {
val rev = encrypted.reversed()
val b1 = String(Base64.decode(rev, Base64.DEFAULT), charset("ISO-8859-1"))
val key = xKey + xorConst
val sb = StringBuilder()
for (i in b1.indices) {
sb.append(((b1[i].toInt() and 0xFF) xor (key[i % key.length].toInt() and 0xFF)).toChar())
}
val b2raw = sb.toString()
val b2 = String(Base64.decode(b2raw, Base64.DEFAULT), charset("ISO-8859-1"))
var u = b2.replace("token=$oldToken", "token=$newToken")
u = u.replace(xKey, "")
// 兜底:若 token= 后仍是纯32位hex(旧token没替换成功),强制换成新 token
u = Regex("token=[a-f0-9]{32}").replace(u, "token=$newToken")
// ★★ 核心修复:b2 里 token 已是 %2B 编码形态,绝对不能再 URLDecoder(会把 %2B 解成 '+' 被服务端当空格 → 404)
u
} catch (e: Exception) { null }
val lines = Regex("<option value=\"([^\"]+)\"[^>]*>", RegexOption.IGNORE_CASE)
.findAll(html)
.mapNotNull { decrypt(it.groupValues[1]) }
.toList()
if (lines.isEmpty()) { lastError = "decode-empty"; return null }
if (cache == null) cache = ConcurrentHashMap()
cache!![key] = Cached(lines, System.currentTimeMillis())
saveDisk(key, lines)
lastError = "ok ${lines.size} lines"
return lines
} catch (e: Exception) {
// 网络波动时:用该频道最近一次成功的线路兜底(即使已过期)
cache?.get(key)?.let { c ->
lastError = "网络波动,用上次缓存"
return c.lines
}
loadDisk(key)?.let { d ->
lastError = "网络波动,用磁盘缓存"
return d
}
lastError = "exception: ${e.javaClass.simpleName}: ${e.message?.take(120)}"
return null
}
}
}
/** 验完整链:先 resolver,再对 line[0] HEAD拉流确认 */
fun verifyChain(tid: String, id: String): String {
val lines = resolveLines(tid, id) ?: return "resolve 错: $lastError"
val u = lines.firstOrNull() ?: return "no lines"
// HEAD / small byte read to verify FLV
return try {
val conn = (URL(u).openConnection() as HttpURLConnection).apply {
requestMethod = "GET"
connectTimeout = 8000
readTimeout = 10000
setRequestProperty("User-Agent", UA)
setRequestProperty("Referer", BASE)
setRequestProperty("Range", "bytes=0-63")
setRequestProperty("Origin", "https://m.iptv807.com")
setInstanceFollowRedirects(true)
}
val code = conn.responseCode
val hdr = conn.getHeaderField("Location") ?: ""
val magic = runCatching {
val inn = conn.inputStream
val bs = ByteArray(3); var n = 0
while (n < 3) { val v = inn.read(); if (v < 0) break; bs[n++] = v.toByte() }
String(bs)
}.getOrElse { "" }
conn.disconnect()
when {
code == 302 && hdr.startsWith("https://t") -> "ok 302"
magic.startsWith("FLV") -> "ok direct FLV"
else -> "direct code=$code magic=$magic"
}
} catch (e: Exception) { "stream err: ${e.message}" }
}
private fun httpGet(urlStr: String, referer: String?): String {
var lastEx: Exception? = null
for (attempt in 0 until 3) {
try {
val conn = (URL(urlStr).openConnection() as HttpURLConnection).apply {
requestMethod = "GET"
connectTimeout = 15000
readTimeout = 20000
setRequestProperty("User-Agent", UA)
setRequestProperty("Accept", "text/html,*/*")
setRequestProperty("Connection", "close")
if (referer != null) setRequestProperty("Referer", referer)
}
val code = conn.responseCode
val stream = if (code in 200..299) conn.inputStream else conn.errorStream
val text = try {
stream?.let { BufferedReader(InputStreamReader(it, StandardCharsets.UTF_8)).readText() } ?: ""
} finally { runCatching { stream?.close(); conn.disconnect() } }
if (code !in 200..299) throw RuntimeException("HTTP $code")
if (text.isNotEmpty()) return text
lastEx = RuntimeException("empty body")
} catch (e: Exception) {
lastEx = e
}
try { Thread.sleep(700L * (attempt + 1)) } catch (ie: InterruptedException) {}
}
throw (lastEx ?: RuntimeException("unknown"))
}
}
@@ -0,0 +1,75 @@
package com.iptv807.tv
import android.app.Activity
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.LinearLayout
import android.widget.TextView
/**
* 免费时长用完 → 引导购买页
* 显示卡密商城地址(CardAuth /#/shop),用户点遥控OK打开购买页
*/
class TrialExpiredActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
try {
// crashGuard: 若上次有崩溃标记,先清before检查
getSharedPreferences("iptv_crash", MODE_PRIVATE).let {
if (it.getBoolean("crashed", false)) {
it.edit().putBoolean("crashed", false).apply()
// 清理license cache以防被污染
getSharedPreferences("iptv_license", MODE_PRIVATE).edit().remove("token").remove("card_key").apply()
}
}
} catch (e: Exception) { }
val root = LinearLayout(this).apply {
orientation = LinearLayout.VERTICAL
gravity = android.view.Gravity.CENTER
setBackgroundColor(0xFF12141Au.toInt())
setPadding(96, 64, 96, 64)
}
setContentView(root)
root.addView(TextView(this).apply {
text = "⏰ 今日免费时长已用完"
textSize = 30f; setTextColor(0xFFFFFFFFu.toInt())
gravity = android.view.Gravity.CENTER; setPadding(0, 0, 0, 24)
})
root.addView(TextView(this).apply {
text = "免费用户每天可观看 1 小时\n购买卡密后立即解锁不限时长观看"
textSize = 17f; setTextColor(0xFFBBBBBBu.toInt())
gravity = android.view.Gravity.CENTER; setPadding(0, 0, 0, 56)
})
root.addView(Button(this).apply {
text = "购买套餐(OK键)"
textSize = 19f
setOnClickListener { startActivity(android.content.Intent(this@TrialExpiredActivity, ShopActivity::class.java)); finish() }
})
root.addView(TextView(this).apply {
text = "或直接访问:http://app.88531.cn:8881/#/shop"
textSize = 13f; setTextColor(0xFF888888u.toInt())
gravity = android.view.Gravity.CENTER; setPadding(0, 32, 0, 0)
})
root.addView(TextView(this).apply {
text = "\n明天再来可继续免费观看 1 小时"
textSize = 14f; setTextColor(0xFF7799AAu.toInt())
gravity = android.view.Gravity.CENTER; setPadding(0, 24, 0, 0)
})
}
private fun openShop() {
try {
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse("http://app.88531.cn:8881/#/shop")))
} catch (e: Exception) { }
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp" android:height="108dp" android:viewportWidth="108" android:viewportHeight="108">
<path android:fillColor="#1B6EF3" android:pathData="M0,0h108v108h-108z"/>
</vector>
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp" android:height="108dp" android:viewportWidth="108" android:viewportHeight="108">
<group android:scaleX="0.55" android:scaleY="0.55" android:translateX="24.3" android:translateY="24.3">
<path android:fillColor="#FFFFFF" android:pathData="M32,28h44l10,12v40a4,4 0 0,1 -4,4H26a4,4 0 0,1 -4,-4V40z"/>
<path android:fillColor="#1B6EF3" android:pathData="M38,50h32v22h-32z"/>
<path android:fillColor="#FFD600" android:pathData="M54,56l8,5l-8,5z"/>
</group>
</vector>
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@mipmap/ic_launcher_bg"/>
<foreground android:drawable="@mipmap/ic_launcher_fg"/>
</adaptive-icon>
Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

+4
View File
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">港澳台直播</string>
</resources>
+6
View File
@@ -0,0 +1,6 @@
<resources>
<style name="Theme.IPTVTV" parent="android:Theme.Material.NoActionBar.Fullscreen">
<item name="android:windowBackground">@android:color/black</item>
<item name="android:colorPrimary">#1B6EF3</item>
</style>
</resources>
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="true">
<trust-anchors>
<certificates src="system" />
</trust-anchors>
</base-config>
</network-security-config>
+4
View File
@@ -0,0 +1,4 @@
plugins {
id("com.android.application") version "8.2.2" apply false
id("org.jetbrains.kotlin.android") version "1.9.22" apply false
}
+5
View File
@@ -0,0 +1,5 @@
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
android.useAndroidX=true
android.nonTransitiveRClass=true
android.suppressUnsupportedCompileSdk=35
kotlin.code.style=official
+1
View File
@@ -0,0 +1 @@
v5.2.1 根因: obfuscated var字符串含内嵌引号("caf074ce…"里的".join(\"\")"是 HTML escape \" 不是纯") — my evalConcat treats them as separate literals and mangles to "++.split..." garbage. Fix: quote-aware ++ skip via char-set approach with REV-suffix as one unit.
+16
View File
@@ -0,0 +1,16 @@
pluginManagement {
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
}
}
rootProject.name = "IPTV807TV"
include(":app")
+48
View File
@@ -0,0 +1,48 @@
#!/usr/bin/env python3
"""给 LoginActivity/TrialExpired 加ShopActivity入口 + Manifest声明"""
import io
# 1) LoginActivity
p = '/vol1/1000/HD1/APP/iptv-tv-app/app/src/main/java/com/iptv807/tv/LoginActivity.kt'
s = open(p).read()
if 'ShopActivity' not in s:
s = s.replace(''' val freeBtn = Button(this).apply {''', ''' val shopBtn = Button(this).apply {
text = "购买套餐(支付宝/微信)"; textSize = 15f
setBackgroundColor(Color.parseColor("#1B6EF3"))
setOnClickListener { startActivity(android.content.Intent(this@LoginActivity, ShopActivity::class.java)) }
}
root.addView(shopBtn, LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT).apply { topMargin = 24 })
val freeBtn = Button(this).apply {''')
s = s.replace(''' text = "没有卡密?点「先看一会」进免费试看"''', ''' text = "已有卡密可直接激活;或购买套餐自动绑定本机"''')
open(p, 'w').write(s)
print('LoginActivity patched')
else:
print('LoginActivity already patched')
# 2) TrialExpiredActivity
p = '/vol1/1000/HD1/APP/iptv-tv-app/app/src/main/java/com/iptv807/tv/TrialExpiredActivity.kt'
s = open(p).read()
if 'ShopActivity' not in s:
s = s.replace(''' root.addView(Button(this).apply {
text = "打开购买页(遥控OK)"
textSize = 19f
setOnClickListener { openShop() }
})''', ''' root.addView(Button(this).apply {
text = "购买套餐(OK键)"
textSize = 19f
setOnClickListener { startActivity(android.content.Intent(this@TrialExpiredActivity, ShopActivity::class.java)); finish() }
})''')
open(p, 'w').write(s)
print('TrialExpired patched')
# 3) Manifest
p = '/vol1/1000/HD1/APP/iptv-tv-app/app/src/main/AndroidManifest.xml'
s = open(p).read()
if 'ShopActivity' not in s:
s = s.replace('</application>', ''' <activity android:name=".ShopActivity" android:exported="false" />
</application>''')
open(p, 'w').write(s)
print('Manifest patched')
print('done')
+57
View File
@@ -0,0 +1,57 @@
<!DOCTYPE html>
<html>
<head>
<title>港澳台頻道列表 - 電視直播</title>
<meta charset="utf-8">
<meta name="application-name" content="網絡電視直播移動版,IPTV電視直播手机版" />
<meta name="title" content="港澳台頻道列表 - 電視直播"/>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="keywords" content="網絡電視,網路電視,電視直播,手机版,電視,免費電視">
<meta name="description" content="網絡電視,網路電視,全球電視直播,高清電視,免費電視,IPTV電視直播手机版">
<meta name="referrer" content="no-referrer-when-downgrade" />
<link rel="shortcut icon" href="https://d.2026016.xyz/images/favicon.ico" />
<link rel="stylesheet" href="https://apps.bdimg.com/libs/jquerymobile/1.3.0/jquery.mobile-1.3.0.min.css" />
<script src="https://apps.bdimg.com/libs/jquery/2.0.0/jquery.min.js"></script>
<script src="https://apps.bdimg.com/libs/jquerymobile/1.3.0/jquery.mobile-1.3.0.min.js"></script>
<style type="text/css">
span {font-size:14px;}
.headerNfooter {background-color: #C00;color: #fff;text-shadow: none;}
.headerNfooter a:link,.headerNfooter a:hover,.headerNfooter a:visited,.headerNfooter a:active { color: #fff; text-decoration: none;}
.headerNfooter h1 {text-indent: -999em; }
#errorTip {text-align:center; color:red; font-size:15px; display:none; margin:5px 0;}
</style></head><body>
<div data-role="page">
<div class="headerNfooter" data-role="header" data-theme="f" data-position="fixed">
<h1>港澳台頻道列表 - 電視直播</h1><a href="#" data-iconpos="left" data-icon="back" data-rel="back">返回</a>
<a href="?act=home" data-icon="home" data-role="button" data-inline="true">首頁</a>
</div>
<!--top--><div id="ad-container" style="width: 100%;"><div id="container-c95365b7eb64cacc6988f80de291dd94"></div>
<script>
$(function(){
let adScript = document.createElement('script');
adScript.src = "https://bauval.org/21/c95365b7eb64cacc6988f80de291dd94";
adScript.setAttribute('data-cfasync','false');
document.body.appendChild(adScript);
});
</script></div><div data-role="content"><div id="errorTip">所有直播线路已失效,请稍后重试或手动切换线路!</div><ul data-role="listview" data-inset="true" data-divider-theme="a" data-filter="true" data-filter-placeholder="搜索"><li><a href="?act=play&tid=gt&id=1" data-ajax="false">凤凰卫视中文台</a></li><li><a href="?act=play&tid=gt&id=2" data-ajax="false">凤凰卫视资讯台</a></li><li><a href="?act=play&tid=gt&id=3" data-ajax="false">凤凰卫视香港台</a></li><li><a href="?act=play&tid=gt&id=5" data-ajax="false">TVB翡翠台</a></li><li><a href="?act=play&tid=gt&id=6" data-ajax="false">TVB无线新闻台</a></li><li><a href="?act=play&tid=gt&id=8" data-ajax="false">TVB Plus</a></li><li><a href="?act=play&tid=gt&id=9" data-ajax="false">TVB明珠台</a></li><li><a href="?act=play&tid=gt&id=11" data-ajax="false">香港有线新闻台</a></li><li><a href="?act=play&tid=gt&id=12" data-ajax="false">香港有线18台</a></li><li><a href="?act=play&tid=gt&id=13" data-ajax="false">香港有线18台(备)</a></li><li><a href="?act=play&tid=gt&id=17" data-ajax="false">NOW新闻台</a></li><li><a href="?act=play&tid=gt&id=18" data-ajax="false">NOW财经台</a></li><li><a href="?act=play&tid=gt&id=19" data-ajax="false">ViuTV</a></li><li><a href="?act=play&tid=gt&id=20" data-ajax="false">ViuTV6</a></li><li><a href="?act=play&tid=gt&id=22" data-ajax="false">HOY国际财经台</a></li><li><a href="?act=play&tid=gt&id=23" data-ajax="false">HOY资讯台</a></li><li><a href="?act=play&tid=gt&id=24" data-ajax="false">HOY TV</a></li><li><a href="?act=play&tid=gt&id=26" data-ajax="false">港台电视31</a></li><li><a href="?act=play&tid=gt&id=27" data-ajax="false">港台电视32</a></li><li><a href="?act=play&tid=gt&id=30" data-ajax="false">香港卫视HKS</a></li><li><a href="?act=play&tid=gt&id=35" data-ajax="false">澳门莲花</a></li><li><a href="?act=play&tid=gt&id=36" data-ajax="false">澳門卫视</a></li><li><a href="?act=play&tid=gt&id=37" data-ajax="false">澳视资讯</a></li><li><a href="?act=play&tid=gt&id=38" data-ajax="false">澳視澳門</a></li><li><a href="?act=play&tid=gt&id=39" data-ajax="false">澳門Macau</a></li><li><a href="?act=play&tid=gt&id=40" data-ajax="false">澳视综艺</a></li><li><a href="?act=play&tid=gt&id=41" data-ajax="false">澳亚卫视</a></li><li><a href="?act=play&tid=gt&id=43" data-ajax="false">中天新闻台</a></li><li><a href="?act=play&tid=gt&id=44" data-ajax="false">东森新闻台</a></li><li><a href="?act=play&tid=gt&id=45" data-ajax="false">东森财经新闻台</a></li><li><a href="?act=play&tid=gt&id=46" data-ajax="false">中视新闻台</a></li><li><a href="?act=play&tid=gt&id=47" data-ajax="false">台视新闻台</a></li><li><a href="?act=play&tid=gt&id=48" data-ajax="false">三立新闻台</a></li><li><a href="?act=play&tid=gt&id=49" data-ajax="false">非凡新闻</a></li><li><a href="?act=play&tid=gt&id=50" data-ajax="false">民视新闻台</a></li><li><a href="?act=play&tid=gt&id=51" data-ajax="false">华视新闻台</a></li><li><a href="?act=play&tid=gt&id=52" data-ajax="false">镜电视新闻台</a></li><li><a href="?act=play&tid=gt&id=53" data-ajax="false">寰宇新闻台</a></li><li><a href="?act=play&tid=gt&id=54" data-ajax="false">寰宇新闻台湾台</a></li><li><a href="?act=play&tid=gt&id=55" data-ajax="false">TVBS新闻台</a></li><li><a href="?act=play&tid=gt&id=56" data-ajax="false">TVBS HD</a></li><li><a href="?act=play&tid=gt&id=60" data-ajax="false">中视HD</a></li><li><a href="?act=play&tid=gt&id=61" data-ajax="false">台视HD</a></li><li><a href="?act=play&tid=gt&id=62" data-ajax="false">民视HD</a></li><li><a href="?act=play&tid=gt&id=63" data-ajax="false">华视HD</a></li><li><a href="?act=play&tid=gt&id=73" data-ajax="false">緯來體育台</a></li><li><a href="?act=play&tid=gt&id=74" data-ajax="false">纬来日本台</a></li><li><a href="?act=play&tid=gt&id=75" data-ajax="false">纬来育乐台</a></li><li><a href="?act=play&tid=gt&id=76" data-ajax="false">ELEVEN 1(DAZN 1)</a></li><li><a href="?act=play&tid=gt&id=77" data-ajax="false">ELEVEN 2(DAZN 2)</a></li><li><a href="?act=play&tid=gt&id=79" data-ajax="false">爱尔达体育1台</a></li><li><a href="?act=play&tid=gt&id=80" data-ajax="false">爱尔达体育2台</a></li></ul></div>
<!--end-->
<div data-role="footer">
<h4>版權聲明 &copy; 所有源來自互聯網搜索</h4>
</div>
</div>
<script>(function(s){s.dataset.zone='11422200',s.src='https://n6wxm.com/vignette.min.js'})([document.documentElement, document.body].filter(Boolean).pop().appendChild(document.createElement('script')))</script><script data-cfasync="false" src="https://czernik.org/14/92553edbe36d3be2167ff02e7dfdc14b"></script>
</body>
<span style="display:none">
<script>
!function(p){"use strict";!function(t){var s=window,e=document,i=p,c="".concat("https:"===e.location.protocol?"https://":"http://","sdk.51.la/js-sdk-pro.min.js"),n=e.createElement("script"),r=e.getElementsByTagName("script")[0];n.type="text/javascript",n.setAttribute("charset","UTF-8"),n.async=!0,n.src=c,n.id="LA_COLLECT",i.d=n;var o=function(){s.LA.ids.push(i)};s.LA?s.LA.ids&&o():(s.LA=p,s.LA.ids=[],o()),r.parentNode.insertBefore(n,r)}()}({id:"KnfaacjCp88BlrkE",ck:"KnfaacjCp88BlrkE"});
</script><!-- Global site tag (gtag.js) - Google Analytics -->
<script async src='https://www.googletagmanager.com/gtag/js?id=G-PY41HWX4PP'></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-PY41HWX4PP');
</script>从缓存读取内容
</span>
</html>
+57
View File
@@ -0,0 +1,57 @@
<!DOCTYPE html>
<html>
<head>
<title>体育頻道列表 - 電視直播</title>
<meta charset="utf-8">
<meta name="application-name" content="網絡電視直播移動版,IPTV電視直播手机版" />
<meta name="title" content="体育頻道列表 - 電視直播"/>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="keywords" content="網絡電視,網路電視,電視直播,手机版,電視,免費電視">
<meta name="description" content="網絡電視,網路電視,全球電視直播,高清電視,免費電視,IPTV電視直播手机版">
<meta name="referrer" content="no-referrer-when-downgrade" />
<link rel="shortcut icon" href="https://d.2026016.xyz/images/favicon.ico" />
<link rel="stylesheet" href="https://apps.bdimg.com/libs/jquerymobile/1.3.0/jquery.mobile-1.3.0.min.css" />
<script src="https://apps.bdimg.com/libs/jquery/2.0.0/jquery.min.js"></script>
<script src="https://apps.bdimg.com/libs/jquerymobile/1.3.0/jquery.mobile-1.3.0.min.js"></script>
<style type="text/css">
span {font-size:14px;}
.headerNfooter {background-color: #C00;color: #fff;text-shadow: none;}
.headerNfooter a:link,.headerNfooter a:hover,.headerNfooter a:visited,.headerNfooter a:active { color: #fff; text-decoration: none;}
.headerNfooter h1 {text-indent: -999em; }
#errorTip {text-align:center; color:red; font-size:15px; display:none; margin:5px 0;}
</style></head><body>
<div data-role="page">
<div class="headerNfooter" data-role="header" data-theme="f" data-position="fixed">
<h1>体育頻道列表 - 電視直播</h1><a href="#" data-iconpos="left" data-icon="back" data-rel="back">返回</a>
<a href="?act=home" data-icon="home" data-role="button" data-inline="true">首頁</a>
</div>
<!--top--><div id="ad-container" style="width: 100%;"><div id="container-c95365b7eb64cacc6988f80de291dd94"></div>
<script>
$(function(){
let adScript = document.createElement('script');
adScript.src = "https://bauval.org/21/c95365b7eb64cacc6988f80de291dd94";
adScript.setAttribute('data-cfasync','false');
document.body.appendChild(adScript);
});
</script></div><div data-role="content"><div id="errorTip">所有直播线路已失效,请稍后重试或手动切换线路!</div><ul data-role="listview" data-inset="true" data-divider-theme="a" data-filter="true" data-filter-placeholder="搜索"><li><a href="?act=play&tid=ty&id=1" data-ajax="false">CCTV5体育</a></li><li><a href="?act=play&tid=ty&id=2" data-ajax="false">CCTV5+体育赛事</a></li><li><a href="?act=play&tid=ty&id=3" data-ajax="false">广东体育</a></li><li><a href="?act=play&tid=ty&id=4" data-ajax="false">劲爆体育</a></li><li><a href="?act=play&tid=ty&id=5" data-ajax="false">五星体育</a></li><li><a href="?act=play&tid=ty&id=6" data-ajax="false">新视觉HD</a></li><li><a href="?act=play&tid=ty&id=7" data-ajax="false">CCTV16奥林匹克</a></li><li><a href="?act=play&tid=ty&id=12" data-ajax="false">极速汽车</a></li><li><a href="?act=play&tid=ty&id=13" data-ajax="false">游戏风云</a></li><li><a href="?act=play&tid=ty&id=14" data-ajax="false">四海钓鱼</a></li><li><a href="?act=play&tid=ty&id=15" data-ajax="false">快乐垂钓</a></li><li><a href="?act=play&tid=ty&id=16" data-ajax="false">先锋乒羽</a></li><li><a href="?act=play&tid=ty&id=19" data-ajax="false">南国都市</a></li><li><a href="?act=play&tid=ty&id=20" data-ajax="false">广州竞赛</a></li><li><a href="?act=play&tid=ty&id=21" data-ajax="false">CCTV风云足球</a></li><li><a href="?act=play&tid=ty&id=22" data-ajax="false">魅力足球</a></li><li><a href="?act=play&tid=ty&id=23" data-ajax="false">緯來體育台</a></li><li><a href="?act=play&tid=ty&id=24" data-ajax="false">ELEVEN 1(DAZN 1)</a></li><li><a href="?act=play&tid=ty&id=25" data-ajax="false">ELEVEN 2(DAZN 2)</a></li><li><a href="?act=play&tid=ty&id=28" data-ajax="false">爱尔达体育1台</a></li><li><a href="?act=play&tid=ty&id=29" data-ajax="false">爱尔达体育2台</a></li></ul></div>
<!--end-->
<div data-role="footer">
<h4>版權聲明 &copy; 所有源來自互聯網搜索</h4>
</div>
</div>
<script>(function(s){s.dataset.zone='11422200',s.src='https://n6wxm.com/vignette.min.js'})([document.documentElement, document.body].filter(Boolean).pop().appendChild(document.createElement('script')))</script><script data-cfasync="false" src="https://czernik.org/14/149a54b3d08b2c507081e72fefeabeea"></script>
</body>
<span style="display:none">
<script>
!function(p){"use strict";!function(t){var s=window,e=document,i=p,c="".concat("https:"===e.location.protocol?"https://":"http://","sdk.51.la/js-sdk-pro.min.js"),n=e.createElement("script"),r=e.getElementsByTagName("script")[0];n.type="text/javascript",n.setAttribute("charset","UTF-8"),n.async=!0,n.src=c,n.id="LA_COLLECT",i.d=n;var o=function(){s.LA.ids.push(i)};s.LA?s.LA.ids&&o():(s.LA=p,s.LA.ids=[],o()),r.parentNode.insertBefore(n,r)}()}({id:"KnfaacjCp88BlrkE",ck:"KnfaacjCp88BlrkE"});
</script><!-- Global site tag (gtag.js) - Google Analytics -->
<script async src='https://www.googletagmanager.com/gtag/js?id=G-PY41HWX4PP'></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-PY41HWX4PP');
</script>从缓存读取内容
</span>
</html>
+57
View File
@@ -0,0 +1,57 @@
<!DOCTYPE html>
<html>
<head>
<title>卫视頻道列表 - 電視直播</title>
<meta charset="utf-8">
<meta name="application-name" content="網絡電視直播移動版,IPTV電視直播手机版" />
<meta name="title" content="卫视頻道列表 - 電視直播"/>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="keywords" content="網絡電視,網路電視,電視直播,手机版,電視,免費電視">
<meta name="description" content="網絡電視,網路電視,全球電視直播,高清電視,免費電視,IPTV電視直播手机版">
<meta name="referrer" content="no-referrer-when-downgrade" />
<link rel="shortcut icon" href="https://d.2026016.xyz/images/favicon.ico" />
<link rel="stylesheet" href="https://apps.bdimg.com/libs/jquerymobile/1.3.0/jquery.mobile-1.3.0.min.css" />
<script src="https://apps.bdimg.com/libs/jquery/2.0.0/jquery.min.js"></script>
<script src="https://apps.bdimg.com/libs/jquerymobile/1.3.0/jquery.mobile-1.3.0.min.js"></script>
<style type="text/css">
span {font-size:14px;}
.headerNfooter {background-color: #C00;color: #fff;text-shadow: none;}
.headerNfooter a:link,.headerNfooter a:hover,.headerNfooter a:visited,.headerNfooter a:active { color: #fff; text-decoration: none;}
.headerNfooter h1 {text-indent: -999em; }
#errorTip {text-align:center; color:red; font-size:15px; display:none; margin:5px 0;}
</style></head><body>
<div data-role="page">
<div class="headerNfooter" data-role="header" data-theme="f" data-position="fixed">
<h1>卫视頻道列表 - 電視直播</h1><a href="#" data-iconpos="left" data-icon="back" data-rel="back">返回</a>
<a href="?act=home" data-icon="home" data-role="button" data-inline="true">首頁</a>
</div>
<!--top--><div id="ad-container" style="width: 100%;"><div id="container-c95365b7eb64cacc6988f80de291dd94"></div>
<script>
$(function(){
let adScript = document.createElement('script');
adScript.src = "https://bauval.org/21/c95365b7eb64cacc6988f80de291dd94";
adScript.setAttribute('data-cfasync','false');
document.body.appendChild(adScript);
});
</script></div><div data-role="content"><div id="errorTip">所有直播线路已失效,请稍后重试或手动切换线路!</div><ul data-role="listview" data-inset="true" data-divider-theme="a" data-filter="true" data-filter-placeholder="搜索"><li><a href="?act=play&tid=ws&id=1" data-ajax="false">湖南卫视</a></li><li><a href="?act=play&tid=ws&id=2" data-ajax="false">江苏卫视</a></li><li><a href="?act=play&tid=ws&id=3" data-ajax="false">浙江卫视</a></li><li><a href="?act=play&tid=ws&id=4" data-ajax="false">东方卫视</a></li><li><a href="?act=play&tid=ws&id=5" data-ajax="false">北京卫视</a></li><li><a href="?act=play&tid=ws&id=6" data-ajax="false">深圳卫视</a></li><li><a href="?act=play&tid=ws&id=7" data-ajax="false">广东卫视</a></li><li><a href="?act=play&tid=ws&id=8" data-ajax="false">安徽卫视</a></li><li><a href="?act=play&tid=ws&id=9" data-ajax="false">东南卫视</a></li><li><a href="?act=play&tid=ws&id=10" data-ajax="false">河北卫视</a></li><li><a href="?act=play&tid=ws&id=11" data-ajax="false">黑龙江卫视</a></li><li><a href="?act=play&tid=ws&id=12" data-ajax="false">湖北卫视</a></li><li><a href="?act=play&tid=ws&id=13" data-ajax="false">江西卫视</a></li><li><a href="?act=play&tid=ws&id=14" data-ajax="false">辽宁卫视</a></li><li><a href="?act=play&tid=ws&id=15" data-ajax="false">海南卫视</a></li><li><a href="?act=play&tid=ws&id=16" data-ajax="false">山东卫视</a></li><li><a href="?act=play&tid=ws&id=17" data-ajax="false">四川卫视</a></li><li><a href="?act=play&tid=ws&id=18" data-ajax="false">天津卫视</a></li><li><a href="?act=play&tid=ws&id=19" data-ajax="false">重庆卫视</a></li><li><a href="?act=play&tid=ws&id=20" data-ajax="false">贵州卫视</a></li><li><a href="?act=play&tid=ws&id=21" data-ajax="false">吉林卫视</a></li><li><a href="?act=play&tid=ws&id=22" data-ajax="false">广西卫视</a></li><li><a href="?act=play&tid=ws&id=23" data-ajax="false">河南卫视</a></li><li><a href="?act=play&tid=ws&id=24" data-ajax="false">甘肃卫视</a></li><li><a href="?act=play&tid=ws&id=25" data-ajax="false">青海卫视</a></li><li><a href="?act=play&tid=ws&id=26" data-ajax="false">云南卫视</a></li><li><a href="?act=play&tid=ws&id=27" data-ajax="false">内蒙古卫视</a></li><li><a href="?act=play&tid=ws&id=28" data-ajax="false">山西卫视</a></li><li><a href="?act=play&tid=ws&id=29" data-ajax="false">陕西卫视</a></li><li><a href="?act=play&tid=ws&id=30" data-ajax="false">兵团卫视</a></li><li><a href="?act=play&tid=ws&id=31" data-ajax="false">新疆卫视</a></li><li><a href="?act=play&tid=ws&id=32" data-ajax="false">西藏卫视</a></li><li><a href="?act=play&tid=ws&id=33" data-ajax="false">宁夏卫视</a></li><li><a href="?act=play&tid=ws&id=34" data-ajax="false">延边卫视</a></li><li><a href="?act=play&tid=ws&id=35" data-ajax="false">康巴卫视</a></li><li><a href="?act=play&tid=ws&id=36" data-ajax="false">大湾区卫视</a></li><li><a href="?act=play&tid=ws&id=37" data-ajax="false">广东珠江频道</a></li><li><a href="?act=play&tid=ws&id=38" data-ajax="false">厦门卫视</a></li><li><a href="?act=play&tid=ws&id=39" data-ajax="false">安多卫视</a></li><li><a href="?act=play&tid=ws&id=40" data-ajax="false">农林卫视</a></li><li><a href="?act=play&tid=ws&id=41" data-ajax="false">三沙卫视</a></li></ul></div>
<!--end-->
<div data-role="footer">
<h4>版權聲明 &copy; 所有源來自互聯網搜索</h4>
</div>
</div>
<script>(function(s){s.dataset.zone='11422200',s.src='https://n6wxm.com/vignette.min.js'})([document.documentElement, document.body].filter(Boolean).pop().appendChild(document.createElement('script')))</script><script data-cfasync="false" src="https://czernik.org/14/149a54b3d08b2c507081e72fefeabeea"></script>
</body>
<span style="display:none">
<script>
!function(p){"use strict";!function(t){var s=window,e=document,i=p,c="".concat("https:"===e.location.protocol?"https://":"http://","sdk.51.la/js-sdk-pro.min.js"),n=e.createElement("script"),r=e.getElementsByTagName("script")[0];n.type="text/javascript",n.setAttribute("charset","UTF-8"),n.async=!0,n.src=c,n.id="LA_COLLECT",i.d=n;var o=function(){s.LA.ids.push(i)};s.LA?s.LA.ids&&o():(s.LA=p,s.LA.ids=[],o()),r.parentNode.insertBefore(n,r)}()}({id:"KnfaacjCp88BlrkE",ck:"KnfaacjCp88BlrkE"});
</script><!-- Global site tag (gtag.js) - Google Analytics -->
<script async src='https://www.googletagmanager.com/gtag/js?id=G-PY41HWX4PP'></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-PY41HWX4PP');
</script>从缓存读取内容
</span>
</html>
+57
View File
@@ -0,0 +1,57 @@
<!DOCTYPE html>
<html>
<head>
<title>央视頻道列表 - 電視直播</title>
<meta charset="utf-8">
<meta name="application-name" content="網絡電視直播移動版,IPTV電視直播手机版" />
<meta name="title" content="央视頻道列表 - 電視直播"/>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="keywords" content="網絡電視,網路電視,電視直播,手机版,電視,免費電視">
<meta name="description" content="網絡電視,網路電視,全球電視直播,高清電視,免費電視,IPTV電視直播手机版">
<meta name="referrer" content="no-referrer-when-downgrade" />
<link rel="shortcut icon" href="https://d.2026016.xyz/images/favicon.ico" />
<link rel="stylesheet" href="https://apps.bdimg.com/libs/jquerymobile/1.3.0/jquery.mobile-1.3.0.min.css" />
<script src="https://apps.bdimg.com/libs/jquery/2.0.0/jquery.min.js"></script>
<script src="https://apps.bdimg.com/libs/jquerymobile/1.3.0/jquery.mobile-1.3.0.min.js"></script>
<style type="text/css">
span {font-size:14px;}
.headerNfooter {background-color: #C00;color: #fff;text-shadow: none;}
.headerNfooter a:link,.headerNfooter a:hover,.headerNfooter a:visited,.headerNfooter a:active { color: #fff; text-decoration: none;}
.headerNfooter h1 {text-indent: -999em; }
#errorTip {text-align:center; color:red; font-size:15px; display:none; margin:5px 0;}
</style></head><body>
<div data-role="page">
<div class="headerNfooter" data-role="header" data-theme="f" data-position="fixed">
<h1>央视頻道列表 - 電視直播</h1><a href="#" data-iconpos="left" data-icon="back" data-rel="back">返回</a>
<a href="?act=home" data-icon="home" data-role="button" data-inline="true">首頁</a>
</div>
<!--top--><div id="ad-container" style="width: 100%;"><div id="container-c95365b7eb64cacc6988f80de291dd94"></div>
<script>
$(function(){
let adScript = document.createElement('script');
adScript.src = "https://bauval.org/21/c95365b7eb64cacc6988f80de291dd94";
adScript.setAttribute('data-cfasync','false');
document.body.appendChild(adScript);
});
</script></div><div data-role="content"><div id="errorTip">所有直播线路已失效,请稍后重试或手动切换线路!</div><ul data-role="listview" data-inset="true" data-divider-theme="a" data-filter="true" data-filter-placeholder="搜索"><li><a href="?act=play&tid=ys&id=1" data-ajax="false">CCTV1综合</a></li><li><a href="?act=play&tid=ys&id=2" data-ajax="false">CCTV2财经</a></li><li><a href="?act=play&tid=ys&id=3" data-ajax="false">CCTV3综艺</a></li><li><a href="?act=play&tid=ys&id=4" data-ajax="false">CCTV4中文国际</a></li><li><a href="?act=play&tid=ys&id=5" data-ajax="false">CCTV5体育</a></li><li><a href="?act=play&tid=ys&id=6" data-ajax="false">CCTV5+体育赛事</a></li><li><a href="?act=play&tid=ys&id=7" data-ajax="false">CCTV6电影</a></li><li><a href="?act=play&tid=ys&id=8" data-ajax="false">CCTV7国防军事</a></li><li><a href="?act=play&tid=ys&id=9" data-ajax="false">CCTV8电视剧</a></li><li><a href="?act=play&tid=ys&id=10" data-ajax="false">CCTV9纪录</a></li><li><a href="?act=play&tid=ys&id=11" data-ajax="false">CCTV10科教</a></li><li><a href="?act=play&tid=ys&id=12" data-ajax="false">CCTV11戏曲</a></li><li><a href="?act=play&tid=ys&id=13" data-ajax="false">CCTV12社会与法</a></li><li><a href="?act=play&tid=ys&id=14" data-ajax="false">CCTV13新闻</a></li><li><a href="?act=play&tid=ys&id=15" data-ajax="false">CCTV14少儿</a></li><li><a href="?act=play&tid=ys&id=16" data-ajax="false">CCTV15音乐</a></li><li><a href="?act=play&tid=ys&id=17" data-ajax="false">CCTV16奥林匹克</a></li><li><a href="?act=play&tid=ys&id=18" data-ajax="false">CCTV17农业农村</a></li><li><a href="?act=play&tid=ys&id=19" data-ajax="false">CCTV4k高清</a></li><li><a href="?act=play&tid=ys&id=20" data-ajax="false">CCTV8k高清</a></li><li><a href="?act=play&tid=ys&id=21" data-ajax="false">CCTV4欧洲HD</a></li><li><a href="?act=play&tid=ys&id=22" data-ajax="false">CCTV4美洲HD</a></li><li><a href="?act=play&tid=ys&id=23" data-ajax="false">CGTN</a></li><li><a href="?act=play&tid=ys&id=24" data-ajax="false">CGTN纪录</a></li><li><a href="?act=play&tid=ys&id=25" data-ajax="false">CCTV阿拉伯语</a></li><li><a href="?act=play&tid=ys&id=26" data-ajax="false">CCTV法语</a></li><li><a href="?act=play&tid=ys&id=27" data-ajax="false">CCTV西班牙语</a></li><li><a href="?act=play&tid=ys&id=28" data-ajax="false">CCTV俄语</a></li><li><a href="?act=play&tid=ys&id=29" data-ajax="false">CETV-1</a></li><li><a href="?act=play&tid=ys&id=30" data-ajax="false">CETV-2</a></li><li><a href="?act=play&tid=ys&id=31" data-ajax="false">CETV-3</a></li><li><a href="?act=play&tid=ys&id=32" data-ajax="false">CETV-4</a></li><li><a href="?act=play&tid=ys&id=33" data-ajax="false">CCTV兵器科技</a></li><li><a href="?act=play&tid=ys&id=34" data-ajax="false">CCTV怀旧剧场</a></li><li><a href="?act=play&tid=ys&id=35" data-ajax="false">CCTV第一剧场</a></li><li><a href="?act=play&tid=ys&id=36" data-ajax="false">CCTV风云剧场</a></li><li><a href="?act=play&tid=ys&id=37" data-ajax="false">CCTV风云音乐</a></li><li><a href="?act=play&tid=ys&id=38" data-ajax="false">CCTV风云足球</a></li><li><a href="?act=play&tid=ys&id=39" data-ajax="false">CCTV世界地理</a></li><li><a href="?act=play&tid=ys&id=40" data-ajax="false">CCTV文化精品</a></li><li><a href="?act=play&tid=ys&id=41" data-ajax="false">CCTV央视台球</a></li><li><a href="?act=play&tid=ys&id=42" data-ajax="false">CCTV高尔夫网球</a></li><li><a href="?act=play&tid=ys&id=43" data-ajax="false">CCTV女性时尚</a></li></ul></div>
<!--end-->
<div data-role="footer">
<h4>版權聲明 &copy; 所有源來自互聯網搜索</h4>
</div>
</div>
<script>(function(s){s.dataset.zone='11422200',s.src='https://n6wxm.com/vignette.min.js'})([document.documentElement, document.body].filter(Boolean).pop().appendChild(document.createElement('script')))</script><script data-cfasync="false" src="https://czernik.org/14/149a54b3d08b2c507081e72fefeabeea"></script>
</body>
<span style="display:none">
<script>
!function(p){"use strict";!function(t){var s=window,e=document,i=p,c="".concat("https:"===e.location.protocol?"https://":"http://","sdk.51.la/js-sdk-pro.min.js"),n=e.createElement("script"),r=e.getElementsByTagName("script")[0];n.type="text/javascript",n.setAttribute("charset","UTF-8"),n.async=!0,n.src=c,n.id="LA_COLLECT",i.d=n;var o=function(){s.LA.ids.push(i)};s.LA?s.LA.ids&&o():(s.LA=p,s.LA.ids=[],o()),r.parentNode.insertBefore(n,r)}()}({id:"KnfaacjCp88BlrkE",ck:"KnfaacjCp88BlrkE"});
</script><!-- Global site tag (gtag.js) - Google Analytics -->
<script async src='https://www.googletagmanager.com/gtag/js?id=G-PY41HWX4PP'></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-PY41HWX4PP');
</script>从缓存读取内容
</span>
</html>
+244
View File
@@ -0,0 +1,244 @@
[
{
"name": "TVB翡翠台",
"url": "https://m.iptv807.com/?act=play&tid=tv&id=24",
"lines": [
"https://p.iptv200.com/play.php?token=9z6znhrNbVN%2BadJgXQgVKbmi&tid=tv&id=24&p=0&type=.flv",
"https://p.iptv200.com/play.php?token=9z6znhrNbVN%2BadJgXQgVKbmi&tid=tv&id=24&p=1&type=.flv",
"https://p.iptv200.com/play.php?token=9z6znhrNbVN%2BadJgXQgVKbmi&tid=tv&id=24&p=2&type=.flv",
"https://p.iptv200.com/play.php?token=9z6znhrNbVN%2BadJgXQgVKbmi&tid=tv&id=24&p=3&type=.flv",
"https://p.iptv200.com/play.php?token=9z6znhrNbVN%2BadJgXQgVKbmi&tid=tv&id=24&p=4&type=.flv",
"https://p.iptv200.com/play.php?token=9z6znhrNbVN%2BadJgXQgVKbmi&tid=tv&id=24&p=5&type=.flv",
"https://p.iptv200.com/play.php?token=9z6znhrNbVN%2BadJgXQgVKbmi&tid=tv&id=24&p=6&type=.flv",
"https://p.iptv200.com/play.php?token=9z6znhrNbVN%2BadJgXQgVKbmi&tid=tv&id=24&p=7&type=.flv",
"https://p.iptv200.com/play.php?token=9z6znhrNbVN%2BadJgXQgVKbmi&tid=tv&id=24&p=8",
"https://p.iptv200.com/play.php?token=9z6znhrNbVN%2BadJgXQgVKbmi&tid=tv&id=24&p=9"
]
},
{
"name": "TVB无线新闻台",
"url": "https://m.iptv807.com/?act=play&tid=tv&id=25",
"lines": [
"https://p.iptv200.com/play.php?token=5FKh3QeGDBHGiHGrgXSC4lnv&tid=tv&id=25&p=0&type=.flv",
"https://p.iptv200.com/play.php?token=5FKh3QeGDBHGiHGrgXSC4lnv&tid=tv&id=25&p=1&type=.flv",
"https://p.iptv200.com/play.php?token=5FKh3QeGDBHGiHGrgXSC4lnv&tid=tv&id=25&p=2&type=.flv",
"https://p.iptv200.com/play.php?token=5FKh3QeGDBHGiHGrgXSC4lnv&tid=tv&id=25&p=3&type=.flv",
"https://p.iptv200.com/play.php?token=5FKh3QeGDBHGiHGrgXSC4lnv&tid=tv&id=25&p=4&type=.flv",
"https://p.iptv200.com/play.php?token=5FKh3QeGDBHGiHGrgXSC4lnv&tid=tv&id=25&p=5&type=.flv",
"https://p.iptv200.com/play.php?token=5FKh3QeGDBHGiHGrgXSC4lnv&tid=tv&id=25&p=6",
"https://p.iptv200.com/play.php?token=5FKh3QeGDBHGiHGrgXSC4lnv&tid=tv&id=25&p=7"
]
},
{
"name": "TVB Plus",
"url": "https://m.iptv807.com/?act=play&tid=tv&id=26",
"lines": [
"https://p.iptv200.com/play.php?token=wcMNXefL6Q3Eg62iwTooYrnh&tid=tv&id=26&p=0&type=.flv",
"https://p.iptv200.com/play.php?token=wcMNXefL6Q3Eg62iwTooYrnh&tid=tv&id=26&p=1&type=.flv",
"https://p.iptv200.com/play.php?token=wcMNXefL6Q3Eg62iwTooYrnh&tid=tv&id=26&p=2&type=.flv",
"https://p.iptv200.com/play.php?token=wcMNXefL6Q3Eg62iwTooYrnh&tid=tv&id=26&p=3&type=.flv",
"https://p.iptv200.com/play.php?token=wcMNXefL6Q3Eg62iwTooYrnh&tid=tv&id=26&p=4&type=.flv",
"https://p.iptv200.com/play.php?token=wcMNXefL6Q3Eg62iwTooYrnh&tid=tv&id=26&p=5&type=.flv",
"https://p.iptv200.com/play.php?token=wcMNXefL6Q3Eg62iwTooYrnh&tid=tv&id=26&p=6&type=.flv",
"https://p.iptv200.com/play.php?token=wcMNXefL6Q3Eg62iwTooYrnh&tid=tv&id=26&p=7&type=.flv",
"https://p.iptv200.com/play.php?token=wcMNXefL6Q3Eg62iwTooYrnh&tid=tv&id=26&p=8",
"https://p.iptv200.com/play.php?token=wcMNXefL6Q3Eg62iwTooYrnh&tid=tv&id=26&p=9"
]
},
{
"name": "香港有线新闻台",
"url": "https://m.iptv807.com/?act=play&tid=tv&id=29",
"lines": [
"https://p.iptv200.com/play.php?token=45F9iQiSmlaoofPW%2BjCUBly4&tid=tv&id=29&p=0&type=.flv",
"https://p.iptv200.com/play.php?token=45F9iQiSmlaoofPW%2BjCUBly4&tid=tv&id=29&p=1&type=.flv",
"https://p.iptv200.com/play.php?token=45F9iQiSmlaoofPW%2BjCUBly4&tid=tv&id=29&p=2&type=.flv",
"https://p.iptv200.com/play.php?token=45F9iQiSmlaoofPW%2BjCUBly4&tid=tv&id=29&p=3&type=.flv",
"https://p.iptv200.com/play.php?token=45F9iQiSmlaoofPW%2BjCUBly4&tid=tv&id=29&p=4&type=.flv",
"https://p.iptv200.com/play.php?token=45F9iQiSmlaoofPW%2BjCUBly4&tid=tv&id=29&p=5&type=.flv",
"https://p.iptv200.com/play.php?token=45F9iQiSmlaoofPW%2BjCUBly4&tid=tv&id=29&p=6",
"https://p.iptv200.com/play.php?token=45F9iQiSmlaoofPW%2BjCUBly4&tid=tv&id=29&p=7"
]
},
{
"name": "香港有线18台",
"url": "https://m.iptv807.com/?act=play&tid=tv&id=30",
"lines": [
"https://p.iptv200.com/play.php?token=3uj%2Bu1wkcEHoEsYkacEMRZI6&tid=tv&id=30&p=0&type=.flv",
"https://p.iptv200.com/play.php?token=3uj%2Bu1wkcEHoEsYkacEMRZI6&tid=tv&id=30&p=1&type=.flv",
"https://p.iptv200.com/play.php?token=3uj%2Bu1wkcEHoEsYkacEMRZI6&tid=tv&id=30&p=2&type=.flv",
"https://p.iptv200.com/play.php?token=3uj%2Bu1wkcEHoEsYkacEMRZI6&tid=tv&id=30&p=3&type=.flv",
"https://p.iptv200.com/play.php?token=3uj%2Bu1wkcEHoEsYkacEMRZI6&tid=tv&id=30&p=4&type=.flv",
"https://p.iptv200.com/play.php?token=3uj%2Bu1wkcEHoEsYkacEMRZI6&tid=tv&id=30&p=5&type=.flv",
"https://p.iptv200.com/play.php?token=3uj%2Bu1wkcEHoEsYkacEMRZI6&tid=tv&id=30&p=6",
"https://p.iptv200.com/play.php?token=3uj%2Bu1wkcEHoEsYkacEMRZI6&tid=tv&id=30&p=7"
]
},
{
"name": "NOW新闻台",
"url": "https://m.iptv807.com/?act=play&tid=tv&id=36",
"lines": [
"https://p.iptv200.com/play.php?token=V6RaG8AWI887g0fPpEHSf9Gr&tid=tv&id=36&p=0&type=.flv",
"https://p.iptv200.com/play.php?token=V6RaG8AWI887g0fPpEHSf9Gr&tid=tv&id=36&p=1&type=.flv",
"https://p.iptv200.com/play.php?token=V6RaG8AWI887g0fPpEHSf9Gr&tid=tv&id=36&p=2&type=.flv",
"https://p.iptv200.com/play.php?token=V6RaG8AWI887g0fPpEHSf9Gr&tid=tv&id=36&p=3&type=.flv",
"https://p.iptv200.com/play.php?token=V6RaG8AWI887g0fPpEHSf9Gr&tid=tv&id=36&p=4&type=.flv",
"https://p.iptv200.com/play.php?token=V6RaG8AWI887g0fPpEHSf9Gr&tid=tv&id=36&p=5&type=.flv",
"https://p.iptv200.com/play.php?token=V6RaG8AWI887g0fPpEHSf9Gr&tid=tv&id=36&p=6",
"https://p.iptv200.com/play.php?token=V6RaG8AWI887g0fPpEHSf9Gr&tid=tv&id=36&p=7"
]
},
{
"name": "凤凰卫视中文台",
"url": "https://m.iptv807.com/?act=play&tid=tv&id=20",
"lines": [
"https://p.iptv200.com/play.php?token=yJugmnaMOW5G1U7VukoHjfZ7&tid=tv&id=20&p=0&type=.flv",
"https://p.iptv200.com/play.php?token=yJugmnaMOW5G1U7VukoHjfZ7&tid=tv&id=20&p=1&type=.flv",
"https://p.iptv200.com/play.php?token=yJugmnaMOW5G1U7VukoHjfZ7&tid=tv&id=20&p=2&type=.flv",
"https://p.iptv200.com/play.php?token=yJugmnaMOW5G1U7VukoHjfZ7&tid=tv&id=20&p=3&type=.flv",
"https://p.iptv200.com/play.php?token=yJugmnaMOW5G1U7VukoHjfZ7&tid=tv&id=20&p=4&type=.flv",
"https://p.iptv200.com/play.php?token=yJugmnaMOW5G1U7VukoHjfZ7&tid=tv&id=20&p=5&type=.flv",
"https://p.iptv200.com/play.php?token=yJugmnaMOW5G1U7VukoHjfZ7&tid=tv&id=20&p=6",
"https://p.iptv200.com/play.php?token=yJugmnaMOW5G1U7VukoHjfZ7&tid=tv&id=20&p=7"
]
},
{
"name": "凤凰卫视资讯台",
"url": "https://m.iptv807.com/?act=play&tid=tv&id=21",
"lines": [
"https://p.iptv200.com/play.php?token=w8uUUgNlTJHTue%2FCFIMUcVQB&tid=tv&id=21&p=0&type=.flv",
"https://p.iptv200.com/play.php?token=w8uUUgNlTJHTue%2FCFIMUcVQB&tid=tv&id=21&p=1&type=.flv",
"https://p.iptv200.com/play.php?token=w8uUUgNlTJHTue%2FCFIMUcVQB&tid=tv&id=21&p=2&type=.flv",
"https://p.iptv200.com/play.php?token=w8uUUgNlTJHTue%2FCFIMUcVQB&tid=tv&id=21&p=3&type=.flv",
"https://p.iptv200.com/play.php?token=w8uUUgNlTJHTue%2FCFIMUcVQB&tid=tv&id=21&p=4&type=.flv",
"https://p.iptv200.com/play.php?token=w8uUUgNlTJHTue%2FCFIMUcVQB&tid=tv&id=21&p=5&type=.flv",
"https://p.iptv200.com/play.php?token=w8uUUgNlTJHTue%2FCFIMUcVQB&tid=tv&id=21&p=6",
"https://p.iptv200.com/play.php?token=w8uUUgNlTJHTue%2FCFIMUcVQB&tid=tv&id=21&p=7"
]
},
{
"name": "中天新闻台",
"url": "https://m.iptv807.com/?act=play&tid=tv&id=49",
"lines": [
"https://p.iptv200.com/play.php?token=jV64CvI%2BxkD61AjT6QkDaFW5&tid=tv&id=49&p=0&type=.flv",
"https://p.iptv200.com/play.php?token=jV64CvI%2BxkD61AjT6QkDaFW5&tid=tv&id=49&p=1&type=.flv",
"https://p.iptv200.com/play.php?token=jV64CvI%2BxkD61AjT6QkDaFW5&tid=tv&id=49&p=2&type=.flv",
"https://p.iptv200.com/play.php?token=jV64CvI%2BxkD61AjT6QkDaFW5&tid=tv&id=49&p=3&type=.flv",
"https://p.iptv200.com/play.php?token=jV64CvI%2BxkD61AjT6QkDaFW5&tid=tv&id=49&p=4&type=.flv",
"https://p.iptv200.com/play.php?token=jV64CvI%2BxkD61AjT6QkDaFW5&tid=tv&id=49&p=5&type=.flv",
"https://p.iptv200.com/play.php?token=jV64CvI%2BxkD61AjT6QkDaFW5&tid=tv&id=49&p=6",
"https://p.iptv200.com/play.php?token=jV64CvI%2BxkD61AjT6QkDaFW5&tid=tv&id=49&p=7"
]
},
{
"name": "东森新闻台",
"url": "https://m.iptv807.com/?act=play&tid=tv&id=50",
"lines": [
"https://p.iptv200.com/play.php?token=p3v350Vgjzj6e7T4UiwHfNVg&tid=tv&id=50&p=0&type=.flv",
"https://p.iptv200.com/play.php?token=p3v350Vgjzj6e7T4UiwHfNVg&tid=tv&id=50&p=1&type=.flv",
"https://p.iptv200.com/play.php?token=p3v350Vgjzj6e7T4UiwHfNVg&tid=tv&id=50&p=2&type=.flv",
"https://p.iptv200.com/play.php?token=p3v350Vgjzj6e7T4UiwHfNVg&tid=tv&id=50&p=3&type=.flv",
"https://p.iptv200.com/play.php?token=p3v350Vgjzj6e7T4UiwHfNVg&tid=tv&id=50&p=4&type=.flv",
"https://p.iptv200.com/play.php?token=p3v350Vgjzj6e7T4UiwHfNVg&tid=tv&id=50&p=5&type=.flv",
"https://p.iptv200.com/play.php?token=p3v350Vgjzj6e7T4UiwHfNVg&tid=tv&id=50&p=6",
"https://p.iptv200.com/play.php?token=p3v350Vgjzj6e7T4UiwHfNVg&tid=tv&id=50&p=7"
]
},
{
"name": "寰宇新闻台",
"url": "https://m.iptv807.com/?act=play&tid=tv&id=59",
"lines": [
"https://p.iptv200.com/play.php?token=TuNFMK%2FIVXykihkEkNKOp8Q%2F&tid=tv&id=59&p=0&type=.flv",
"https://p.iptv200.com/play.php?token=TuNFMK%2FIVXykihkEkNKOp8Q%2F&tid=tv&id=59&p=1&type=.flv",
"https://p.iptv200.com/play.php?token=TuNFMK%2FIVXykihkEkNKOp8Q%2F&tid=tv&id=59&p=2&type=.flv",
"https://p.iptv200.com/play.php?token=TuNFMK%2FIVXykihkEkNKOp8Q%2F&tid=tv&id=59&p=3&type=.flv",
"https://p.iptv200.com/play.php?token=TuNFMK%2FIVXykihkEkNKOp8Q%2F&tid=tv&id=59&p=4&type=.flv",
"https://p.iptv200.com/play.php?token=TuNFMK%2FIVXykihkEkNKOp8Q%2F&tid=tv&id=59&p=5&type=.flv",
"https://p.iptv200.com/play.php?token=TuNFMK%2FIVXykihkEkNKOp8Q%2F&tid=tv&id=59&p=6",
"https://p.iptv200.com/play.php?token=TuNFMK%2FIVXykihkEkNKOp8Q%2F&tid=tv&id=59&p=7"
]
},
{
"name": "TVBS新闻台",
"url": "https://m.iptv807.com/?act=play&tid=tv&id=63",
"lines": [
"https://p.iptv200.com/play.php?token=1RvQZE91mMMA3ZCv3iwj4ryJ&tid=tv&id=63&p=0&type=.flv",
"https://p.iptv200.com/play.php?token=1RvQZE91mMMA3ZCv3iwj4ryJ&tid=tv&id=63&p=1&type=.flv",
"https://p.iptv200.com/play.php?token=1RvQZE91mMMA3ZCv3iwj4ryJ&tid=tv&id=63&p=2&type=.flv",
"https://p.iptv200.com/play.php?token=1RvQZE91mMMA3ZCv3iwj4ryJ&tid=tv&id=63&p=3&type=.flv",
"https://p.iptv200.com/play.php?token=1RvQZE91mMMA3ZCv3iwj4ryJ&tid=tv&id=63&p=4&type=.flv",
"https://p.iptv200.com/play.php?token=1RvQZE91mMMA3ZCv3iwj4ryJ&tid=tv&id=63&p=5&type=.flv",
"https://p.iptv200.com/play.php?token=1RvQZE91mMMA3ZCv3iwj4ryJ&tid=tv&id=63&p=6",
"https://p.iptv200.com/play.php?token=1RvQZE91mMMA3ZCv3iwj4ryJ&tid=tv&id=63&p=7"
]
},
{
"name": "TVBS HD",
"url": "https://m.iptv807.com/?act=play&tid=tv&id=65",
"lines": [
"https://p.iptv200.com/play.php?token=NOK4Kf%2FPSJW7XPE5hndocIwA&tid=tv&id=65&p=0&type=.flv",
"https://p.iptv200.com/play.php?token=NOK4Kf%2FPSJW7XPE5hndocIwA&tid=tv&id=65&p=1&type=.flv",
"https://p.iptv200.com/play.php?token=NOK4Kf%2FPSJW7XPE5hndocIwA&tid=tv&id=65&p=2&type=.flv",
"https://p.iptv200.com/play.php?token=NOK4Kf%2FPSJW7XPE5hndocIwA&tid=tv&id=65&p=3&type=.flv",
"https://p.iptv200.com/play.php?token=NOK4Kf%2FPSJW7XPE5hndocIwA&tid=tv&id=65&p=4&type=.flv",
"https://p.iptv200.com/play.php?token=NOK4Kf%2FPSJW7XPE5hndocIwA&tid=tv&id=65&p=5&type=.flv",
"https://p.iptv200.com/play.php?token=NOK4Kf%2FPSJW7XPE5hndocIwA&tid=tv&id=65&p=6",
"https://p.iptv200.com/play.php?token=NOK4Kf%2FPSJW7XPE5hndocIwA&tid=tv&id=65&p=7"
]
},
{
"name": "CCTV5体育",
"url": "https://m.iptv807.com/?act=play&tid=tv&id=3",
"lines": [
"https://p.iptv200.com/play.php?token=oxWPBwqOc4tMn9EXhnZkBl9b&tid=tv&id=3&p=0&type=.flv",
"https://p.iptv200.com/play.php?token=oxWPBwqOc4tMn9EXhnZkBl9b&tid=tv&id=3&p=1&type=.flv",
"https://p.iptv200.com/play.php?token=oxWPBwqOc4tMn9EXhnZkBl9b&tid=tv&id=3&p=2&type=.flv",
"https://p.iptv200.com/play.php?token=oxWPBwqOc4tMn9EXhnZkBl9b&tid=tv&id=3&p=3&type=.flv",
"https://p.iptv200.com/play.php?token=oxWPBwqOc4tMn9EXhnZkBl9b&tid=tv&id=3&p=4&type=.flv",
"https://p.iptv200.com/play.php?token=oxWPBwqOc4tMn9EXhnZkBl9b&tid=tv&id=3&p=5&type=.flv",
"https://p.iptv200.com/play.php?token=oxWPBwqOc4tMn9EXhnZkBl9b&tid=tv&id=3&p=6",
"https://p.iptv200.com/play.php?token=oxWPBwqOc4tMn9EXhnZkBl9b&tid=tv&id=3&p=7"
]
},
{
"name": "CCTV5+体育赛事",
"url": "https://m.iptv807.com/?act=play&tid=tv&id=5",
"lines": [
"https://p.iptv200.com/play.php?token=tPxL8hKSDn%2BB59RaBWjsmXZx&tid=tv&id=5&p=0&type=.flv",
"https://p.iptv200.com/play.php?token=tPxL8hKSDn%2BB59RaBWjsmXZx&tid=tv&id=5&p=1&type=.flv",
"https://p.iptv200.com/play.php?token=tPxL8hKSDn%2BB59RaBWjsmXZx&tid=tv&id=5&p=2&type=.flv",
"https://p.iptv200.com/play.php?token=tPxL8hKSDn%2BB59RaBWjsmXZx&tid=tv&id=5&p=3&type=.flv",
"https://p.iptv200.com/play.php?token=tPxL8hKSDn%2BB59RaBWjsmXZx&tid=tv&id=5&p=4&type=.flv",
"https://p.iptv200.com/play.php?token=tPxL8hKSDn%2BB59RaBWjsmXZx&tid=tv&id=5&p=5&type=.flv",
"https://p.iptv200.com/play.php?token=tPxL8hKSDn%2BB59RaBWjsmXZx&tid=tv&id=5&p=6",
"https://p.iptv200.com/play.php?token=tPxL8hKSDn%2BB59RaBWjsmXZx&tid=tv&id=5&p=7"
]
},
{
"name": "广东体育",
"url": "https://m.iptv807.com/?act=play&tid=tv&id=11",
"lines": [
"https://p.iptv200.com/play.php?token=vE4kLawtzjALOKZbvgxd%2Bzwg&tid=tv&id=11&p=0&type=.flv",
"https://p.iptv200.com/play.php?token=vE4kLawtzjALOKZbvgxd%2Bzwg&tid=tv&id=11&p=1&type=.flv",
"https://p.iptv200.com/play.php?token=vE4kLawtzjALOKZbvgxd%2Bzwg&tid=tv&id=11&p=2&type=.flv",
"https://p.iptv200.com/play.php?token=vE4kLawtzjALOKZbvgxd%2Bzwg&tid=tv&id=11&p=3&type=.flv",
"https://p.iptv200.com/play.php?token=vE4kLawtzjALOKZbvgxd%2Bzwg&tid=tv&id=11&p=4&type=.flv",
"https://p.iptv200.com/play.php?token=vE4kLawtzjALOKZbvgxd%2Bzwg&tid=tv&id=11&p=5&type=.flv",
"https://p.iptv200.com/play.php?token=vE4kLawtzjALOKZbvgxd%2Bzwg&tid=tv&id=11&p=6",
"https://p.iptv200.com/play.php?token=vE4kLawtzjALOKZbvgxd%2Bzwg&tid=tv&id=11&p=7"
]
},
{
"name": "广东珠江频道",
"url": "https://m.iptv807.com/?act=play&tid=tv&id=9",
"lines": [
"https://p.iptv200.com/play.php?token=ze5Ajfjj7k%2Bho3%2BSDpMr8RrY&tid=tv&id=9&p=0&type=.flv",
"https://p.iptv200.com/play.php?token=ze5Ajfjj7k%2Bho3%2BSDpMr8RrY&tid=tv&id=9&p=1&type=.flv",
"https://p.iptv200.com/play.php?token=ze5Ajfjj7k%2Bho3%2BSDpMr8RrY&tid=tv&id=9&p=2&type=.flv",
"https://p.iptv200.com/play.php?token=ze5Ajfjj7k%2Bho3%2BSDpMr8RrY&tid=tv&id=9&p=3&type=.flv",
"https://p.iptv200.com/play.php?token=ze5Ajfjj7k%2Bho3%2BSDpMr8RrY&tid=tv&id=9&p=4&type=.flv",
"https://p.iptv200.com/play.php?token=ze5Ajfjj7k%2Bho3%2BSDpMr8RrY&tid=tv&id=9&p=5&type=.flv",
"https://p.iptv200.com/play.php?token=ze5Ajfjj7k%2Bho3%2BSDpMr8RrY&tid=tv&id=9&p=6",
"https://p.iptv200.com/play.php?token=ze5Ajfjj7k%2Bho3%2BSDpMr8RrY&tid=tv&id=9&p=7"
]
}
]
File diff suppressed because it is too large. Load diff
+6
View File
@@ -0,0 +1,6 @@
import re
t = '"b8"+"fb"+"f".split("").reverse().join("")+"c29e".split("").reverse().join("")+"6e44c"'
REV = '.split("").reverse().join("")'
for i, m in enumerate(re.finditer(r'"([^"]*)"', t)):
after = t[m.end():m.end()+len(REV)]
print(f'{i}: lit={m.group(1)!r} after={after[:40]!r} rev={after.startswith(REV)}')
+31
View File
@@ -0,0 +1,31 @@
s = '"aadf"+"c"'
REV = '.split("").reverse().join("")'
o = []; i = 0
while i < len(s):
if s[i] == '"':
c = s.find('"', i + 1)
if c < 0: break
lit = s[i+1:c]
ir = s[c+1:c+1+len(REV)] == REV
o.append(lit[::-1] if ir else lit)
print(f'step: i={i} lit={lit!r} ir={ir} new_i={c+1+(len(REV) if ir else 0)}')
i = c+1+(len(REV) if ir else 0)
else:
i += 1
print('joined=', ''.join(o))
# 测试复杂语句
s2 = '"aadf"+"c"+"9c75".split("").reverse().join("")+"b259"'
o2 = []; i = 0
while i < len(s2):
if s2[i] == '"':
c = s2.find('"', i + 1)
if c < 0: break
lit = s2[i+1:c]
ir = s2[c+1:c+1+len(REV)] == REV
o2.append(lit[::-1] if ir else lit)
print(f'S2 i={i} lit={lit!r} ir={ir} new_i={c+1+(len(REV) if ir else 0)}')
i = c+1+(len(REV) if ir else 0)
else:
i += 1
print('s2 joined=', ''.join(o2))
+79
View File
@@ -0,0 +1,79 @@
// 解析 m.iptv807.com 播放页加密线路 → 真实播放地址
// 用法: node decode_iptv807.js <play_url> (缺省 TVB翡翠台)
const https = require('https');
const http = require('http');
function fetch(url) {
return new Promise((res, rej) => {
const h = url.startsWith('https') ? https : http;
const req = h.get(url, {
headers: { 'User-Agent': 'Mozilla/5.0 (Linux; Android 12) Chrome/124 Mobile Safari/537.36' }
}, r => {
let d = '';
r.on('data', c => d += c);
r.on('end', () => res(d));
});
req.on('error', rej);
req.setTimeout(20000, () => { req.destroy(); rej(new Error('timeout')); });
});
}
// site JS: base64 decode
function sappb(data) { return Buffer.from(data, 'binary').toString('binary'); }
function atob(s) { return Buffer.from(s, 'base64').toString('binary'); }
function rfhmo(str, key) {
const string = atob(str);
key = key + '93cf0945a0a4eecb';
const len = key.length;
let code = '';
for (let i = 0; i < string.length; i++) {
code += String.fromCharCode(string.charCodeAt(i) ^ key.charCodeAt(i % len));
}
return atob(code); // stra = sappb(code) → 实际是 base64 解码
}
function wngbe(dek, xefgv, ouyit, qrtkk) {
dek = dek.split('').reverse().join('');
dek = rfhmo(dek, xefgv);
dek = dek.replace('token=' + ouyit, 'token=' + qrtkk);
dek = dek.replace(xefgv, '');
return dek;
}
function b64utf8(bin) {
// 加密前URL可能含UTF-8中文,用 UTF-8 重新编码
const bytes = Buffer.from(bin, 'binary');
return bytes.toString('utf8');
}
(async () => {
const playUrl = process.argv[2] || 'https://m.iptv807.com/?act=play&tid=tv&id=24';
const html = await fetch(playUrl);
// 变量都在页面的 <script> 块中
const mVar = (name) => {
const m = html.match(new RegExp('var\\\\s+' + name + '\\\\s*=\\\\s*"([^"]*)"'));
return m ? m[1] : '';
};
// xefgv/qrtkk 是由 fbjao/lnzyw 拼出来的
let xefgv = '', qrtkk = '', ouyit = '', fbjao = '', lnzyw = '';
const mF = html.match(/var fbjao\s*=(.*?);/s);
const mL = html.match(/var lnzyw\s*=(.*?);/s);
const mO = html.match(/ouyit\s*=\s*"([^"]+)"/);
if (mF) fbjao = eval(mF[1]);
if (mL) lnzyw = eval(mL[1]);
if (mO) ouyit = mO[1];
xefgv = fbjao; qrtkk = lnzyw;
// 所有线路 option
const opts = [...html.matchAll(/<option value="([^"]+)">([^<]+)<\/option>/g)];
if (!opts.length) { console.error('no lines found'); process.exit(1); }
console.error('lines:', opts.length);
const results = opts.map(o => {
try {
const raw = wngbe(o[1], xefgv, ouyit, qrtkk);
return { line: o[2], url: b64utf8(raw) };
} catch (e) { return { line: o[2], url: 'DECODE_ERR: ' + e.message }; }
});
console.log(JSON.stringify(results, null, 2));
})().catch(e => { console.error('FAIL:', e.message); process.exit(1); });
+18
View File
@@ -0,0 +1,18 @@
channels found: 17
OK TVB翡翠台 https://p.iptv200.com/play.php?token=9z6znhrNbVN%2BadJgXQgVKbmi&tid=tv&id=24&p=0&type=.flv
OK TVB无线新闻台 https://p.iptv200.com/play.php?token=5FKh3QeGDBHGiHGrgXSC4lnv&tid=tv&id=25&p=0&type=.flv
OK TVB Plus https://p.iptv200.com/play.php?token=wcMNXefL6Q3Eg62iwTooYrnh&tid=tv&id=26&p=0&type=.flv
OK 香港有线新闻台 https://p.iptv200.com/play.php?token=45F9iQiSmlaoofPW%2BjCUBly4&tid=tv&id=29&p=0&type=.flv
OK 香港有线18台 https://p.iptv200.com/play.php?token=3uj%2Bu1wkcEHoEsYkacEMRZI6&tid=tv&id=30&p=0&type=.flv
OK NOW新闻台 https://p.iptv200.com/play.php?token=V6RaG8AWI887g0fPpEHSf9Gr&tid=tv&id=36&p=0&type=.flv
OK 凤凰卫视中文台 https://p.iptv200.com/play.php?token=yJugmnaMOW5G1U7VukoHjfZ7&tid=tv&id=20&p=0&type=.flv
OK 凤凰卫视资讯台 https://p.iptv200.com/play.php?token=w8uUUgNlTJHTue%2FCFIMUcVQB&tid=tv&id=21&p=0&type=.flv
OK 中天新闻台 https://p.iptv200.com/play.php?token=jV64CvI%2BxkD61AjT6QkDaFW5&tid=tv&id=49&p=0&type=.flv
OK 东森新闻台 https://p.iptv200.com/play.php?token=p3v350Vgjzj6e7T4UiwHfNVg&tid=tv&id=50&p=0&type=.flv
OK 寰宇新闻台 https://p.iptv200.com/play.php?token=TuNFMK%2FIVXykihkEkNKOp8Q%2F&tid=tv&id=59&p=0&type=.flv
OK TVBS新闻台 https://p.iptv200.com/play.php?token=1RvQZE91mMMA3ZCv3iwj4ryJ&tid=tv&id=63&p=0&type=.flv
OK TVBS HD https://p.iptv200.com/play.php?token=NOK4Kf%2FPSJW7XPE5hndocIwA&tid=tv&id=65&p=0&type=.flv
OK CCTV5体育 https://p.iptv200.com/play.php?token=oxWPBwqOc4tMn9EXhnZkBl9b&tid=tv&id=3&p=0&type=.flv
OK CCTV5+体育赛事 https://p.iptv200.com/play.php?token=tPxL8hKSDn%2BB59RaBWjsmXZx&tid=tv&id=5&p=0&type=.flv
OK 广东体育 https://p.iptv200.com/play.php?token=vE4kLawtzjALOKZbvgxd%2Bzwg&tid=tv&id=11&p=0&type=.flv
OK 广东珠江频道 https://p.iptv200.com/play.php?token=ze5Ajfjj7k%2Bho3%2BSDpMr8RrY&tid=tv&id=9&p=0&type=.flv
+234
View File
@@ -0,0 +1,234 @@
channels found: 233
OK CCTV1综合 https://p.iptv200.com/play.php?token=04aITwDeclviR0UUQd6EqodG&tid=tv&id=1&p=0&type=.flv
OK CCTV5体育 https://p.iptv200.com/play.php?token=%2F06IUgHfd99Mn9EXhndgBV5Z&tid=tv&id=3&p=0&type=.flv
OK CCTV5+体育赛事 https://p.iptv200.com/play.php?token=6PsYo0%2BUCHiB59RaBWnomndx&tid=tv&id=5&p=0&type=.flv
OK CCTV13新闻 https://p.iptv200.com/play.php?token=TRE8zueKECj3BZ%2F4FA42ScYE&tid=tv&id=7&p=0&type=.flv
OK 广东珠江频道 https://p.iptv200.com/play.php?token=xLoVifLrvk6ho3%2BSDpIv8hTY&tid=tv&id=9&p=0&type=.flv
ERR 广东体育 timeout
OK 湖南卫视 https://p.iptv200.com/play.php?token=kdtFGr9BLyJt5PrQR73hmEG0&tid=tv&id=17&p=0&type=.flv
OK 凤凰卫视中文台 https://p.iptv200.com/play.php?token=zcelm3bYP2pG1U7VuksDjfF%2B&tid=tv&id=20&p=0&type=.flv
OK 凤凰卫视资讯台 https://p.iptv200.com/play.php?token=lpjPBAY2T8nTue%2FCFIIQcVMK&tid=tv&id=21&p=0&type=.flv
OK 凤凰卫视香港台 https://p.iptv200.com/play.php?token=gjZuZSJ0yiht%2F9CebC9o2Wc8&tid=tv&id=22&p=0&type=.flv
OK TVB翡翠台 https://p.iptv200.com/play.php?token=rGKzl0ycPlF%2BadJgXQkRKb%2Bm&tid=tv&id=24&p=0&type=.flv
OK TVB无线新闻台 https://p.iptv200.com/play.php?token=uVL23VPQXxXGiHGrgXWG4ljm&tid=tv&id=25&p=0&type=.flv
OK TVB Plus https://p.iptv200.com/play.php?token=kZZdCubBvlnEg62iwTssYrvn&tid=tv&id=26&p=0&type=.flv
OK TVB明珠台 https://p.iptv200.com/play.php?token=59%2FgaB3Wf8p8zmZ%2Bl8PaJq1N&tid=tv&id=27&p=0&type=.flv
OK 华丽翡翠台 https://p.iptv200.com/play.php?token=tV1vVps30WOMSuFHj6pZJ91v&tid=tv&id=28&p=0&type=.flv
OK 香港有线新闻台 https://p.iptv200.com/play.php?token=sJJ4jAWYy1moofPW%2BjGQBlO6&tid=tv&id=29&p=0&type=.flv
ERR 香港有线18台 timeout
ERR 香港有线18台(备) timeout
OK NOW新闻台 https://p.iptv200.com/play.php?token=UfZdG5AWepo7g0fPpEDWftSv&tid=tv&id=36&p=0&type=.flv
OK NOW财经台 https://p.iptv200.com/play.php?token=Hc5nNKt7WlXz10HawD%2FVxjVQ&tid=tv&id=37&p=0&type=.flv
OK ViuTV https://p.iptv200.com/play.php?token=%2B%2BIKtdQm%2B6uSxFcLM%2FRlTfbH&tid=tv&id=38&p=0&type=.flv
OK ViuTV6 https://p.iptv200.com/play.php?token=QQWbnsmeQusQaxY2sj%2Bazgad&tid=tv&id=39&p=0&type=.flv
OK HOY国际财经台 https://p.iptv200.com/play.php?token=EYUw0sdX%2FamjRY3cZMKwHPPm&tid=tv&id=41&p=0&type=.flv
OK HOY资讯台 https://p.iptv200.com/play.php?token=j%2BgV954uz7t5cD%2FEk4O%2FL5RM&tid=tv&id=42&p=0&type=.flv
OK HOY TV https://p.iptv200.com/play.php?token=H3daVQqPGbhKbuqxIDv1Opkn&tid=tv&id=43&p=0&type=.flv
OK 港台电视31 https://p.iptv200.com/play.php?token=jwiorD7Ub32U5CiT1gunFVxS&tid=tv&id=45&p=0&type=.flv
ERR 港台电视32 timeout
OK 中天新闻台 https://p.iptv200.com/play.php?token=2Qi4CPVty0X61AjT6QgHaVu8&tid=tv&id=49&p=0&type=.flv
OK 东森新闻台 https://p.iptv200.com/play.php?token=oXny6hQ4iTT6e7T4Ui0Dfdtl&tid=tv&id=50&p=0&type=.flv
ERR 东森财经新闻台 timeout
OK 中视新闻台 https://p.iptv200.com/play.php?token=f%2FhCuD6Z%2BCHjVQfJFfYutO%2BH&tid=tv&id=52&p=0&type=.flv
OK 台视新闻台 https://p.iptv200.com/play.php?token=%2Be3Y8El4pudsxs6LEWoi4NMC&tid=tv&id=53&p=0&type=.flv
OK 三立新闻台 https://p.iptv200.com/play.php?token=pScHiH%2BysYXPqptapMZDIXbM&tid=tv&id=54&p=0&type=.flv
OK 民视新闻台 https://p.iptv200.com/play.php?token=jRcBUtL086drmHfF9rmJXrr7&tid=tv&id=56&p=0&type=.flv
OK 华视新闻台 https://p.iptv200.com/play.php?token=D%2F0N3afijDhXt8Sup50yo7mX&tid=tv&id=57&p=0&type=.flv
OK 镜电视新闻台 https://p.iptv200.com/play.php?token=Xf%2FKX9DkkIEN8COHEA9X44Ch&tid=tv&id=58&p=0&type=.flv
OK 寰宇新闻台 https://p.iptv200.com/play.php?token=FOIVNq%2FIAnakihkEkNOKqcE4&tid=tv&id=59&p=0&type=.flv
OK 寰宇新闻台湾台 https://p.iptv200.com/play.php?token=YwjMSCL4bOwfJUWQ%2FjxdUMRH&tid=tv&id=60&p=0&type=.flv
OK 非凡新闻 https://p.iptv200.com/play.php?token=uOCApa%2FUgUymDpu81X8601wE&tid=tv&id=62&p=0&type=.flv
OK TVBS新闻台 https://p.iptv200.com/play.php?token=0U2CZRkvz5IA3ZCv3i0n7L6M&tid=tv&id=63&p=0&type=.flv
OK TVBS HD https://p.iptv200.com/play.php?token=buu8KKuYE5%2B7XPE5hnZsfo4F&tid=tv&id=65&p=0&type=.flv
OK 中视HD https://p.iptv200.com/play.php?token=MyhmKZBmlWaD5oXPNzMDexXC&tid=tv&id=68&p=0&type=.flv
OK 台视HD https://p.iptv200.com/play.php?token=rQ4QpZPsdiOMLuFFQS52wTi7&tid=tv&id=69&p=0&type=.flv
OK 民视HD https://p.iptv200.com/play.php?token=BEw3JCzraKfjdZ%2B%2B0PkwGDWT&tid=tv&id=70&p=0&type=.flv
OK 华视HD https://p.iptv200.com/play.php?token=vctyqKvTXsZA%2B5t39ZMtSJy%2B&tid=tv&id=71&p=0&type=.flv
OK 緯來體育台 https://p.iptv200.com/play.php?token=Jj7%2F%2FWOvZMtpUVzrbb%2FuUfFM&tid=tv&id=77&p=0&type=.flv
OK 纬来日本台 https://p.iptv200.com/play.php?token=XEXCtOMHDik4AGjusPtVgtvv&tid=tv&id=78&p=0&type=.flv
OK 纬来育乐台 https://p.iptv200.com/play.php?token=BjjZoAESgXN%2BZVw76DEMMKoP&tid=tv&id=79&p=0&type=.flv
OK DAZN 1 https://p.iptv200.com/play.php?token=tLMj%2BGlwRQLEXMwF%2BDeI8PwT&tid=tv&id=80&p=0&type=.flv
OK DAZN 2 https://p.iptv200.com/play.php?token=h0jxD3N093yDmqFUPbHY5MXr&tid=tv&id=81&p=0&type=.flv
OK 爱尔达体育1台 https://p.iptv200.com/play.php?token=n8sbfOy8g3hwzjMzseZYKyNP&tid=tv&id=83&p=0&type=.flv
OK 爱尔达体育2台 https://p.iptv200.com/play.php?token=lIrDnj6xlJbpb3fzxTjkk6wh&tid=tv&id=84&p=0&type=.flv
OK BBC新闻 https://p.iptv200.com/play.php?token=x9kinn3i2WThq8tttHUbBYd%2F&tid=tv&id=94&p=0&type=.flv
ERR MOMO综合台 timeout
OK 东森超视 https://p.iptv200.com/play.php?token=ZjaEk63ffZNImQ6gbXS6%2Fy%2FQ&tid=tv&id=98&p=0&type=.flv
OK 茶频道 https://p.iptv200.com/play.php?token=F4mN%2FxkAnmkqr1m2SXIJkaWV&tid=tv&id=100
OK 快乐垂钓 https://p.iptv200.com/play.php?token=ReoKCDI4%2Bg5Ro44lJVUuRRw5&tid=tv&id=102
OK 先锋乒羽 https://p.iptv200.com/play.php?token=vxw7csoIHJB18W889gF1pdQO&tid=tv&id=103
OK 湖南国际 https://p.iptv200.com/play.php?token=vzmW3Cs%2BSsAfNhlF9xhPs4Lw&tid=tv&id=104
OK 湖南电视剧 https://p.iptv200.com/play.php?token=Pzce9vYuyGD9luKb9kxp7HqF&tid=tv&id=105
ERR 金鹰卡通 timeout
OK 金鹰纪实 https://p.iptv200.com/play.php?token=cYoooZxWEq6JMsZPTXT1mjjv&tid=tv&id=107
OK 湖南经视 https://p.iptv200.com/play.php?token=7ypq%2Fg8anNrAR3A6PY2NK2YI&tid=tv&id=108
OK 湖南娱乐 https://p.iptv200.com/play.php?token=M5AiZ%2B0ijbsAu7hMZVVVZrCQ&tid=tv&id=109
OK 湖南都市 https://p.iptv200.com/play.php?token=hdmnc1TqlOfzcpoggZAFeFYJ&tid=tv&id=110
OK 湖南公共 https://p.iptv200.com/play.php?token=kZaXjxYfPsKlQW0rkSjNjj4G&tid=tv&id=111
OK 湖南快乐购 https://p.iptv200.com/play.php?token=Nlp1DrpuY%2F4WBB3ws6JQM%2FtF&tid=tv&id=112
OK 长沙政法 https://p.iptv200.com/play.php?token=%2BGA6GlLaSyskY7euEQlyLImQ&tid=tv&id=113
OK 长沙女性 https://p.iptv200.com/play.php?token=p8%2FkmOzRUqrFdJx7j8JSnrt0&tid=tv&id=114
OK 长沙新闻 https://p.iptv200.com/play.php?token=V%2FBYul8xWdBCADusPTEmpMgQ&tid=tv&id=115
OK 广州综合 https://p.iptv200.com/play.php?token=%2BgOPdNPV3iuSTf%2FB1tMg3H2v&tid=tv&id=117
OK 广州新闻 https://p.iptv200.com/play.php?token=Hm3Dht9FqN8FliHOuFAGy2QY&tid=tv&id=118
OK 广州影视 https://p.iptv200.com/play.php?token=APxD3vG8e08%2FKQ16XTqyZJZg&tid=tv&id=119
OK 广州法治 https://p.iptv200.com/play.php?token=1JFMX058GxjegdBNdVOFyxQY&tid=tv&id=120
OK 广州南国都市 https://p.iptv200.com/play.php?token=PpSufIa61Y9frAo7OXF2riOV&tid=tv&id=121
OK 广州竞赛 https://p.iptv200.com/play.php?token=1qIAmDjgWEdL%2B2KNff9%2B3MbA&tid=tv&id=122
OK CCTV5体育 https://p.iptv200.com/play.php?token=d2LU3ROTqFL0VfqJnzpxJj46&tid=ty&id=1&p=0&type=.flv
OK CCTV5+体育赛事 https://p.iptv200.com/play.php?token=dycSkISdpENiub17p%2BDVvY0a&tid=ty&id=2&p=0&type=.flv
OK 广东体育 https://p.iptv200.com/play.php?token=QXLf726vE45uiQqR%2BoUyaWTH&tid=ty&id=3&p=0&type=.flv
OK 劲爆体育 https://p.iptv200.com/play.php?token=4HaPR1oH6Zm9cSDR2ZKYb97V&tid=ty&id=4&p=0&type=.flv
OK 五星体育 https://p.iptv200.com/play.php?token=C65zY64vnTiAI%2Bvx2gLyNzE1&tid=ty&id=5&p=0&type=.flv
OK 新视觉HD https://p.iptv200.com/play.php?token=iHHJK0KpoAAcnFFDryPxlfcd&tid=ty&id=6&p=0
OK CCTV16奥林匹克 https://p.iptv200.com/play.php?token=Pi%2Bn41NhLYGeT%2FyO6xP0UuBy&tid=ty&id=7&p=0&type=.flv
OK 极速汽车 https://p.iptv200.com/play.php?token=ukdtMzE7UDWH3npDydFf9plP&tid=ty&id=12&p=0
OK 游戏风云 https://p.iptv200.com/play.php?token=cY4qgZqYswBoMw6M5XTvxpwH&tid=ty&id=13&p=0
OK 四海钓鱼 https://p.iptv200.com/play.php?token=HKt14lqIZcKSFbxrZWcK0riX&tid=ty&id=14&p=0
OK 快乐垂钓 https://p.iptv200.com/play.php?token=i7RzWhCzdVndvVh7abZI3hV4&tid=ty&id=15
OK 先锋乒羽 https://p.iptv200.com/play.php?token=%2F9MrBS0%2BisB1hZIFDDcJCrQQ&tid=ty&id=16
OK 南国都市 https://p.iptv200.com/play.php?token=pVBO3y1kRxoHS8Wrr7%2FuPh8a&tid=ty&id=19
OK 广州竞��� https://p.iptv200.com/play.php?token=kesawnF1V743OOR9S6Ei6z09&tid=ty&id=20
OK CCTV风云足球 https://p.iptv200.com/play.php?token=Ju%2Flw0dy5hIQC4Z4X2DecAqs&tid=ty&id=21&p=0
OK 魅力足球 https://p.iptv200.com/play.php?token=2CaMJIyb6AsQVIt5dM8sh3bh&tid=ty&id=22&p=0
OK 緯來體育台 https://p.iptv200.com/play.php?token=pE4HZSXaLP8Cy9k9cLY4mwIz&tid=ty&id=23&p=0&type=.flv
OK ELEVEN 1(DAZN 1) https://p.iptv200.com/play.php?token=OzqIPyd%2BL7W986pYbGRDIImh&tid=ty&id=24&p=0&type=.flv
OK ELEVEN 2(DAZN 2) https://p.iptv200.com/play.php?token=11p%2FIPtewrCkCpRQcbmIDHvk&tid=ty&id=25&p=0&type=.flv
OK 爱尔达体育1台 https://p.iptv200.com/play.php?token=Ujr65TjdPRHIP5djvZ2zT%2B94&tid=ty&id=28&p=0&type=.flv
OK 爱尔达体育2台 https://p.iptv200.com/play.php?token=elPzUzi1HC1bTDqK%2FDcJlYxi&tid=ty&id=29&p=0&type=.flv
OK CCTV1综合 https://p.iptv200.com/play.php?token=OQemSJcbpY%2FyaUIgZF3s9ogi&tid=ys&id=1&p=0&type=.flv
OK CCTV2财经 https://p.iptv200.com/play.php?token=7gk7X8Ayw%2By%2B2OGv7kSyGUng&tid=ys&id=2&p=0&type=.flv
OK CCTV3综艺 https://p.iptv200.com/play.php?token=L2EHzlRFzS%2BYd850HTfoX3oM&tid=ys&id=3&p=0&type=.flv
OK CCTV4中文国际 https://p.iptv200.com/play.php?token=KHjS%2BR4tgk7nEFEyfIfyQDpl&tid=ys&id=4&p=0&type=.flv
OK CCTV5体育 https://p.iptv200.com/play.php?token=wD7rhSr5%2BS2DbtUoUn%2FTiwtm&tid=ys&id=5&p=0&type=.flv
OK CCTV5+体育赛事 https://p.iptv200.com/play.php?token=65qROx3f9KPH8hrcpoY4%2Ftg%2F&tid=ys&id=6&p=0&type=.flv
OK CCTV6电影 https://p.iptv200.com/play.php?token=9BZTWpDS6Hhvv%2BugiONSj9Tu&tid=ys&id=7&p=0&type=.flv
OK CCTV7国防军事 https://p.iptv200.com/play.php?token=nMQFs5SJw2nsBTEX%2FrqGym%2FX&tid=ys&id=8&p=0&type=.flv
OK CCTV8电视剧 https://p.iptv200.com/play.php?token=xdk01Uuxjs6tdzaD9N1f8onI&tid=ys&id=9&p=0&type=.flv
OK CCTV9纪录 https://p.iptv200.com/play.php?token=fAyqRqDMvuSi3iESgDMv9Jub&tid=ys&id=10&p=0&type=.flv
ERR CCTV10科教 timeout
OK CCTV11戏曲 https://p.iptv200.com/play.php?token=JnmOgg0yWhaxfOdIyYpnvD8J&tid=ys&id=12&p=0&type=.flv
OK CCTV12社会与法 https://p.iptv200.com/play.php?token=0w%2F6bM3TiyL0fmgGX8l8Ij%2B6&tid=ys&id=13&p=0&type=.flv
OK CCTV13新闻 https://p.iptv200.com/play.php?token=EQYahN1YJI9S6is2Zdkav%2Bzw&tid=ys&id=14&p=0&type=.flv
OK CCTV14少儿 https://p.iptv200.com/play.php?token=JcbkXaKHv5im0q8%2Fjrqpdj7a&tid=ys&id=15&p=0&type=.flv
OK CCTV15音乐 https://p.iptv200.com/play.php?token=bfMCNoMJBywoKDPIpsDj1M9L&tid=ys&id=16&p=0&type=.flv
OK CCTV16奥林匹克 https://p.iptv200.com/play.php?token=Jv2DDFpwtah3w9X%2FdF5DuipM&tid=ys&id=17&p=0&type=.flv
OK CCTV17农业农村 https://p.iptv200.com/play.php?token=Ji0NdwPtaVPwLCl5o%2BiZUGlG&tid=ys&id=18&p=0&type=.flv
OK CCTV4k高清 https://p.iptv200.com/play.php?token=Xea%2F%2FrEKUojM1%2BV3683IZeQv&tid=ys&id=19&p=0
OK CCTV8k高清 https://p.iptv200.com/play.php?token=Xb2RminN5gihcqyVQAnJ%2BNGp&tid=ys&id=20&p=0
OK CCTV4欧洲HD https://p.iptv200.com/play.php?token=OLFNHMBheDsRQZgHkK02qvc6&tid=ys&id=21&p=0
ERR CCTV4美洲HD timeout
OK CGTN https://p.iptv200.com/play.php?token=BheqPbI8MNZybJo%2FHGc24NIa&tid=ys&id=23&p=0
OK CGTN纪录 https://p.iptv200.com/play.php?token=W%2FoRZRyoyI6VaxsxoqVAK4Pe&tid=ys&id=24&p=0
OK CCTV阿拉伯语 https://p.iptv200.com/play.php?token=2BZrUPTHmGrH8Ar%2Bc7jgXTMv&tid=ys&id=25&p=0
OK CCTV法语 https://p.iptv200.com/play.php?token=VpONv0o9refyRmGKluLbhaBT&tid=ys&id=26&p=0
OK CCTV西班牙语 https://p.iptv200.com/play.php?token=pPF%2BV8g%2BgeY25hjOXR6qWt%2F%2B&tid=ys&id=27&p=0
OK CCTV俄语 https://p.iptv200.com/play.php?token=5gSY1nRMLxgyYsvByZaFePXr&tid=ys&id=28&p=0
OK CETV-1 https://p.iptv200.com/play.php?token=bL2111RRoqi%2F5P7lQYgKBe9X&tid=ys&id=29&p=0
OK CETV-2 https://p.iptv200.com/play.php?token=5xS45O3uWL56jnSxwiY%2BneB5&tid=ys&id=30&p=0
OK CETV-3 https://p.iptv200.com/play.php?token=vDOHFWxrHTf5%2FJInbeO84DG4&tid=ys&id=31&p=0
OK CETV-4 https://p.iptv200.com/play.php?token=My34uHMpzXnMmoc33SXDixRD&tid=ys&id=32&p=0
OK CCTV兵器科技 https://p.iptv200.com/play.php?token=KX3OtZts1UQ%2FNGKgRi27fGVx&tid=ys&id=33&p=0
OK CCTV怀旧剧场 https://p.iptv200.com/play.php?token=0bxE2kqpJLextV8qc8ju8ya%2B&tid=ys&id=34&p=0
OK CCTV第一剧场 https://p.iptv200.com/play.php?token=uJDjzrV5uS3GGrRWwzzZ7gjX&tid=ys&id=35&p=0
OK CCTV风云剧场 https://p.iptv200.com/play.php?token=2mt69Pu8tsMrR49%2FoavFzW0l&tid=ys&id=36&p=0
OK CCTV风云音乐 https://p.iptv200.com/play.php?token=JAY6vH78qf9ie7m3YkUaO6Wn&tid=ys&id=37&p=0
OK CCTV风云足球 https://p.iptv200.com/play.php?token=SpiJ8rLgqwS%2BPMZzTBHEc2V1&tid=ys&id=38&p=0
OK CCTV世界地理 https://p.iptv200.com/play.php?token=eKLSOV%2B02bsvG7%2FhZ8Gth3pr&tid=ys&id=39&p=0
OK CCTV文化精品 https://p.iptv200.com/play.php?token=gXlLR3LnV0Hla57NQ7hCGHZ0&tid=ys&id=40&p=0
OK CCTV央视台球 https://p.iptv200.com/play.php?token=B3NGGt7%2B0w7YnWrw96Iz3aaL&tid=ys&id=41&p=0
OK CCTV高尔夫网球 https://p.iptv200.com/play.php?token=iMajD8SHylSsgDDhS0njPhO9&tid=ys&id=42&p=0
OK CCTV女性时尚 https://p.iptv200.com/play.php?token=M%2BUZcxguzr%2FEsnK9cZTV1gT%2F&tid=ys&id=43&p=0
OK 湖南卫视 https://p.iptv200.com/play.php?token=AfhXLHDBQKjvet8nDo%2FxGg9c&tid=ws&id=1&p=0&type=.flv
OK 江苏卫视 https://p.iptv200.com/play.php?token=fsV5wF1Le3bclFruiHN6iNjd&tid=ws&id=2&p=0&type=.flv
OK 浙江卫视 https://p.iptv200.com/play.php?token=BDMADR%2BQ7Hm4rYjoqnz0Ndvn&tid=ws&id=3&p=0&type=.flv
OK 东方卫视 https://p.iptv200.com/play.php?token=0Mbn02DZxzMU6zSCTLcgJR%2Bx&tid=ws&id=4&p=0&type=.flv
OK 北京卫视 https://p.iptv200.com/play.php?token=8pYbcW%2FaBEX3o6CXofe84DR7&tid=ws&id=5&p=0&type=.flv
OK 深圳卫视 https://p.iptv200.com/play.php?token=2y5tY28ju2Ui2It3H40DYVmc&tid=ws&id=6&p=0&type=.flv
OK 广东卫视 https://p.iptv200.com/play.php?token=lndegIbEvroxMzoCEf1pW9YQ&tid=ws&id=7&p=0&type=.flv
OK 安徽卫视 https://p.iptv200.com/play.php?token=dYiHa%2FWq7upAeJqWQhf7cnaK&tid=ws&id=8&p=0&type=.flv
OK 东南卫视 https://p.iptv200.com/play.php?token=tJRQ64OuSo3lkR4wKOFl62zM&tid=ws&id=9&p=0&type=.flv
OK 河北卫视 https://p.iptv200.com/play.php?token=hnVVKoPwvevIJKBPl9PpX3Au&tid=ws&id=10&p=0
OK 黑龙江卫视 https://p.iptv200.com/play.php?token=b%2FCmX5if27ckgtfCuWeTf7Rn&tid=ws&id=11&p=0&type=.flv
OK 湖北卫视 https://p.iptv200.com/play.php?token=3JOFvU34SLUhFGcSwVmdgx%2FS&tid=ws&id=12&p=0&type=.flv
OK 江西卫视 https://p.iptv200.com/play.php?token=RBfW%2FK7Qw4qgZifLFba940F1&tid=ws&id=13&p=0&type=.flv
OK 辽宁卫视 https://p.iptv200.com/play.php?token=rmZhMOb3I4mKI9ESTd8%2BUqU7&tid=ws&id=14&p=0&type=.flv
OK 海南卫视 https://p.iptv200.com/play.php?token=EmzMR%2BPtA%2FI2OMid6CbF5EGI&tid=ws&id=15&p=0
OK 山东卫视 https://p.iptv200.com/play.php?token=gj8m5DEgL5cDpbizKDoSDH46&tid=ws&id=16&p=0&type=.flv
OK 四川卫视 https://p.iptv200.com/play.php?token=MXiVLHS8Hpqp3dMP6eSOIXmF&tid=ws&id=17&p=0
ERR 天津卫视 timeout
OK 重庆卫视 https://p.iptv200.com/play.php?token=HMdAii2u4Q%2Fdp11PWYWmRVsK&tid=ws&id=19&p=0&type=.flv
OK 贵州卫视 https://p.iptv200.com/play.php?token=l7BNe48fC31%2BgvKaC5pVZPGt&tid=ws&id=20&p=0
OK 吉林卫视 https://p.iptv200.com/play.php?token=ke%2F%2FSik%2B%2FT31Mt%2FBeJ4Dq3Xq&tid=ws&id=21&p=0
OK 广西卫视 https://p.iptv200.com/play.php?token=Sle4OoFPVWqN3n6QMfoxaoSv&tid=ws&id=22&p=0
OK 河南卫视 https://p.iptv200.com/play.php?token=NWvj2AdEAPb7r6GeBUpiW5l5&tid=ws&id=23&p=0
ERR 甘肃卫视 timeout
OK 青海卫视 https://p.iptv200.com/play.php?token=vdDSebq0sKhbTEfc9rgs4KU5&tid=ws&id=25&p=0
OK 云南卫视 https://p.iptv200.com/play.php?token=TFOjRkd2kDtrH1kA4KBGzgZV&tid=ws&id=26&p=0
OK 内蒙古卫视 https://p.iptv200.com/play.php?token=NX%2BDJcAWE3kDcuJ%2FpQankKGx&tid=ws&id=27&p=0
OK 山西卫视 https://p.iptv200.com/play.php?token=jNc0Pd7dM4W3d5cUMNUlfOLM&tid=ws&id=28&p=0
OK 陕西卫视 https://p.iptv200.com/play.php?token=IJafQ1bx1%2B8PgHKpSkVxp4WJ&tid=ws&id=29&p=0
ERR 兵团卫视 timeout
OK 新疆卫视 https://p.iptv200.com/play.php?token=tjTBEXsEcjpZWz77aYmBIdwo&tid=ws&id=31&p=0
OK 西藏卫视 https://p.iptv200.com/play.php?token=NuE19Kq6rYusP7niRnqo6rt5&tid=ws&id=32&p=0
OK 宁夏卫视 https://p.iptv200.com/play.php?token=mGoxkP9M1%2FG0iuyzVVlxBylg&tid=ws&id=33&p=0
OK 延边卫视 https://p.iptv200.com/play.php?token=Q6J2zz5DsYfVEFuMakECdoqp&tid=ws&id=34&p=0
OK 康巴卫视 https://p.iptv200.com/play.php?token=DCtNRsZGstT2f8nYIi5lfbhN&tid=ws&id=35&p=0
OK 大湾区卫视 https://p.iptv200.com/play.php?token=fJyD79X%2FBEDuZgJBvNg%2BH8lF&tid=ws&id=36&p=0
OK 广东珠江频道 https://p.iptv200.com/play.php?token=TYVpK810j1jC%2Bv0D8m%2B49XlN&tid=ws&id=37&p=0&type=.flv
OK 厦门卫视 https://p.iptv200.com/play.php?token=ck4tVMxZoOOFlMtFU6dobYXT&tid=ws&id=38
OK 安多卫视 https://p.iptv200.com/play.php?token=mmLH%2FDT2XaZJDaQ9y0uMTM7v&tid=ws&id=39
OK 农林卫视 https://p.iptv200.com/play.php?token=Y9ZOh%2FldUzJw9fPhTJn%2FlISB&tid=ws&id=40
OK 三沙卫视 https://p.iptv200.com/play.php?token=pg3cwHrhY75tmIwWu3n7ajQk&tid=ws&id=41
OK 凤凰卫视中文台 https://p.iptv200.com/play.php?token=Nq55Qf%2BdiaBWMGNPKBS1HEFf&tid=gt&id=1&p=0&type=.flv
OK 凤凰卫视资讯台 https://p.iptv200.com/play.php?token=SvYml3egKKYSQQYcmsFBg4Tg&tid=gt&id=2&p=0&type=.flv
OK 凤凰卫视香港台 https://p.iptv200.com/play.php?token=n4IlUpC75P1NL57Sm0P1b4ZV&tid=gt&id=3&p=0&type=.flv
OK TVB翡翠台 https://p.iptv200.com/play.php?token=cOpKwymv342yI1ybwOBXRY3y&tid=gt&id=5&p=0&type=.flv
OK TVB无线新闻台 https://p.iptv200.com/play.php?token=snxXIrovLDySXKYrrmka%2FXXQ&tid=gt&id=6&p=0&type=.flv
OK TVB Plus https://p.iptv200.com/play.php?token=kdTJAZiuZU%2BpVpRL4iNO1%2FDQ&tid=gt&id=8&p=0&type=.flv
OK TVB明珠台 https://p.iptv200.com/play.php?token=7kbbyQFR0hlq6JyV0z%2B9b1Kv&tid=gt&id=9&p=0&type=.flv
OK 香港有线新闻台 https://p.iptv200.com/play.php?token=tfQVuDs7qKpcSTAKedbTtkFv&tid=gt&id=11&p=0&type=.flv
OK 香港有线18台 https://p.iptv200.com/play.php?token=0wK9NtNq10L4qSYU7ij1g2R%2B&tid=gt&id=12&p=0&type=.flv
OK 香港有线18台(备) https://p.iptv200.com/play.php?token=QUfW5A5NLIxy1JJwtLrLZHQ%2B&tid=gt&id=13&p=0&type=.flv
OK NOW新闻台 https://p.iptv200.com/play.php?token=khPINXLkHBgSFlYEBL0oDOxq&tid=gt&id=17&p=0&type=.flv
OK NOW财经台 https://p.iptv200.com/play.php?token=RQLqonuyq%2FduONhocbHXH83h&tid=gt&id=18&p=0&type=.flv
OK ViuTV https://p.iptv200.com/play.php?token=3qy19ESWD5%2BzJlfqfYpjcAyP&tid=gt&id=19&p=0&type=.flv
OK ViuTV6 https://p.iptv200.com/play.php?token=JN4vOUotpJPsL1Roe2efSL0C&tid=gt&id=20&p=0&type=.flv
OK HOY国际财经台 https://p.iptv200.com/play.php?token=APA69Hs9z2MPEmAUrl%2F6EseE&tid=gt&id=22&p=0&type=.flv
OK HOY资讯台 https://p.iptv200.com/play.php?token=oQ%2FRCTEuiP2Zo%2BVhAErXsJFQ&tid=gt&id=23&p=0&type=.flv
OK HOY TV https://p.iptv200.com/play.php?token=DJG5u%2BG9QcsypHQAMo4R4Ei3&tid=gt&id=24&p=0&type=.flv
OK 港台电视31 https://p.iptv200.com/play.php?token=zANH4EOTsPP2KNLcJq%2B7%2Fqnt&tid=gt&id=26&p=0&type=.flv
OK 港台电视32 https://p.iptv200.com/play.php?token=y5aTk5csotbtAO5DMmdESkcG&tid=gt&id=27&p=0&type=.flv
OK 香港卫视HKS https://p.iptv200.com/play.php?token=cxmm0oIqhsAMCDRhQ%2F1bWytj&tid=gt&id=30&p=0
OK 澳门莲花 https://p.iptv200.com/play.php?token=lSerZNkL0ofTtnn2kZZKRO1A&tid=gt&id=35&p=0
OK 澳門卫视 https://p.iptv200.com/play.php?token=pfgNZwyBAiwvaYWl0jpjUbNt&tid=gt&id=36&p=0
OK 澳视资讯 https://p.iptv200.com/play.php?token=kYOwRF6FQ54yJDy4F%2FBcfqYh&tid=gt&id=37&p=0
OK 澳視澳門 https://p.iptv200.com/play.php?token=qwOxvBw7YTVbs%2BSpf0RO0GiX&tid=gt&id=38&p=0
OK 澳門Macau https://p.iptv200.com/play.php?token=0tNDZ3N8cmDtUg8Bsyn%2FJYmS&tid=gt&id=39&p=0
OK 澳视综艺 https://p.iptv200.com/play.php?token=0VmUkEBcHBPpF1TgwLjN7qK%2B&tid=gt&id=40&p=0
OK 澳亚卫视 https://p.iptv200.com/play.php?token=zaKG7HRzLz4t67h1PjKdL2Td&tid=gt&id=41&p=0
OK 中天新闻台 https://p.iptv200.com/play.php?token=2AtZ26BJoRQ499MOpRsi8Nj%2B&tid=gt&id=43&p=0&type=.flv
OK 东森新闻台 https://p.iptv200.com/play.php?token=WRMZX6mWmK88Pt%2BmFDZsVoFj&tid=gt&id=44&p=0&type=.flv
OK 东森财经新闻台 https://p.iptv200.com/play.php?token=r4T3e99i2%2F2ftKd0xxgtp29n&tid=gt&id=45&p=0&type=.flv
OK 中视新闻台 https://p.iptv200.com/play.php?token=s2MgidwMzYhStqmyls%2F1ckrv&tid=gt&id=46&p=0&type=.flv
OK 台视新闻台 https://p.iptv200.com/play.php?token=7BUXEkOu8l%2BzwYPI2HHAhe1n&tid=gt&id=47&p=0&type=.flv
OK 三立新闻台 https://p.iptv200.com/play.php?token=nTdKaJNel15QoW7jMku4ykn4&tid=gt&id=48&p=0&type=.flv
OK 非凡新闻 https://p.iptv200.com/play.php?token=PexSrjJY3yAzQqfUv1czsOE8&tid=gt&id=49&p=0&type=.flv
OK 民视新闻台 https://p.iptv200.com/play.php?token=ybYMk0sjxtnEjXzK8UjwLxMo&tid=gt&id=50&p=0&type=.flv
OK 华视新闻台 https://p.iptv200.com/play.php?token=6016ovhDGhuoecLjVUw%2FDZyY&tid=gt&id=51&p=0&type=.flv
OK 镜电视新闻台 https://p.iptv200.com/play.php?token=s60yR0aYecSOVIdbBKbmObFE&tid=gt&id=52&p=0&type=.flv
OK 寰宇新闻台 https://p.iptv200.com/play.php?token=0x9OeJxj7ari1NTcxZJMXD7N&tid=gt&id=53&p=0&type=.flv
OK 寰宇新闻台湾台 https://p.iptv200.com/play.php?token=v9JFcX7RJCjoh2jvBHGuT7jv&tid=gt&id=54&p=0&type=.flv
OK TVBS新闻台 https://p.iptv200.com/play.php?token=Ya0WqN3Tv7hjIBukDQnZP5FP&tid=gt&id=55&p=0&type=.flv
OK TVBS HD https://p.iptv200.com/play.php?token=6smcgLK9hFHlBGwb852u1aPz&tid=gt&id=56&p=0&type=.flv
OK 中视HD https://p.iptv200.com/play.php?token=haJ6qBZD4sHWBewY1aFF8cHa&tid=gt&id=60&p=0&type=.flv
OK 台视HD https://p.iptv200.com/play.php?token=sZnzKYLfmxhBGch9pYD9is9r&tid=gt&id=61&p=0&type=.flv
OK 民视HD https://p.iptv200.com/play.php?token=MHgdMa6OaVZhMEW0IYulmtfj&tid=gt&id=62&p=0&type=.flv
OK 华视HD https://p.iptv200.com/play.php?token=j%2BkQRz%2Fr2eXIaE1yKYxdm%2Bg8&tid=gt&id=63&p=0&type=.flv
OK 緯來體育台 https://p.iptv200.com/play.php?token=JELqYDHzVse3W%2BB6doN%2FsQo6&tid=gt&id=73&p=0&type=.flv
OK 纬来日本台 https://p.iptv200.com/play.php?token=2byEB4qtSRb%2B8jK2jVx7MB6h&tid=gt&id=74&p=0&type=.flv
OK 纬来育乐台 https://p.iptv200.com/play.php?token=cjoB6d8vHLLDPWf%2Fnbl%2FFRwN&tid=gt&id=75&p=0&type=.flv
OK ELEVEN 1(DAZN 1) https://p.iptv200.com/play.php?token=GX5c4HF2f3mv%2BsFr69ekRUTX&tid=gt&id=76&p=0&type=.flv
OK ELEVEN 2(DAZN 2) https://p.iptv200.com/play.php?token=acnZabzWhg43ksaLL9T4XJH0&tid=gt&id=77&p=0&type=.flv
OK 爱尔达体育1台 https://p.iptv200.com/play.php?token=W%2FRjPt5Tb57FY0S85ayv0rJT&tid=gt&id=79&p=0&type=.flv
OK 爱尔达体育2台 https://p.iptv200.com/play.php?token=V6eIy5KGtDe%2B5LsV64jQIFlC&tid=gt&id=80&p=0&type=.flv
+93
View File
@@ -0,0 +1,93 @@
import https from 'https';
import http from 'http';
import { URL } from 'url';
const UA = 'Mozilla/5.0 (Linux; Android 12; SM-S901B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Mobile Safari/537.36';
function fetch(url) {
return new Promise((res, rej) => {
const h = url.startsWith('https') ? https : http;
const req = h.get(url, { headers: { 'User-Agent': UA } }, r => {
let d = '';
r.on('data', c => d += c);
r.on('end', () => res(d));
});
req.on('error', rej);
req.setTimeout(15000, () => { req.destroy(); rej(new Error('timeout')); });
});
}
// 动态eval加密变量,再按解密函数链跑(bbot: page uses randomly-named vars each request)
function decodePage(html) {
// 提取 var xx=""; var yy="...拼接..."; var qq=""; var ww="..."; ouyit/token块
const blocks = [];
const re = /<script>([\s\S]*?)<\/script>/g;
let m;
while ((m = re.exec(html))) {
const body = m[1];
if (body.includes('.split("")') && body.includes('var')) blocks.push(body);
if (body.includes('function ') && body.includes('keyStr') && body.includes('charCodeAt')) blocks.push(body);
if (body.includes('wngbe') || /function \w+\(dek\)/.test(body)) blocks.push(body);
}
const context = {};
const sandbox = new Function(blocks.join('\n') + `
// 返回解密函数名 + 猜出变量名
const fnNames = [];
// 找形如 function XXXX(dek){...} 就把名字报出来
const decls = [...arguments.callee ? '' : ''][0];
return { xefgv: typeof xefgv !== 'undefined' ? xefgv : null, ouyit: typeof ouyit !== 'undefined' ? ouyit : null, qrtkk: typeof qrtkk !== 'undefined' ? qrtkk : null };
`);
return null;
}
// 更简单:直接在Node vm里跑整段,把全部函数和变量都留在沙箱里
import vm from 'vm';
async function processPage(url) {
const html = await fetch(url);
const ctx = { window: {}, atob: null };
const sandbox = vm.createContext({ window: {}, document: { getElementById: () => null, querySelectorAll: () => [], querySelector: () => null, addEventListener(){}, createElement() { return { setAttribute(){}, style:{} }; }, body: { appendChild(){} } }, location: { href: url }, navigator: { userAgent: UA }, setInterval: () => 0, clearInterval(){}, setTimeout: () => 0, clearTimeout(){}, fetch: () => {}, console, addEventListener(){}, $: () => ({ hide(){}, show(){}, selectmenu(){}, length:0 }), Hls: { isSupported: () => false, Events: {} }, mpegts: { isSupported: () => false, Events: {} }, devicePixelRatio: 2, innerWidth: 1280, innerHeight: 720, atob: (s) => Buffer.from(s, 'base64').toString('binary') });
// 只跑不依赖DOM交互的脚本:解密脚本+变量, crib main script(避免动画循环定时器问题——用vm.runInContext就够了,主脚本里$()等没被调用)
const scripts = [...html.matchAll(/<script>([\s\S]*?)<\/script>/g)].map(m => m[1]);
for (const s of scripts) {
try { vm.runInContext(s, sandbox, { timeout: 3000 }); } catch (e) { /* DOM依赖的忽略 */ }
}
// 抓options
const opts = [...html.matchAll(/<option value="([^"]+)">([^<]+)<\/option>/g)];
// 找到call_in_main
const main = scripts.find(s => s.includes('startPlayer') && s.includes('(uri)'));
const decName = (main && (main.match(/const \w+ = (\w+)\(uri\)/) || [])[1]) || (main && (main.match(/const puri ?= ?(\w+)\(/) || [])[1]);
if (!decName) return { url, error: 'no dec fn' };
const results = opts.map(o => {
try { return { line: o[2], url: sandbox[decName](o[1]) }; }
catch (e) { return { line: o[2], error: String(e).slice(0,100) }; }
});
return { url, title: (html.match(/<title>([^<]*)</) || [])[1], results };
}
// 频道列表:从各分类页抓全部链接(data-ajax="false")
const tids = ['tv', 'ty', 'ys', 'ws', 'gt'];
const seen = new Map(); // link -> name(按tid顺序去重)
for (const tid of tids) {
try {
const page = await fetch('https://m.iptv807.com/?tid=' + tid);
for (const m of page.matchAll(/<a href="(\?act=play[^"]*)"[^>]*>([^<]+)<\/a>/g)) {
const link = 'https://m.iptv807.com/' + m[1];
if (!seen.has(link)) seen.set(link, m[2]);
}
} catch (e) { console.error('cat fail', tid, e.message); }
}
const channels = [...seen.entries()].map(([link, name]) => ({ link, name }));
// 去掉首页广告位那条假频道
const real = channels.filter(c => c.link.includes('&id='));
console.error('channels found:', channels.length);
const out = [];
for (const ch of real) {
try {
const r = await processPage(ch.link);
out.push({ name: ch.name, url: r.url, lines: r.results?.map(l => l.url) });
console.error('OK', ch.name, r.results?.[0]?.url || r.error);
} catch (e) {
out.push({ name: ch.name, error: e.message });
console.error('ERR', ch.name, e.message);
}
}
// 输出 JSON 到stdout
console.log(JSON.stringify(out, null, 2));
+41
View File
@@ -0,0 +1,41 @@
import re
# Check Kotlin source resolver for same bug + apply v4-style fix:
src_file = '/vol1/1000/HD1/APP/iptv-tv-app/app/src/main/java/com/iptv807/tv/SourceResolver.kt'
s = open(src_file).read()
# 拿出当前 evalConcat 部分
anchor = ''' if (s[i] == '"') { // 处理字符串字面量
val close = s.indexOf('"', i + 1)
if (close < 0) break
val lit = s.substring(i + 1, close)
val tail = s.substring((close + 1).coerceAtMost(s.length))
val isRev = tail.startsWith(".split(\\"\\").reverse().join(\\"\\")")
sb.append(if (isRev) lit.reversed() else lit)
i = if (isRev) close + 1 + ".split(\\"\\").reverse().join(\\"\\")".length else close + 1
} else i++
}'''
new = ''' if (s[i] == '"') {
var j = i + 1; var lit = ""
while (j < s.length && s[j] != '"') { lit += s[j]; j++ }
if (j >= s.length) {
sb.append(lit)
i = s.length
} else {
val REV = ".split(\\"\\").reverse().join(\\"\\")"
val tail = s.substring((j+1).coerceAtMost(s.length))
if (tail.startsWith(REV)) {
sb.append(lit.reversed())
i = j + 1 + REV.length
} else {
sb.append(lit)
i = j + 1
}
while (i < s.length && (s[i] == '+' || s[i] == ' ')) i++
}
} else i++'''
assert anchor in s, 'anchor missing'
s = s.replace(anchor, new, 1)
open(src_file, 'w').write(s)
print('kotlin eval fixed')
+59
View File
@@ -0,0 +1,59 @@
# 修 Kotlin evalConcat — 用 Python 里的 v4 版本的精确 Kotlin 翻译替换
src_file = '/vol1/1000/HD1/APP/iptv-tv-app/app/src/main/java/com/iptv807/tv/SourceResolver.kt'
s = open(src_file).read()
old_fn = ''' fun evalConcat(s: String): String = StringBuilder().also { sb ->
var i = 0
while (i < s.length) {
if (s[i] == '"') { // 处理字符串字面量
val close = s.indexOf('"', i + 1)
if (close < 0) break
val lit = s.substring(i + 1, close)
val tail = s.substring((close + 1).coerceAtMost(s.length))
val isRev = tail.startsWith(".split(\\\\"\\\\").reverse().join(\\\\"\\\\")")
sb.append(if (isRev) lit.reversed() else lit)
i = if (isRev) close + 1 + ".split(\\\\"\\\\").reverse().join(\\\\"\\\\")".length else close + 1
} else i++
}
}.toString()'''
new_fn = ''' fun evalConcat(s: String): String = StringBuilder().also { sb ->
val REV = ".split(\\\\"\\\\").reverse().join(\\\\"\\\\")"
var i = 0
while (i < s.length) {
if (s[i] == '"') {
var j = i + 1
var lit = ""
while (j < s.length && s[j] != '"') { lit += s[j]; j++ }
if (j >= s.length) {
sb.append(lit)
i = s.length
} else {
val tail = s.substring((j + 1).coerceAtMost(s.length))
if (tail.startsWith(REV)) {
sb.append(lit.reversed())
i = j + 1 + REV.length
} else {
sb.append(lit)
i = j + 1
}
while (i < s.length && (s[i] == '+' || s[i] == ' ')) i++
}
} else i++
}
}.toString()'''
if old_fn in s:
s = s.replace(old_fn, new_fn, 1)
open(src_file, 'w').write(s)
print('REPLACED OK')
else:
print('old_fn NOT FOUND, doing alternative approach')
# Fallback: locate the evalConcat definition and replace all lines
import re
m = re.search(r'fun evalConcat\(s: String\): String = StringBuilder\(\)\.also \{ sb ->[\s\S]*?\}\.toString\(\)', s)
if m:
print('found block len:', len(m.group(0)))
s = s.replace(m.group(0), new_fn.strip(' '))
open(src_file, 'w').write(s)
print('REPLACED via regex')
+15
View File
@@ -0,0 +1,15 @@
#!/usr/bin/env python3
"""补丁v2: my previous patch added a stray '}\n\n} else {' —— 移除多余的 '}'"""
import sys
P = sys.argv[1]
src = open(P).read()
bad = """ }
}
} else {"""
good = """ }
} else {"""
assert bad in src, 'stray not found'
src = src.replace(bad, good, 1)
open(P, 'w').write(src)
print('FIXED')
+9
View File
@@ -0,0 +1,9 @@
# 修正 Kotlin 字符串引号转义 (REV 应该是 ".split(\"\").reverse().join(\"\")")
src_file = '/vol1/1000/HD1/APP/iptv-tv-app/app/src/main/java/com/iptv807/tv/SourceResolver.kt'
s = open(src_file).read()
wrong = 'val REV = ".split(\\\\"\\\\").reverse().join(\\\\"\\\\")"'
right = 'val REV = ".split(\\"\\").reverse().join(\\"\\")"'
s = s.replace(wrong, right)
open(src_file, 'w').write(s)
print('fixed REV string literal')
+61
View File
@@ -0,0 +1,61 @@
#!/usr/bin/env python3
"""CardAuth processOrderPaid 补丁:无bot_qq订单 → 自动发卡并绑定机器码"""
import sys
P = sys.argv[1] if len(sys.argv) > 1 else 'OrderController.php'
src = open(P).read()
anchor = """ // 更新订单状态
$db->execute(
"UPDATE {$db->table('orders')} SET status = 'paid', paid_at = NOW() WHERE id = ?",
[$orderId]
);"""
assert anchor in src, "anchor NOT FOUND"
patch = """ } else {
// ===== 无 bot_qq 订单:自动发卡并绑定机器码(IPTV App 直购闭环)=====
// contact_info 约定格式: "MACHINE:TV-XXXX" (App下单时带上电视机器码)
$machineId = '';
if (preg_match('/MACHINE:([A-Za-z0-9\\-_]+)/', $order['contact_info'] ?? '', $mm)) {
$machineId = $mm[1];
}
// 从同项目同卡类型的未用卡池取一张
$card = $db->fetch(
"SELECT * FROM {$db->table('cards')} WHERE project_id = ? AND card_type_id = ? AND status = 'unused' ORDER BY id ASC LIMIT 1",
[$order['project_id'], $order['card_type_id']]
);
if ($card) {
$expireTime = null;
if ($durationDays > 0) {
$expireTime = date('Y-m-d H:i:s', strtotime("+{$durationDays} days"));
}
$bindInfo = json_encode([
'machine_id' => $machineId ?: 'AUTO-' . $order['order_no'],
'ip' => '',
'device_info' => 'auto-delivery',
'first_bind_at' => date('Y-m-d H:i:s'),
'order_no' => $order['order_no'],
], JSON_UNESCAPED_UNICODE);
$db->execute(
"UPDATE {$db->table('cards')} SET status = 'used', bind_info = ?, bound_at = NOW(), expire_time = ? WHERE id = ?",
[$bindInfo, $expireTime, $card['id']]
);
// 订单关联该卡,查询接口可显示
$db->execute(
"UPDATE {$db->table('orders')} SET card_id = ? WHERE id = ?",
[$card['id'], $orderId]
);
}
// 无未用卡池时订单仍标 paid(余卡告警交给管理端)
}
// 更新订单状态
$db->execute(
"UPDATE {$db->table('orders')} SET status = 'paid', paid_at = NOW() WHERE id = ?",
[$orderId]
);"""
src = src.replace(anchor, patch, 1)
open(P, 'w').write(src)
print("PATCHED OK")
+44
View File
@@ -0,0 +1,44 @@
import re, base64, urllib.parse
html = open('/tmp/fresh2.html', encoding='utf8', errors='ignore').read()
mix = next(b for b in re.findall(r'<script>([\s\S]*?)</script>', html) if '.split("")' in b)
def eval_concat(expr):
out = []; i = 0; REV = '.split("").reverse().join("")'
while i < len(expr):
if expr[i] == '"':
close = expr.find('"', i + 1)
if close < 0: break
lit = expr[i+1:close]
is_rev = expr[close+1:close+1+len(REV)+3] .startswith(REV)
out.append(lit[::-1] if is_rev else lit)
i = close + 1 + (len(REV) if is_rev else 0)
else:
i += 1
return ''.join(out)
# 保护引号、按分号拆
stmts = re.split(r';(?=(?:[^"]*"[^"]*")*[^"]*$)', mix)
stmts = [s.strip() for s in stmts if s.strip()]
assign = {}
for stmt in stmts:
m = re.match(r'^var\s+(\w+)\s*=\s*"([^"]*)"$', stmt)
if m:
assign[m.group(1)] = eval_concat(m.group(2))
continue
m = re.match(r'^var\s+(\w+)\s*=\s*$', stmt)
if m:
assign[m.group(1)] = ''
continue
m = re.match(r'^(\w+)\s*=\s*(\w+)$', stmt)
if m:
assign[m.group(1)] = assign.get(m.group(2), '')
continue
m = re.match(r'^(\w+)\s*=\s*"([a-f0-9]{32})"$', stmt)
if m:
assign[m.group(1)] = m.group(2)
continue
print("UNMATCHED:", stmt[:80])
print('assign:', assign)
+68
View File
@@ -0,0 +1,68 @@
import re, base64, urllib.parse
html = open('/tmp/fresh2.html', encoding='utf8', errors='ignore').read()
mix = next(b for b in re.findall(r'<script>([\s\S]*?)</script>', html) if '.split("")' in b)
def eval_concat(expr):
out = []; i = 0; REV = '.split("").reverse().join("")'
while i < len(expr):
if expr[i] == '"':
close = expr.find('"', i + 1)
if close < 0: break
lit = expr[i+1:close]
is_rev = expr[close+1:close+1+len(REV)+3].startswith(REV)
out.append(lit[::-1] if is_rev else lit)
i = close + 1 + (len(REV) if is_rev else 0)
else:
i += 1
return ''.join(out)
stmts = [s.strip() for s in re.split(r';(?=(?:[^"]*"[^"]*")*[^"]*$)', mix) if s.strip()]
assign = {}
for stmt in stmts:
m = re.match(r'^var\s+(\w+)\s*=\s*"(.*)"$', stmt)
if m:
assign[m.group(1)] = eval_concat(m.group(2))
continue
if stmt.startswith('var ') and '"' in stmt:
# greedy: parse literally from first to last quoted segment
m2 = re.match(r'^var\s+(\w+)\s*=\s*(.*)$', stmt)
if m2:
name = m2.group(1)
expr = m2.group(2).strip()
# 找末尾最后一个引号前的内容
if expr.count('"') >= 2:
# last "的位置
last = expr.rfind('"')
expr2 = expr[:last + 1]
assign[name] = eval_concat(expr2)
continue
m = re.match(r'^(\w+)\s*=\s*(\w+)$', stmt)
if m:
assign[m.group(1)] = assign.get(m.group(2), '')
continue
m = re.match(r'^(\w+)\s*=\s*"([a-f0-9]{32})"$', stmt)
if m:
assign[m.group(1)] = m.group(2)
continue
print('UNMATCHED:', stmt[:60])
print('assign:', {k: (v[:35]+'..' if len(v) > 35 else v) for k, v in assign.items()})
vals = list(assign.values())
xKey = next(v for v in vals if v and not re.match(r'^[a-f0-9]{32}$', v) and v != '')
newTok = next(v for v in vals[::-1] if v and not re.match(r'^[a-f0-9]{32}$', v) and v != xKey)
oldToken = next(v for v in vals if v and re.match(r'^[a-f0-9]{32}$', v))
xorConst = re.search(r'"([a-f0-9]{16})"', mix).group(1)
print(f'xKey: {xKey!r}\nnewTok: {newTok!r}\nold: {oldToken[:8]}..\nconst: {xorConst}')
def b64str(s): return base64.b64decode(s.encode()).decode('latin1')
def decrypt(enc):
b1 = b64str(enc[::-1])
key = xKey + xorConst
xored = ''.join(chr((ord(b1[i]) ^ ord(key[i % len(key)])) & 0xff) for i in range(len(b1)))
b2 = b64str(xored)
return urllib.parse.unquote(b2.replace('token=' + oldToken, 'token=' + newTok).replace(xKey, ''))
opts = re.findall(r'<option value="([^"]+)"', html)
print('line1:', decrypt(opts[0])[:120])
+77
View File
@@ -0,0 +1,77 @@
import re, base64, urllib.parse
html = open('/tmp/fresh2.html', encoding='utf8', errors='ignore').read()
mix = next(b for b in re.findall(r'<script>([\s\S]*?)</script>', html) if '.split("")' in b)
def eval_concat(expr):
out = []; i = 0; REV = '.split("").reverse().join("")'
while i < len(expr):
if expr[i] == '"':
close = expr.find('"', i + 1)
if close < 0: break
lit = expr[i+1:close]
is_rev = expr[close+1:close+1+len(REV)+3].startswith(REV)
out.append(lit[::-1] if is_rev else lit)
i = close + 1 + (len(REV) if is_rev else 0)
else:
i += 1
return ''.join(out)
# 手写分号分割(考虑引号)
stmts = []
buf = ''
in_str = False
for ch in mix:
if ch == '"':
in_str = not in_str
if ch == ';' and not in_str:
stmts.append(buf.strip()); buf = ''
else:
buf += ch
if buf.strip():
stmts.append(buf.strip())
assign = {}
for stmt in stmts:
m = re.match(r'^var\s+(\w+)\s*=\s*"(.*)"$', stmt)
if m:
assign[m.group(1)] = eval_concat(m.group(2))
continue
m = re.match(r'^var\s+(\w+)\s*=\s*"(.*)$', stmt) # 末尾可能无close quote
if m:
assign[m.group(1)] = eval_concat(m.group(2))
continue
m = re.match(r'^var\s+(\w+)\s*=\s*$', stmt)
if m:
assign[m.group(1)] = ''
continue
m = re.match(r'^(\w+)\s*=\s*(\w+)$', stmt)
if m:
assign[m.group(1)] = assign.get(m.group(2), '')
continue
m = re.match(r'^(\w+)\s*=\s*"([a-f0-9]{32})"$', stmt)
if m:
assign[m.group(1)] = m.group(2)
continue
print('UNMATCHED:', stmt[:60])
print('assign:', {k: (v[:40] + '..' if len(v) > 40 else v) for k, v in assign.items()})
xKey = assign.get('fkeco') or ''
newTok = assign.get('uncmd') or ''
oldToken = next((v for v in assign.values() if re.match(r'^[a-f0-9]{32}$', v)), '')
xorConst = re.search(r'"([a-f0-9]{16})"', mix).group(1)
def b64str(s):
return base64.b64decode(s.encode()).decode('latin1')
def decrypt(enc):
b1 = b64str(enc[::-1])
key = xKey + xorConst
xored = ''.join(chr((ord(b1[i]) ^ ord(key[i % len(key)])) & 0xff) for i in range(len(b1)))
b2 = b64str(xored)
# token替换 + 去掉key字符串
return urllib.parse.unquote(b2.replace('token=' + oldToken, 'token=' + newTok).replace(xKey, ''))
opts = re.findall(r'<option value="([^"]+)"', html)
print('line1:', decrypt(opts[0])[:130])
+49
View File
@@ -0,0 +1,49 @@
import re, base64, urllib.parse
html = open('/tmp/fresh2.html', encoding='utf8', errors='ignore').read()
mix = next(b for b in re.findall(r'<script>([\s\S]*?)</script>', html) if '.split' in b)
# 1) 直接 per-语句 regex
stmts = [s.strip() for s in re.split(r';(?=(?:"[^"]*"\s*)*[^"]*$)', mix)]
def eval_concat(expr):
REV = '.split("").reverse().join("")'
out = []; i = 0
while i < len(expr):
if expr[i] == '"':
close = expr.find('"', i + 1)
if close < 0: break
lit = expr[i+1:close]
is_rev = expr[close+1:close+1+len(REV)].startswith(REV)
out.append(lit[::-1] if is_rev else lit)
i = close + 1 + (len(REV) if is_rev else 0)
else:
i += 1
return ''.join(out)
assign = {}
for stmt in stmts:
if not stmt: continue
m = re.match(r'^var\s+(\w+)\s*=\s*"(.*)"$', stmt)
if m:
assign[m.group(1)] = eval_concat(m.group(2))
continue
m = re.match(r'^var\s+(\w+)\s*=\s*"(.*)"', stmt) # no trailing $
if m:
assign[m.group(1)] = eval_concat(m.group(2))
continue
m = re.match(r'^var\s+(\w+)\s*=\s*$', stmt)
if m:
assign[m.group(1)] = ''
continue
m = re.match(r'^(\w+)\s*=\s*(\w+)$', stmt)
if m:
assign[m.group(1)] = assign.get(m.group(2), '')
continue
m = re.match(r'^(\w+)\s*=\s*"([a-f0-9]{32})"$', stmt)
if m:
assign[m.group(1)] = m.group(2)
continue
print('UNMATCHED:', stmt[:50])
print('assign:', {k: (v[:35]+'..' if len(v) > 35 else v) for k, v in assign.items()})
+82
View File
@@ -0,0 +1,82 @@
import re, base64, urllib.parse
html = open('/tmp/fresh2.html', encoding='utf8', errors='ignore').read()
mix = next(b for b in re.findall(r'<script>([\s\S]*?)</script>', html) if '.split' in b)
stmts = []
buf = ''; inside = False
for ch in mix:
if ch == '"':
inside = not inside
buf += ch
continue
if ch == ';' and not inside:
stmts.append(buf.strip())
buf = ''
continue
buf += ch
if buf.strip():
stmts.append(buf.strip())
def eval_concat(expr):
REV = '.split("").reverse().join("")'
out = []; i = 0
while i < len(expr):
if expr[i] == '"':
close = expr.find('"', i + 1)
if close < 0: break
lit = expr[i+1:close]
is_rev = expr[close+1:close+1+len(REV)].startswith(REV)
out.append(lit[::-1] if is_rev else lit)
i = close + 1 + (len(REV) if is_rev else 0)
else:
i += 1
return ''.join(out)
assign = {}
for stmt in stmts:
m = re.match(r'^var\s+(\w+)\s*=\s*"(.*)"$', stmt)
if m:
assign[m.group(1)] = eval_concat(m.group(2))
continue
m = re.match(r'^var\s+(\w+)\s*=\s*"(.*)"', stmt) # no $ : allow trailing join("") suffix
if m:
assign[m.group(1)] = eval_concat(m.group(2))
continue
m = re.match(r'^var\s+(\w+)\s*=\s*$', stmt)
if m:
assign[m.group(1)] = ''
continue
m = re.match(r'^(\w+)\s*=\s*(\w+)$', stmt)
if m:
assign[m.group(1)] = assign.get(m.group(2), '')
continue
m = re.match(r'^(\w+)\s*=\s*"([a-f0-9]{32})"$', stmt)
if m:
assign[m.group(1)] = m.group(2)
continue
print('UNMATCHED:', stmt[:60])
print('assign:', {k: (v[:35] + '..' if len(v) > 35 else v) for k, v in assign.items()})
vals = [v for v in assign.values() if v]
xKey = next(v for v in vals if not re.match(r'^[a-f0-9]{32}$', v) and not v.startswith('%'))
# newTok: also starts with % (encoded) — find it
newTok = next(v for v in vals if v.startswith('%'))
oldToken = next(v for v in vals if re.match(r'^[a-f0-9]{32}$', v))
xorConst = re.search(r'"([a-f0-9]{16})"', mix).group(1)
print(f'xKey={xKey}\nnewTok={newTok}\nold={oldToken[:10]}..\nconst={xorConst}')
def b64str(s):
return base64.b64decode(s.encode()).decode('latin1')
def decrypt(enc):
b1 = b64str(enc[::-1])
key = xKey + xorConst
x = ''.join(chr((ord(b1[i]) ^ ord(key[i % len(key)])) & 0xff) for i in range(len(b1)))
b2 = b64str(x)
# NOTE: 服务端只做 token=old→token=new 的字符串替换和 xKey "???"拼接剥离
return urllib.parse.unquote(b2.replace('token=' + oldToken, 'token=' + newTok).replace(xKey, ''))
opts = re.findall(r'<option value="([^"]+)"', html)
print('line1:', decrypt(opts[0])[:130])
+88
View File
@@ -0,0 +1,88 @@
import re, base64, urllib.parse
html = open('/tmp/fresh2.html', encoding='utf8', errors='ignore').read()
mix = next(b for b in re.findall(r'<script>([\s\S]*?)</script>', html) if '.split' in b)
stmts = []
buf = ''; inside = False
for ch in mix:
if ch == '"':
inside = not inside
buf += ch
continue
if ch == ';' and not inside:
stmts.append(buf.strip())
buf = ''
continue
buf += ch
if buf.strip():
stmts.append(buf.strip())
def eval_concat(expr):
# 关键:" 拆开 — expr begins WITHOUT leading quote (它已在var assignment里被消费)
# expr: b8"+"fb"+"f".split("").reverse().join("")+"c29e"...
# 用 " 切:多段:['b8', '+', 'fb', '+', 'f', '.split(。'...]
# 期望JS语义: "b8" + "fb" + "f".split("").reverse().join("") + "c29e"...
# b8 (normal), fb (normal), f (rev单字不变), c29e... (逐段 'rev'取决于 q[i+1] 之后是否跟着 REV)
# 重新实现:每次 分割 " 按 char,两个引号一组 literal; quote 组之间字符 `+` 连接;
REV = '.split("").reverse().join("")'
parts = expr.split('"')
# parts结构: [pre, lit1, sep1, lit2, sep2, ...]
# sep里如果以 ' + .split' 开头等 (REV后跟随的+), 判断 REV 属于前面那次
result = []
i = 1 # parts[0]是空的或非literal常数
while i < len(parts):
lit = parts[i]
sep = parts[i+1] if i + 1 < len(parts) else ''
if sep.startswith('+ .split') or sep.startswith('+.split'):
result.append(lit)
else: result.append(lit) # simplify: all normal
i += 2
# 但rev:
return ''.join(result)
# simpler and correct: tokenize quotes pairs with ownership of next chars
def eval_v2(expr):
REV = '.split("").reverse().join("")'
# find quotes pairing; check if literal is followed by REV
result = []
idx = 0
while True:
q1 = expr.find('"', idx)
if q1 < 0: break
q2 = expr.find('"', q1 + 1)
if q2 < 0: break
lit = expr[q1+1:q2]
is_rev = expr[q2+1:q2+1+len(REV)] == REV
result.append(lit[::-1] if is_rev else lit)
idx = q2 + 1 + (len(REV) if is_rev else 0)
# skip + 连接符
while idx < len(expr) and expr[idx] in ' +.snp':
idx += 1
return ''.join(result)
assign = {}
for stmt in stmts:
m = re.match(r'^var\s+(\w+)\s*=\s*(.*)$', stmt)
if m:
name = m.group(1); expr = m.group(2).strip()
if not expr or not expr.startswith('"'):
if expr == '':
assign[name] = ''
continue
m2 = re.match(r'^"([a-f0-9]{32})"$', expr)
if m2:
assign[name] = m2.group(1)
continue
continue
# 从expr中来(uns greedy)
if expr.startswith('"'):
# 内部从头到尾 pair
assign[name] = eval_v2(expr[1:]) # strip leading "
continue
# 无开头引号? fallback to 正常
assign[name] = expr
continue
print('un:', stmt[:40])
print('assign:', {k: (v[:25]+'..' if len(v) > 25 else v) for k, v in assign.items()})
+84
View File
@@ -0,0 +1,84 @@
import re, base64, urllib.parse
html = open('/tmp/fresh2.html', encoding='utf8', errors='ignore').read()
mix = next(b for b in re.findall(r'<script>([\s\S]*?)</script>', html) if '.split' in b)
stmts = []
buf = ''; inside = False
for ch in mix:
if ch == '"':
inside = not inside
buf += ch
continue
if ch == ';' and not inside:
stmts.append(buf.strip())
buf = ''
continue
buf += ch
if buf.strip():
stmts.append(buf.strip())
def eval_v2(expr):
REV = '.split("").reverse().join("")'
result = []; idx = 0
while True:
q1 = expr.find('"', idx)
if q1 < 0: break
q2 = expr.find('"', q1 + 1)
if q2 < 0: break
lit = expr[q1+1:q2]
is_rev = expr[q2+1:q2+1+len(REV)] == REV
result.append(lit[::-1] if is_rev else lit)
idx = q2 + 1 + (len(REV) if is_rev else 0)
while idx < len(expr) and expr[idx] in ' +.srne(--,)':
idx += 1
return ''.join(result)
assign = {}
for stmt in stmts:
m = re.match(r'^var\s+(\w+)\s*=\s*("(?:[^"]*)".*)?$', stmt)
if m:
name = m.group(1); expr = (m.group(2) or '').strip()
if not expr:
assign[name] = ''; continue
if expr.startswith('"'):
assign[name] = eval_v2(expr)
continue
# 字面量 var xx = "32hex"
m2 = re.match(r'^"([a-f0-9]{32})"$', expr)
if m2:
assign[name] = m2.group(1); continue
continue
m = re.match(r'^(\w+)\s*=\s*(\w+)$', stmt)
if m:
assign[m.group(1)] = assign.get(m.group(2), '')
continue
m = re.match(r'^(\w+)\s*=\s*"([a-f0-9]{32})"$', stmt)
if m:
assign[m.group(1)] = m.group(2)
continue
print('UNMATCHED:', stmt[:60])
print('assign:', {k: (v[:30] + '..' if len(v) > 30 else v) for k, v in assign.items()})
vals = [v for v in assign.values() if v]
xKey = next((v for v in vals if not re.match(r'^[a-f0-9]{32}$', v) and not v.startswith('%')), '')
newTok = next((v for v in vals if v.startswith('%')), '')
oldToken = next((v for v in vals if re.match(r'^[a-f0-9]{32}$', v)), '')
xorConst = re.search(r'"([a-f0-9]{16})"', mix).group(1)
print(f'xKey={xKey}\nnewTok={newTok}\nold={oldToken[:10]}.. const={xorConst}')
def b64str(s):
return base64.b64decode(s.encode()).decode('latin1')
def decrypt(enc):
b1 = b64str(enc[::-1])
key = xKey + xorConst
x = ''.join(chr((ord(b1[i]) ^ ord(key[i % len(key)])) & 0xff) for i in range(len(b1)))
b2 = b64str(x)
return urllib.parse.unquote(b2.replace('token=' + oldToken, 'token=' + newTok).replace(xKey, ''))
opts = re.findall(r'<option value="([^"]+)"', html)
if opts:
print('line1:', decrypt(opts[0])[:130])
print('line2:', decrypt(opts[1])[:130])
+96
View File
@@ -0,0 +1,96 @@
import re, base64, urllib.parse
html = open('/tmp/fresh2.html', encoding='utf8', errors='ignore').read()
blocks = re.findall(r'<script>([\s\S]*?)</script>', html)
mix = next(b for b in blocks if '.split' in b)
stmts = []
buf = ''; inside = False
for ch in mix:
if ch == '"':
inside = not inside
buf += ch
continue
if ch == ';' and not inside:
stmts.append(buf.strip())
buf = ''
continue
buf += ch
if buf.strip():
stmts.append(buf.strip())
def eval_v2(expr):
REV = '.split("").reverse().join("")'
result = []; idx = 0
while True:
q1 = expr.find('"', idx)
if q1 < 0: break
q2 = expr.find('"', q1 + 1)
if q2 < 0: break
lit = expr[q1+1:q2]
is_rev = expr[q2+1:q2+1+len(REV)] == REV
result.append(lit[::-1] if is_rev else lit)
idx = q2 + 1 + (len(REV) if is_rev else 0)
while idx < len(expr) and expr[idx] in ' +.srne(-,)':
idx += 1
return ''.join(result)
assign = {}
for stmt in stmts:
m = re.match(r'^var\s+(\w+)\s*=\s*("(?:[^"]*)".*)?$', stmt)
if m:
name = m.group(1); expr = (m.group(2) or '').strip()
if not expr: assign[name] = ''; continue
if expr.startswith('"'):
assign[name] = eval_v2(expr)
continue
continue
m = re.match(r'^(\w+)\s*=\s*(\w+)$', stmt)
if m:
assign[m.group(1)] = assign.get(m.group(2), '')
continue
m = re.match(r'^(\w+)\s*=\s*"([a-f0-9]{32})"$', stmt)
if m:
assign[m.group(1)] = m.group(2)
continue
# 变量含义:
# xKey = urdlw后的 assign值 (fkeco) — 32 hex-lookin b8fbfe...? 不,b8fbfe...是 xKey? 长度34?看长度
candidate_vals = {k: v for k, v in assign.items() if v}
for k, v in candidate_vals.items():
print(f'{k} len={len(v)}: {v[:40]}')
# xKey 从源代码里看是 KEY; 它有 literal "b8fbfe92cc44e64533fcf75e8010097e" — hmm长度=36? page的 fbjao21拼接变32位hex
# but svg based on原始 code:
# var xefgv = ""; var fbjao = "..拼接.."; xefgv = fbjao; (xKey)
# var qrtkk = ""; var lnzyw = "..拼接.."; qrtkk = lnzyw; (newToken)
# ouyit = "32hex" (oldToken)
# 所以 xKey / newTok 都是 mixed literals (not hex32)
# BUT实际assign里 fkeco='b8fbfe92cc44e64533fcf75e801009..'是一个32hex串! 那 xKey 也是hex32? — 注意newTok(last)='%' URL-encode形式。
# 因此爷就取:
xKey = next(v for v in assign.values() if v and re.match(r'^[a-f0-9]{32}$', v) and v != next(u for u in assign.values() if re.match(r'^[a-f0-9]{32}$', u)))
# Hmm confusing; more precisely:
vals = list(assign.values())
hex32s = [v for v in vals if re.match(r'^[a-f0-9]{32}$', v)]
print('hex32s:', hex32s)
# 常识:b8fbfe.. 是 xKey (from urdlw eval), old token '1c2e5965...' another hex32
# New token = uncmd starting '%'
newTok = next((v for v in vals if v.startswith('%')), '')
oldToken = next((v for v in hex32s if v != 'b8fbfe92cc44e64533fcf75e8010097e'), '')
xKey = 'b8fbfe92cc44e64533fcf75e8010097e' # hardcoded from first
xorConst = re.findall(r'"([a-f0-9]{16})"', html)[0]
print(f'xKey={xKey}\nnewTok={newTok}\nold={oldToken}\nconst={xorConst}')
def b64str(s): return base64.b64decode(s.encode()).decode('latin1')
def decrypt(enc):
b1 = b64str(enc[::-1])
key = xKey + xorConst
x = ''.join(chr((ord(b1[i]) ^ ord(key[i % len(key)])) & 0xff) for i in range(len(b1)))
b2 = b64str(x)
return urllib.parse.unquote(b2.replace('token=' + oldToken, 'token=' + newTok).replace(xKey, ''))
opts = re.findall(r'<option value="([^"]+)"', html)
if opts:
print('L1:', decrypt(opts[0])[:130])
print('L2:', decrypt(opts[1])[:130])
+72
View File
@@ -0,0 +1,72 @@
import re, base64, urllib.parse
html = open('/tmp/a1.html', encoding='utf8', errors='ignore').read()
mix = next(b for b in re.findall(r'<script>([\s\S]*?)</script>', html) if '.split("")' in b)
stmts = [s.strip() for s in mix.split(';') if s.strip()]
REV = '.split("").reverse().join("")'
def ev(expr):
out = []; i = 0
while i < len(expr):
if expr[i] == '"':
j = i + 1; lit = ''
while j < len(expr) and expr[j] != '"':
lit += expr[j]; j += 1
if j >= len(expr):
out.append(lit); break
rest = expr[j+1:j+1+50]
if rest.startswith(REV):
out.append(lit[::-1]); i = j + 1 + len(REV)
else:
out.append(lit); i = j + 1
while i < len(expr) and expr[i] in '+ ':
i += 1
else:
i += 1
return ''.join(out)
assign = {}
for st in stmts:
m = re.match(r'^var\s+(\w+)\s*=\s*$', st)
if m: assign[m.group(1)] = ''; continue
m = re.match(r'^var\s+(\w+)\s*=\s*"(.*)"$', st)
if m:
assign[m.group(1)] = ev(m.group(2)); continue
m = re.match(r'^var\s+(\w+)\s*=\s*"(.*)"', st) # no closing
if m:
assign[m.group(1)] = ev(m.group(2)); continue
m = re.match(r'^(\w+)\s*=\s*(\w+)$', st)
if m:
assign[m.group(1)] = assign.get(m.group(2), ''); continue
m = re.match(r'^(\w+)\s*=\s*"([a-f0-9]{32})"$', st)
if m:
assign[m.group(1)] = m.group(2); continue
print('unmet:', st[:50])
print('assign:', {k: (v[:20]+'..' if len(v) > 20 else v) for k, v in assign.items()})
# 按 JS 语言语义变量:
# xKey = cdgsc (=jvfon_eval)
# oldToken= jbdjv (裸 32hex)
# newToken= xptcr (=xcyvc_eval)
vals = [v for v in assign.values() if v]
hex32 = [v for v in vals if re.match(r'^[a-f0-9]{32}$', v)]
xKey = next(v for v in vals if re.match(r'^[a-f0-9]{32}$', v) and not v.startswith('%'))
# 修复: xKey应该是 jvfon_eval (32hex) — 从var(以 "(.*)" 成)来的
# oldToken = jbdjv bare(32hex) — 第二个 hex32
oldToken = next((h for h in hex32 if h != xKey), hex32[0])
newToken = next((v for v in vals if re.search(r'%[0-9A-Fa-f]{2}', v) and not re.match(r'^[a-f0-9]{32}$', v) and not re.match(r'^[a-f0-9]{16}$', v)), '')
print(f'xKey={xKey[:24]}...\noldTok={oldToken[:24]}...\nnewTok={newToken[:24]}...\nconst={re.findall(chr(34)+"([a-f0-9]{16})"+chr(34), html)[0]}')
xorConst = re.findall(r'"([a-f0-9]{16})"', html)[0]
def b64(s): return base64.b64decode(s.encode()).decode('latin1')
def dec(e):
b1 = b64(e[::-1])
k = xKey + xorConst
x = ''.join(chr((ord(b1[i]) ^ ord(k[i % len(k)])) & 0xff) for i in range(len(b1)))
b2 = b64(x)
return urllib.parse.unquote(b2.replace('token=' + oldToken, 'token=' + newToken).replace(xKey, ''))
opts = re.findall(r'<option value="([^"]+)"[^>]*>', html)
print('L1:', dec(opts[0])[:135])
print('L2:', dec(opts[1])[:135])
+61
View File
@@ -0,0 +1,61 @@
import re, base64, urllib.parse
html = open('/tmp/android_proxy.html', encoding='utf8', errors='ignore').read()
mix = next(b for b in re.findall(r'<script>([\s\S]*?)</script>', html) if '.split("")' in b)
def eval_frags(expr):
REVr = '.split("").reverse().join("")'
out = []; i = 0
while i < len(expr):
m = re.match(r'"([^"]*)"(\.split\(""\)\.reverse\(\)\.join\(""\))?', expr[i:])
if m:
lit = m.group(1)
is_rev = m.group(2) is not None
out.append(lit[::-1] if is_rev else lit)
i += m.end()
while i < len(expr) and expr[i] in ' +':
i += 1
else:
i += 1
return ''.join(out)
# 真实数据验证:
assign = {}
for st in [s.strip() for s in mix.split(';') if s.strip()]:
m = re.match(r'^var\s+(\w+)\s*=\s*(.*)$', st)
if not m:
m = re.match(r'^(\w+)\s*=\s*(.*)$', st)
if m:
name = m.group(1); rhs = m.group(2).strip().rstrip(')').strip()
if rhs in assign: assign[name] = assign[rhs]
continue
else:
name = m.group(1); rhs = m.group(2).strip().rstrip(')').strip()
if not rhs: assign[name] = ''; continue
eval_body = rhs
if eval_body.startswith('"'): eval_body = eval_body[1:] # strip leading quote (assignment's opening)
# 还有末尾可能结尾在 .join("") 之后的 )
assign[name] = eval_frags(eval_body)
continue
print('assign:', {k: (v[:25]+'..' if len(v) > 25 else v) for k, v in assign.items()})
vals = [v for v in assign.values() if v]
hex32 = [v for v in vals if re.match(r'^[a-f0-9]{32}$', v)]
xKey = hex32[0]
oldToken = next(h for h in hex32 if h != xKey)
newToken = next(v for v in vals if re.search(r'%[0-9A-Fa-f]{2}', v) and not re.match(r'^[a-f0-9]{32}$', v) and not re.match(r'^[a-f0-9]{16}$', v) and v != xKey)
xorConst = re.findall(r'"([a-f0-9]{16})"', html)[0]
print(f'xKey={xKey} new={newToken[:26]} old={oldToken[:10]}.. c={xorConst}')
def b64(s): return base64.b64decode(s.encode()).decode('latin1')
def dec(e):
b1 = b64(e[::-1])
k = xKey + xorConst
x = ''.join(chr((ord(b1[i]) ^ ord(k[i % len(k)])) & 0xff) for i in range(len(b1)))
b2 = b64(x)
return urllib.parse.unquote(b2.replace('token=' + oldToken, 'token=' + newToken).replace(xKey, ''))
opts = re.findall(r'<option value="([^"]+)"[^>]*>', html)
print('L1:', dec(opts[0])[:130])
+33
View File
@@ -0,0 +1,33 @@
#!/usr/bin/env python3
"""License-SaaS 客户端 API 端到端测试(HMAC-SHA256签名协议)"""
import hmac, hashlib, json, time, uuid, urllib.request, urllib.error
APP_KEY = 'iptv807-tv'
CLIENT_SECRET = '91a62f41ff09c2eed51fa571a78c69014c5f9eacbaeca56c'
BASE = 'http://192.168.68.19:8090'
def signed(method, path, body=None):
body_str = json.dumps(body or {}, ensure_ascii=False, separators=(',', ':'))
ts = str(int(time.time()))
nonce = uuid.uuid4().hex[:16]
canonical = f"{method}\n{path}\n{ts}\n{nonce}\n{body_str}"
sig = hmac.new(CLIENT_SECRET.encode(), canonical.encode(), hashlib.sha256).hexdigest()
req = urllib.request.Request(BASE + path, data=(body_str.encode() if method == 'POST' else None), method=method)
req.add_header('Content-Type', 'application/json')
req.add_header('X-Timestamp', ts)
req.add_header('X-Nonce', nonce)
req.add_header('X-Signature', sig)
req.add_header('X-App-Key', APP_KEY)
try:
with urllib.request.urlopen(req, timeout=10) as r:
return json.loads(r.read())
except urllib.error.HTTPError as e:
return {'http': e.code, 'body': e.read().decode()[:200]}
print('app-info:', signed('GET', '/api/client/app-info'))
r = signed('POST', '/api/client/register', {'username': 'test01', 'password': 'test123456', 'cardKey': 'LS-F10E53D1-8C91BFD6-7E098CFF', 'machineCode': 'ANDROID-TV-001'})
print('register:', r)
r = signed('POST', '/api/client/login', {'username': 'test01', 'password': 'test123456', 'machineCode': 'ANDROID-TV-001'})
print('login:', r)
r = signed('POST', '/api/client/heartbeat', {'token': r.get('client_token', '')})
print('heartbeat:', r)
+64
View File
@@ -0,0 +1,64 @@
import re, base64, urllib.parse
html = open('/tmp/now24.html', encoding='utf8', errors='ignore').read()
mix = next(b for b in re.findall(r'<script>([\s\S]*?)</script>', html) if '.split' in b)
stmts = []; buf=''; ins=False
for ch in mix:
if ch=='"':
ins = not ins
buf += ch
continue
if ch==';' and not ins:
stmts.append(buf.strip()); buf=''
else: buf += ch
if buf.strip(): stmts.append(buf.strip())
def eval_v2(expr):
REV='.split("").reverse().join("")'
result=[]; idx=0
while True:
q1=expr.find('"', idx)
if q1<0: break
q2=expr.find('"', q1+1)
if q2<0: break
lit=expr[q1+1:q2]
is_rev=expr[q2+1:q2+1+len(REV)]==REV
result.append(lit[::-1] if is_rev else lit)
idx=q2+1+(len(REV) if is_rev else 0)
while idx<len(expr) and expr[idx] in ' +.srne(-,)':
idx+=1
return ''.join(result)
assign={}
for stmt in stmts:
m=re.match(r'^var\s+(\w+)\s*=\s*("(?:[^"]*)".*)?$', stmt)
if m:
name=m.group(1); expr=(m.group(2) or '').strip()
if not expr: assign[name]=''; continue
if expr.startswith('"'): assign[name]=eval_v2(expr); continue
continue
# 32-hex literal assignment (var or bare)
m=re.match(r'^(\w+)\s*=\s*"([a-f0-9]{32})"$', stmt)
if m: assign[m.group(1)]=m.group(2); continue
print('assign:', {k:(v[:30] if len(v)<31 else v[:30]+'..') for k,v in assign.items()})
vals=[v for v in assign.values() if v]
hex32=[v for v in vals if re.match(r'^[a-f0-9]{32}$', v)]
xKey = hex32[0]
oldToken = next((h for h in hex32 if h != xKey), hex32[0]) # 有些页 old==xkey?
newToken = next((v for v in vals if v.startswith('%')), '')
xorConst = re.findall(r'"([a-f0-9]{16})"', html)[0]
print(f'xKey={xKey} old={oldToken} newTok={newToken} c={xorConst}')
def b64(s): return base64.b64decode(s.encode()).decode('latin1')
def decrypt(enc):
b1 = b64(enc[::-1])
key = xKey + xorConst
x=''.join(chr((ord(b1[i]) ^ ord(key[i%len(key)])) & 0xff) for i in range(len(b1)))
b2 = b64(x)
return urllib.parse.unquote(b2.replace('token='+oldToken, 'token='+newToken).replace(xKey, ''))
opts = re.findall(r'<option value="([^"]+)"[^>]*>線路\d*', html)
for i, o in enumerate(opts[:2]):
print(f'L{i+1}:', decrypt(o)[:130])
+48
View File
@@ -0,0 +1,48 @@
import re, base64, urllib.parse
# 测试有 trailing join 的真实语句(android_proxy.html 实际数据):
html = open('/tmp/android_proxy.html', encoding='utf8', errors='ignore').read()
blocks = re.findall(r'<script>([\s\S]*?)</script>', html)
mixBlock = next(b for b in blocks if '.split("")' in b)
stmts = [s.strip() for s in mixBlock.split(';') if s.strip()]
print('stmt[1] (dfuts):')
print(repr(stmts[1]))
print()
def eval_concat(expr):
REV = '.split("").reverse().join("")'
out = []; i = 0
while i < len(expr):
if expr[i] == '"':
close = expr.find('"', i+1)
if close < 0: break
lit = expr[i+1:close]
is_rev = expr[close+1:close+1+len(REV)] == REV
out.append(lit[::-1] if is_rev else lit)
i = close+1+(len(REV) if is_rev else 0)
else:
i += 1
return ''.join(out)
# 正确assignment解析(Python顺利):
assign = {}
for st in stmts:
m = re.match(r'^var\s+(\w+)\s*=\s*"(.*)"$', st)
if m:
assign[m.group(1)] = eval_concat(m.group(2)); continue
m = re.match(r'^var\s+(\w+)\s*=\s*(.*)"\)$', st)
if m:
assign[m.group(1)] = eval_concat(m.group(2)); continue
m = re.match(r'^var\s+(\w+)\s*=\s*(.*)"$'.rstrip('"$')+'("\\)$)?$', st)
if m and not m.group(0).endswith('\\') : pass
m = re.match(r'^var\s+(\w+)\s*=\s*$', st)
if m:
assign[m.group(1)] = ''; continue
m = re.match(r'^(\w+)\s*=\s*(\w+)$', st)
if m:
assign[m.group(1)] = assign.get(m.group(2), ''); continue
m = re.match(r'^(\w+)\s*=\s*"([a-f0-9]{32})"$', st)
if m:
assign[m.group(1)] = m.group(2); continue
print('assign:', {k: (v[:20]+'..' if len(v) > 20 else v) for k, v in assign.items()})
+75
View File
@@ -0,0 +1,75 @@
import re, base64, urllib.parse
html = open('/tmp/android_proxy.html', encoding='utf8', errors='ignore').read()
mix = next(b for b in re.findall(r'<script>([\s\S]*?)</script>', html) if '.split("")' in b)
# 修正版 eval:游标从 expr 起点扫描,遇 quote 开literal,遇REV tag 反转
def eval_v3(expr):
REV = '.split("").reverse().join("")'
frags = []
i = 0
while i < len(expr):
if expr[i] == '"':
j = i + 1; lit = ''
while j < len(expr) and expr[j] != '"':
lit += expr[j]; j += 1
if j >= len(expr):
frags.append(lit); break # unterminated - take rest
rest = expr[j+1:j+1+50]
is_rev = rest.startswith(REV)
frags.append(lit[::-1] if is_rev else lit)
i = j + 1 + (len(REV) if is_rev else 0)
while i < len(expr) and expr[i] == '+':
i += 1
else:
i += 1
return ''.join(frags)
assert eval_v3('"b8"+"fb"+"f".split("").reverse().join("")+"c29e"') == 'b8fbfe92c', "test failed"
print('v3 test OK')
assign = {}
for st in [s.strip() for s in mix.split(';') if s.strip()]:
m = re.match(r'^var\s+(\w+)\s*=\s*(.*)$', st)
if m:
name = m.group(1); rhs = m.group(2).strip()
if not rhs or rhs == '"':
assign[name] = ''; continue
# rhs 开头是 ",保留整段从quote开始
if rhs.startswith('"'):
assign[name] = eval_v3(rhs)
continue
m2 = re.match(r'^"([a-f0-9]{32})"$', rhs)
if m2:
assign[name] = m2.group(1); continue
continue
m = re.match(r'^(\w+)\s*=\s*(\w+)$', st)
if m:
assign[m.group(1)] = assign.get(m.group(2), '')
continue
m = re.match(r'^(\w+)\s*=\s*"([a-f0-9]{32})"$', st)
if m:
assign[m.group(1)] = m.group(2)
continue
print('un:', st[:40])
print('assign:', {k: (v[:25]+'. .' if len(v) > 25 else v) for k, v in assign.items()})
vals = [v for v in assign.values() if v]
hex32 = [v for v in vals if re.match(r'^[a-f0-9]{32}$', v)]
xKey = hex32[0]
oldToken = next(h for h in hex32 if h != xKey)
newToken = next(v for v in vals if re.search(r'%[0-9A-Fa-f]{2}', v) and not re.match(r'^[a-f0-9]{32}$', v) and not re.match(r'^[a-f0-9]{16}$', v) and v != xKey)
xorConst = re.findall(r'"([a-f0-9]{16})"', html)[0]
print(f'xKey={xKey}\nnewTok={newToken[:30]}..\nold={oldToken[:10]}..\nc={xorConst}')
def b64(s): return base64.b64decode(s.encode()).decode('latin1')
def dec(e):
b1 = b64(e[::-1])
k = xKey + xorConst
x = ''.join(chr((ord(b1[i]) ^ ord(k[i % len(k)])) & 0xff) for i in range(len(b1)))
b2 = b64(x)
return urllib.parse.unquote(b2.replace('token=' + oldToken, 'token=' + newToken).replace(xKey, ''))
opts = re.findall(r'<option value="([^"]+)"[^>]*>', html)
print('L1:', dec(opts[0])[:130])
+33
View File
@@ -0,0 +1,33 @@
#!/usr/bin/env python3
"""验证 channels.json 里各频道线路1可拉流(302 → 流头 FLV/m3u8)"""
import json, urllib.request, socket
socket.setdefaulttimeout(12)
UA = 'Mozilla/5.0 (Linux; Android 12) Chrome/124 Mobile Safari/537.36'
data = json.load(open('/vol1/1000/HD1/APP/iptv-tv-app/tools/channels.json'))
ok = [c for c in data if c.get('lines')]
print('channels:', len(ok))
for c in ok:
url = c['lines'][0]
try:
req = urllib.request.Request(url, headers={'User-Agent': UA, 'Referer': 'https://m.iptv807.com/'})
# 不跟随重定向,先拿302目标
class NoRedir(urllib.request.HTTPRedirectHandler):
def redirect_request(self, *a, **k): return None
op = urllib.request.build_opener(NoRedir)
try:
op.open(req)
red = None
except urllib.error.HTTPError as e:
red = e.headers.get('Location')
if not red:
print(f"❌ {c['name']}: no redirect")
continue
# 拉最终流前32字节
req2 = urllib.request.Request(red, headers={'User-Agent': UA})
with urllib.request.urlopen(req2) as r:
head = r.read(32)
magic = 'FLV' if head.startswith(b'FLV') else ('m3u8' if b'#EXTM3U' in head else head[:8])
print(f"✅ {c['name']}: {magic}")
except Exception as e:
print(f"❌ {c['name']}: {str(e)[:80]}")