技术 在线生成 M3U 和 TXT 订阅管理系统

iptv_320 · 2025年07月28日 · 84 次阅读

界面截图 image.png

不想自己搭建也可以使用:https://www.judy.xx.kg/m3u8.php

更多内容请看主页 https://www.judy.xx.kg

<?php
$allowed_ext = ['m3u', 'txt'];
$upload_dir = __DIR__ . '/uploads/';
if (!is_dir($upload_dir)) mkdir($upload_dir, 0777, true);

// 网站上线时间(请按实际上线时间修改)
define('SITE_LAUNCH_TIME', '2025-07-27 08:00:00');

// 记录上传者IP及文件名字,格式为 IP-----文件名字,覆盖写入
function log_upload_ip($ip, $orig_name) {
    $ip_dir = __DIR__ . '/ip/';
    if (!is_dir($ip_dir)) mkdir($ip_dir, 0777, true);
    $ipfile = $ip_dir . $ip;
    file_put_contents($ipfile, $ip . '-----' . $orig_name, LOCK_EX);
}

// 统计信息
$all_files = array_diff(scandir($upload_dir), ['.', '..']);
$total_files = count($all_files);
$total_size = 0;
foreach ($all_files as $f) $total_size += filesize($upload_dir . $f);

// 返回 JSON 请求(AJAX 上传)
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['file']) && isset($_SERVER['HTTP_X_REQUESTED_WITH'])) {
    $file = $_FILES['file'];
    $ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
    if (!in_array($ext, $allowed_ext)) {
        http_response_code(400);
        echo json_encode(['error' => '只允许 .m3u 或 .txt 文件']);
        exit;
    }

    $filename = uniqid('upload_', true) . '.' . $ext;
    $dest = $upload_dir . $filename;
    if (move_uploaded_file($file['tmp_name'], $dest)) {
        log_upload_ip($_SERVER['REMOTE_ADDR'], $file['name']);
        echo json_encode(['success' => true, 'link' => get_file_url($filename)]);
        exit;
    } else {
        http_response_code(500);
        echo json_encode(['error' => '文件上传失败']);
        exit;
    }
}

// 普通远程下载
$msg = '';
$link = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['remote_url'])) {
    $remote_url = trim($_POST['remote_url']);
    $basename = basename(parse_url($remote_url, PHP_URL_PATH));
    $ext = strtolower(pathinfo($basename, PATHINFO_EXTENSION));
    if (!in_array($ext, $allowed_ext)) {
        $msg = "只允许下载 .m3u 或 .txt 文件";
    } else {
        $newname = uniqid('remote_', true) . '.' . $ext;
        $dest = $upload_dir . $newname;
        $filedata = @file_get_contents($remote_url, false, stream_context_create(['http'=>['timeout'=>15]]), 0, 10*1024*1024);
        if ($filedata !== false) {
            file_put_contents($dest, $filedata);
            log_upload_ip($_SERVER['REMOTE_ADDR'], $basename);
            $msg = "远程下载成功!";
            $link = get_file_url($newname);
        } else {
            $msg = "远程下载失败,无法获取文件。";
        }
    }
}

function get_file_url($filename) {
    $url = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? "https" : "http")
         . "://$_SERVER[HTTP_HOST]"
         . dirname($_SERVER['PHP_SELF']);
    $url = rtrim($url, '/\\');
    return $url . '/uploads/' . rawurlencode($filename);
}
function size_format($size) {
    if ($size >= 1073741824) return round($size / 1073741824, 2) . ' GB';
    if ($size >= 1048576)   return round($size / 1048576, 2) . ' MB';
    if ($size >= 1024)      return round($size / 1024, 2) . ' KB';
    return $size . ' B';
}
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>文件上传 | M3U / TXT 托管</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
:root {
    --c-bg: linear-gradient(135deg, #dbeafe, #eff6ff, #ffffff);
    --c-card: #fff;
    --c-main: #2a7bf6;
    --c-shadow: 0 8px 32px rgba(42,123,246,0.08);
    --c-border: #e9f0fb;
    --c-success: #28c07d;
    --c-error: #eb4c4c;
    --c-muted: #bfcfe7;
    --c-btn: #eef5ff;
}
html, body {
    height: 100%;
    margin: 0;
    padding: 0;
    background: var(--c-bg);
    font-family: 'Inter', sans-serif;
}
.container {
    background: var(--c-card);
    border-radius: 2.2em;
    box-shadow: var(--c-shadow);
    max-width: 480px;
    margin: 6vh auto;
    padding: 2.5em 2em;
}
h2 {
    text-align: center;
    color: var(--c-main);
}
.dropzone {
    border: 2px dashed var(--c-main);
    background: #f0f8ff;
    padding: 2em;
    border-radius: 1.5em;
    text-align: center;
    cursor: pointer;
    transition: background 0.3s;
}
.dropzone.dragover {
    background: #e0f0ff;
}
.dropzone input {
    display: none;
}
.dropzone-text {
    font-size: 1em;
    color: #2a7bf6;
}
form {
    margin-top: 1em;
    display: flex;
    gap: .6em;
}
input[type="url"] {
    flex: 1;
    padding: .6em .9em;
    border: 1px solid var(--c-border);
    border-radius: 1.2em;
}
button {
    padding: .6em 1.4em;
    border: none;
    background: var(--c-main);
    color: white;
    border-radius: 1.2em;
    cursor: pointer;
}
.progress-container {
    margin-top: 1em;
    width: 100%;
    background-color: #eef3fc;
    border-radius: 1.2em;
    height: 1.2em;
    position: relative;
    overflow: hidden;
    display: none;
}
.progress-bar {
    height: 100%;
    background-color: var(--c-main);
    width: 0%;
    transition: width 0.3s ease;
}
.progress-text {
    position: absolute;
    left: 50%;
    top: 0;
    transform: translateX(-50%);
    font-size: .86em;
    color: #333;
    line-height: 1.2em;
}
.msg {
    margin-top: 1.2em;
    padding: 0.8em;
    text-align: center;
    border-radius: 1.2em;
    background: #f9fbff;
    border: 1px solid var(--c-border);
}
.msg.success {
    color: var(--c-success);
    background: #eafcf6;
}
.msg.error {
    color: var(--c-error);
    background: #fff5f6;
}
.upload-link {
    margin-top: 1.2em;
    word-break: break-all;
    background: #f5faff;
    border-radius: 1.2em;
    padding: .6em .9em;
    border: 1px solid var(--c-border);
    font-size: .95em;
    color: #257af8;
}
.stats {
    text-align: center;
    margin-top: 2.2em;
    font-size: .92em;
    color: #666;
}
/* 运行时间流光效果 */
.runtime-bar {
    margin-top: 3em;
    text-align: center;
    font-size: 0.95em;
    padding: 1em;
    background: linear-gradient(90deg, #2a7bf6, #9cbff8, #2a7bf6);
    background-size: 200% auto;
    color: white;
    animation: shimmer 3s linear infinite;
    border-radius: 1.2em;
    box-shadow: 0 0 12px rgba(0, 140, 255, 0.3);
}
@keyframes shimmer {
    0% { background-position: 0% 50%; }
    100% { background-position: 200% 50%; }
}
</style>
</head>
<body>
<div class="container">
    <h2>IPTV-M3U / TXT 上传托管</h2>

    <div class="dropzone" id="dropzone">
        <span class="dropzone-text">点击或拖拽 .m3u/.txt 文件至此上传</span>
        <form id="uploadForm"><input type="file" id="fileInput" name="file" accept=".m3u,.txt"></form>
    </div>
    <div class="progress-container" id="progressBox">
        <div class="progress-bar" id="progressBar"></div>
        <div class="progress-text" id="progressText">0%</div>
    </div>
    <div class="msg" id="uploadMsg" style="display:none;"></div>
    <div class="upload-link" id="uploadLink" style="display:none;"></div>

    <form method="post" style="margin-top: 1.5em;">
        <input type="url" name="remote_url" placeholder="粘贴远程 .m3u/.txt 地址" pattern="https?://.+" required>
        <button type="submit">远程下载</button>
    </form>
</div>

<div class="stats">
    当前已托管 <strong><?php echo $total_files;?></strong> 个文件,合计 <strong><?php echo size_format($total_size);?></strong>
</div>

<!-- 网站累计运行时长 流光效果 -->
<div class="runtime-bar" id="runtimeBar">
    网站已累计运行 <span id="runtime">加载中…</span>
</div>

<?php if ($msg): ?>
    <script>
    window.addEventListener('DOMContentLoaded', function() {
        const m = document.getElementById("uploadMsg");
        m.innerText = <?php echo json_encode($msg); ?>;
        m.classList.add(<?php echo strpos($msg, "成功") !== false ? "'success'" : "'error'"; ?>);
        m.style.display = "block";
        <?php if ($link): ?>
        const l = document.getElementById("uploadLink");
        l.innerText = <?php echo json_encode($link); ?>;
        l.style.display = "block";
        <?php endif; ?>
    });
    </script>
<?php endif; ?>

<script>
const dropzone = document.getElementById('dropzone');
const fileInput = document.getElementById('fileInput');
const progressBar = document.getElementById('progressBar');
const progressText = document.getElementById('progressText');
const progressBox = document.getElementById('progressBox');
const uploadMsg = document.getElementById('uploadMsg');
const uploadLink = document.getElementById('uploadLink');

dropzone.addEventListener('click', () => fileInput.click());

dropzone.addEventListener('dragover', (e) => {
    e.preventDefault();
    dropzone.classList.add('dragover');
});
dropzone.addEventListener('dragleave', () => dropzone.classList.remove('dragover'));
dropzone.addEventListener('drop', (e) => {
    e.preventDefault();
    dropzone.classList.remove('dragover');
    const files = e.dataTransfer.files;
    if (files.length) {
        uploadFile(files[0]);
    }
});
fileInput.addEventListener('change', () => {
    if (fileInput.files.length) uploadFile(fileInput.files[0]);
});

function uploadFile(file) {
    const ext = file.name.split('.').pop().toLowerCase();
    if (!['m3u', 'txt'].includes(ext)) {
        alert('只允许上传 .m3u 或 .txt 文件');
        return;
    }

    const formData = new FormData();
    formData.append('file', file);
    const xhr = new XMLHttpRequest();
    xhr.open('POST', '', true);
    xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');

    xhr.upload.addEventListener('progress', (e) => {
        if (e.lengthComputable) {
            const percent = Math.round((e.loaded / e.total) * 100);
            progressBar.style.width = percent + '%';
            progressText.textContent = percent + '%';
        }
    });
    xhr.onloadstart = () => {
        progressBox.style.display = 'block';
        uploadMsg.style.display = 'none';
        uploadLink.style.display = 'none';
        progressBar.style.width = '0%';
        progressText.textContent = '0%';
    };
    xhr.onload = () => {
        if (xhr.status === 200) {
            const res = JSON.parse(xhr.responseText);
            if (res.success) {
                uploadMsg.textContent = '上传成功!';
                uploadMsg.className = 'msg success';
                uploadMsg.style.display = 'block';
                uploadLink.textContent = res.link;
                uploadLink.style.display = 'block';
            }
        } else {
            uploadMsg.textContent = '上传失败';
            uploadMsg.className = 'msg error';
            uploadMsg.style.display = 'block';
        }
    };
    xhr.onerror = () => {
        uploadMsg.textContent = '网络错误,上传失败';
        uploadMsg.className = 'msg error';
        uploadMsg.style.display = 'block';
    };
    xhr.send(formData);
}

// 网站累计运行时间(年月日时分秒)
(function(){
    var launchTime = <?php echo strtotime(SITE_LAUNCH_TIME); ?> * 1000;
    function formatTime(diff){
        var seconds = Math.floor(diff / 1000);
        var years = Math.floor(seconds / (365*24*3600));
        seconds = seconds % (365*24*3600);
        var months = Math.floor(seconds / (30*24*3600));
        seconds = seconds % (30*24*3600);
        var days = Math.floor(seconds / (24*3600));
        seconds = seconds % (24*3600);
        var hours = Math.floor(seconds / 3600);
        seconds = seconds % 3600;
        var minutes = Math.floor(seconds / 60);
        seconds = seconds % 60;
        var parts = [];
        if(years) parts.push(years+"");
        if(months) parts.push(months+"");
        if(days) parts.push(days+"");
        parts.push(hours+""+minutes+""+seconds+"");
        return parts.join('');
    }
    function updateRuntime(){
        var now = Date.now();
        var diff = now - launchTime;
        document.getElementById('runtime').textContent = formatTime(diff);
        setTimeout(updateRuntime, 1000);
    }
    updateRuntime();
})();
</script>
</body>
</html>

暂无回复。
需要 登录 后方可回复, 如果你还没有账号请 注册新账号