在广附总部集训的14年(度日如年)中,由于通信需要,经过2年的研发,开发出一款充满bug但勉强能用的通信工具,只要局域网没断就能使用,具体见下面

操作(密码:操作)

代码(密码:代码)

注:操作纯手写

操作区

选择一个要共享的文档

右键打开菜单栏,点击属性

点击共享

点击共享按钮 选择Everyone,添加

权限为读取+写入

点击共享

随后返回共享大按钮

点击高级共享

勾选共享此文件

随后点权限

添加Everyone,权限为完全控制

和上面类似

点应用+确定

随后点安全

点编辑

依旧添加Everyone

随后点击检查名称

点击确定

权限改为完全控制

点应用+确定

新建一个文本文档

前往“代码区”拿到代码

复制进去

确保电脑里有python

按Win+R,输入cmd,输入python --version

如果有显示版本号(如 Python 3.x.x)继续,否则先去"python区"下python

复制完后,将文件名改为“server.py”(到时想改代码就双击打开,选择用记事本打开)

随后按住Shift键+右键共享文件夹内部

点击“在此处打开Powershell窗口”

将python server.py粘贴进去

成功运行会显示

输入红圈圈起来的网址即可

结束使用记得按Ctrl+C


代码区
import http.server
import socketserver
import os
import urllib.parse
import json
import cgi
from datetime import datetime
import time
import threading
import base64

PORT = 8080
DIRECTORY = os.path.dirname(os.path.abspath(__file__))
if not os.path.exists(DIRECTORY):
    os.makedirs(DIRECTORY)

messages = []
private_messages = {}
MAX_MSG = 200
OWNERS = ["XH", "黄延希"]

users = {}
kicked_users = {}
banned_users = {}
muted_users = {}
KICK_COOLDOWN = 3 * 24 * 3600

user_version = 0
user_version_lock = threading.Lock()

DEFAULT_GROUP = 'default'
groups = {}
groups[DEFAULT_GROUP] = {
    'name': '大群',
    'owner': 'XH',
    'admins': ['黄延希'],
    'members': [],
    'blocked': [],
    'announcement': '欢迎加入大群!',
    'messages': [],
    'created_at': time.time()
}

# ================== 修复 LOGIN_PAGE ==================
LOGIN_PAGE = """<!DOCTYPE html>
<html>
<head><meta charset="UTF-8"><title>登录</title>
<style>
body{{font-family:"Microsoft YaHei";background:#f0f2f5;display:flex;justify-content:center;align-items:center;height:100vh;margin:0}}
.login-box{{background:#fff;padding:40px;border-radius:12px;box-shadow:0 4px 20px rgba(0,0,0,0.1);width:350px;text-align:center}}
.login-box h2{{color:#1a1a2e}}
.login-box input{{width:100%;padding:12px;margin:8px 0;border:1px solid #ddd;border-radius:6px;box-sizing:border-box;font-size:16px}}
.login-box button{{width:100%;padding:12px;background:#4a90d9;color:#fff;border:none;border-radius:6px;font-size:16px;cursor:pointer}}
.login-box button:hover{{background:#357abd}}
.error{{color:#e74c3c;margin-top:10px}}
.tip{{color:#888;font-size:13px;margin-top:10px}}
</style>
</head>
<body>
<div class="login-box">
<h2>登录</h2>
<form method="POST" action="/login">
<input type="text" name="username" placeholder="输入你的真实姓名..." required>
<button type="submit">进入</button>
</form>
<div class="error">{error}</div>
<div class="tip">请输入真实姓名,登录后不可更改</div>
</div>
</body>
</html>"""

MAIN_PAGE = """<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>局域网共享 + 聊天</title>
<style>
*{box-sizing:border-box;margin:0;padding:0}
body{font-family:"Microsoft YaHei";background:#eef1f5;height:100vh;display:flex;flex-direction:column}
.top-bar{background:#fff;padding:12px 24px;box-shadow:0 2px 8px rgba(0,0,0,0.06);display:flex;justify-content:space-between;align-items:center;flex-shrink:0;z-index:10}
.top-bar .welcome{font-size:15px;color:#1e293b}
.top-bar .logout{color:#ef4444;text-decoration:none;font-size:14px}
.top-bar .logout:hover{text-decoration:underline}
.page-container{flex:1;overflow:hidden;position:relative}
.page{display:none;width:100%;height:100%;padding:20px;overflow-y:auto;position:absolute;top:0;left:0;background:#eef1f5}
.page.active{display:block}
.page-content{max-width:1400px;margin:0 auto;height:100%;display:flex;flex-direction:column}
.bottom-nav{background:#fff;border-top:1px solid #e2e8f0;display:flex;flex-shrink:0;z-index:10}
.bottom-nav button{flex:1;padding:12px 0;border:none;background:transparent;font-size:14px;color:#94a3b8;cursor:pointer;transition:all 0.2s;border-top:3px solid transparent;font-weight:500}
.bottom-nav button:hover{color:#1e293b}
.bottom-nav button.active{color:#4a90d9;border-top-color:#4a90d9;background:#f8fafc}
.card{background:#fff;border-radius:14px;padding:20px;box-shadow:0 2px 8px rgba(0,0,0,0.06);flex:1;display:flex;flex-direction:column;min-height:0}
.card-title{font-size:16px;font-weight:600;color:#1e293b;border-bottom:2px solid #4a90d9;padding-bottom:10px;margin-bottom:14px;display:flex;justify-content:space-between;align-items:center}
.card-title .actions{display:flex;gap:8px;font-size:13px;font-weight:400}
.card-title .actions button{padding:4px 12px;border:none;border-radius:6px;cursor:pointer;font-size:12px}
.btn-clear{background:#ef4444;color:#fff}
.btn-clear:hover{background:#dc2626}
.btn-create{background:#4a90d9;color:#fff}
.btn-create:hover{background:#357abd}
.chat-box{flex:1;min-height:140px;overflow-y:auto;background:#f8fafc;border-radius:10px;padding:12px;font-size:14px;margin-bottom:10px}
.chat-msg{padding:4px 8px;border-radius:4px;margin:2px 0;word-wrap:break-word}
.chat-msg .time{color:#94a3b8;font-size:12px;margin-right:8px}
.chat-msg .user{font-weight:600;color:#1e293b;margin-right:6px}
.owner-badge{background:#ef4444;color:#fff;font-size:10px;padding:1px 10px;border-radius:12px;margin-left:4px;display:inline-block}
.admin-badge{background:#f59e0b;color:#fff;font-size:10px;padding:1px 10px;border-radius:12px;margin-left:4px;display:inline-block}
.muted-badge{background:#94a3b8;color:#fff;font-size:10px;padding:1px 10px;border-radius:12px;margin-left:4px;display:inline-block}
.chat-msg.system{color:#94a3b8;font-style:italic;text-align:center;background:#f1f5f9;border-radius:16px;padding:4px 16px;margin:4px auto;display:table}
.chat-msg.announcement{color:#1a1a2e;font-weight:bold;text-align:center;background:#fef3c7;border-radius:16px;padding:4px 16px;margin:4px auto;display:table;border:1px solid #f59e0b}
.chat-msg.clear{color:#94a3b8;font-style:italic;text-align:center;background:#fef3c7;border-radius:16px;padding:4px 16px;margin:4px auto;display:table}
.chat-msg.file{color:#2563eb;text-align:center;background:#dbeafe;border-radius:16px;padding:4px 16px;margin:4px auto;display:table}
.chat-input{display:flex;gap:8px;flex-shrink:0;align-items:center}
.chat-input input[type=text]{flex:1;padding:10px 14px;border:1px solid #e2e8f0;border-radius:10px;font-size:14px}
.chat-input input[type=text]:focus{outline:none;border-color:#4a90d9}
.chat-input button{padding:10px 28px;background:#4a90d9;color:#fff;border:none;border-radius:10px;cursor:pointer;font-size:14px;font-weight:500}
.chat-input button:hover{background:#357abd}
.chat-input button:disabled{background:#94a3b8;cursor:not-allowed}
.chat-input .file-btn{background:#f59e0b;padding:10px 16px}
.chat-input .file-btn:hover{background:#d97706}
.split-layout{display:flex;gap:16px;flex:1;min-height:0}
.split-left{width:260px;flex-shrink:0;border-right:1px solid #e2e8f0;padding-right:16px;overflow-y:auto}
.split-right{flex:1;display:flex;flex-direction:column;min-width:0}
.group-item{padding:10px 12px;border-radius:8px;cursor:pointer;margin-bottom:2px;display:flex;justify-content:space-between;align-items:center}
.group-item:hover{background:#f1f5f9}
.group-item.active{background:#dbeafe}
.group-item .group-name{font-weight:500;font-size:14px}
.group-item .group-badge{font-size:11px;color:#94a3b8}
.contact-item{padding:10px 12px;border-radius:8px;cursor:pointer;display:flex;justify-content:space-between;align-items:center;margin-bottom:2px}
.contact-item:hover{background:#f1f5f9}
.contact-item.active{background:#dbeafe}
.contact-item .status{font-size:12px}
.contact-item .online{color:#22c55e}
.user-list{list-style:none;padding:0;margin:0;font-size:14px;max-height:400px;overflow-y:auto}
.user-list li{padding:8px 12px;border-bottom:1px solid #f1f5f9;display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:4px}
.user-list .online{color:#22c55e;font-size:12px}
.user-list .offline{color:#94a3b8;font-size:12px}
.user-list .muted{color:#94a3b8;font-size:12px;text-decoration:line-through}
.user-list .me{color:#94a3b8;font-size:12px}
.user-list .last-seen{color:#94a3b8;font-size:11px}
.mute-btn{background:#f59e0b;color:#fff;border:none;border-radius:6px;padding:3px 12px;font-size:12px;cursor:pointer}
.mute-btn:hover{background:#d97706}
.unmute-btn{background:#22c55e;color:#fff;border:none;border-radius:6px;padding:3px 12px;font-size:12px;cursor:pointer}
.unmute-btn:hover{background:#16a34a}
.ban-btn{background:#1a1a1a;color:#fff;border:none;border-radius:6px;padding:3px 12px;font-size:12px;cursor:pointer}
.ban-btn:hover{background:#000}
.setadmin-btn{background:#f59e0b;color:#fff;border:none;border-radius:6px;padding:3px 12px;font-size:12px;cursor:pointer}
.setadmin-btn:hover{background:#d97706}
.revokeadmin-btn{background:#f59e0b;color:#fff;border:none;border-radius:6px;padding:3px 12px;font-size:12px;cursor:pointer}
.revokeadmin-btn:hover{background:#d97706}
.file-item{display:flex;justify-content:space-between;padding:6px 8px;border-bottom:1px solid #f1f5f9;font-size:14px;align-items:center}
.file-item a{color:#2563eb;text-decoration:none}
.file-item a:hover{text-decoration:underline}
.file-item .file-actions{display:flex;gap:6px}
.file-del-btn{background:#ef4444;color:#fff;border:none;border-radius:6px;padding:2px 10px;font-size:12px;cursor:pointer}
.file-del-btn:hover{background:#dc2626}
.upload-box{display:flex;flex-wrap:wrap;gap:8px;align-items:center;margin-top:12px;flex-shrink:0}
.upload-box input[type=file]{flex:1;padding:6px;border:1px solid #e2e8f0;border-radius:8px;font-size:13px;min-width:120px}
.upload-box button{padding:6px 20px;background:#4a90d9;color:#fff;border:none;border-radius:8px;cursor:pointer;font-size:13px}
.file-list-box{flex:1;overflow-y:auto;min-height:60px}
.empty-state{color:#94a3b8;text-align:center;padding:30px 20px;font-size:15px}
.create-group-box{display:flex;gap:8px;margin-top:8px}
.create-group-box input{flex:1;padding:8px 12px;border:1px solid #e2e8f0;border-radius:8px;font-size:14px}
.create-group-box button{padding:8px 20px;background:#4a90d9;color:#fff;border:none;border-radius:8px;cursor:pointer}
.create-group-box button:hover{background:#357abd}
.announcement-box{background:#fef3c7;border-radius:8px;padding:8px 12px;margin-bottom:8px;font-size:13px;color:#92400e;display:flex;justify-content:space-between;align-items:center}
.announcement-box .edit-announcement{color:#4a90d9;cursor:pointer;font-size:12px}
.announcement-box .edit-announcement:hover{text-decoration:underline}
.hidden-file-input{display:none}
</style>
</head>
<body>
<div class="top-bar">
<div class="welcome">👋 你好,<b id="myName"></b></div>
<a href="/logout" class="logout">🚪 退出</a>
</div>

<div class="page-container">
<div class="page active" id="pageGroup">
<div class="page-content">
<div class="card" style="flex:1">
<div class="card-title">
<span>💬 群聊</span>
<div class="actions">
<button class="btn-create" onclick="showCreateGroup()">➕ 建群</button>
<button class="btn-clear" onclick="clearChat()">🗑 清屏</button>
</div>
</div>
<div class="split-layout">
<div class="split-left">
<div style="font-size:13px;color:#94a3b8;margin-bottom:10px">我的群聊</div>
<div id="groupList"></div>
<div class="create-group-box">
<input type="text" id="newGroupName" placeholder="新群名称...">
<button onclick="createGroup()">创建</button>
</div>
</div>
<div class="split-right">
<div id="groupAnnouncement" style="display:none"></div>
<div class="chat-box" id="groupChat"><div class="empty-state">选择或创建一个群</div></div>
<div class="chat-input">
<input type="text" id="groupInput" placeholder="输入消息..." onkeydown="if(event.key==='Enter')sendGroupMsg()">
<button onclick="sendGroupMsg()" id="groupSendBtn">发送</button>
<button class="file-btn" onclick="document.getElementById('fileInput').click()">📎</button>
<input type="file" id="fileInput" class="hidden-file-input" onchange="sendFile()">
</div>
</div>
</div>
</div>
</div>
</div>

<div class="page" id="pagePrivate">
<div class="page-content">
<div class="card" style="flex:1">
<div class="card-title">💬 私信</div>
<div class="split-layout">
<div class="split-left">
<div style="font-size:13px;color:#94a3b8;margin-bottom:10px">在线联系人</div>
<div id="contactList"></div>
</div>
<div class="split-right">
<div style="font-size:13px;color:#94a3b8;margin-bottom:6px" id="privateTargetName">请选择联系人</div>
<div class="chat-box" id="privateChat" style="min-height:200px;flex:1"><div class="empty-state">选择一个人开始私聊</div></div>
<div class="chat-input">
<input type="text" id="privateInput" placeholder="私信..." onkeydown="if(event.key==='Enter')sendPrivateMsg()">
<button onclick="sendPrivateMsg()" id="privateSendBtn">发送</button>
</div>
</div>
</div>
</div>
</div>
</div>

<div class="page" id="pageFiles">
<div class="page-content">
<div class="card" style="flex:1">
<div class="card-title">📁 共享文件 <span style="font-weight:400;font-size:13px;color:#94a3b8" id="fileCount"></span></div>
<div class="file-list-box" id="fileList"><div class="empty-state">加载中...</div></div>
<div class="upload-box">
<form method="POST" enctype="multipart/form-data" action="/upload" style="display:flex;flex-wrap:wrap;gap:8px;width:100%">
<input type="file" name="file" multiple>
<button type="submit">⬆ 上传</button>
</form>
</div>
</div>
</div>
</div>

<div class="page" id="pageMembers">
<div class="page-content">
<div class="card" style="flex:1">
<div class="card-title">👥 所有成员 <span style="font-weight:400;font-size:13px;color:#94a3b8" id="memberCount"></span></div>
<ul class="user-list" id="memberList"><li style="color:#94a3b8;padding:20px;text-align:center">加载中...</li></ul>
</div>
</div>
</div>
</div>

<div class="bottom-nav">
<button class="active" onclick="switchPage('group')" id="navGroup">💬 群聊</button>
<button onclick="switchPage('private')" id="navPrivate">✉️ 私信</button>
<button onclick="switchPage('files')" id="navFiles">📁 文件</button>
<button onclick="switchPage('members')" id="navMembers">👥 成员</button>
</div>

<script>
function getCookie(n){var c=document.cookie.split('; ');for(var i=0;i<c.length;i++){var p=c[i].split('=');if(p[0]===n)return decodeURIComponent(p[1]);}return '';}
var username=getCookie('username');
if(!username){location.href='/login';}
document.getElementById('myName').textContent=username;
var ownerList = ["XH", "黄延希"];
var lastMsgCount=0;
var lastPrivateCount=0;
var currentPrivateTarget='';
var currentGroupId='default';
var isAdmin=false;
var userVersion=0;
var currentUserData=[];

function switchPage(name){document.querySelectorAll('.page').forEach(function(p){p.classList.remove('active');});document.getElementById('page'+name.charAt(0).toUpperCase()+name.slice(1)).classList.add('active');document.querySelectorAll('.bottom-nav button').forEach(function(b){b.classList.remove('active');});document.getElementById('nav'+name.charAt(0).toUpperCase()+name.slice(1)).classList.add('active');if(name==='private'){renderContactList(currentUserData);loadPrivateChat();}if(name==='members'){renderMemberList(currentUserData);}if(name==='files'){loadFiles();}if(name==='group'){loadGroupList();loadGroupChat();}}

function loadFiles(){fetch('/api/files').then(function(r){return r.json();}).then(function(data){var h='';document.getElementById('fileCount').textContent='('+data.length+'个)';if(!data.length){h='<div class="empty-state">📭 空</div>';}else{for(var i=0;i<data.length;i++){var f=data[i];var icon=f.is_dir?'📂':'📄';var delBtn='';if(isAdmin||ownerList.includes(username)){delBtn='<button class="file-del-btn" onclick="deleteFile(\\''+encodeURIComponent(f.name)+'\\')">删除</button>';}h+='<div class="file-item"><a href="/'+encodeURIComponent(f.name)+'">'+icon+' '+f.name+'</a><span style="color:#94a3b8;font-size:12px">'+(f.size||'')+'</span><span class="file-actions">'+delBtn+'</span></div>';}}document.getElementById('fileList').innerHTML=h;});}

function renderMemberList(data){var h='';var totalUsers=data.filter(function(u){return !u.is_banned && !u.is_kicked;}).length;document.getElementById('memberCount').textContent='('+totalUsers+'人)';isAdmin=ownerList.includes(username)||data.find(function(u){return u.name===username&&u.is_admin;})?.is_admin||false;var sorted=data.slice().sort(function(a,b){if(a.is_banned)return 1;if(b.is_banned)return -1;if(a.is_kicked && !b.is_kicked)return 1;if(!a.is_kicked && b.is_kicked)return -1;if(a.is_online && !b.is_online)return -1;if(!a.is_online && b.is_online)return 1;return a.name.localeCompare(b.name);});if(!sorted.length){h='<li style="color:#94a3b8;padding:20px;text-align:center">暂无用户</li>';}else{for(var i=0;i<sorted.length;i++){var u=sorted[i];var isOwner=ownerList.includes(u.name);var isUserAdmin=u.is_admin||false;var isMe=(u.name===username);var isKicked=u.is_kicked||false;var isMuted=u.is_muted||false;var isBanned=u.is_banned||false;if(isBanned)continue;var statusClass=isKicked?'kicked':(isMuted?'muted':(u.is_online?'online':'offline'));var statusText=isKicked?'⛔ 已踢出':(isMuted?'🔇 禁言':(u.is_online?'●':'○'));var badge='';if(isOwner){badge='<span class="owner-badge">群主</span>';}else if(isUserAdmin){badge='<span class="admin-badge">管理</span>';}if(isKicked){badge+='<span class="kicked-badge">已踢</span>';}if(isMuted){badge+='<span class="muted-badge">禁言</span>';}var lastSeen=u.last_seen?new Date(u.last_seen).toLocaleString():'从未登录';var actions='';if(!isKicked && isAdmin && !isOwner && !isMe){if(isMuted){actions+='<button class="unmute-btn" onclick="unmuteUser(\\''+u.name+'\\')">解禁</button>';}else{actions+='<button class="mute-btn" onclick="muteUser(\\''+u.name+'\\')">禁言</button>';}}if(!isKicked && !isMe && isAdmin && !isOwner){actions+='<button class="ban-btn" onclick="banUser(\\''+u.name+'\\')">封号</button>';}if(isKicked && isAdmin){actions+='<button class="unban-btn" onclick="unbanUser(\\''+u.name+'\\')">加回</button>';}if(isAdmin && !isOwner && !isMe && isUserAdmin){actions+='<button class="revokeadmin-btn" onclick="revokeAdmin(\\''+u.name+'\\')">撤销管理</button>';}if(isAdmin && !isOwner && !isMe && !isUserAdmin && !isKicked){actions+='<button class="setadmin-btn" onclick="setAdmin(\\''+u.name+'\\')">设为管理</button>';}h+='<li>'+u.name+(isMe?' <span class="me">(我)</span>':'')+' '+badge+' <span class="'+statusClass+'">'+statusText+'</span> <span class="last-seen">'+lastSeen+'</span> '+actions+'</li>';}}document.getElementById('memberList').innerHTML=h;}

function renderContactList(data){var h='';var onlineUsers=data.filter(function(u){return u.is_online && !u.is_kicked && !u.is_banned && u.name!==username;});if(!onlineUsers.length){h='<div class="empty-state" style="padding:20px">暂无在线联系人</div>';}else{for(var i=0;i<onlineUsers.length;i++){var u=onlineUsers[i];var active=(currentPrivateTarget===u.name)?'active':'';h+='<div class="contact-item '+active+'" onclick="selectPrivateTarget(\\''+u.name+'\\')">'+u.name+' <span class="status online">●</span></div>';}}document.getElementById('contactList').innerHTML=h;}

function loadMembers(){fetch('/api/users').then(function(r){return r.json();}).then(function(data){currentUserData=data;renderMemberList(data);});}

function loadContactList(){if(currentUserData.length>0){renderContactList(currentUserData);}else{fetch('/api/users').then(function(r){return r.json();}).then(function(data){currentUserData=data;renderContactList(data);});}}

function refreshUsers(){fetch('/api/users').then(function(r){return r.json();}).then(function(data){currentUserData=data;renderMemberList(data);renderContactList(data);}).catch(function(){setTimeout(refreshUsers,3000);});}

function waitUsers(version){fetch('/api/wait-users?version='+version).then(function(r){if(r.status===304){userVersion=version;waitUsers(version);return;}return r.json();}).then(function(data){if(data){userVersion=data.version;currentUserData=data.users;renderMemberList(data.users);renderContactList(data.users);}waitUsers(userVersion);}).catch(function(){setTimeout(function(){waitUsers(userVersion);},3000);});}

function loadGroupList(){fetch('/api/groups').then(function(r){return r.json();}).then(function(data){var h='';var groups=data.groups||[];if(!groups.length){h='<div class="empty-state" style="padding:20px">暂无群聊</div>';}else{for(var i=0;i<groups.length;i++){var g=groups[i];var active=(g.id===currentGroupId)?'active':'';var badge=g.is_owner?'👑':(g.is_admin?'⭐':'');h+='<div class="group-item '+active+'" onclick="switchGroup(\\''+g.id+'\\')"><span class="group-name">'+badge+' '+g.name+'</span><span class="group-badge">'+g.member_count+'人</span></div>';}}document.getElementById('groupList').innerHTML=h;});}

function switchGroup(groupId){currentGroupId=groupId;loadGroupList();loadGroupChat();}

function showCreateGroup(){document.getElementById('newGroupName').focus();}

function createGroup(){var name=document.getElementById('newGroupName').value.trim();if(!name){alert('请输入群名称');return;}fetch('/api/create-group',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body:'name='+encodeURIComponent(name)}).then(function(r){return r.json();}).then(function(data){if(data.ok){document.getElementById('newGroupName').value='';currentGroupId=data.group_id;loadGroupList();loadGroupChat();}else{alert(data.error||'创建失败');}});}

function loadGroupChat(){if(!currentGroupId){document.getElementById('groupChat').innerHTML='<div class="empty-state">选择或创建一个群</div>';document.getElementById('groupAnnouncement').style.display='none';return;}fetch('/api/group-messages?group_id='+encodeURIComponent(currentGroupId)).then(function(r){return r.json();}).then(function(data){var box=document.getElementById('groupChat');var msgs=data.messages||[];var announcement=data.announcement||'';var isOwner=data.is_owner||false;var isAdmin=data.is_admin||false;
var annDiv=document.getElementById('groupAnnouncement');if(announcement){annDiv.style.display='flex';annDiv.innerHTML='📢 '+announcement+' <span class="edit-announcement" onclick="editAnnouncement()">'+(isOwner||isAdmin?'✏️ 编辑':'')+'</span>';}else{annDiv.style.display='none';}
if(!msgs.length){box.innerHTML='<div class="empty-state">暂无消息</div>';return;}var html='';for(var i=0;i<msgs.length;i++){var m=msgs[i];var isOwnerUser=ownerList.includes(m.user);var ownerBadge=isOwnerUser?'<span class="owner-badge">群主</span>':'';var cls=(m.user==='系统')?'system':(m.type==='announcement')?'announcement':(m.type==='file')?'file':'';html+='<div class="chat-msg '+cls+'"><span class="time">'+m.time+'</span><span class="user">'+m.user+'</span>'+ownerBadge+m.msg+'</div>';}box.innerHTML=html;box.scrollTop=box.scrollHeight;});}

function editAnnouncement(){var newAnn=prompt('输入新的群公告:');if(newAnn===null)return;fetch('/api/set-announcement',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body:'group_id='+encodeURIComponent(currentGroupId)+'&announcement='+encodeURIComponent(newAnn)}).then(function(){loadGroupChat();});}

function sendGroupMsg(){if(!currentGroupId){alert('请先选择群聊');return;}var i=document.getElementById('groupInput');var msg=i.value.trim();if(!msg)return;fetch('/api/send-group-msg',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body:'group_id='+encodeURIComponent(currentGroupId)+'&msg='+encodeURIComponent(msg)}).then(function(r){if(r.status===403){alert('你已被禁言或封号,无法发送消息');location.reload();}else{i.value='';loadGroupChat();}});}

function sendFile(){var input=document.getElementById('fileInput');var file=input.files[0];if(!file)return;if(!currentGroupId){alert('请先选择群聊');input.value='';return;}var formData=new FormData();formData.append('file',file);formData.append('group_id',currentGroupId);fetch('/api/send-file',{method:'POST',body:formData}).then(function(r){if(r.status===403){alert('你已被禁言或封号');location.reload();}else{input.value='';loadGroupChat();}});}

function clearChat(){if(!confirm('确定要清空当前群聊记录吗?'))return;fetch('/api/clear-group',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body:'group_id='+encodeURIComponent(currentGroupId)}).then(function(){loadGroupChat();});}

function loadGroupChatPolling(){if(document.getElementById('pageGroup').classList.contains('active')){loadGroupChat();}}

function selectPrivateTarget(name){currentPrivateTarget=name;document.getElementById('privateTargetName').textContent='正在与 '+name+' 聊天';loadPrivateChat();loadContactList();}

function loadPrivateChat(){if(!currentPrivateTarget){document.getElementById('privateChat').innerHTML='<div class="empty-state">选择一个人开始私聊</div>';return;}fetch('/api/privmsg?target='+encodeURIComponent(currentPrivateTarget)).then(function(r){return r.json();}).then(function(data){var box=document.getElementById('privateChat');var count=data.length;if(!data.length){box.innerHTML='<div class="empty-state">暂无私信</div>';return;}var html='';for(var i=0;i<data.length;i++){var m=data[i];html+='<div class="chat-msg"><span class="time">'+m.time+'</span><span class="user">'+m.from+'</span>'+m.msg+'</div>';}box.innerHTML=html;box.scrollTop=box.scrollHeight;});}

function sendPrivateMsg(){if(!currentPrivateTarget){alert('请先选择私信对象');return;}var i=document.getElementById('privateInput');var msg=i.value.trim();if(!msg)return;fetch('/sendprivate',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body:'target='+encodeURIComponent(currentPrivateTarget)+'&msg='+encodeURIComponent(msg)}).then(function(r){if(r.status===403){alert('你已被禁言或封号');location.reload();}else{i.value='';loadPrivateChat();loadContactList();}});}

function deleteFile(name){if(!confirm('确定要删除文件 '+decodeURIComponent(name)+' 吗?'))return;fetch('/deletefile?name='+name,{method:'GET'}).then(function(r){if(r.ok){loadFiles();}else{alert('删除失败');}});}

function kickUser(target){if(!confirm('确定要把 '+target+' 踢出吗?(3天后可加回)'))return;fetch('/kick?target='+encodeURIComponent(target)).then(function(){refreshUsers();loadGroupChat();});}

function banUser(target){if(!confirm('确定要封禁 '+target+' 吗?该账号将被隐藏,但本人可申请解封'))return;fetch('/ban?target='+encodeURIComponent(target)).then(function(){refreshUsers();loadGroupChat();});}

function unbanUser(target){fetch('/unban?target='+encodeURIComponent(target)).then(function(){refreshUsers();});}

function muteUser(target){if(!confirm('确定要禁言 '+target+' 吗?'))return;fetch('/mute?target='+encodeURIComponent(target)).then(function(){refreshUsers();loadGroupChat();});}

function unmuteUser(target){fetch('/unmute?target='+encodeURIComponent(target)).then(function(){refreshUsers();});}

function setAdmin(target){if(!confirm('确定要把 '+target+' 设为管理员吗?'))return;fetch('/setadmin?target='+encodeURIComponent(target)).then(function(){refreshUsers();});}

function revokeAdmin(target){if(!confirm('确定要撤销 '+target+' 的管理员权限吗?'))return;fetch('/revokeadmin?target='+encodeURIComponent(target)).then(function(){refreshUsers();});}

loadFiles();loadGroupList();loadGroupChat();refreshUsers();waitUsers(0);
setInterval(function(){loadGroupChatPolling();}, 3000);
setInterval(function(){refreshUsers();}, 5000);
setInterval(function(){loadGroupList();}, 10000);
</script>
</body>
</html>"""

class ThreadedTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
    allow_reuse_address = True
    daemon_threads = True

class Handler(http.server.SimpleHTTPRequestHandler):
    timeout = 30

    def log_message(self, format, *args):
        pass

    def get_user_list(self):
        user_list = []
        for name, info in users.items():
            is_kicked = name in kicked_users
            is_banned = name in banned_users
            is_muted = name in muted_users
            if is_banned:
                continue
            user_list.append({
                'name': name,
                'is_online': info.get('is_online', False),
                'last_seen': info.get('last_seen', None),
                'is_admin': info.get('is_admin', False),
                'is_kicked': is_kicked,
                'is_banned': is_banned,
                'is_muted': is_muted,
                'kick_time': kicked_users.get(name, {}).get('kick_time', 0)
            })
        return user_list

    def bump_version(self):
        with user_version_lock:
            global user_version
            user_version += 1

    def do_GET(self):
        try:
            parsed = urllib.parse.urlparse(self.path)
            path = urllib.parse.unquote(parsed.path)
            query = urllib.parse.parse_qs(parsed.query)

            if path == '/login':
                self.send_response(200)
                self.send_header('Content-type', 'text/html; charset=utf-8')
                self.end_headers()
                self.wfile.write(LOGIN_PAGE.format(error='').encode())
                return

            if path == '/logout':
                u = self.get_username()
                if u and u in users:
                    users[u]['is_online'] = False
                    users[u]['last_seen'] = time.time()
                    self.bump_version()
                self.send_response(302)
                self.send_header('Set-Cookie', 'username=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/')
                self.send_header('Location', '/login')
                self.end_headers()
                return

            if path == '/' or path == '':
                u = self.get_username()
                if not u:
                    self.send_response(302)
                    self.send_header('Location', '/login')
                    self.end_headers()
                    return
                if u in banned_users:
                    self.send_response(403)
                    self.send_header('Content-type', 'text/html; charset=utf-8')
                    self.end_headers()
                    self.wfile.write('<h2>⛔ 账号已被封禁</h2><p>该账号已被管理员封禁。</p><a href="/unban-self">点击此处申请解封</a> | <a href="/login">返回登录</a>'.encode())
                    return
                if u in kicked_users:
                    kick_time = kicked_users[u]['kick_time']
                    if time.time() - kick_time < KICK_COOLDOWN:
                        self.send_response(403)
                        self.send_header('Content-type', 'text/html; charset=utf-8')
                        self.end_headers()
                        remaining = int((KICK_COOLDOWN - (time.time() - kick_time)) / 3600 / 24)
                        self.wfile.write(f'<h2>你已被踢出群聊</h2><p>剩余冷却时间:{remaining}天</p><a href="/login">返回登录</a>'.encode())
                        return
                    else:
                        del kicked_users[u]
                if u not in users:
                    users[u] = {'is_online': True, 'last_seen': time.time(), 'is_admin': u in OWNERS}
                    if DEFAULT_GROUP in groups and u not in groups[DEFAULT_GROUP]['members']:
                        groups[DEFAULT_GROUP]['members'].append(u)
                else:
                    users[u]['is_online'] = True
                    users[u]['last_seen'] = time.time()
                self.bump_version()
                self.send_response(200)
                self.send_header('Content-type', 'text/html; charset=utf-8')
                self.end_headers()
                self.wfile.write(MAIN_PAGE.encode())
                return

            if path == '/unban-self':
                u = self.get_username()
                if not u:
                    self.send_response(302)
                    self.send_header('Location', '/login')
                    self.end_headers()
                    return
                if u in banned_users:
                    del banned_users[u]
                    if u not in users:
                        users[u] = {'is_online': True, 'last_seen': time.time(), 'is_admin': u in OWNERS}
                    else:
                        users[u]['is_online'] = True
                        users[u]['last_seen'] = time.time()
                    self.bump_version()
                    self.send_response(200)
                    self.send_header('Content-type', 'text/html; charset=utf-8')
                    self.end_headers()
                    self.wfile.write('<h2>✅ 解封成功</h2><p>你已成功解封,<a href="/">点击进入聊天</a></p>'.encode())
                else:
                    self.send_response(302)
                    self.send_header('Location', '/')
                    self.end_headers()
                return

            if path == '/api/groups':
                u = self.get_username()
                group_list = []
                for gid, g in groups.items():
                    if u in g.get('members', []) or g['owner'] == u:
                        group_list.append({
                            'id': gid,
                            'name': g['name'],
                            'owner': g['owner'],
                            'member_count': len(g.get('members', [])) + 1,
                            'is_owner': g['owner'] == u,
                            'is_admin': u in g.get('admins', []) or g['owner'] == u
                        })
                self.send_response(200)
                self.send_header('Content-type', 'application/json; charset=utf-8')
                self.end_headers()
                self.wfile.write(json.dumps({'groups': group_list}).encode())
                return

            if path == '/api/group-messages':
                group_id = query.get('group_id', [''])[0]
                u = self.get_username()
                if group_id not in groups:
                    self.send_response(404)
                    self.end_headers()
                    return
                g = groups[group_id]
                if u not in g.get('members', []) and g['owner'] != u:
                    self.send_response(403)
                    self.end_headers()
                    return
                self.send_response(200)
                self.send_header('Content-type', 'application/json; charset=utf-8')
                self.end_headers()
                self.wfile.write(json.dumps({
                    'messages': g.get('messages', [])[-50:],
                    'announcement': g.get('announcement', ''),
                    'is_owner': g['owner'] == u,
                    'is_admin': u in g.get('admins', []) or g['owner'] == u
                }).encode())
                return

            if path == '/api/wait-users':
                client_version = int(query.get('version', ['0'])[0])
                with user_version_lock:
                    current_version = user_version
                if client_version == current_version:
                    for _ in range(16):
                        time.sleep(0.5)
                        with user_version_lock:
                            current_version = user_version
                        if client_version != current_version:
                            break
                with user_version_lock:
                    current_version = user_version
                user_list = self.get_user_list()
                self.send_response(200)
                self.send_header('Content-type', 'application/json; charset=utf-8')
                self.end_headers()
                self.wfile.write(json.dumps({'version': current_version, 'users': user_list}).encode())
                return

            if path == '/api/users':
                self.send_response(200)
                self.send_header('Content-type', 'application/json; charset=utf-8')
                self.end_headers()
                self.wfile.write(json.dumps(self.get_user_list()).encode())
                return

            if path == '/api/files':
                self.send_response(200)
                self.send_header('Content-type', 'application/json; charset=utf-8')
                self.end_headers()
                items = []
                try:
                    for n in os.listdir(DIRECTORY):
                        f = os.path.join(DIRECTORY, n)
                        if os.path.isdir(f):
                            items.append({'name': n, 'is_dir': True, 'size': ''})
                        else:
                            s = os.path.getsize(f)
                            if s < 1024:
                                size_str = f'{s}B'
                            elif s < 1048576:
                                size_str = f'{s/1024:.1f}KB'
                            else:
                                size_str = f'{s/1048576:.1f}MB'
                            items.append({'name': n, 'is_dir': False, 'size': size_str})
                except:
                    pass
                self.wfile.write(json.dumps(items).encode())
                return

            if path == '/api/privmsg':
                target = query.get('target', [''])[0]
                self.send_response(200)
                self.send_header('Content-type', 'application/json; charset=utf-8')
                self.end_headers()
                self.wfile.write(json.dumps(private_messages.get(target, [])[-50:]).encode())
                return

            if path == '/kick':
                target = query.get('target', [''])[0]
                u = self.get_username()
                is_admin = users.get(u, {}).get('is_admin', False)
                if is_admin and target in users:
                    if target in OWNERS:
                        self.send_response(403)
                        self.end_headers()
                        return
                    if target in banned_users:
                        self.send_response(403)
                        self.end_headers()
                        return
                    kicked_users[target] = {'kick_time': time.time(), 'kicked_by': u}
                    users[target]['is_online'] = False
                    self.bump_version()
                    for g in groups.values():
                        if target in g.get('members', []):
                            g['members'].remove(target)
                    messages.append({'user': '系统', 'msg': f'{target} 被 {u} 踢出(冷却3天)', 'time': datetime.now().strftime('%H:%M:%S')})
                self.send_response(200)
                self.end_headers()
                self.wfile.write(b'ok')
                return

            if path == '/ban':
                target = query.get('target', [''])[0]
                u = self.get_username()
                is_admin = users.get(u, {}).get('is_admin', False)
                if is_admin and target in users:
                    if target in OWNERS:
                        self.send_response(403)
                        self.end_headers()
                        return
                    banned_users[target] = time.time()
                    if target in kicked_users:
                        del kicked_users[target]
                    users[target]['is_online'] = False
                    self.bump_version()
                    messages.append({'user': '系统', 'msg': f'{target} 被 {u} 封号', 'time': datetime.now().strftime('%H:%M:%S')})
                self.send_response(200)
                self.end_headers()
                self.wfile.write(b'ok')
                return

            if path == '/unban':
                target = query.get('target', [''])[0]
                u = self.get_username()
                is_admin = users.get(u, {}).get('is_admin', False)
                if is_admin and target in kicked_users:
                    del kicked_users[target]
                    self.bump_version()
                    messages.append({'user': '系统', 'msg': f'{target} 被 {u} 加回', 'time': datetime.now().strftime('%H:%M:%S')})
                self.send_response(200)
                self.end_headers()
                self.wfile.write(b'ok')
                return

            if path == '/mute':
                target = query.get('target', [''])[0]
                u = self.get_username()
                is_admin = users.get(u, {}).get('is_admin', False)
                if is_admin and target in users:
                    if target in OWNERS:
                        self.send_response(403)
                        self.end_headers()
                        return
                    muted_users[target] = time.time()
                    self.bump_version()
                    messages.append({'user': '系统', 'msg': f'{target} 被 {u} 禁言', 'time': datetime.now().strftime('%H:%M:%S')})
                self.send_response(200)
                self.end_headers()
                self.wfile.write(b'ok')
                return

            if path == '/unmute':
                target = query.get('target', [''])[0]
                u = self.get_username()
                is_admin = users.get(u, {}).get('is_admin', False)
                if is_admin and target in muted_users:
                    del muted_users[target]
                    self.bump_version()
                    messages.append({'user': '系统', 'msg': f'{target} 被 {u} 解除禁言', 'time': datetime.now().strftime('%H:%M:%S')})
                self.send_response(200)
                self.end_headers()
                self.wfile.write(b'ok')
                return

            if path == '/setadmin':
                target = query.get('target', [''])[0]
                u = self.get_username()
                is_admin = users.get(u, {}).get('is_admin', False)
                if is_admin and target in users:
                    if target in banned_users:
                        self.send_response(403)
                        self.end_headers()
                        return
                    users[target]['is_admin'] = True
                    self.bump_version()
                    messages.append({'user': '系统', 'msg': f'{target} 被 {u} 设为管理员', 'time': datetime.now().strftime('%H:%M:%S')})
                self.send_response(200)
                self.end_headers()
                self.wfile.write(b'ok')
                return

            if path == '/revokeadmin':
                target = query.get('target', [''])[0]
                u = self.get_username()
                if u in OWNERS and target in users:
                    users[target]['is_admin'] = False
                    self.bump_version()
                    messages.append({'user': '系统', 'msg': f'{target} 被群主撤销管理员权限', 'time': datetime.now().strftime('%H:%M:%S')})
                self.send_response(200)
                self.end_headers()
                self.wfile.write(b'ok')
                return

            if path == '/deletefile':
                name = query.get('name', [''])[0]
                if name:
                    name = urllib.parse.unquote(name)
                u = self.get_username()
                is_admin = users.get(u, {}).get('is_admin', False) or u in OWNERS
                if not is_admin:
                    self.send_response(403)
                    self.end_headers()
                    self.wfile.write(b'forbidden')
                    return
                if not name:
                    self.send_response(400)
                    self.end_headers()
                    self.wfile.write(b'missing name')
                    return
                try:
                    full = os.path.join(DIRECTORY, name)
                    if os.path.exists(full) and os.path.isfile(full):
                        os.remove(full)
                        messages.append({'user': '系统', 'msg': f'{u} 删除了文件 {name}', 'time': datetime.now().strftime('%H:%M:%S')})
                        self.send_response(200)
                        self.end_headers()
                        self.wfile.write(b'ok')
                    else:
                        self.send_response(404)
                        self.end_headers()
                        self.wfile.write(b'file not found')
                except Exception as e:
                    self.send_response(500)
                    self.end_headers()
                    self.wfile.write(str(e).encode())
                return

            clean = path.lstrip('/')
            if clean:
                full = os.path.join(DIRECTORY, clean)
                if os.path.exists(full):
                    if os.path.isdir(full):
                        return super().do_GET()
                    else:
                        with open(full, 'rb') as f:
                            content = f.read()
                        self.send_response(200)
                        self.send_header('Content-type', 'application/octet-stream')
                        fn = os.path.basename(full)
                        try:
                            fn.encode('ascii')
                            self.send_header('Content-Disposition', f'attachment; filename="{fn}"')
                        except:
                            self.send_header('Content-Disposition', f"attachment; filename*=UTF-8''{urllib.parse.quote(fn)}")
                        self.end_headers()
                        self.wfile.write(content)
                        return

            self.send_response(404)
            self.end_headers()
        except (ConnectionResetError, BrokenPipeError, ConnectionAbortedError):
            pass
        except Exception as e:
            print(f'GET Error: {e}')
            try:
                self.send_response(500)
                self.end_headers()
            except:
                pass

    def do_POST(self):
        try:
            parsed = urllib.parse.urlparse(self.path)
            path = parsed.path

            if path == '/login':
                try:
                    form = cgi.FieldStorage(fp=self.rfile, headers=self.headers, environ={'REQUEST_METHOD': 'POST', 'CONTENT_TYPE': self.headers.get('Content-Type', '')})
                    u = form.getvalue('username', '').strip()
                    if u:
                        if u in banned_users:
                            self.send_response(200)
                            self.send_header('Content-type', 'text/html; charset=utf-8')
                            self.end_headers()
                            self.wfile.write(LOGIN_PAGE.format(error='⛔ 该账号已被封禁,请前往解封').encode())
                            return
                        if u in users:
                            if users[u].get('is_online', False):
                                self.send_response(200)
                                self.send_header('Content-type', 'text/html; charset=utf-8')
                                self.end_headers()
                                self.wfile.write(LOGIN_PAGE.format(error='❌ 该账号已在线,请勿重复登录').encode())
                                return
                            users[u]['is_online'] = True
                            users[u]['last_seen'] = time.time()
                        else:
                            users[u] = {'is_online': True, 'last_seen': time.time(), 'is_admin': u in OWNERS}
                            if DEFAULT_GROUP in groups and u not in groups[DEFAULT_GROUP]['members']:
                                groups[DEFAULT_GROUP]['members'].append(u)
                        if u in kicked_users:
                            kick_time = kicked_users[u]['kick_time']
                            if time.time() - kick_time < KICK_COOLDOWN:
                                remaining = int((KICK_COOLDOWN - (time.time() - kick_time)) / 3600 / 24)
                                self.send_response(200)
                                self.send_header('Content-type', 'text/html; charset=utf-8')
                                self.end_headers()
                                self.wfile.write(LOGIN_PAGE.format(error=f'你已被踢出,剩余{remaining}天冷却').encode())
                                return
                            else:
                                del kicked_users[u]
                        self.bump_version()
                        encoded_u = urllib.parse.quote(u, encoding='utf-8')
                        self.send_response(302)
                        self.send_header('Set-Cookie', f'username={encoded_u}; path=/')
                        self.send_header('Location', '/')
                        self.end_headers()
                    else:
                        self.send_response(200)
                        self.send_header('Content-type', 'text/html; charset=utf-8')
                        self.end_headers()
                        self.wfile.write(LOGIN_PAGE.format(error='名字不能为空').encode())
                except:
                    pass
                return

            if path == '/upload':
                try:
                    form = cgi.FieldStorage(fp=self.rfile, headers=self.headers, environ={'REQUEST_METHOD': 'POST', 'CONTENT_TYPE': self.headers.get('Content-Type', '')})
                    for key in form.keys():
                        item = form[key]
                        if hasattr(item, 'filename') and item.filename:
                            fn = os.path.basename(item.filename)
                            if fn:
                                sp = os.path.join(DIRECTORY, fn)
                                base, ext = os.path.splitext(fn)
                                cnt = 1
                                while os.path.exists(sp):
                                    sp = os.path.join(DIRECTORY, f'{base}_{cnt}{ext}')
                                    cnt += 1
                                with open(sp, 'wb') as f:
                                    f.write(item.file.read())
                    self.send_response(302)
                    self.send_header('Location', '/')
                    self.end_headers()
                except:
                    self.send_response(302)
                    self.send_header('Location', '/')
                    self.end_headers()
                return

            if path == '/api/create-group':
                form = cgi.FieldStorage(fp=self.rfile, headers=self.headers, environ={'REQUEST_METHOD': 'POST', 'CONTENT_TYPE': self.headers.get('Content-Type', '')})
                name = form.getvalue('name', '').strip()
                u = self.get_username()
                if not name:
                    self.send_response(200)
                    self.send_header('Content-type', 'application/json; charset=utf-8')
                    self.end_headers()
                    self.wfile.write(json.dumps({'ok': False, 'error': '群名不能为空'}).encode())
                    return
                group_id = str(int(time.time() * 1000)) + '_' + u[:4]
                groups[group_id] = {
                    'name': name,
                    'owner': u,
                    'admins': [],
                    'members': [u],
                    'announcement': f'{u} 创建了群聊 "{name}"',
                    'messages': [{'user': '系统', 'msg': f'{u} 创建了群聊 "{name}"', 'time': datetime.now().strftime('%H:%M:%S')}],
                    'created_at': time.time()
                }
                self.bump_version()
                self.send_response(200)
                self.send_header('Content-type', 'application/json; charset=utf-8')
                self.end_headers()
                self.wfile.write(json.dumps({'ok': True, 'group_id': group_id}).encode())
                return

            if path == '/api/send-file':
                try:
                    form = cgi.FieldStorage(fp=self.rfile, headers=self.headers, environ={'REQUEST_METHOD': 'POST', 'CONTENT_TYPE': self.headers.get('Content-Type', '')})
                    group_id = form.getvalue('group_id', '')
                    u = self.get_username()
                    if u in muted_users or u in banned_users:
                        self.send_response(403)
                        self.end_headers()
                        return
                    if group_id not in groups:
                        self.send_response(404)
                        self.end_headers()
                        return
                    g = groups[group_id]
                    if u not in g.get('members', []) and g['owner'] != u:
                        self.send_response(403)
                        self.end_headers()
                        return
                    file_item = form['file']
                    if file_item.filename:
                        fn = os.path.basename(file_item.filename)
                        sp = os.path.join(DIRECTORY, fn)
                        base, ext = os.path.splitext(fn)
                        cnt = 1
                        while os.path.exists(sp):
                            sp = os.path.join(DIRECTORY, f'{base}_{cnt}{ext}')
                            cnt += 1
                        with open(sp, 'wb') as f:
                            f.write(file_item.file.read())
                        g['messages'].append({'user': u, 'msg': f'📎 上传了文件: {os.path.basename(sp)}', 'type': 'file', 'time': datetime.now().strftime('%H:%M:%S')})
                        if len(g['messages']) > MAX_MSG:
                            g['messages'] = g['messages'][-MAX_MSG:]
                        self.bump_version()
                    self.send_response(200)
                    self.end_headers()
                except Exception as e:
                    self.send_response(500)
                    self.end_headers()
                    self.wfile.write(str(e).encode())
                return

            if path == '/api/send-group-msg':
                form = cgi.FieldStorage(fp=self.rfile, headers=self.headers, environ={'REQUEST_METHOD': 'POST', 'CONTENT_TYPE': self.headers.get('Content-Type', '')})
                group_id = form.getvalue('group_id', '')
                msg = form.getvalue('msg', '').strip()
                u = self.get_username()
                if u in muted_users or u in banned_users:
                    self.send_response(403)
                    self.end_headers()
                    return
                if group_id in groups and msg:
                    g = groups[group_id]
                    if u not in g.get('members', []) and g['owner'] != u:
                        self.send_response(403)
                        self.end_headers()
                        return
                    g['messages'].append({'user': u, 'msg': msg, 'time': datetime.now().strftime('%H:%M:%S')})
                    if len(g['messages']) > MAX_MSG:
                        g['messages'] = g['messages'][-MAX_MSG:]
                    self.bump_version()
                self.send_response(200)
                self.end_headers()
                return

            if path == '/api/set-announcement':
                form = cgi.FieldStorage(fp=self.rfile, headers=self.headers, environ={'REQUEST_METHOD': 'POST', 'CONTENT_TYPE': self.headers.get('Content-Type', '')})
                group_id = form.getvalue('group_id', '')
                announcement = form.getvalue('announcement', '').strip()
                u = self.get_username()
                if group_id in groups:
                    g = groups[group_id]
                    if g['owner'] == u or u in g.get('admins', []):
                        g['announcement'] = announcement
                        if announcement:
                            g['messages'].append({'user': '系统', 'msg': f'📢 群公告已更新', 'time': datetime.now().strftime('%H:%M:%S')})
                        self.bump_version()
                self.send_response(200)
                self.end_headers()
                return

            if path == '/api/clear-group':
                form = cgi.FieldStorage(fp=self.rfile, headers=self.headers, environ={'REQUEST_METHOD': 'POST', 'CONTENT_TYPE': self.headers.get('Content-Type', '')})
                group_id = form.getvalue('group_id', '')
                u = self.get_username()
                if group_id in groups:
                    g = groups[group_id]
                    if g['owner'] == u or u in g.get('admins', []):
                        g['messages'] = []
                        g['messages'].append({'user': '系统', 'msg': '🔄 聊天记录已清空', 'time': datetime.now().strftime('%H:%M:%S')})
                        self.bump_version()
                self.send_response(200)
                self.end_headers()
                return

            if path == '/sendprivate':
                u = self.get_username()
                if u in muted_users or u in banned_users:
                    self.send_response(403)
                    self.end_headers()
                    return
                try:
                    form = cgi.FieldStorage(fp=self.rfile, headers=self.headers, environ={'REQUEST_METHOD': 'POST', 'CONTENT_TYPE': self.headers.get('Content-Type', '')})
                    target = form.getvalue('target', '')
                    msg = form.getvalue('msg', '')
                    if msg.strip() and target:
                        if target not in private_messages:
                            private_messages[target] = []
                        private_messages[target].append({'from': u or '匿名', 'msg': msg, 'time': datetime.now().strftime('%H:%M:%S')})
                        if u not in private_messages:
                            private_messages[u] = []
                        private_messages[u].append({'from': u or '匿名', 'msg': f'[私信给 {target}] {msg}', 'time': datetime.now().strftime('%H:%M:%S')})
                    self.send_response(200)
                    self.end_headers()
                except:
                    self.send_response(200)
                    self.end_headers()
                return

            self.send_response(404)
            self.end_headers()
        except (ConnectionResetError, BrokenPipeError, ConnectionAbortedError):
            pass
        except Exception as e:
            print(f'POST Error: {e}')
            try:
                self.send_response(500)
                self.end_headers()
            except:
                pass

    def get_username(self):
        cookie = self.headers.get('Cookie', '')
        for part in cookie.split(';'):
            part = part.strip()
            if part.startswith('username='):
                val = part[9:]
                try:
                    return urllib.parse.unquote(val, encoding='utf-8')
                except:
                    return val
        return None

if __name__ == '__main__':
    import socket
    try:
        ip = socket.gethostbyname(socket.gethostname())
    except:
        ip = '127.0.0.1'
    print('=' * 50)
    print('群主: XH, 黄延希')
    print('共享+聊天已启动')
    print(f'本机: http://localhost:{PORT}')
    print(f'局域网: http://{ip}:{PORT}')
    print('按 Ctrl+C 停止')
    os.chdir(DIRECTORY)

    with ThreadedTCPServer(("", PORT), Handler) as s:
        try:
            s.serve_forever()
        except KeyboardInterrupt:
            print('\n已停止')
        except Exception as e:
            print(f'服务器错误: {e}')
            

python区

python3.12下载