Files
a/scripts/牛二.php
2026-03-24 18:40:17 +08:00

326 lines
15 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
// 设置时区为上海(东八区)
date_default_timezone_set('Asia/Shanghai');
// 快速排序.php - TVBox 站点排序删除管理(最终纯净版)
// ✅ 左滑 >1/3 宽度 → 自动删除(无确认)
// ✅ 删除后立即上移
// ✅ 重置 = 删除缓存 + 重新拉取
// ✅ 保存后自动刷新
// ✅ 来源:天机阁
// ✅ 时间:北京时间(东八区)
header('Content-Type: text/html; charset=utf-8');
// ========== 处理 ?reset=1 ==========
if (isset($_GET['reset']) && $_GET['reset'] === '1') {
@unlink(__DIR__ . '/cache/niu.json');
header('Location: ' . strtok($_SERVER['REQUEST_URI'], '?'));
exit;
}
// ========== 配置 ==========
$urls = [
"https://9280.kstore.vip/newwex.json",
];
$cache_file = __DIR__ . '/cache/niu.json';
$cache_ttl = 300;
// 创建缓存目录
if (!is_dir(dirname($cache_file))) {
@mkdir(dirname($cache_file), 0755, true);
}
// ========== 辅助函数 ==========
function fetch_json($url) {
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => trim($url),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 5,
CURLOPT_CONNECTTIMEOUT => 1.5,
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_SSL_VERIFYHOST => false,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_USERAGENT => 'okhttp/3.15',
]);
$data = curl_exec($ch);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($code >= 200 && $code < 400 && $data) {
$json = json_decode($data, true);
if (json_last_error() === JSON_ERROR_NONE && !empty($json['sites'])) {
return $json;
}
}
return null;
}
function get_sites_data() {
global $urls, $cache_file, $cache_ttl;
if (file_exists($cache_file) && (time() - filemtime($cache_file)) <= $cache_ttl) {
$data = json_decode(file_get_contents($cache_file), true);
if (json_last_error() === JSON_ERROR_NONE && !empty($data['sites'])) {
return $data;
}
}
foreach ($urls as $i => $url) {
if ($data = fetch_json($url)) {
$data['_source'] = '天机阁';
$data['_fetched_at'] = date('Y-m-d H:i:s');
file_put_contents($cache_file, json_encode($data, JSON_UNESCAPED_UNICODE));
return $data;
}
}
return ['sites' => [], '_source' => '天机阁', '_fetched_at' => date('Y-m-d H:i:s')];
}
// ========== UA 检测TVBox 直接返回 JSON ==========
$ua = $_SERVER['HTTP_USER_AGENT'] ?? '';
if (stripos($ua, 'okhttp') !== false || stripos($ua, 'TVBox') !== false || stripos($ua, 'FongMi') !== false) {
$data = get_sites_data();
header('Content-Type: application/json; charset=utf-8');
header('Access-Control-Allow-Origin: *');
echo json_encode($data, JSON_UNESCAPED_UNICODE);
exit;
}
// ========== 保存处理 ==========
if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'save') {
$order = json_decode($_POST['order'], true);
$all_sites = json_decode($_POST['all_sites'], true);
if (!$order || !$all_sites) {
http_response_code(400);
echo json_encode(['success' => false, 'message' => '❌ 数据格式错误']);
exit; }
$new_sites = [];
foreach ($order as $key) {
if (isset($all_sites[$key])) {
$new_sites[] = $all_sites[$key];
}
}
$original_data = get_sites_data();
$output = array_merge($original_data, [
'sites' => $new_sites,
'_custom_sorted' => true,
'_sorted_at' => date('Y-m-d H:i:s'),
'_source' => '天机阁'
]);
if (file_put_contents($cache_file, json_encode($output, JSON_UNESCAPED_UNICODE)) !== false) {
echo json_encode(['success' => true, 'message' => '✅ 保存成功!' . count($new_sites) . '个站点已更新']);
} else {
http_response_code(500);
echo json_encode(['success' => false, 'message' => '❌ 保存失败!检查 cache 目录权限']);
}
exit;
}
// 获取数据
$data = get_sites_data();
$sites = $data['sites'] ?? [];
$source_info = $data['_source'] ?? '天机阁';
$fetched_at = $data['_fetched_at'] ?? date('Y-m-d H:i:s');
$sites_map = [];
foreach ($sites as $site) {
$key = $site['key'] ?? md5(json_encode($site));
$sites_map[$key] = $site;
}
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>📺 TVBox 站点排序删除管理</title>
<style>
*{margin:0;padding:0;box-sizing:border-box;-webkit-tap-highlight-color:transparent}
body{font-family:-apple-system,BlinkMacSystemFont,"PingFang SC","Microsoft YaHei",sans-serif;background:linear-gradient(135deg,#667eea 0%,#764ba2 100%);color:#333;padding:env(safe-area-inset-top) env(safe-area-inset-right) 90px env(safe-area-inset-left);min-height:100vh}
.header{background:white;padding:15px 20px;text-align:center;box-shadow:0 2px 15px rgba(0,0,0,0.1);position:sticky;top:0;z-index:100;border-radius:0 0 16px 16px}
.header h1{font-size:22px;font-weight:700;color:#4361ee;display:flex;align-items:center;justify-content:center;gap:8px}
.stats{display:flex;justify-content:space-around;margin-top:8px;font-size:13px;color:#64748b;background:#f8fafc;border-radius:12px;padding:8px 0} .list-container{background:white;border-radius:20px;margin:15px;box-shadow:0 5px 25px rgba(0,0,0,0.12);max-height:calc(100vh - 230px);overflow-y:auto}
.list-header{padding:14px 20px;background:linear-gradient(to right,#4361ee,#3a0ca3);color:white;font-weight:600;font-size:16px;border-radius:18px 18px 0 0;display:flex;justify-content:space-between;align-items:center}
.item{position:relative;display:flex;align-items:center;padding:16px 20px;border-bottom:1px solid #f1f5f9;background:#fff;touch-action:pan-y;overflow:hidden;cursor:grab}
.item:last-child{border-bottom:none}
.item-content{flex:1;min-width:0;padding-right:10px}
.name{font-size:17px;color:#1e293b;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.item-delete{position:absolute;right:-60px;top:0;height:100%;width:60px;background:linear-gradient(135deg,#ff6b6b,#ee5a5a);color:white;display:flex;align-items:center;justify-content:center;font-size:28px;font-weight:700;z-index:10;transition:right 0.2s ease;pointer-events:none}
.action-bar{position:fixed;bottom:0;left:0;right:0;background:white;padding:12px 15px env(safe-area-inset-bottom);box-shadow:0 -3px 20px rgba(0,0,0,0.15);display:grid;grid-template-columns:1fr 1fr 1fr;gap:10px;z-index:200}
.main-btn{height:52px;border-radius:14px;border:none;font-size:17px;font-weight:600;color:white;display:flex;align-items:center;justify-content:center;gap:6px;box-shadow:0 3px 12px rgba(0,0,0,0.15);touch-action:manipulation;transition:all 0.2s}
.save-btn{background:linear-gradient(to right,#4361ee,#3a0ca3)}
.refresh-btn{background:linear-gradient(to right,#4cc9f0,#4361ee)}
.restore-btn{background:linear-gradient(to right,#7209b7,#480ca8)}
.toast{position:fixed;bottom:90px;left:50%;transform:translateX(-50%) translateY(100px);background:white;color:#1e293b;padding:14px 28px;border-radius:50px;box-shadow:0 5px 25px rgba(0,0,0,0.25);font-size:17px;font-weight:500;z-index:1000;opacity:0;transition:all 0.3s cubic-bezier(0.23,1,0.32,1);text-align:center;max-width:85%}
.toast.show{transform:translateX(-50%) translateY(0);opacity:1}
.toast.success{background:linear-gradient(to right,#10b981,#0da271);color:white}
.toast.error{background:linear-gradient(to right,#ef4444,#dc2626);color:white}
.empty-state{text-align:center;padding:40px 20px;color:#94a3b8}
</style>
</head>
<body>
<div class="header">
<h1>📺 TVBox站点排序删除管理</h1>
<div class="stats">
<div>📍 来源: <strong><?= htmlspecialchars($source_info) ?></strong></div>
<div>⏰ <?= date('H:i') ?></div> <!-- 使用当前北京时间 -->
<div>📊 <strong id="count"><?= count($sites) ?></strong>个</div>
</div>
</div>
<div class="list-container">
<div class="list-header">
<span>↑ 拖拽排序左滑删除 (<span id="visible-count"><?= count($sites) ?></span>)</span>
<span style="background:rgba(255,255,255,0.2);padding:2px 8px;border-radius:10px;font-size:13px">URL#1 → URL#2</span>
</div>
<div id="list">
<?php if (empty($sites)): ?>
<div class="empty-state">
<div style="font-size:48px;margin-bottom:10px">📭</div>
<div>暂无站点数据</div>
<div>点击【重置】获取最新数据</div>
</div>
<?php else: foreach ($sites as $site):
$key = htmlspecialchars($site['key'] ?? '');
$name = htmlspecialchars($site['name'] ?? $key);
?>
<div class="item" data-key="<?= $key ?>" draggable="true">
<div class="item-content"><div class="name"><?= $name ?></div></div>
<div class="item-delete">×</div>
</div>
<?php endforeach; endif; ?> </div>
</div>
<div class="action-bar">
<button class="main-btn refresh-btn" onclick="refreshData()"><span>🔄</span> 刷新</button>
<button class="main-btn restore-btn" onclick="restoreAll()"><span>↺</span> 重置</button>
<button class="main-btn save-btn" onclick="saveData()"><span>💾</span> 保存</button>
</div>
<div class="toast" id="toast"></div>
<script>
const allSites = <?= json_encode($sites_map, JSON_UNESCAPED_UNICODE) ?>;
const list = document.getElementById('list');
// ========== 桌面拖拽 ==========
document.querySelectorAll('.item').forEach(item => {
item.addEventListener('dragstart', () => setTimeout(() => item.classList.add('dragging'), 0));
item.addEventListener('dragend', () => item.classList.remove('dragging'));
item.addEventListener('dragover', e => e.preventDefault());
item.addEventListener('drop', e => {
e.preventDefault();
const dragged = document.querySelector('.dragging');
if (dragged && dragged !== item) {
const rect = item.getBoundingClientRect();
const mid = rect.top + rect.height / 2;
if (e.clientY < mid) {
item.parentNode.insertBefore(dragged, item);
} else {
item.parentNode.insertBefore(dragged, item.nextSibling);
}
updateVisibleCount();
}
});
});
// ========== 手机触摸:左滑删除 + 立即上移 ==========
let activeItem = null;
list.addEventListener('touchstart', e => {
const item = e.target.closest('.item');
if (item && e.touches.length === 1) {
activeItem = item;
item.dataset.startX = e.touches[0].clientX;
document.body.style.touchAction = 'none';
}
});
list.addEventListener('touchmove', e => {
if (activeItem && e.touches.length === 1) {
const dx = e.touches[0].clientX - activeItem.dataset.startX; if (Math.abs(dx) > 10) {
e.preventDefault();
activeItem.querySelector('.item-delete').style.right = dx < 0 ? `-${Math.min(-dx, 60)}px` : '-60px';
}
}
});
list.addEventListener('touchend', e => {
if (activeItem) {
const dx = e.changedTouches[0].clientX - activeItem.dataset.startX;
if (dx < -40) {
const name = activeItem.querySelector('.name').textContent || '未知站点';
activeItem.remove();
updateVisibleCount();
// 强制重排(关键!)
list.parentElement.style.pointerEvents = 'none';
void list.parentElement.offsetHeight;
list.parentElement.style.pointerEvents = 'auto';
showToast(`🗑️ 已删除: ${name}`, 'success');
} else {
activeItem.querySelector('.item-delete').style.right = '-60px';
}
activeItem = null;
document.body.style.touchAction = '';
}
});
// ========== 功能 ==========
function saveData() {
const items = document.querySelectorAll('.item');
if (items.length === 0) return showToast('⚠️ 无站点可保存', 'error');
const order = Array.from(items).map(el => el.dataset.key);
const btn = document.querySelector('.save-btn');
btn.innerHTML = '<span>⏳</span> 保存中...';
btn.disabled = true;
fetch(location.href, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: `action=save&order=${encodeURIComponent(JSON.stringify(order))}&all_sites=${encodeURIComponent(JSON.stringify(allSites))}`
})
.then(r => r.json())
.then(data => {
if (data.success) {
showToast(data.message, 'success');
setTimeout(() => location.reload(), 1200);
} else {
showToast(data.message, 'error');
} })
.catch(err => showToast('❌ 保存失败: ' + err.message, 'error'))
.finally(() => {
btn.innerHTML = '<span>💾</span> 保存';
btn.disabled = false;
});
}
function refreshData() {
if (!confirm('确定刷新?将覆盖当前编辑内容!')) return;
location.href = location.href.split('?')[0] + '?t=' + Date.now();
}
function restoreAll() {
if (!confirm('确定重置?\n将删除缓存并重新拉取最新数据')) return;
location.href = location.href.split('?')[0] + '?reset=1';
}
function updateVisibleCount() {
const n = document.querySelectorAll('.item').length;
document.getElementById('visible-count').textContent = n;
document.getElementById('count').textContent = n;
}
function showToast(msg, type = 'success') {
const t = document.getElementById('toast');
t.textContent = msg;
t.className = `toast ${type} show`;
setTimeout(() => t.classList.remove('show'), 2800);
}
</script>
</body>
</html>