neuron / app.py
X
Update app.py
9c2bd44 verified
Raw
History Blame
28.3 kB
import json
import uuid
import os
import subprocess
import threading
import time
import socket
import hashlib
from pathlib import Path
from fastapi import FastAPI, Request
from fastapi.responses import HTMLResponse, JSONResponse
import uvicorn
# ========== КОНФИГ ==========
PORT = int(os.getenv("PORT", 7860))
DATA_DIR = Path("/data")
DATA_DIR.mkdir(exist_ok=True)
USERS_FILE = DATA_DIR / "users.json"
FRIENDS_FILE = DATA_DIR / "friends.json"
for f in [USERS_FILE, FRIENDS_FILE]:
if not f.exists():
with open(f, "w") as fp:
json.dump({}, fp)
# ========== РАБОТА С ДАННЫМИ ==========
def load_json(file):
with open(file, "r") as f:
return json.load(f)
def save_json(file, data):
with open(file, "w") as f:
json.dump(data, f, indent=2)
# ========== СЛОВА ДЛЯ КОДОВ ==========
WORDS = [
"солнце", "луна", "звезда", "небо", "море", "ветер", "дождь", "снег",
"гора", "река", "лес", "поле", "цветок", "трава", "дерево", "птица",
"рыба", "волк", "лиса", "медведь", "заяц", "ёжик", "белка", "сова",
"орёл", "сокол", "дельфин", "кит", "тигр", "лев", "пантера", "гепард",
"радуга", "молния", "гром", "туча", "роса", "иней", "туман", "буря",
"мир", "друг", "свет", "тепло", "радость", "счастье", "любовь", "надежда"
]
def generate_word_code():
import random
word1 = random.choice(WORDS)
word2 = random.choice(WORDS)
word3 = str(random.randint(10, 99))
return f"{word1}-{word2}-{word3}".upper()
# ========== ЗАПУСК PEERJS ==========
def start_peerjs():
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
result = sock.connect_ex(('127.0.0.1', 9000))
sock.close()
if result != 0:
print("🚀 Запускаем PeerJS Server...")
subprocess.Popen(
["peer", "--port", "9000", "--path", "/peerjs"],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
start_new_session=True
)
time.sleep(3)
print("✅ PeerJS Server запущен")
except Exception as e:
print(f"⚠️ Ошибка запуска PeerJS: {e}")
threading.Thread(target=start_peerjs, daemon=True).start()
# ========== FASTAPI ==========
app = FastAPI()
@app.get("/", response_class=HTMLResponse)
async def index(request: Request):
html = """
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>🔐 HF Message</title>
<script src="https://unpkg.com/peerjs@1.5.1/dist/peerjs.min.js"></script>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: 'Segoe UI', sans-serif;
background: linear-gradient(135deg, #0f0c29, #302b63, #24243e);
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
padding: 20px;
}
.container {
background: rgba(255,255,255,0.95);
border-radius: 20px;
box-shadow: 0 20px 60px rgba(0,0,0,0.5);
max-width: 650px;
width: 100%;
padding: 30px;
}
h1 {
color: #2d3748;
text-align: center;
font-size: 28px;
}
.subtitle {
text-align: center;
color: #718096;
font-size: 14px;
margin-bottom: 20px;
}
.section {
background: #f7fafc;
border-radius: 12px;
padding: 20px;
margin-bottom: 15px;
border: 1px solid #e2e8f0;
}
.section h3 {
color: #2d3748;
margin-bottom: 12px;
font-size: 15px;
}
input, button {
width: 100%;
padding: 12px;
border: 2px solid #e2e8f0;
border-radius: 8px;
font-size: 14px;
margin-bottom: 8px;
transition: all 0.3s;
}
input:focus {
outline: none;
border-color: #6c63ff;
}
button {
background: linear-gradient(135deg, #6c63ff, #5a52d5);
color: white;
border: none;
font-weight: 600;
cursor: pointer;
}
button:hover {
transform: translateY(-2px);
box-shadow: 0 5px 20px rgba(108, 99, 255, 0.4);
}
.btn-success { background: linear-gradient(135deg, #48bb78, #38a169); }
.btn-danger { background: linear-gradient(135deg, #fc8181, #e53e3e); }
.btn-copy { background: linear-gradient(135deg, #4299e1, #3182ce); }
.btn-small { width: auto; padding: 6px 16px; font-size: 12px; }
.code-display {
background: #2d3748;
color: #f7fafc;
padding: 15px;
border-radius: 8px;
font-family: 'Courier New', monospace;
font-size: 20px;
text-align: center;
letter-spacing: 1px;
margin: 10px 0;
word-break: break-all;
}
.status {
padding: 10px;
border-radius: 8px;
margin-top: 8px;
font-size: 13px;
}
.status.success { background: #c6f6d5; color: #22543d; }
.status.error { background: #fed7d7; color: #9b2c2c; }
.status.info { background: #bee3f8; color: #2a69ac; }
.chat-box {
border: 2px solid #e2e8f0;
border-radius: 8px;
height: 300px;
overflow-y: auto;
padding: 15px;
background: white;
margin-bottom: 10px;
display: none;
}
.chat-box.active { display: block; }
.message {
margin-bottom: 8px;
padding: 8px 12px;
border-radius: 8px;
max-width: 80%;
word-wrap: break-word;
}
.message.sent { background: #6c63ff; color: white; margin-left: auto; }
.message.received { background: #e2e8f0; color: #2d3748; margin-right: auto; }
.message.system { background: #fefcbf; color: #744210; text-align: center; max-width: 100%; font-style: italic; }
.chat-input {
display: none;
gap: 10px;
}
.chat-input.active { display: flex; }
.chat-input input { flex: 1; margin-bottom: 0; }
.chat-input button { width: auto; padding: 12px 24px; }
.peer-id { font-size: 12px; color: #718096; text-align: center; margin-top: 10px; word-break: break-all; }
.hidden { display: none; }
.flex { display: flex; gap: 8px; }
.flex button { width: auto; flex: 1; }
.stats {
text-align: center;
font-size: 12px;
color: #718096;
margin-top: 15px;
padding-top: 15px;
border-top: 1px solid #e2e8f0;
}
.stats span { font-weight: 600; color: #2d3748; }
.badge {
display: inline-block;
background: #6c63ff;
color: white;
font-size: 11px;
padding: 2px 10px;
border-radius: 20px;
margin-left: 8px;
}
.friend-item {
background: white;
padding: 10px;
border-radius: 8px;
margin-bottom: 8px;
border: 1px solid #e2e8f0;
display: flex;
justify-content: space-between;
align-items: center;
}
.friend-item .name { font-weight: 600; color: #2d3748; }
.friend-item .id { font-size: 11px; color: #718096; }
.friend-item button { width: auto; padding: 6px 16px; font-size: 12px; }
</style>
</head>
<body>
<div class="container">
<h1>🔐 HF Message</h1>
<div class="subtitle">Код-слово = вход в аккаунт · ID = добавление в друзья</div>
<!-- Секция: Вход по коду-слову -->
<div class="section" id="login-section">
<h3>🔑 Войти в аккаунт по коду-слову</h3>
<input type="text" id="login-code" placeholder="СОЛНЦЕ-ЛУНА-42" style="text-transform:uppercase;">
<button onclick="loginWithCode()">Войти / Создать аккаунт</button>
<div id="login-status"></div>
</div>
<!-- Секция: Мой профиль (появляется после входа) -->
<div class="section hidden" id="profile-section">
<h3>👤 Мой профиль</h3>
<div style="background:#edf2f7;padding:10px;border-radius:8px;text-align:center;">
<div style="font-weight:600;color:#2d3748;" id="profile-name">Имя</div>
<div style="font-family:monospace;font-size:13px;color:#4a5568;margin-top:4px;" id="profile-id">ID</div>
</div>
<button class="btn-copy" onclick="copyProfileId()" style="margin-top:8px;">📋 Копировать ID</button>
<button class="btn-success" onclick="generateNewCode()" style="margin-top:8px;">🔄 Сгенерировать новый код-слово</button>
<div id="new-code-result" class="hidden" style="margin-top:8px;">
<div class="code-display" id="new-code-display"></div>
<button class="btn-copy" onclick="copyNewCode()">📋 Копировать новый код</button>
</div>
</div>
<!-- Секция: Добавить друга по ID -->
<div class="section hidden" id="friends-section">
<h3>➕ Добавить друга по ID</h3>
<div class="flex">
<input type="text" id="friend-id-input" placeholder="Вставь Peer ID друга">
<button onclick="addFriend()" style="width:auto;padding:12px 20px;">➕</button>
</div>
<div id="friends-list" style="margin-top:10px;"></div>
<div id="friend-status"></div>
</div>
<!-- Секция: Чат -->
<div class="section hidden" id="chat-section">
<h3>💬 Чат с <span id="chat-peer-id" style="color:#6c63ff;">...</span></h3>
<div class="chat-box" id="chat-box"></div>
<div class="chat-input" id="chat-input">
<input type="text" id="message-input" placeholder="Введите сообщение..." onkeypress="if(event.key==='Enter') sendMessage()">
<button onclick="sendMessage()">Отправить</button>
</div>
<button class="btn-danger" onclick="disconnect()">❌ Отключиться</button>
</div>
<div class="stats">
👥 <span id="stats-users">0</span> пользователей
</div>
</div>
<script>
// ========== ГЛОБАЛЬНЫЕ ==========
let myPeer = null;
let myId = null;
let myName = null;
let targetPeerId = null;
let connection = null;
let friends = [];
let currentCode = null;
// ========== ИНИЦИАЛИЗАЦИЯ PEER ==========
function initPeer(callback) {
if (myPeer && myPeer.open) {
if (callback) callback();
return;
}
let savedId = localStorage.getItem('hf_peer_id');
if (!savedId) {
savedId = 'user-' + Math.random().toString(36).substring(2, 10);
localStorage.setItem('hf_peer_id', savedId);
}
myId = savedId;
myPeer = new Peer(myId, {
host: window.location.hostname,
port: window.location.port || (window.location.protocol === 'https:' ? 443 : 80),
path: '/peerjs',
secure: window.location.protocol === 'https:'
});
myPeer.on('open', (id) => {
console.log('✅ Peer открыт:', id);
if (callback) callback();
});
myPeer.on('connection', (conn) => {
handleConnection(conn);
});
myPeer.on('error', (err) => {
console.error('Peer error:', err);
});
// Если Peer уже открыт
if (myPeer.open) {
if (callback) callback();
}
}
// ========== ВХОД ПО КОДУ-СЛОВУ ==========
async function loginWithCode() {
const code = document.getElementById('login-code').value.trim().toUpperCase();
const status = document.getElementById('login-status');
if (!code) {
status.innerHTML = '<div class="status error">❌ Введи код-слово</div>';
return;
}
// Проверяем формат: СЛОВО-СЛОВО-ЧИСЛО
const parts = code.split('-');
if (parts.length !== 3 || isNaN(parts[2])) {
status.innerHTML = '<div class="status error">❌ Неверный формат. Пример: СОЛНЦЕ-ЛУНА-42</div>';
return;
}
status.innerHTML = '<div class="status info">⏳ Вход...</div>';
try {
const r = await fetch('/api/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ code })
});
const d = await r.json();
if (d.success) {
currentCode = code;
myName = d.name;
localStorage.setItem('hf_code', code);
localStorage.setItem('hf_name', d.name);
localStorage.setItem('hf_peer_id', d.peer_id);
myId = d.peer_id;
status.innerHTML = `<div class="status success">✅ Добро пожаловать, ${d.name}!</div>`;
// Показываем профиль
document.getElementById('profile-section').classList.remove('hidden');
document.getElementById('friends-section').classList.remove('hidden');
document.getElementById('profile-name').textContent = d.name;
document.getElementById('profile-id').textContent = d.peer_id;
document.getElementById('login-section').style.display = 'none';
// Инициализируем Peer
initPeer(() => {
loadFriends();
});
updateStats();
} else {
status.innerHTML = `<div class="status error">❌ ${d.error}</div>`;
}
} catch (e) {
status.innerHTML = `<div class="status error">❌ ${e.message}</div>`;
}
}
// ========== ГЕНЕРАЦИЯ НОВОГО КОДА ==========
async function generateNewCode() {
const status = document.getElementById('login-status');
status.innerHTML = '<div class="status info">⏳ Генерация...</div>';
try {
const r = await fetch('/api/generate_code', { method: 'POST' });
const d = await r.json();
if (d.success) {
document.getElementById('new-code-display').textContent = d.code;
document.getElementById('new-code-result').classList.remove('hidden');
status.innerHTML = '<div class="status success">✅ Новый код создан! Сохрани его.</div>';
} else {
status.innerHTML = `<div class="status error">❌ ${d.error}</div>`;
}
} catch (e) {
status.innerHTML = `<div class="status error">❌ ${e.message}</div>`;
}
}
function copyNewCode() {
const code = document.getElementById('new-code-display').textContent;
navigator.clipboard.writeText(code);
}
function copyProfileId() {
const id = document.getElementById('profile-id').textContent;
navigator.clipboard.writeText(id);
}
// ========== ДРУЗЬЯ ==========
async function addFriend() {
const input = document.getElementById('friend-id-input');
const id = input.value.trim();
const status = document.getElementById('friend-status');
if (!id) {
status.innerHTML = '<div class="status error">❌ Введи ID друга</div>';
return;
}
try {
const r = await fetch('/api/add_friend', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ peer_id: id, name: 'Друг' })
});
const d = await r.json();
if (d.success) {
status.innerHTML = '<div class="status success">✅ Друг добавлен!</div>';
input.value = '';
loadFriends();
} else {
status.innerHTML = `<div class="status error">❌ ${d.error}</div>`;
}
} catch (e) {
status.innerHTML = `<div class="status error">❌ ${e.message}</div>`;
}
}
async function loadFriends() {
try {
const r = await fetch('/api/friends');
const d = await r.json();
friends = d.friends || [];
renderFriends();
} catch(e) {}
}
function renderFriends() {
const container = document.getElementById('friends-list');
if (!friends.length) {
container.innerHTML = '<p style="color:#718096;font-size:13px;">Нет добавленных друзей</p>';
return;
}
container.innerHTML = friends.map(f => `
<div class="friend-item">
<div>
<div class="name">${f.name}</div>
<div class="id">${f.peer_id}</div>
</div>
<button onclick="connectToFriend('${f.peer_id}')" class="btn-small">
💬 Чат
</button>
</div>
`).join('');
}
function connectToFriend(peerId) {
initPeer(() => {
connectToPeer(peerId);
});
}
// ========== P2P ПОДКЛЮЧЕНИЕ ==========
function connectToPeer(targetId) {
targetPeerId = targetId;
if (!myPeer) {
initPeer(() => connectToPeer(targetId));
return;
}
try {
connection = myPeer.connect(targetId, { reliable: true });
handleConnection(connection);
} catch (e) {
document.getElementById('login-status').innerHTML =
`<div class="status error">❌ Ошибка: ${e.message}</div>`;
}
}
function handleConnection(conn) {
connection = conn;
conn.on('open', () => {
document.getElementById('chat-section').classList.remove('hidden');
document.getElementById('chat-box').classList.add('active');
document.getElementById('chat-input').classList.add('active');
document.getElementById('chat-peer-id').textContent = targetPeerId;
addMessage('system', '🔗 Соединение установлено!');
});
conn.on('data', (data) => {
if (data.type === 'message') {
addMessage('received', data.text);
}
});
conn.on('close', () => {
addMessage('system', '❌ Соединение разорвано');
document.getElementById('chat-input').classList.remove('active');
document.getElementById('chat-box').classList.remove('active');
});
}
// ========== ОТПРАВКА ==========
function sendMessage() {
const input = document.getElementById('message-input');
const text = input.value.trim();
if (!text || !connection) return;
connection.send({ type: 'message', text });
addMessage('sent', text);
input.value = '';
}
function addMessage(type, text) {
const box = document.getElementById('chat-box');
const div = document.createElement('div');
div.className = `message ${type}`;
div.textContent = text;
box.appendChild(div);
box.scrollTop = box.scrollHeight;
}
function disconnect() {
if (connection) connection.close();
if (myPeer) myPeer.destroy();
location.reload();
}
// ========== СТАТИСТИКА ==========
async function updateStats() {
try {
const r = await fetch('/api/stats');
const d = await r.json();
document.getElementById('stats-users').textContent = d.total_users;
} catch(e) {}
}
updateStats();
setInterval(updateStats, 30000);
// ========== АВТОВХОД ==========
window.onload = function() {
const savedCode = localStorage.getItem('hf_code');
const savedName = localStorage.getItem('hf_name');
const savedId = localStorage.getItem('hf_peer_id');
if (savedCode && savedName && savedId) {
document.getElementById('login-code').value = savedCode;
document.getElementById('login-section').style.display = 'none';
document.getElementById('profile-section').classList.remove('hidden');
document.getElementById('friends-section').classList.remove('hidden');
document.getElementById('profile-name').textContent = savedName;
document.getElementById('profile-id').textContent = savedId;
myId = savedId;
initPeer(() => {
loadFriends();
});
updateStats();
}
};
</script>
</body>
</html>
"""
return HTMLResponse(html)
# ========== API ==========
@app.post("/api/login")
async def login(data: dict):
try:
code = data.get("code", "").upper()
users = load_json(USERS_FILE)
if code in users:
# Вход в существующий аккаунт
return JSONResponse({
"success": True,
"name": users[code]["name"],
"peer_id": users[code]["peer_id"]
})
else:
# Создание нового аккаунта
peer_id = str(uuid.uuid4())
name = f"User_{len(users) + 1}"
users[code] = {
"name": name,
"peer_id": peer_id,
"created_at": time.time()
}
save_json(USERS_FILE, users)
return JSONResponse({
"success": True,
"name": name,
"peer_id": peer_id
})
except Exception as e:
return JSONResponse({"success": False, "error": str(e)})
@app.post("/api/generate_code")
async def generate_code():
try:
users = load_json(USERS_FILE)
code = generate_word_code()
# Проверяем уникальность
while code in users:
code = generate_word_code()
return JSONResponse({"success": True, "code": code})
except Exception as e:
return JSONResponse({"success": False, "error": str(e)})
@app.post("/api/add_friend")
async def add_friend(data: dict):
try:
peer_id = data.get("peer_id", "").strip()
name = data.get("name", "Друг")
if not peer_id:
return JSONResponse({"success": False, "error": "ID не указан"})
friends = load_json(FRIENDS_FILE)
if peer_id not in friends:
friends[peer_id] = {"name": name, "added_at": time.time()}
save_json(FRIENDS_FILE, friends)
return JSONResponse({"success": True})
except Exception as e:
return JSONResponse({"success": False, "error": str(e)})
@app.get("/api/friends")
async def get_friends():
friends = load_json(FRIENDS_FILE)
return JSONResponse({
"friends": [{"peer_id": k, "name": v.get("name", "Друг")} for k, v in friends.items()]
})
@app.get("/api/stats")
async def get_stats():
users = load_json(USERS_FILE)
return JSONResponse({"total_users": len(users)})
# ========== ЗАПУСК ==========
if __name__ == "__main__":
print("=" * 50)
print("🔐 HF Message - Безопасный P2P Чат")
print("=" * 50)
print(f"📁 Данные в: {DATA_DIR}")
print("🚀 Запуск...")
uvicorn.run(app, host="0.0.0.0", port=PORT, log_level="info")