meky112 1.1.2
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.
- package/.github/workflows/publish.yml +22 -0
- package/app.js +1084 -0
- package/config.json +3 -0
- package/index.html +28 -0
- package/package.json +9 -0
- package/requirements.txt +1 -0
- package/server.py +278 -0
- package/style.css +452 -0
package/config.json
ADDED
package/index.html
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="it">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>STC - Smart TV Client</title>
|
|
7
|
+
<script type="text/javascript" src="$WEBAPIS/webapis/webapis.js"></script>
|
|
8
|
+
<link rel="stylesheet" href="style.css" />
|
|
9
|
+
<script src="https://cdn.jsdelivr.net/npm/hls.js@1.5.8/dist/hls.min.js"></script>
|
|
10
|
+
</head>
|
|
11
|
+
<body>
|
|
12
|
+
<div id="search-bar" class="search-container">
|
|
13
|
+
<form id="search-form" action="" onsubmit="return false;" style="display: flex; align-items: center;">
|
|
14
|
+
<input type="search" id="search-input" placeholder="Cerca un titolo..." enterkeyhint="search" autocomplete="off" />
|
|
15
|
+
<button id="btn-clear" type="button" class="clear-btn">Cancella</button>
|
|
16
|
+
</form>
|
|
17
|
+
<div class="filters">
|
|
18
|
+
<button id="filter-all" class="filter-btn active">Tutti</button>
|
|
19
|
+
<button id="filter-movie" class="filter-btn">Film</button>
|
|
20
|
+
<button id="filter-tv" class="filter-btn">Serie TV</button>
|
|
21
|
+
</div>
|
|
22
|
+
</div>
|
|
23
|
+
<main id="gallery" class="grid"></main>
|
|
24
|
+
<div id="details-view" class="details-container" style="display: none;"></div>
|
|
25
|
+
<div id="player-view" class="player-container" style="display: none;"></div>
|
|
26
|
+
<script src="app.js"></script>
|
|
27
|
+
</body>
|
|
28
|
+
</html>
|
package/package.json
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "meky112",
|
|
3
|
+
"version": "1.1.2",
|
|
4
|
+
"packageType": "app",
|
|
5
|
+
"appName": "STC",
|
|
6
|
+
"description": "Client non ufficiale di StreamingCommunity per Smart TV Samsung (Tizen).",
|
|
7
|
+
"appPath": "index.html",
|
|
8
|
+
"keys": ["MediaPlayPause", "MediaPlay", "MediaPause", "MediaStop", "MediaFastForward", "MediaRewind", "MediaTrackNext", "MediaTrackPrevious"]
|
|
9
|
+
}
|
package/requirements.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# No third-party dependencies required. Standard Python library only.
|
package/server.py
ADDED
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Server locale di sviluppo per StreamCom.
|
|
4
|
+
Avvia un server HTTP che:
|
|
5
|
+
1. Serve i file locali (index.html, style.css, app.js)
|
|
6
|
+
2. Fa da proxy trasparente verso streamingcommunityz.associates
|
|
7
|
+
su /proxy/... aggirando il blocco CORS del browser.
|
|
8
|
+
Uso: python server.py
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import http.server
|
|
12
|
+
import socketserver
|
|
13
|
+
import urllib.request
|
|
14
|
+
import urllib.error
|
|
15
|
+
import ssl
|
|
16
|
+
import sys
|
|
17
|
+
import os
|
|
18
|
+
import json
|
|
19
|
+
import re
|
|
20
|
+
|
|
21
|
+
PORT = int(os.environ.get("PORT", 8000))
|
|
22
|
+
CONFIG_FILE = "config.json"
|
|
23
|
+
DEFAULT_BASE_SITE = "https://streamingcommunityz.associates"
|
|
24
|
+
|
|
25
|
+
def load_config():
|
|
26
|
+
base_site = DEFAULT_BASE_SITE
|
|
27
|
+
if os.path.exists(CONFIG_FILE):
|
|
28
|
+
try:
|
|
29
|
+
with open(CONFIG_FILE, "r", encoding="utf-8") as f:
|
|
30
|
+
config = json.load(f)
|
|
31
|
+
if "base_site" in config:
|
|
32
|
+
base_site = config["base_site"].rstrip("/")
|
|
33
|
+
except Exception as e:
|
|
34
|
+
print(f"Errore nel caricamento di {CONFIG_FILE}: {e}. Uso il default.")
|
|
35
|
+
|
|
36
|
+
if "://cdn." not in base_site:
|
|
37
|
+
cdn_site = base_site.replace("://", "://cdn.")
|
|
38
|
+
else:
|
|
39
|
+
cdn_site = base_site
|
|
40
|
+
|
|
41
|
+
return base_site, cdn_site
|
|
42
|
+
|
|
43
|
+
BASE_SITE, CDN_SITE = load_config()
|
|
44
|
+
|
|
45
|
+
LOGS_LIST = []
|
|
46
|
+
|
|
47
|
+
def log_debug(msg):
|
|
48
|
+
log_line = str(msg)
|
|
49
|
+
print(log_line)
|
|
50
|
+
sys.stdout.flush()
|
|
51
|
+
LOGS_LIST.append(log_line)
|
|
52
|
+
if len(LOGS_LIST) > 200:
|
|
53
|
+
LOGS_LIST.pop(0)
|
|
54
|
+
|
|
55
|
+
class StreamComHandler(http.server.SimpleHTTPRequestHandler):
|
|
56
|
+
"""Gestisce sia i file locali che le richieste proxy verso il sito."""
|
|
57
|
+
|
|
58
|
+
def log_message(self, format, *args):
|
|
59
|
+
# Log all messages (including errors) to console
|
|
60
|
+
msg = format % args
|
|
61
|
+
log_debug(f"[HTTP] {msg}")
|
|
62
|
+
super().log_message(format, *args)
|
|
63
|
+
|
|
64
|
+
def _cors_headers(self):
|
|
65
|
+
self.send_header("Access-Control-Allow-Origin", "*")
|
|
66
|
+
self.send_header("Access-Control-Allow-Methods", "GET, OPTIONS")
|
|
67
|
+
self.send_header("Access-Control-Allow-Headers", "Content-Type")
|
|
68
|
+
|
|
69
|
+
def end_headers(self):
|
|
70
|
+
self.send_header("Cache-Control", "no-cache, no-store, must-revalidate")
|
|
71
|
+
self.send_header("Pragma", "no-cache")
|
|
72
|
+
self.send_header("Expires", "0")
|
|
73
|
+
super().end_headers()
|
|
74
|
+
|
|
75
|
+
def send_error(self, code, message=None, explain=None):
|
|
76
|
+
try:
|
|
77
|
+
self.send_response(code)
|
|
78
|
+
self._cors_headers()
|
|
79
|
+
self.send_header("Content-Type", "text/plain")
|
|
80
|
+
self.end_headers()
|
|
81
|
+
if self.command != "HEAD" and message:
|
|
82
|
+
self.wfile.write(str(message).encode("utf-8"))
|
|
83
|
+
except Exception:
|
|
84
|
+
pass
|
|
85
|
+
|
|
86
|
+
def do_OPTIONS(self):
|
|
87
|
+
self.send_response(204)
|
|
88
|
+
self._cors_headers()
|
|
89
|
+
self.end_headers()
|
|
90
|
+
|
|
91
|
+
def do_HEAD(self):
|
|
92
|
+
self.do_GET()
|
|
93
|
+
|
|
94
|
+
def do_GET(self):
|
|
95
|
+
# --- Info di Configurazione (restituisce BASE_SITE e CDN_SITE) ---
|
|
96
|
+
if self.path == "/proxy-config":
|
|
97
|
+
self.send_response(200)
|
|
98
|
+
self._cors_headers()
|
|
99
|
+
self.send_header("Content-Type", "application/json")
|
|
100
|
+
self.end_headers()
|
|
101
|
+
if self.command != "HEAD":
|
|
102
|
+
config_data = {
|
|
103
|
+
"base_site": BASE_SITE,
|
|
104
|
+
"cdn_site": CDN_SITE
|
|
105
|
+
}
|
|
106
|
+
json_bytes = json.dumps(config_data).encode("utf-8")
|
|
107
|
+
self.wfile.write(json_bytes)
|
|
108
|
+
self.wfile.flush()
|
|
109
|
+
return
|
|
110
|
+
|
|
111
|
+
elif self.path == "/view-logs":
|
|
112
|
+
self.send_response(200)
|
|
113
|
+
self._cors_headers()
|
|
114
|
+
self.send_header("Content-Type", "text/plain; charset=utf-8")
|
|
115
|
+
self.end_headers()
|
|
116
|
+
if self.command != "HEAD":
|
|
117
|
+
logs_str = "\n".join(LOGS_LIST)
|
|
118
|
+
self.wfile.write(logs_str.encode("utf-8"))
|
|
119
|
+
return
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
# --- Proxy verso il sito principale ---
|
|
123
|
+
elif self.path.startswith("/proxy/"):
|
|
124
|
+
target_path = self.path[len("/proxy"):] # /it/archive, /it/search?q=..., ecc.
|
|
125
|
+
self._proxy_request(BASE_SITE + target_path)
|
|
126
|
+
|
|
127
|
+
# --- Proxy verso il CDN (immagini) ---
|
|
128
|
+
elif self.path.startswith("/cdn/"):
|
|
129
|
+
target_path = self.path[len("/cdn"):]
|
|
130
|
+
self._proxy_request(CDN_SITE + target_path)
|
|
131
|
+
|
|
132
|
+
# --- Proxy verso Vixcloud ---
|
|
133
|
+
elif self.path.startswith("/vixcloud/"):
|
|
134
|
+
target_path = self.path[len("/vixcloud"):]
|
|
135
|
+
self._proxy_request("https://vixcloud.co" + target_path)
|
|
136
|
+
|
|
137
|
+
# --- Proxy verso Vixcontent (CDN video) ---
|
|
138
|
+
elif self.path.startswith("/vixcontent/"):
|
|
139
|
+
parts = self.path[len("/vixcontent/"):].split("/", 1)
|
|
140
|
+
subdomain = parts[0]
|
|
141
|
+
remaining_path = parts[1] if len(parts) > 1 else ""
|
|
142
|
+
target_url = f"https://{subdomain}.vix-content.net/{remaining_path}"
|
|
143
|
+
self._proxy_request(target_url)
|
|
144
|
+
|
|
145
|
+
# --- File locali con fallback proxy ---
|
|
146
|
+
else:
|
|
147
|
+
clean_path = self.path.split("?")[0].lstrip("/")
|
|
148
|
+
local_path = os.path.join(os.getcwd(), clean_path) if clean_path else os.path.join(os.getcwd(), "index.html")
|
|
149
|
+
|
|
150
|
+
if os.path.exists(local_path) and os.path.isfile(local_path):
|
|
151
|
+
super().do_GET()
|
|
152
|
+
else:
|
|
153
|
+
# Controlla Referer per instradare le richieste di asset relativi al host giusto
|
|
154
|
+
referer = self.headers.get("Referer", "")
|
|
155
|
+
if "/vixcloud/" in referer or "vixcloud.co" in referer:
|
|
156
|
+
self._proxy_request("https://vixcloud.co" + self.path)
|
|
157
|
+
else:
|
|
158
|
+
self._proxy_request(BASE_SITE + self.path)
|
|
159
|
+
|
|
160
|
+
def _proxy_request(self, url):
|
|
161
|
+
ctx = ssl.create_default_context()
|
|
162
|
+
ctx.check_hostname = False
|
|
163
|
+
ctx.verify_mode = ssl.CERT_NONE
|
|
164
|
+
|
|
165
|
+
headers = {
|
|
166
|
+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36",
|
|
167
|
+
"Accept": "*/*",
|
|
168
|
+
"Accept-Language": "it-IT,it;q=0.9,en;q=0.8",
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
# Copia gli header Range ed altri essenziali per lo streaming video
|
|
172
|
+
for h in ["Range", "If-Range"]:
|
|
173
|
+
if h in self.headers:
|
|
174
|
+
headers[h] = self.headers[h]
|
|
175
|
+
|
|
176
|
+
# Configura Referer e Origin autorizzati per bypassare i controlli WAF/Cloudflare
|
|
177
|
+
if "vixcloud.co" in url or "vix-content.net" in url or "streamingcommunity" in url:
|
|
178
|
+
headers["Referer"] = "https://streamingcommunityz.associates/"
|
|
179
|
+
headers["Origin"] = "https://streamingcommunityz.associates"
|
|
180
|
+
|
|
181
|
+
req = urllib.request.Request(url, headers=headers)
|
|
182
|
+
|
|
183
|
+
try:
|
|
184
|
+
with urllib.request.urlopen(req, context=ctx, timeout=15) as resp:
|
|
185
|
+
body = resp.read()
|
|
186
|
+
content_type = resp.headers.get("Content-Type", "")
|
|
187
|
+
|
|
188
|
+
# Se la risorsa contiene testo/script/playlist, riscrivi i domini Vixcloud per forzare il same-origin
|
|
189
|
+
if any(t in content_type for t in ["text/html", "javascript", "mpegurl", "mpegURL", "json", "xml"]):
|
|
190
|
+
try:
|
|
191
|
+
text = body.decode("utf-8", errors="ignore")
|
|
192
|
+
host = self.headers.get("Host", f"localhost:{PORT}")
|
|
193
|
+
proto = self.headers.get("X-Forwarded-Proto", "http" if "localhost" in host or "127.0.0.1" in host else "https")
|
|
194
|
+
proxy_base = f"{proto}://{host}"
|
|
195
|
+
log_debug(f"[DEBUG] Proxying: {url} | Content-Type: {content_type}")
|
|
196
|
+
log_debug(f"[DEBUG] proxy_base: {proxy_base}")
|
|
197
|
+
log_debug(f"[DEBUG] Before replace vixcloud count: {text.count('https://vixcloud.co')}")
|
|
198
|
+
|
|
199
|
+
text = text.replace("https://vixcloud.co", f"{proxy_base}/vixcloud")
|
|
200
|
+
text = text.replace(r"https:\/\/vixcloud.co", fr"{proxy_base}\/vixcloud")
|
|
201
|
+
text = re.sub(r"https://([a-zA-Z0-9\-]+)\.vix\-content\.net", fr"{proxy_base}/vixcontent/\1", text)
|
|
202
|
+
vixcontent_repl = proxy_base.replace('/', '\\/') + '\\/vixcontent\\/\\1'
|
|
203
|
+
text = re.sub(r"https:\\/\\/([a-zA-Z0-9\-]+)\.vix\-content\.net", vixcontent_repl, text)
|
|
204
|
+
|
|
205
|
+
# Riscrivi anche il dominio di StreamingCommunity per convogliare gli asset statici (JS/CSS/fonts) nel proxy ed evitare errori CORS
|
|
206
|
+
text = text.replace(BASE_SITE, f"{proxy_base}/proxy")
|
|
207
|
+
escaped_base = BASE_SITE.replace("/", r"\/")
|
|
208
|
+
escaped_proxy = f"{proxy_base}/proxy".replace("/", r"\/")
|
|
209
|
+
text = text.replace(escaped_base, escaped_proxy)
|
|
210
|
+
|
|
211
|
+
text = text.replace(CDN_SITE, f"{proxy_base}/cdn")
|
|
212
|
+
escaped_cdn = CDN_SITE.replace("/", r"\/")
|
|
213
|
+
escaped_proxy_cdn = f"{proxy_base}/cdn".replace("/", r"\/")
|
|
214
|
+
text = text.replace(escaped_cdn, escaped_proxy_cdn)
|
|
215
|
+
|
|
216
|
+
log_debug(f"[DEBUG] After replace vixcloud count: {text.count('https://vixcloud.co')}")
|
|
217
|
+
log_debug(f"[DEBUG] After replace proxy/vixcloud count: {text.count(proxy_base + '/vixcloud')}")
|
|
218
|
+
|
|
219
|
+
# Rimuovi script pubblicitari e anti-debugger da Vixcloud
|
|
220
|
+
if "text/html" in content_type:
|
|
221
|
+
text = re.sub(r'<script[^>]*sechw\.com[^>]*>.*?</script>', '', text, flags=re.DOTALL)
|
|
222
|
+
text = re.sub(r'<script[^>]*>(?:(?!<script)[\s\S])*?minimalUserResponseInMiliseconds[\s\S]*?</script>', '', text, flags=re.IGNORECASE)
|
|
223
|
+
text = re.sub(r'<script[^>]*>(?:(?!<script)[\s\S])*?oe\.entries[\s\S]*?</script>', '', text, flags=re.IGNORECASE)
|
|
224
|
+
# Previene i redirect javascript forzati sostituendo window.top e location.replace
|
|
225
|
+
text = text.replace("window.top", "window.self")
|
|
226
|
+
text = text.replace("top.location", "self.location")
|
|
227
|
+
|
|
228
|
+
body = text.encode("utf-8")
|
|
229
|
+
except Exception as e:
|
|
230
|
+
log_debug(f"[ERROR] Exception in rewrite: {type(e)} {e}")
|
|
231
|
+
|
|
232
|
+
self.send_response(resp.status)
|
|
233
|
+
if content_type:
|
|
234
|
+
self.send_header("Content-Type", content_type)
|
|
235
|
+
self.send_header("Content-Length", str(len(body)))
|
|
236
|
+
|
|
237
|
+
# Copia intestazioni per lo streaming e range requests
|
|
238
|
+
for h in ["Content-Range", "Accept-Ranges", "Content-Encoding"]:
|
|
239
|
+
val = resp.headers.get(h)
|
|
240
|
+
if val is not None:
|
|
241
|
+
self.send_header(h, val)
|
|
242
|
+
|
|
243
|
+
self.send_header("Cache-Control", "no-cache, no-store, must-revalidate")
|
|
244
|
+
self.send_header("Pragma", "no-cache")
|
|
245
|
+
self.send_header("Expires", "0")
|
|
246
|
+
self._cors_headers()
|
|
247
|
+
self.end_headers()
|
|
248
|
+
if self.command != "HEAD":
|
|
249
|
+
self.wfile.write(body)
|
|
250
|
+
|
|
251
|
+
except urllib.error.HTTPError as e:
|
|
252
|
+
self.send_error(e.code, str(e))
|
|
253
|
+
except urllib.error.URLError as e:
|
|
254
|
+
self.send_error(502, f"Proxy error: {e.reason}")
|
|
255
|
+
except Exception as e:
|
|
256
|
+
self.send_error(500, str(e))
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def main():
|
|
260
|
+
socketserver.TCPServer.allow_reuse_address = True
|
|
261
|
+
with socketserver.TCPServer(("", PORT), StreamComHandler) as httpd:
|
|
262
|
+
print("=" * 55)
|
|
263
|
+
print(" StreamCom – Server locale avviato")
|
|
264
|
+
print("=" * 55)
|
|
265
|
+
print(f" Apri nel browser: http://localhost:{PORT}")
|
|
266
|
+
print(f" Proxy sito: http://localhost:{PORT}/proxy/it/archive")
|
|
267
|
+
print(f" Proxy CDN: http://localhost:{PORT}/cdn/...")
|
|
268
|
+
print(" Premi Ctrl+C per fermare.")
|
|
269
|
+
print("=" * 55)
|
|
270
|
+
try:
|
|
271
|
+
httpd.serve_forever()
|
|
272
|
+
except KeyboardInterrupt:
|
|
273
|
+
print("\nServer fermato.")
|
|
274
|
+
sys.exit(0)
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
if __name__ == "__main__":
|
|
278
|
+
main()
|