X commited on
Commit
9c2bd44
·
verified ·
1 Parent(s): 93266f9

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +191 -184
app.py CHANGED
@@ -16,12 +16,10 @@ PORT = int(os.getenv("PORT", 7860))
16
  DATA_DIR = Path("/data")
17
  DATA_DIR.mkdir(exist_ok=True)
18
 
19
- INVITES_FILE = DATA_DIR / "invites.json"
20
- PEERS_FILE = DATA_DIR / "peers.json"
21
  FRIENDS_FILE = DATA_DIR / "friends.json"
22
 
23
- # Создаем файлы если их нет
24
- for f in [INVITES_FILE, PEERS_FILE, FRIENDS_FILE]:
25
  if not f.exists():
26
  with open(f, "w") as fp:
27
  json.dump({}, fp)
@@ -35,15 +33,14 @@ def save_json(file, data):
35
  with open(file, "w") as f:
36
  json.dump(data, f, indent=2)
37
 
38
- # ========== ГЕНЕРАЦИЯ КОДА ИЗ СЛОВ ==========
39
  WORDS = [
40
  "солнце", "луна", "звезда", "небо", "море", "ветер", "дождь", "снег",
41
  "гора", "река", "лес", "поле", "цветок", "трава", "дерево", "птица",
42
  "рыба", "волк", "лиса", "медведь", "заяц", "ёжик", "белка", "сова",
43
  "орёл", "сокол", "дельфин", "кит", "тигр", "лев", "пантера", "гепард",
44
  "радуга", "молния", "гром", "туча", "роса", "иней", "туман", "буря",
45
- "мир", "друг", "свет", "тепло", "радость", "счастье", "любовь", "надежда",
46
- "мечта", "звезда", "огонь", "вода", "земля", "воздух", "космос", "галактика"
47
  ]
48
 
49
  def generate_word_code():
@@ -51,16 +48,10 @@ def generate_word_code():
51
  word1 = random.choice(WORDS)
52
  word2 = random.choice(WORDS)
53
  word3 = str(random.randint(10, 99))
54
- code = f"{word1}-{word2}-{word3}"
55
- return code.upper()
56
 
57
- def hash_peer_id(peer_id):
58
- """Хешируем peer_id для безопасности"""
59
- return hashlib.sha256(peer_id.encode()).hexdigest()[:16]
60
-
61
- # ========== ЗАПУСК PEERJS SERVER ==========
62
  def start_peerjs():
63
- """Запускает PeerJS Server в фоновом процессе"""
64
  try:
65
  sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
66
  result = sock.connect_ex(('127.0.0.1', 9000))
@@ -84,7 +75,6 @@ threading.Thread(target=start_peerjs, daemon=True).start()
84
  # ========== FASTAPI ==========
85
  app = FastAPI()
86
 
87
- # ========== HTML СТРАНИЦА ==========
88
  @app.get("/", response_class=HTMLResponse)
89
  async def index(request: Request):
90
  html = """
@@ -164,6 +154,7 @@ async def index(request: Request):
164
  .btn-success { background: linear-gradient(135deg, #48bb78, #38a169); }
165
  .btn-danger { background: linear-gradient(135deg, #fc8181, #e53e3e); }
166
  .btn-copy { background: linear-gradient(135deg, #4299e1, #3182ce); }
 
167
  .code-display {
168
  background: #2d3748;
169
  color: #f7fafc;
@@ -253,44 +244,36 @@ async def index(request: Request):
253
  <body>
254
  <div class="container">
255
  <h1>🔐 HF Message</h1>
256
- <div class="subtitle">Безопасный P2P чат с кодами-словами</div>
257
-
258
- <!-- Секция: Мой ID -->
259
- <div class="section" id="my-id-section">
260
- <h3>🆔 Мой ID</h3>
261
- <div id="my-id-display" style="background:#edf2f7;padding:10px;border-radius:8px;text-align:center;font-family:monospace;font-size:14px;color:#4a5568;">
262
- Загрузка...
263
- </div>
264
- <button class="btn-copy" onclick="copyMyId()" style="margin-top:8px;">📋 Копировать ID</button>
265
  </div>
266
 
267
- <!-- Секция: Инвайт-код (слова) -->
268
- <div class="section" id="invite-section">
269
- <h3>🎫 Создать инвайт-код <span class="badge">слова</span></h3>
270
- <button onclick="generateInvite()">Сгенерировать код</button>
271
- <div id="invite-result" class="hidden">
272
- <div class="code-display" id="invite-code"></div>
273
- <p style="font-size:12px;color:#718096;text-align:center;margin:5px 0;">
274
- Код состоит из слов — легко запомнить и передать
275
- </p>
276
- <button class="btn-copy" onclick="copyInvite()">📋 Копировать код</button>
 
 
277
  </div>
278
- <div id="invite-status"></div>
279
- </div>
280
-
281
- <!-- Секция: Вход по коду -->
282
- <div class="section" id="join-section">
283
- <h3>🔑 Войти по коду-слову</h3>
284
- <input type="text" id="invite-input" placeholder="Например: СОЛНЦЕ-ЛУНА-42" style="text-transform:uppercase;">
285
- <button onclick="joinWithInvite()">Подключиться к другу</button>
286
- <div id="join-status"></div>
287
  </div>
288
 
289
  <!-- Секция: Добавить друга по ID -->
290
- <div class="section" id="add-friend-section">
291
  <h3>➕ Добавить друга по ID</h3>
292
  <div class="flex">
293
- <input type="text" id="friend-id-input" placeholder="Вставь Peer ID друга" style="flex:1;">
294
  <button onclick="addFriend()" style="width:auto;padding:12px 20px;">➕</button>
295
  </div>
296
  <div id="friends-list" style="margin-top:10px;"></div>
@@ -305,14 +288,11 @@ async def index(request: Request):
305
  <input type="text" id="message-input" placeholder="Введите сообщение..." onkeypress="if(event.key==='Enter') sendMessage()">
306
  <button onclick="sendMessage()">Отправить</button>
307
  </div>
308
- <div class="peer-id" id="my-peer-id"></div>
309
  <button class="btn-danger" onclick="disconnect()">❌ Отключиться</button>
310
  </div>
311
 
312
  <div class="stats">
313
- 📊 <span id="stats-total">0</span> кодов ·
314
- <span id="stats-used">0</span> использовано ·
315
- <span id="stats-peers">0</span> подключений
316
  </div>
317
  </div>
318
 
@@ -320,46 +300,26 @@ async def index(request: Request):
320
  // ========== ГЛОБАЛЬНЫЕ ==========
321
  let myPeer = null;
322
  let myId = null;
 
323
  let targetPeerId = null;
324
  let connection = null;
325
  let friends = [];
 
326
 
327
- async function loadStats() {
328
- try {
329
- const r = await fetch('/api/stats');
330
- const d = await r.json();
331
- document.getElementById('stats-total').textContent = d.total_invites;
332
- document.getElementById('stats-used').textContent = d.used_invites;
333
- document.getElementById('stats-peers').textContent = d.active_peers;
334
- } catch(e) {}
335
- }
336
- loadStats();
337
-
338
- // ========== МОЙ ID ==========
339
- function initMyId() {
340
- let id = localStorage.getItem('hf_peer_id');
341
- if (!id) {
342
- id = 'user-' + Math.random().toString(36).substring(2, 10);
343
- localStorage.setItem('hf_peer_id', id);
344
  }
345
- myId = id;
346
- document.getElementById('my-id-display').textContent = id;
347
- }
348
- initMyId();
349
 
350
- function copyMyId() {
351
- navigator.clipboard.writeText(myId).then(() => {
352
- const btn = document.querySelector('#my-id-section .btn-copy');
353
- const orig = btn.textContent;
354
- btn.textContent = '✅ Скопировано!';
355
- setTimeout(() => btn.textContent = orig, 2000);
356
- });
357
- }
358
 
359
- // ========== ИНИЦИАЛИЗАЦИЯ PEER ==========
360
- function initPeer() {
361
- if (myPeer) return;
362
-
363
  myPeer = new Peer(myId, {
364
  host: window.location.hostname,
365
  port: window.location.port || (window.location.protocol === 'https:' ? 443 : 80),
@@ -369,8 +329,7 @@ async def index(request: Request):
369
 
370
  myPeer.on('open', (id) => {
371
  console.log('✅ Peer открыт:', id);
372
- document.getElementById('my-peer-id').textContent = `Твой ID: ${id}`;
373
- loadFriends();
374
  });
375
 
376
  myPeer.on('connection', (conn) => {
@@ -380,22 +339,64 @@ async def index(request: Request):
380
  myPeer.on('error', (err) => {
381
  console.error('Peer error:', err);
382
  });
 
 
 
 
 
383
  }
384
 
385
- // ========== ИНВАЙТ-КОД (СЛОВА) ==========
386
- async function generateInvite() {
387
- const status = document.getElementById('invite-status');
388
- status.innerHTML = '<div class="status info">⏳ Генерация...</div>';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
389
 
390
  try {
391
- const r = await fetch('/api/generate_invite', { method: 'POST' });
 
 
 
 
 
392
  const d = await r.json();
393
 
394
  if (d.success) {
395
- document.getElementById('invite-code').textContent = d.code;
396
- document.getElementById('invite-result').classList.remove('hidden');
397
- status.innerHTML = '<div class="status success">✅ Код создан! Отправь его другу.</div>';
398
- loadStats();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
399
  } else {
400
  status.innerHTML = `<div class="status error">❌ ${d.error}</div>`;
401
  }
@@ -404,42 +405,19 @@ async def index(request: Request):
404
  }
405
  }
406
 
407
- function copyInvite() {
408
- const code = document.getElementById('invite-code').textContent;
409
- navigator.clipboard.writeText(code).then(() => {
410
- const btn = document.querySelector('#invite-section .btn-copy');
411
- const orig = btn.textContent;
412
- btn.textContent = '✅ Скопировано!';
413
- setTimeout(() => btn.textContent = orig, 2000);
414
- });
415
- }
416
-
417
- // ========== ВХОД ПО КОДУ ==========
418
- async function joinWithInvite() {
419
- const code = document.getElementById('invite-input').value.trim().toUpperCase();
420
- const status = document.getElementById('join-status');
421
-
422
- if (!code) {
423
- status.innerHTML = '<div class="status error">❌ Введи код</div>';
424
- return;
425
- }
426
-
427
- status.innerHTML = '<div class="status info">⏳ Проверка...</div>';
428
 
429
  try {
430
- const r = await fetch('/api/use_invite', {
431
- method: 'POST',
432
- headers: { 'Content-Type': 'application/json' },
433
- body: JSON.stringify({ code })
434
- });
435
-
436
  const d = await r.json();
437
 
438
  if (d.success) {
439
- status.innerHTML = '<div class="status success">✅ Код принят! Подключаемся...</div>';
440
- loadStats();
441
- initPeer();
442
- setTimeout(() => connectToPeer(d.peer_id), 500);
443
  } else {
444
  status.innerHTML = `<div class="status error">❌ ${d.error}</div>`;
445
  }
@@ -448,7 +426,17 @@ async def index(request: Request):
448
  }
449
  }
450
 
451
- // ========== ДОБАВЛЕНИЕ ДРУГА ПО ID ==========
 
 
 
 
 
 
 
 
 
 
452
  async function addFriend() {
453
  const input = document.getElementById('friend-id-input');
454
  const id = input.value.trim();
@@ -463,7 +451,7 @@ async def index(request: Request):
463
  const r = await fetch('/api/add_friend', {
464
  method: 'POST',
465
  headers: { 'Content-Type': 'application/json' },
466
- body: JSON.stringify({ peer_id: id })
467
  });
468
 
469
  const d = await r.json();
@@ -499,10 +487,10 @@ async def index(request: Request):
499
  container.innerHTML = friends.map(f => `
500
  <div class="friend-item">
501
  <div>
502
- <div class="name">${f.name || 'Друг'}</div>
503
  <div class="id">${f.peer_id}</div>
504
  </div>
505
- <button onclick="connectToFriend('${f.peer_id}')" style="width:auto;padding:6px 16px;font-size:12px;">
506
  💬 Чат
507
  </button>
508
  </div>
@@ -510,8 +498,9 @@ async def index(request: Request):
510
  }
511
 
512
  function connectToFriend(peerId) {
513
- initPeer();
514
- setTimeout(() => connectToPeer(peerId), 500);
 
515
  }
516
 
517
  // ========== P2P ПОДКЛЮЧЕНИЕ ==========
@@ -519,8 +508,7 @@ async def index(request: Request):
519
  targetPeerId = targetId;
520
 
521
  if (!myPeer) {
522
- initPeer();
523
- setTimeout(() => connectToPeer(targetId), 1000);
524
  return;
525
  }
526
 
@@ -528,7 +516,7 @@ async def index(request: Request):
528
  connection = myPeer.connect(targetId, { reliable: true });
529
  handleConnection(connection);
530
  } catch (e) {
531
- document.getElementById('join-status').innerHTML =
532
  `<div class="status error">❌ Ошибка: ${e.message}</div>`;
533
  }
534
  }
@@ -537,15 +525,10 @@ async def index(request: Request):
537
  connection = conn;
538
 
539
  conn.on('open', () => {
540
- document.getElementById('join-status').innerHTML =
541
- '<div class="status success">✅ Подключено!</div>';
542
  document.getElementById('chat-section').classList.remove('hidden');
543
  document.getElementById('chat-box').classList.add('active');
544
  document.getElementById('chat-input').classList.add('active');
545
  document.getElementById('chat-peer-id').textContent = targetPeerId;
546
- document.getElementById('invite-section').style.display = 'none';
547
- document.getElementById('join-section').style.display = 'none';
548
- document.getElementById('add-friend-section').style.display = 'none';
549
 
550
  addMessage('system', '🔗 Соединение установлено!');
551
  });
@@ -589,10 +572,37 @@ async def index(request: Request):
589
  location.reload();
590
  }
591
 
592
- // ========== АВТОЗАПУСК ==========
 
 
 
 
 
 
 
 
 
 
 
593
  window.onload = function() {
594
- initPeer();
595
- setInterval(loadStats, 30000);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
596
  };
597
  </script>
598
  </body>
@@ -601,44 +611,46 @@ async def index(request: Request):
601
  return HTMLResponse(html)
602
 
603
  # ========== API ==========
604
- @app.post("/api/generate_invite")
605
- async def generate_invite():
606
  try:
607
- invites = load_json(INVITES_FILE)
608
- code = generate_word_code()
609
- peer_id = str(uuid.uuid4())
610
-
611
- invites[code] = {
612
- "used": False,
613
- "peer_id": peer_id,
614
- "created_at": time.time()
615
- }
616
- save_json(INVITES_FILE, invites)
617
 
618
- return JSONResponse({"success": True, "code": code, "peer_id": peer_id})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
619
  except Exception as e:
620
  return JSONResponse({"success": False, "error": str(e)})
621
 
622
- @app.post("/api/use_invite")
623
- async def use_invite(data: dict):
624
  try:
625
- code = data.get("code", "").upper()
626
- invites = load_json(INVITES_FILE)
627
-
628
- if code not in invites:
629
- return JSONResponse({"success": False, "error": "Неверный код"})
630
- if invites[code]["used"]:
631
- return JSONResponse({"success": False, "error": "Код уже использован"})
632
-
633
- peer_id = invites[code]["peer_id"]
634
- invites[code]["used"] = True
635
- save_json(INVITES_FILE, invites)
636
-
637
- peers = load_json(PEERS_FILE)
638
- peers[peer_id] = {"invite_code": code, "connected_at": time.time()}
639
- save_json(PEERS_FILE, peers)
640
-
641
- return JSONResponse({"success": True, "peer_id": peer_id})
642
  except Exception as e:
643
  return JSONResponse({"success": False, "error": str(e)})
644
 
@@ -646,12 +658,14 @@ async def use_invite(data: dict):
646
  async def add_friend(data: dict):
647
  try:
648
  peer_id = data.get("peer_id", "").strip()
 
 
649
  if not peer_id:
650
  return JSONResponse({"success": False, "error": "ID не указан"})
651
 
652
  friends = load_json(FRIENDS_FILE)
653
  if peer_id not in friends:
654
- friends[peer_id] = {"name": "Друг", "added_at": time.time()}
655
  save_json(FRIENDS_FILE, friends)
656
 
657
  return JSONResponse({"success": True})
@@ -667,14 +681,8 @@ async def get_friends():
667
 
668
  @app.get("/api/stats")
669
  async def get_stats():
670
- invites = load_json(INVITES_FILE)
671
- peers = load_json(PEERS_FILE)
672
- used = sum(1 for v in invites.values() if v["used"])
673
- return JSONResponse({
674
- "total_invites": len(invites),
675
- "used_invites": used,
676
- "active_peers": len(peers)
677
- })
678
 
679
  # ========== ЗАПУСК ==========
680
  if __name__ == "__main__":
@@ -683,5 +691,4 @@ if __name__ == "__main__":
683
  print("=" * 50)
684
  print(f"📁 Данные в: {DATA_DIR}")
685
  print("🚀 Запуск...")
686
-
687
  uvicorn.run(app, host="0.0.0.0", port=PORT, log_level="info")
 
16
  DATA_DIR = Path("/data")
17
  DATA_DIR.mkdir(exist_ok=True)
18
 
19
+ USERS_FILE = DATA_DIR / "users.json"
 
20
  FRIENDS_FILE = DATA_DIR / "friends.json"
21
 
22
+ for f in [USERS_FILE, FRIENDS_FILE]:
 
23
  if not f.exists():
24
  with open(f, "w") as fp:
25
  json.dump({}, fp)
 
33
  with open(file, "w") as f:
34
  json.dump(data, f, indent=2)
35
 
36
+ # ========== СЛОВА ДЛЯ КОДОВ ==========
37
  WORDS = [
38
  "солнце", "луна", "звезда", "небо", "море", "ветер", "дождь", "снег",
39
  "гора", "река", "лес", "поле", "цветок", "трава", "дерево", "птица",
40
  "рыба", "волк", "лиса", "медведь", "заяц", "ёжик", "белка", "сова",
41
  "орёл", "сокол", "дельфин", "кит", "тигр", "лев", "пантера", "гепард",
42
  "радуга", "молния", "гром", "туча", "роса", "иней", "туман", "буря",
43
+ "мир", "друг", "свет", "тепло", "радость", "счастье", "любовь", "надежда"
 
44
  ]
45
 
46
  def generate_word_code():
 
48
  word1 = random.choice(WORDS)
49
  word2 = random.choice(WORDS)
50
  word3 = str(random.randint(10, 99))
51
+ return f"{word1}-{word2}-{word3}".upper()
 
52
 
53
+ # ========== ЗАПУСК PEERJS ==========
 
 
 
 
54
  def start_peerjs():
 
55
  try:
56
  sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
57
  result = sock.connect_ex(('127.0.0.1', 9000))
 
75
  # ========== FASTAPI ==========
76
  app = FastAPI()
77
 
 
78
  @app.get("/", response_class=HTMLResponse)
79
  async def index(request: Request):
80
  html = """
 
154
  .btn-success { background: linear-gradient(135deg, #48bb78, #38a169); }
155
  .btn-danger { background: linear-gradient(135deg, #fc8181, #e53e3e); }
156
  .btn-copy { background: linear-gradient(135deg, #4299e1, #3182ce); }
157
+ .btn-small { width: auto; padding: 6px 16px; font-size: 12px; }
158
  .code-display {
159
  background: #2d3748;
160
  color: #f7fafc;
 
244
  <body>
245
  <div class="container">
246
  <h1>🔐 HF Message</h1>
247
+ <div class="subtitle">Код-слово = вход в аккаунт · ID = добавление в друзья</div>
248
+
249
+ <!-- Секция: Вход по коду-слову -->
250
+ <div class="section" id="login-section">
251
+ <h3>🔑 Войти в аккаунт по коду-слову</h3>
252
+ <input type="text" id="login-code" placeholder="СОЛНЦЕ-ЛУНА-42" style="text-transform:uppercase;">
253
+ <button onclick="loginWithCode()">Войти / Создать аккаунт</button>
254
+ <div id="login-status"></div>
 
255
  </div>
256
 
257
+ <!-- Секция: Мой профиль (появляется после входа) -->
258
+ <div class="section hidden" id="profile-section">
259
+ <h3>👤 Мой профиль</h3>
260
+ <div style="background:#edf2f7;padding:10px;border-radius:8px;text-align:center;">
261
+ <div style="font-weight:600;color:#2d3748;" id="profile-name">Имя</div>
262
+ <div style="font-family:monospace;font-size:13px;color:#4a5568;margin-top:4px;" id="profile-id">ID</div>
263
+ </div>
264
+ <button class="btn-copy" onclick="copyProfileId()" style="margin-top:8px;">📋 Копировать ID</button>
265
+ <button class="btn-success" onclick="generateNewCode()" style="margin-top:8px;">🔄 Сгенерировать новый код-слово</button>
266
+ <div id="new-code-result" class="hidden" style="margin-top:8px;">
267
+ <div class="code-display" id="new-code-display"></div>
268
+ <button class="btn-copy" onclick="copyNewCode()">📋 Копировать новый код</button>
269
  </div>
 
 
 
 
 
 
 
 
 
270
  </div>
271
 
272
  <!-- Секция: Добавить друга по ID -->
273
+ <div class="section hidden" id="friends-section">
274
  <h3>➕ Добавить друга по ID</h3>
275
  <div class="flex">
276
+ <input type="text" id="friend-id-input" placeholder="Вставь Peer ID друга">
277
  <button onclick="addFriend()" style="width:auto;padding:12px 20px;">➕</button>
278
  </div>
279
  <div id="friends-list" style="margin-top:10px;"></div>
 
288
  <input type="text" id="message-input" placeholder="Введите сообщение..." onkeypress="if(event.key==='Enter') sendMessage()">
289
  <button onclick="sendMessage()">Отправить</button>
290
  </div>
 
291
  <button class="btn-danger" onclick="disconnect()">❌ Отключиться</button>
292
  </div>
293
 
294
  <div class="stats">
295
+ 👥 <span id="stats-users">0</span> пользователей
 
 
296
  </div>
297
  </div>
298
 
 
300
  // ========== ГЛОБАЛЬНЫЕ ==========
301
  let myPeer = null;
302
  let myId = null;
303
+ let myName = null;
304
  let targetPeerId = null;
305
  let connection = null;
306
  let friends = [];
307
+ let currentCode = null;
308
 
309
+ // ========== ИНИЦИАЛИЗАЦИЯ PEER ==========
310
+ function initPeer(callback) {
311
+ if (myPeer && myPeer.open) {
312
+ if (callback) callback();
313
+ return;
 
 
 
 
 
 
 
 
 
 
 
 
314
  }
 
 
 
 
315
 
316
+ let savedId = localStorage.getItem('hf_peer_id');
317
+ if (!savedId) {
318
+ savedId = 'user-' + Math.random().toString(36).substring(2, 10);
319
+ localStorage.setItem('hf_peer_id', savedId);
320
+ }
321
+ myId = savedId;
 
 
322
 
 
 
 
 
323
  myPeer = new Peer(myId, {
324
  host: window.location.hostname,
325
  port: window.location.port || (window.location.protocol === 'https:' ? 443 : 80),
 
329
 
330
  myPeer.on('open', (id) => {
331
  console.log('✅ Peer открыт:', id);
332
+ if (callback) callback();
 
333
  });
334
 
335
  myPeer.on('connection', (conn) => {
 
339
  myPeer.on('error', (err) => {
340
  console.error('Peer error:', err);
341
  });
342
+
343
+ // Если Peer уже открыт
344
+ if (myPeer.open) {
345
+ if (callback) callback();
346
+ }
347
  }
348
 
349
+ // ========== ВХОД ПО КОДУ-СЛОВУ ==========
350
+ async function loginWithCode() {
351
+ const code = document.getElementById('login-code').value.trim().toUpperCase();
352
+ const status = document.getElementById('login-status');
353
+
354
+ if (!code) {
355
+ status.innerHTML = '<div class="status error">❌ Введи код-слово</div>';
356
+ return;
357
+ }
358
+
359
+ // Проверяем формат: СЛОВО-СЛОВО-ЧИСЛО
360
+ const parts = code.split('-');
361
+ if (parts.length !== 3 || isNaN(parts[2])) {
362
+ status.innerHTML = '<div class="status error">❌ Неверный формат. Пример: СОЛНЦЕ-ЛУНА-42</div>';
363
+ return;
364
+ }
365
+
366
+ status.innerHTML = '<div class="status info">⏳ Вход...</div>';
367
 
368
  try {
369
+ const r = await fetch('/api/login', {
370
+ method: 'POST',
371
+ headers: { 'Content-Type': 'application/json' },
372
+ body: JSON.stringify({ code })
373
+ });
374
+
375
  const d = await r.json();
376
 
377
  if (d.success) {
378
+ currentCode = code;
379
+ myName = d.name;
380
+ localStorage.setItem('hf_code', code);
381
+ localStorage.setItem('hf_name', d.name);
382
+ localStorage.setItem('hf_peer_id', d.peer_id);
383
+ myId = d.peer_id;
384
+
385
+ status.innerHTML = `<div class="status success">✅ Добро пожаловать, ${d.name}!</div>`;
386
+
387
+ // Показываем профиль
388
+ document.getElementById('profile-section').classList.remove('hidden');
389
+ document.getElementById('friends-section').classList.remove('hidden');
390
+ document.getElementById('profile-name').textContent = d.name;
391
+ document.getElementById('profile-id').textContent = d.peer_id;
392
+ document.getElementById('login-section').style.display = 'none';
393
+
394
+ // Инициализируем Peer
395
+ initPeer(() => {
396
+ loadFriends();
397
+ });
398
+
399
+ updateStats();
400
  } else {
401
  status.innerHTML = `<div class="status error">❌ ${d.error}</div>`;
402
  }
 
405
  }
406
  }
407
 
408
+ // ========== ГЕНЕРАЦИЯ НОВОГО КОДА ==========
409
+ async function generateNewCode() {
410
+ const status = document.getElementById('login-status');
411
+ status.innerHTML = '<div class="status info">⏳ Генерация...</div>';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
412
 
413
  try {
414
+ const r = await fetch('/api/generate_code', { method: 'POST' });
 
 
 
 
 
415
  const d = await r.json();
416
 
417
  if (d.success) {
418
+ document.getElementById('new-code-display').textContent = d.code;
419
+ document.getElementById('new-code-result').classList.remove('hidden');
420
+ status.innerHTML = '<div class="status success">✅ Новый код создан! Сохрани его.</div>';
 
421
  } else {
422
  status.innerHTML = `<div class="status error">❌ ${d.error}</div>`;
423
  }
 
426
  }
427
  }
428
 
429
+ function copyNewCode() {
430
+ const code = document.getElementById('new-code-display').textContent;
431
+ navigator.clipboard.writeText(code);
432
+ }
433
+
434
+ function copyProfileId() {
435
+ const id = document.getElementById('profile-id').textContent;
436
+ navigator.clipboard.writeText(id);
437
+ }
438
+
439
+ // ========== ДРУЗЬЯ ==========
440
  async function addFriend() {
441
  const input = document.getElementById('friend-id-input');
442
  const id = input.value.trim();
 
451
  const r = await fetch('/api/add_friend', {
452
  method: 'POST',
453
  headers: { 'Content-Type': 'application/json' },
454
+ body: JSON.stringify({ peer_id: id, name: 'Друг' })
455
  });
456
 
457
  const d = await r.json();
 
487
  container.innerHTML = friends.map(f => `
488
  <div class="friend-item">
489
  <div>
490
+ <div class="name">${f.name}</div>
491
  <div class="id">${f.peer_id}</div>
492
  </div>
493
+ <button onclick="connectToFriend('${f.peer_id}')" class="btn-small">
494
  💬 Чат
495
  </button>
496
  </div>
 
498
  }
499
 
500
  function connectToFriend(peerId) {
501
+ initPeer(() => {
502
+ connectToPeer(peerId);
503
+ });
504
  }
505
 
506
  // ========== P2P ПОДКЛЮЧЕНИЕ ==========
 
508
  targetPeerId = targetId;
509
 
510
  if (!myPeer) {
511
+ initPeer(() => connectToPeer(targetId));
 
512
  return;
513
  }
514
 
 
516
  connection = myPeer.connect(targetId, { reliable: true });
517
  handleConnection(connection);
518
  } catch (e) {
519
+ document.getElementById('login-status').innerHTML =
520
  `<div class="status error">❌ Ошибка: ${e.message}</div>`;
521
  }
522
  }
 
525
  connection = conn;
526
 
527
  conn.on('open', () => {
 
 
528
  document.getElementById('chat-section').classList.remove('hidden');
529
  document.getElementById('chat-box').classList.add('active');
530
  document.getElementById('chat-input').classList.add('active');
531
  document.getElementById('chat-peer-id').textContent = targetPeerId;
 
 
 
532
 
533
  addMessage('system', '🔗 Соединение установлено!');
534
  });
 
572
  location.reload();
573
  }
574
 
575
+ // ========== СТАТИСТИКА ==========
576
+ async function updateStats() {
577
+ try {
578
+ const r = await fetch('/api/stats');
579
+ const d = await r.json();
580
+ document.getElementById('stats-users').textContent = d.total_users;
581
+ } catch(e) {}
582
+ }
583
+ updateStats();
584
+ setInterval(updateStats, 30000);
585
+
586
+ // ========== АВТОВХОД ==========
587
  window.onload = function() {
588
+ const savedCode = localStorage.getItem('hf_code');
589
+ const savedName = localStorage.getItem('hf_name');
590
+ const savedId = localStorage.getItem('hf_peer_id');
591
+
592
+ if (savedCode && savedName && savedId) {
593
+ document.getElementById('login-code').value = savedCode;
594
+ document.getElementById('login-section').style.display = 'none';
595
+ document.getElementById('profile-section').classList.remove('hidden');
596
+ document.getElementById('friends-section').classList.remove('hidden');
597
+ document.getElementById('profile-name').textContent = savedName;
598
+ document.getElementById('profile-id').textContent = savedId;
599
+ myId = savedId;
600
+
601
+ initPeer(() => {
602
+ loadFriends();
603
+ });
604
+ updateStats();
605
+ }
606
  };
607
  </script>
608
  </body>
 
611
  return HTMLResponse(html)
612
 
613
  # ========== API ==========
614
+ @app.post("/api/login")
615
+ async def login(data: dict):
616
  try:
617
+ code = data.get("code", "").upper()
618
+ users = load_json(USERS_FILE)
 
 
 
 
 
 
 
 
619
 
620
+ if code in users:
621
+ # Вход в существующий аккаунт
622
+ return JSONResponse({
623
+ "success": True,
624
+ "name": users[code]["name"],
625
+ "peer_id": users[code]["peer_id"]
626
+ })
627
+ else:
628
+ # Создание нового аккаунта
629
+ peer_id = str(uuid.uuid4())
630
+ name = f"User_{len(users) + 1}"
631
+ users[code] = {
632
+ "name": name,
633
+ "peer_id": peer_id,
634
+ "created_at": time.time()
635
+ }
636
+ save_json(USERS_FILE, users)
637
+ return JSONResponse({
638
+ "success": True,
639
+ "name": name,
640
+ "peer_id": peer_id
641
+ })
642
  except Exception as e:
643
  return JSONResponse({"success": False, "error": str(e)})
644
 
645
+ @app.post("/api/generate_code")
646
+ async def generate_code():
647
  try:
648
+ users = load_json(USERS_FILE)
649
+ code = generate_word_code()
650
+ # Проверяем уникальность
651
+ while code in users:
652
+ code = generate_word_code()
653
+ return JSONResponse({"success": True, "code": code})
 
 
 
 
 
 
 
 
 
 
 
654
  except Exception as e:
655
  return JSONResponse({"success": False, "error": str(e)})
656
 
 
658
  async def add_friend(data: dict):
659
  try:
660
  peer_id = data.get("peer_id", "").strip()
661
+ name = data.get("name", "Друг")
662
+
663
  if not peer_id:
664
  return JSONResponse({"success": False, "error": "ID не указан"})
665
 
666
  friends = load_json(FRIENDS_FILE)
667
  if peer_id not in friends:
668
+ friends[peer_id] = {"name": name, "added_at": time.time()}
669
  save_json(FRIENDS_FILE, friends)
670
 
671
  return JSONResponse({"success": True})
 
681
 
682
  @app.get("/api/stats")
683
  async def get_stats():
684
+ users = load_json(USERS_FILE)
685
+ return JSONResponse({"total_users": len(users)})
 
 
 
 
 
 
686
 
687
  # ========== ЗАПУСК ==========
688
  if __name__ == "__main__":
 
691
  print("=" * 50)
692
  print(f"📁 Данные в: {DATA_DIR}")
693
  print("🚀 Запуск...")
 
694
  uvicorn.run(app, host="0.0.0.0", port=PORT, log_level="info")