Changeset 505f39a for sctry.py


Ignore:
Timestamp:
01/21/26 13:35:44 (6 months ago)
Author:
istevanoska <ilinastevanoska@…>
Branches:
master
Children:
d42aac3
Parents:
2058e5c
Message:

Added OAuth/prototype

File:
1 edited

Legend:

Unmodified
Added
Removed
  • sctry.py

    r2058e5c r505f39a  
    11#!/usr/bin/env python3
    22"""
    3 POPRAVEN CLIENT со фикс за Sysmon
     3netIntel Client (Fixed + more reliable)
     4- Sends payload to /receive with X-Env-Token header
    45"""
    56
     
    910import json
    1011import requests
    11 from datetime import datetime, timedelta
     12from datetime import datetime
    1213import psutil
    1314import time
    1415import subprocess
    15 import re
    16 from collections import defaultdict
    17 
    18 
     16import getpass
     17
     18
     19# ----------------------------
     20# Helpers
     21# ----------------------------
     22def safe_username():
     23    try:
     24        return os.getlogin()
     25    except Exception:
     26        return getpass.getuser()
     27
     28
     29def get_local_ip_fallback():
     30    """
     31    More reliable local IP than socket.gethostbyname(hostname).
     32    """
     33    try:
     34        s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
     35        s.connect(("8.8.8.8", 80))
     36        ip = s.getsockname()[0]
     37        s.close()
     38        return ip
     39    except Exception:
     40        try:
     41            return socket.gethostbyname(socket.gethostname())
     42        except Exception:
     43            return "127.0.0.1"
     44
     45
     46# ----------------------------
     47# Sysmon-like collector (simulated)
     48# ----------------------------
    1949class FixedSysmonCollector:
    20     """Симплифициран колектор што работи без инсталиран Sysmon"""
     50    """Simplified collector - works even without Sysmon installed"""
    2151
    2252    def __init__(self):
     
    2454
    2555    def collect_sysmon_events(self, limit=50):
    26         """Симулирај Sysmon настани од процеси и мрежа"""
    2756        events = []
    28 
    29         try:
    30             # 1. Процес настани (Event ID 1 = Process Creation)
     57        now = datetime.now()
     58
     59        try:
     60            # 1) Process events (Event ID 1)
    3161            for proc in psutil.process_iter(['pid', 'name', 'create_time', 'username']):
    3262                try:
    3363                    pinfo = proc.info
    34                     if pinfo['create_time']:
    35                         create_time = datetime.fromtimestamp(pinfo['create_time'])
    36                         # Додади само процеси креирани во последните 5 минути
    37                         if not self.last_collection or create_time > self.last_collection:
     64                    ct = pinfo.get('create_time')
     65                    if ct:
     66                        create_time = datetime.fromtimestamp(ct)
     67                        if (not self.last_collection) or (create_time > self.last_collection):
    3868                            events.append({
    3969                                "event_id": 1,
    4070                                "event_type": "Process Creation",
    41                                 "message": f"Process created: {pinfo['name']} (PID: {pinfo['pid']}, User: {pinfo['username']})",
     71                                "message": f"Process created: {pinfo.get('name')} (PID: {pinfo.get('pid')}, User: {pinfo.get('username')})",
    4272                                "timestamp": create_time.isoformat(),
    43                                 "process_name": pinfo['name'],
    44                                 "pid": pinfo['pid'],
    45                                 "user": pinfo['username']
     73                                "process_name": pinfo.get('name'),
     74                                "pid": pinfo.get('pid'),
     75                                "user": pinfo.get('username'),
    4676                            })
    4777                except (psutil.NoSuchProcess, psutil.AccessDenied):
     
    5181                    break
    5282
    53             # 2. Мрежни конекции (Event ID 3 = Network Connection)
     83            # 2) Network connections (Event ID 3)
    5484            for conn in psutil.net_connections(kind='inet'):
    5585                try:
     
    5989                            "event_id": 3,
    6090                            "event_type": "Network Connection",
    61                             "message": f"Network {conn.status}: {conn.laddr.ip if conn.laddr else 'N/A'}:{conn.laddr.port if conn.laddr else 'N/A'} -> {conn.raddr.ip}:{conn.raddr.port}",
    62                             "timestamp": datetime.now().isoformat(),
     91                            "message": f"Network {conn.status}: "
     92                                       f"{conn.laddr.ip if conn.laddr else 'N/A'}:{conn.laddr.port if conn.laddr else 'N/A'} "
     93                                       f"-> {conn.raddr.ip}:{conn.raddr.port}",
     94                            "timestamp": now.isoformat(),
    6395                            "local_address": f"{conn.laddr.ip}:{conn.laddr.port}" if conn.laddr else None,
    6496                            "remote_address": f"{conn.raddr.ip}:{conn.raddr.port}",
    6597                            "status": conn.status,
    6698                            "pid": conn.pid,
    67                             "process_name": proc.name() if proc else "Unknown"
     99                            "process_name": proc.name() if proc else "Unknown",
    68100                        })
    69101                except (psutil.NoSuchProcess, psutil.AccessDenied):
     
    73105                    break
    74106
    75             # 3. Системски перформанси настани
     107            # 3) Performance alerts (optional)
    76108            cpu = psutil.cpu_percent(interval=0.1)
    77109            if cpu > 80:
     
    80112                    "event_type": "High CPU Usage",
    81113                    "message": f"High CPU usage detected: {cpu:.1f}%",
    82                     "timestamp": datetime.now().isoformat(),
     114                    "timestamp": now.isoformat(),
    83115                    "cpu_percent": cpu
    84116                })
     
    90122                    "event_type": "High Memory Usage",
    91123                    "message": f"High RAM usage: {ram.percent:.1f}%",
    92                     "timestamp": datetime.now().isoformat(),
     124                    "timestamp": now.isoformat(),
    93125                    "ram_percent": ram.percent
    94126                })
     
    97129            print(f"FixedSysmon error: {e}")
    98130
    99         self.last_collection = datetime.now()
     131        self.last_collection = now
    100132        return events
    101133
    102     def collect_network_connections_detailed(self):
    103         """Детални мрежни конекции"""
     134    def collect_network_connections_detailed(self, limit=30):
    104135        connections = []
     136        now = datetime.now().isoformat()
     137
    105138        try:
    106139            for conn in psutil.net_connections(kind='inet'):
    107140                try:
    108141                    proc = psutil.Process(conn.pid) if conn.pid else None
    109 
    110142                    conn_info = {
    111143                        "pid": conn.pid,
     
    113145                        "family": str(conn.family),
    114146                        "type": str(conn.type),
    115                         "timestamp": datetime.now().isoformat()
     147                        "timestamp": now
    116148                    }
    117149
     
    127159
    128160                    if proc:
    129                         conn_info.update({
    130                             "process_name": proc.name(),
    131                             "process_cmdline": ' '.join(proc.cmdline()) if proc.cmdline() else None,
    132                             "process_username": proc.username() if hasattr(proc, 'username') else None
    133                         })
     161                        conn_info["process_name"] = proc.name()
     162                        try:
     163                            cmd = proc.cmdline()
     164                            conn_info["process_cmdline"] = " ".join(cmd) if cmd else None
     165                        except Exception:
     166                            conn_info["process_cmdline"] = None
     167                        try:
     168                            conn_info["process_username"] = proc.username()
     169                        except Exception:
     170                            conn_info["process_username"] = None
    134171
    135172                    connections.append(conn_info)
     
    138175                    continue
    139176
    140                 if len(connections) >= 30:
     177                if len(connections) >= limit:
    141178                    break
    142179
     
    147184
    148185    def collect_all_security_data(self):
    149         """Собери сите безбедносни податоци (очекуваниот метод!)"""
    150186        return {
    151187            "sysmon_events": self.collect_sysmon_events(limit=50),
    152             "network_connections": self.collect_network_connections_detailed(),
    153             "file_changes": [],  # Празен лист, можеш да го имплементираш подоцна
     188            "network_connections": self.collect_network_connections_detailed(limit=30),
     189            "file_changes": [],
    154190            "collection_time": datetime.now().isoformat()
    155191        }
    156192
    157193
     194# ----------------------------
     195# Client
     196# ----------------------------
    158197class EnhancedFixedClient:
    159198    def __init__(self, server_ip="192.168.1.13", port=5555, env_token=None):
     
    161200        self.port = port
    162201        self.computer_name = socket.gethostname()
    163         self.user = os.getlogin()
     202        self.user = safe_username()
    164203        self.sysmon_collector = FixedSysmonCollector()
    165204        self.env_token = env_token
    166         # Користи поправената верзија
    167 
    168     def get_detailed_info(self):
    169         """Собирај подробни податоци"""
    170         info = {
    171             "computer_name": self.computer_name,
    172             "user": self.user,
    173             "ip_address": socket.gethostbyname(self.computer_name),
    174             "os": f"{platform.system()} {platform.release()} {platform.version()}",
    175             "architecture": platform.architecture()[0],
    176             "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
    177             "is_sysmon_available": self._check_sysmon_installed()
    178         }
    179 
    180         info.update(self.get_performance_metrics())
    181         info.update(self.get_hardware_info())
    182 
    183         return info
     205
     206        # warmup cpu_percent so it doesn't start at 0
     207        try:
     208            psutil.cpu_percent(interval=None)
     209        except Exception:
     210            pass
    184211
    185212    def _check_sysmon_installed(self):
    186         """Провери дали Sysmon е инсталиран"""
    187213        try:
    188214            if platform.system() == "Windows":
     
    192218                )
    193219                return result.returncode == 0
    194         except:
     220        except Exception:
    195221            pass
    196222        return False
    197223
    198224    def get_performance_metrics(self):
    199         """Метрики за перформанси - ПОПРАВЕНО БЕЗ DEPRECATION WARNING"""
    200         try:
    201             cpu_percent = psutil.cpu_percent(interval=1)
     225        try:
     226            cpu_percent = psutil.cpu_percent(interval=0.5)
    202227            cpu_freq = psutil.cpu_freq()
    203 
    204228            ram = psutil.virtual_memory()
    205229            swap = psutil.swap_memory()
    206 
    207230            disk = psutil.disk_usage("C:/" if platform.system() == "Windows" else "/")
    208231
    209             # ДОБАВИ TRY-EXCEPT за disk_io_counters
    210232            disk_read_mb = 0
    211233            disk_write_mb = 0
     
    215237                    disk_read_mb = round(disk_io.read_bytes / (1024 ** 2), 2)
    216238                    disk_write_mb = round(disk_io.write_bytes / (1024 ** 2), 2)
    217             except:
     239            except Exception:
    218240                pass
    219241
    220242            net_io = psutil.net_io_counters()
     243
     244            boot_dt = datetime.fromtimestamp(psutil.boot_time())
     245            uptime_hours = round((datetime.now() - boot_dt).total_seconds() / 3600, 2)
    221246
    222247            return {
     
    237262                "network_packets_sent": net_io.packets_sent,
    238263                "network_packets_recv": net_io.packets_recv,
    239                 "boot_time": datetime.fromtimestamp(psutil.boot_time()).strftime("%Y-%m-%d %H:%M:%S"),
    240                 "uptime_hours": round((datetime.now() - datetime.fromtimestamp(psutil.boot_time())).seconds / 3600, 2)
     264                "boot_time": boot_dt.strftime("%Y-%m-%d %H:%M:%S"),
     265                "uptime_hours": uptime_hours
    241266            }
    242267        except Exception as e:
     
    245270
    246271    def get_hardware_info(self):
    247         """Хардвер информации"""
    248272        try:
    249273            partitions = []
     
    260284                        "percent_used": usage.percent
    261285                    })
    262                 except:
    263                     continue
    264 
    265             # Мрежни интерфејси
     286                except Exception:
     287                    continue
     288
    266289            net_if_addrs = psutil.net_if_addrs()
    267290            interfaces = {}
     
    271294                        "family": str(addr.family),
    272295                        "address": addr.address,
    273                         "netmask": addr.netmask if hasattr(addr, 'netmask') else None
     296                        "netmask": getattr(addr, "netmask", None)
    274297                    }
    275                     for addr in addrs if addr.address
     298                    for addr in addrs if getattr(addr, "address", None)
    276299                ]
     300
     301            batt = None
     302            try:
     303                b = psutil.sensors_battery()
     304                batt = b.percent if b else None
     305            except Exception:
     306                batt = None
    277307
    278308            return {
     
    281311                "disks": partitions,
    282312                "network_interfaces": interfaces,
    283                 "battery_percent": psutil.sensors_battery().percent if hasattr(psutil.sensors_battery(),
    284                                                                                'percent') else None,
     313                "battery_percent": batt,
    285314                "temperatures": self._get_temperatures()
    286315            }
     
    290319
    291320    def _get_temperatures(self):
    292         """Добиј температури на системот"""
    293321        try:
    294322            temps = {}
    295             if hasattr(psutil, 'sensors_temperatures'):
     323            if hasattr(psutil, "sensors_temperatures"):
    296324                sensors = psutil.sensors_temperatures()
    297325                for name, entries in sensors.items():
     
    299327                        temps[name] = entries[0].current
    300328            return temps
    301         except:
     329        except Exception:
    302330            return {}
    303331
    304     def get_detailed_processes(self):
    305         """Детални информации за процесите - ПОПРАВЕНО БЕЗ DEPRECATION WARNING"""
     332    def get_detailed_processes(self, limit=100):
    306333        processes = []
    307334        try:
    308             for proc in psutil.process_iter(['pid', 'name', 'username', 'cpu_percent',
    309                                              'memory_percent', 'create_time', 'status']):
     335            for proc in psutil.process_iter(['pid', 'name', 'username', 'cpu_percent', 'memory_percent',
     336                                             'create_time', 'status']):
    310337                try:
    311338                    pinfo = proc.info
    312 
    313                     # КОРИСТИ psutil.Process() за да добиеш дополнителни информации
    314339                    with proc.oneshot():
    315340                        processes.append({
    316                             "pid": pinfo['pid'],
    317                             "name": pinfo['name'],
    318                             "user": pinfo['username'],
    319                             "cpu_percent": pinfo['cpu_percent'],
     341                            "pid": pinfo.get("pid"),
     342                            "name": pinfo.get("name"),
     343                            "user": pinfo.get("username"),
     344                            "cpu_percent": pinfo.get("cpu_percent"),
    320345                            "memory_mb": round(proc.memory_info().rss / (1024 ** 2), 2),
    321                             "memory_percent": pinfo['memory_percent'],
    322                             "create_time": datetime.fromtimestamp(pinfo['create_time']).strftime("%Y-%m-%d %H:%M:%S") if
    323                             pinfo['create_time'] else None,
    324                             "status": pinfo['status'],
    325                             "exe": proc.exe() if hasattr(proc, 'exe') else None,
    326                             "cmdline": ' '.join(proc.cmdline()) if hasattr(proc, 'cmdline') else None,
    327                             "num_threads": proc.num_threads() if hasattr(proc, 'num_threads') else None,
    328                             # НЕ КОРИСТИ proc.connections() - тоа е deprecated!
    329                             "connections_count": 0  # Стави 0 или избриши го ова поле
     346                            "memory_percent": pinfo.get("memory_percent"),
     347                            "create_time": datetime.fromtimestamp(pinfo["create_time"]).strftime("%Y-%m-%d %H:%M:%S")
     348                            if pinfo.get("create_time") else None,
     349                            "status": pinfo.get("status"),
     350                            "exe": proc.exe() if hasattr(proc, "exe") else None,
     351                            "cmdline": " ".join(proc.cmdline()) if hasattr(proc, "cmdline") else None,
     352                            "num_threads": proc.num_threads() if hasattr(proc, "num_threads") else None,
     353                            "connections_count": 0
    330354                        })
    331355                except (psutil.NoSuchProcess, psutil.AccessDenied):
    332356                    continue
    333 
    334                 if len(processes) >= 100:
     357                except Exception:
     358                    continue
     359
     360                if len(processes) >= limit:
    335361                    break
    336362
     
    340366        return processes
    341367
     368    def get_detailed_info(self):
     369        info = {
     370            "computer_name": self.computer_name,
     371            "user": self.user,
     372            "ip_address": get_local_ip_fallback(),
     373            "os": f"{platform.system()} {platform.release()} {platform.version()}",
     374            "architecture": platform.architecture()[0],
     375            "timestamp": datetime.now().isoformat(),
     376            "is_sysmon_available": self._check_sysmon_installed()
     377        }
     378        info.update(self.get_performance_metrics())
     379        info.update(self.get_hardware_info())
     380        return info
     381
    342382    def collect_and_send(self):
    343         """Собери и испрати сите податоци"""
    344         print(f"\n[{datetime.now().strftime('%H:%M:%S')}] 🔍 Собирање податоци...")
    345 
    346         # Собери сите податоци
     383        print(f"\n[{datetime.now().strftime('%H:%M:%S')}] 🔍 Collecting...")
     384
    347385        data = {
    348386            "info": self.get_detailed_info(),
    349             "processes": self.get_detailed_processes(),
    350             "security_data": self.sysmon_collector.collect_all_security_data(),  # ОВА СЕГА РАБОТИ!
     387            "processes": self.get_detailed_processes(limit=100),
     388            "security_data": self.sysmon_collector.collect_all_security_data(),
    351389            "collection_time": datetime.now().isoformat(),
    352             "client_version": "2.1_fixed"
     390            "client_version": "2.2_fixed"
    353391        }
    354392
    355         # Прикажи основни информации
    356393        info = data["info"]
    357         sec_data = data["security_data"]
    358 
    359         print(f"   💻 Компјутер: {info['computer_name']}")
    360         print(f"   👤 Корисник: {info['user']}")
    361         print(
    362             f"   📊 CPU: {info.get('cpu_usage', 0)}% | RAM: {info.get('ram_usage', 0)}% | Disk: {info.get('disk_usage', 0)}%")
    363         print(f"   🔄 Процеси: {len(data['processes'])}")
    364         print(f"   🛡️  Sysmon настани: {len(sec_data.get('sysmon_events', []))}")
    365         print(f"   🌐 Мрежни конекции: {len(sec_data.get('network_connections', []))}")
    366 
    367         # Испрати ги податоците
    368         try:
    369             headers = {"X-Env-Token": self.env_token} if self.env_token else {}
    370 
    371             response = requests.post(
    372                 f"http://{self.server_ip}:{self.port}/receive",
    373                 json=data,
    374                 headers=headers,
    375                 timeout=30
    376             )
    377 
    378             if response.status_code == 200:
    379                 result = response.json()
    380                 print(f"   ✅ Податоците се успешно испратени!")
    381                 if 'event_count' in result:
    382                     print(f"   📝 Зачувани {result['event_count']} Sysmon настани")
     394        sec = data["security_data"]
     395
     396        print(f"   💻 {info.get('computer_name')} | 👤 {info.get('user')}")
     397        print(f"   📊 CPU {info.get('cpu_usage', 0)}% | RAM {info.get('ram_usage', 0)}% | Disk {info.get('disk_usage', 0)}%")
     398        print(f"   🔄 Processes: {len(data['processes'])}")
     399        print(f"   🛡️ Sysmon events: {len(sec.get('sysmon_events', []))}")
     400        print(f"   🌐 Net conns: {len(sec.get('network_connections', []))}")
     401
     402        try:
     403            headers = {}
     404            if self.env_token:
     405                headers["X-Env-Token"] = self.env_token
     406            headers["X-Client-Id"] = self.computer_name  # optional
     407
     408            url = f"http://{self.server_ip}:{self.port}/receive"
     409            r = requests.post(url, json=data, headers=headers, timeout=30)
     410
     411            if r.ok:
     412                resp = r.json()
     413                print("   ✅ Sent OK")
     414                if "sysmon_events_saved" in resp:
     415                    print(f"   📝 Saved sysmon: {resp['sysmon_events_saved']}")
    383416                return True
     417
     418            # errors
     419            if r.status_code == 401:
     420                try:
     421                    msg = r.json()
     422                except Exception:
     423                    msg = {"error": r.text[:200]}
     424                print("   🔒 Token invalid/expired. Generate new token in Admin panel.")
     425                print(f"   Server says: {msg}")
    384426            else:
    385                 if response.status_code == 401:
    386                     try:
    387                         msg = response.json()
    388                     except:
    389                         msg = {}
    390                     print("   🔒 Token invalid/expired. Побарај нов token од Admin панел.")
    391                     print(f"   Server says: {msg}")
    392                 else:
    393                     print(f"   ❌ Грешка: {response.status_code}")
    394                     try:
    395                         print("   ", response.text[:300])
    396                     except:
    397                         pass
    398                 return False
    399 
    400         except Exception as e:
    401             print(f"   ❌ Проблем со серверот: {e}")
     427                print(f"   ❌ HTTP {r.status_code}: {r.text[:300]}")
    402428            return False
    403429
     430        except Exception as e:
     431            print(f"   ❌ Connection problem: {e}")
     432            return False
     433
    404434
    405435def main():
    406     """Главна програма"""
    407436    print("=" * 70)
    408     print("🖥️  FIXED LAN LOG COLLECTOR v2.1")
     437    print("🖥️  netIntel Collector (Fixed)")
    409438    print("=" * 70)
    410     print(f"Компјутер: {socket.gethostname()}")
    411     print(f"Корисник: {os.getlogin()}")
    412     print(f"IP: {socket.gethostbyname(socket.gethostname())}")
     439    print(f"Computer: {socket.gethostname()}")
     440    print(f"User: {safe_username()}")
     441    print(f"Local IP: {get_local_ip_fallback()}")
    413442    print("=" * 70)
    414     print("Податоците ќе се собираат и испраќаат на секои 30 секунди.")
    415     print("Притисни Ctrl+C за да го прекинеш.")
     443    print("Will send every 30 seconds. Ctrl+C to stop.")
    416444    print("=" * 70)
    417445
    418     # Провери дали серверот е достапен
    419     server_ip = input("Внеси сервер IP (default 192.168.1.13): ").strip() or "192.168.1.13"
    420 
    421     env_token = input("Внеси ENV TOKEN (од Admin панел): ").strip()
     446    server_ip = input("Server IP (default 192.168.1.13): ").strip() or "192.168.1.13"
     447    env_token = input("ENV TOKEN (from Admin panel): ").strip()
    422448    if not env_token:
    423         print("❌ Мора ENV TOKEN за да се регистрира уредот.")
     449        print("❌ ENV TOKEN is required.")
    424450        return
    425451
    426452    client = EnhancedFixedClient(server_ip, 5555, env_token=env_token)
    427     # Тест конекција
    428     print(f"\n🔌 Тестирање на врска со серверот {server_ip}...")
     453
     454    print(f"\n🔌 Testing server {server_ip}...")
    429455    try:
    430         response = requests.get(f"http://{server_ip}:5555/ping", timeout=5)
    431         if response.status_code == 200:
    432             print("   ✅ Серверот е достапен!")
     456        r = requests.get(f"http://{server_ip}:5555/ping", timeout=5)
     457        if r.ok:
     458            print("   ✅ Server reachable")
    433459        else:
    434             print(f"   ⚠️  Серверот одговори со: {response.status_code}")
    435             confirm = input("   Сакаш ли да продолжиш? (y/n): ").lower()
    436             if confirm != 'y':
    437                 return
     460            print(f"   ⚠️ Server responded: {r.status_code}")
    438461    except Exception as e:
    439         print(f"   ❌ Не можам да се поврзам со серверот {server_ip}: {e}")
    440         confirm = input("   Сакаш ли да продолжиш? (y/n): ").lower()
    441         if confirm != 'y':
    442             return
     462        print(f"   ❌ Can't reach server: {e}")
     463        return
    443464
    444465    try:
    445466        count = 1
    446467        while True:
    447             print(f"\n📤 Трансмисија #{count}")
    448 
    449             success = client.collect_and_send()
    450 
     468            print(f"\n📤 Transmission #{count}")
     469            client.collect_and_send()
    451470            count += 1
    452             print(f"\n⏳ Чекам 30 секунди до следната трансмисија...")
    453 
    454             # Countdown
     471
     472            print("\n⏳ Waiting 30s...")
    455473            for i in range(30, 0, -1):
    456474                if i % 10 == 0 or i <= 5:
    457                     print(f"\r   {i} секунди остануваат...", end="")
     475                    print(f"\r   {i} seconds remaining...", end="")
    458476                time.sleep(1)
    459477            print("\r" + " " * 40 + "\r", end="")
    460478
    461479    except KeyboardInterrupt:
    462         print("\n\n🛑 Програмата е зајчана.")
    463         print(f"📊 Dashboard: http://{server_ip}:5555/")
     480        print("\n\n🛑 Stopped.")
     481        print(f"📊 Dashboard/API: http://{server_ip}:5555/")
    464482
    465483
    466484if __name__ == "__main__":
    467485    try:
    468         import psutil
    469         import requests
     486        import psutil  # noqa
     487        import requests  # noqa
    470488    except ImportError:
    471         print("Инсталирај ги модулите:")
     489        print("Install modules:")
    472490        print("pip install psutil requests")
    473         input("Притисни ENTER за излез...")
    474         exit(1)
     491        input("Press ENTER to exit...")
     492        raise SystemExit(1)
    475493
    476494    main()
Note: See TracChangeset for help on using the changeset viewer.