Initial commit: xStream Downloader

This commit is contained in:
2026-07-06 14:27:51 +02:00
commit 103a489985
139 changed files with 4153 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
/home/matthiasberner/Schreibtisch/kodi/xstreamAddon/plugin.video.xstream/resources/art/domain_check.png
+1
View File
@@ -0,0 +1 @@
/home/matthiasberner/Schreibtisch/kodi/xstreamAddon/plugin.video.xstream/resources/art/no_cover.png
+1
View File
@@ -0,0 +1 @@
/home/matthiasberner/Schreibtisch/kodi/xstreamAddon/plugin.video.xstream/resources/art/plugin_info.png
+1
View File
@@ -0,0 +1 @@
/home/matthiasberner/Schreibtisch/kodi/xstreamAddon/plugin.video.xstream/resources/art/resolveurl_settings.png
+1
View File
@@ -0,0 +1 @@
/home/matthiasberner/Schreibtisch/kodi/xstreamAddon/plugin.video.xstream/resources/art/resolveurl_update.png
+1
View File
@@ -0,0 +1 @@
/home/matthiasberner/Schreibtisch/kodi/xstreamAddon/plugin.video.xstream/resources/art/search.png
+1
View File
@@ -0,0 +1 @@
/home/matthiasberner/Schreibtisch/kodi/xstreamAddon/plugin.video.xstream/resources/art/settings.png
+1
View File
@@ -0,0 +1 @@
/home/matthiasberner/Schreibtisch/kodi/xstreamAddon/plugin.video.xstream/resources/art/sites/animetoast.png
+1
View File
@@ -0,0 +1 @@
/home/matthiasberner/Schreibtisch/kodi/xstreamAddon/plugin.video.xstream/resources/art/sites/aniworld.png
+1
View File
@@ -0,0 +1 @@
/home/matthiasberner/Schreibtisch/kodi/xstreamAddon/plugin.video.xstream/resources/art/sites/api_sites.png
+1
View File
@@ -0,0 +1 @@
/home/matthiasberner/Schreibtisch/kodi/xstreamAddon/plugin.video.xstream/resources/art/sites/burningseries.png
+1
View File
@@ -0,0 +1 @@
/home/matthiasberner/Schreibtisch/kodi/xstreamAddon/plugin.video.xstream/resources/art/sites/einschalten.png
+1
View File
@@ -0,0 +1 @@
/home/matthiasberner/Schreibtisch/kodi/xstreamAddon/plugin.video.xstream/resources/art/sites/fhdfilme.png
+1
View File
@@ -0,0 +1 @@
/home/matthiasberner/Schreibtisch/kodi/xstreamAddon/plugin.video.xstream/resources/art/sites/filmpalast.png
+1
View File
@@ -0,0 +1 @@
/home/matthiasberner/Schreibtisch/kodi/xstreamAddon/plugin.video.xstream/resources/art/sites/hdfilme.png
+1
View File
@@ -0,0 +1 @@
/home/matthiasberner/Schreibtisch/kodi/xstreamAddon/plugin.video.xstream/resources/art/sites/internetarchive.png
+1
View File
@@ -0,0 +1 @@
/home/matthiasberner/Schreibtisch/kodi/xstreamAddon/plugin.video.xstream/resources/art/sites/kinoger.png
+1
View File
@@ -0,0 +1 @@
/home/matthiasberner/Schreibtisch/kodi/xstreamAddon/plugin.video.xstream/resources/art/sites/kkiste.png
+1
View File
@@ -0,0 +1 @@
/home/matthiasberner/Schreibtisch/kodi/xstreamAddon/plugin.video.xstream/resources/art/sites/megakino.png
+1
View File
@@ -0,0 +1 @@
/home/matthiasberner/Schreibtisch/kodi/xstreamAddon/plugin.video.xstream/resources/art/sites/moflix-stream.png
+1
View File
@@ -0,0 +1 @@
/home/matthiasberner/Schreibtisch/kodi/xstreamAddon/plugin.video.xstream/resources/art/sites/moviedream.png
+1
View File
@@ -0,0 +1 @@
/home/matthiasberner/Schreibtisch/kodi/xstreamAddon/plugin.video.xstream/resources/art/sites/netzkino.png
+1
View File
@@ -0,0 +1 @@
/home/matthiasberner/Schreibtisch/kodi/xstreamAddon/plugin.video.xstream/resources/art/sites/serienstream.png
+1
View File
@@ -0,0 +1 @@
/home/matthiasberner/Schreibtisch/kodi/xstreamAddon/plugin.video.xstream/resources/art/sites/streamcloud.png
+1
View File
@@ -0,0 +1 @@
/home/matthiasberner/Schreibtisch/kodi/xstreamAddon/plugin.video.xstream/resources/art/sites/tmdb_browser.png
+1
View File
@@ -0,0 +1 @@
/home/matthiasberner/Schreibtisch/kodi/xstreamAddon/plugin.video.xstream/resources/art/sites/topstreamfilm.png
+1
View File
@@ -0,0 +1 @@
/home/matthiasberner/Schreibtisch/kodi/xstreamAddon/plugin.video.xstream/resources/art/tmdb_browser.png
+1
View File
@@ -0,0 +1 @@
/home/matthiasberner/Schreibtisch/kodi/xstreamAddon/plugin.video.xstream/resources/art/vavoo.png
+1
View File
@@ -0,0 +1 @@
/home/matthiasberner/Schreibtisch/kodi/xstreamAddon/plugin.video.xstream/resources/art/xstream_settings.png
+1
View File
@@ -0,0 +1 @@
/home/matthiasberner/Schreibtisch/kodi/xstreamAddon/plugin.video.xstream/resources/lib/__init__.py
+182
View File
@@ -0,0 +1,182 @@
# Fixed tools.py - all string inputs normalized, no Kodi imports
import hashlib, re, os, pyaes, base64
from urllib.parse import quote, unquote, quote_plus, unquote_plus, urlparse
from html.entities import name2codepoint
from difflib import SequenceMatcher
from functools import lru_cache
def _e(s):
if s is None: return ''
if isinstance(s, bytes): return s.decode('utf-8')
if not isinstance(s, str): return str(s)
return s
class cParser:
@staticmethod
def _get_compiled_pattern(pattern, flags=0):
return re.compile(pattern, flags)
@staticmethod
def _replaceSpecialCharacters(s):
try:
for t in (('\\/', '/'), ('&', '&'), ('\\u00c4', 'Ä'), ('\\u00e4', 'ä'),
('\\u00d6', 'Ö'), ('\\u00f6', 'ö'), ('\\u00dc', 'Ü'), ('\\u00fc', 'ü'),
('\\u00df', 'ß'), ('\\u2013', '-'), ('\\u00b2', '²'), ('\\u00b3', '³'),
('\\u00e9', 'é'), ('\\u2018', "'"), ('\\u201e', '"'), ('\\u201c', '"'),
('\\u00c9', 'É'), ('\\u2026', '...'), ('\\u202f', 'h'), ('\\u2019', "'"),
('\\u0308', '̈'), ('\\u00e8', 'è'), ('#038;', ''), ('\\u00f8', 'ø'),
('', '/'), ('\\u00e1', 'á'), ('–', '-'), ('“', '"'), ('„', '"'),
('’', "'"), ('…', ''), ('\\u00bc', '¼'), ('\\u00bd', '½'), ('\\u00be', '¾'),
('\\u2153', ''), ('\\u002A', '*')):
s = s.replace(*t)
for h in (('\\/', '/'), ('&', '&'), (''', "'"), ("'", "'"),
('Ä', 'Ä'), ('ä', 'ä'), ('Ö', 'Ö'), ('ö', 'ö'),
('Ü', 'Ü'), ('ü', 'ü'), ('ß', 'ß'), ('²', '²'),
('Ü', '³'), ('¼', '¼'), ('½', '½'), ('¾', '¾'),
('⅓', ''), ('∗', '*')):
s = s.replace(*h)
except: pass
return s
@staticmethod
def parseSingleResult(sHtmlContent, pattern, ignoreCase=False):
if sHtmlContent:
flags = re.S | re.M
if ignoreCase: flags |= re.I
matches = cParser._get_compiled_pattern(pattern, flags).search(sHtmlContent)
if matches:
if matches.lastindex is not None and matches.lastindex >= 1:
return True, cParser._replaceSpecialCharacters(matches.group(1))
else:
return True, cParser._replaceSpecialCharacters(matches.group(0))
return False, None
@staticmethod
def parse(sHtmlContent, pattern, iMinFoundValue=1, ignoreCase=False):
if sHtmlContent:
flags = re.DOTALL
if ignoreCase: flags |= re.I
aMatches = cParser._get_compiled_pattern(pattern, flags).findall(sHtmlContent)
if len(aMatches) >= iMinFoundValue:
if isinstance(aMatches[0], tuple):
aMatches = [tuple(cParser._replaceSpecialCharacters(x) if isinstance(x, str) and x is not None else '' for x in match) for match in aMatches]
else:
aMatches = [cParser._replaceSpecialCharacters(x) if isinstance(x, str) and x is not None else '' for x in aMatches]
return True, aMatches
return False, None
@staticmethod
def replace(pattern, sReplaceString, sValue):
return cParser._get_compiled_pattern(pattern).sub(sReplaceString, sValue)
@staticmethod
def search(pattern, sValue, ignoreCase=True):
flags = re.IGNORECASE if ignoreCase else 0
return cParser._get_compiled_pattern(pattern, flags).search(sValue)
@staticmethod
def escape(sValue):
return re.escape(sValue)
@staticmethod
def getNumberFromString(sValue):
aMatches = re.compile(r'\d+').findall(_e(sValue))
return int(aMatches[0]) if aMatches else 0
@staticmethod
def urlparse(sUrl):
return urlparse(_e(sUrl).replace('www.', '')).netloc.title()
@staticmethod
def urlDecode(sUrl):
return unquote(_e(sUrl))
@staticmethod
def urlEncode(sUrl, safe=''):
return quote(_e(sUrl), safe)
@staticmethod
def quote(sUrl):
return quote(_e(sUrl))
@staticmethod
def unquotePlus(sUrl):
return unquote_plus(_e(sUrl))
@staticmethod
def quotePlus(sUrl):
return quote_plus(_e(sUrl))
@staticmethod
def B64decode(text):
return base64.b64decode(_e(text)).decode('utf-8')
class cUtil:
@staticmethod
def removeHtmlTags(sValue, sReplace=''):
return re.compile(r'<.*?>').sub(sReplace, sValue)
@staticmethod
def unescape(text):
def fixup(m):
text = m.group(0)
if not text.endswith(';'): text += ';'
if text[:2] == '&#':
try:
if text[:3] == '&#x': return chr(int(text[3:-1], 16))
else: return chr(int(text[2:-1]))
except ValueError: pass
else:
try: return chr(name2codepoint[text[1:-1]])
except KeyError: pass
return text
t = _e(text)
try: t = t.decode('utf-8')
except: pass
return re.compile(r'&(\w+;|#x?\d+;?)').sub(fixup, t.strip())
@staticmethod
def cleanse_text(text):
if text is None: text = ''
return cUtil.removeHtmlTags(text)
@staticmethod
def evp_decode(cipher_text, passphrase, salt=None):
ct = _e(cipher_text).encode('latin-1') if isinstance(cipher_text, str) else cipher_text
pwd = _e(passphrase).encode('latin-1') if isinstance(passphrase, str) else passphrase
if not salt: salt = ct[8:16]; ct = ct[16:]
else: salt = _e(salt).encode('latin-1') if isinstance(salt, str) else salt
key, iv = cUtil.evpKDF(pwd, salt)
decrypter = pyaes.Decrypter(pyaes.AESModeOfOperationCBC(key, iv))
plain_text = decrypter.feed(ct)
plain_text += decrypter.feed()
return plain_text.decode("utf-8")
@staticmethod
def evpKDF(pwd, salt, key_size=32, iv_size=16):
temp = b''
fd = b''
while len(fd) < key_size + iv_size:
h = hashlib.md5()
h.update(temp + pwd + salt)
temp = h.digest()
fd += temp
return fd[0:key_size], fd[key_size:key_size + iv_size]
@staticmethod
def isSimilar(sSearch, sText, threshold=0.9):
return SequenceMatcher(None, _e(sSearch), _e(sText)).ratio() >= threshold
@staticmethod
@lru_cache(maxsize=200000)
def get_seq_match_ratio(token1, token2):
return SequenceMatcher(None, _e(token1), _e(token2)).ratio()
@staticmethod
def isSimilarByToken(sSearch, sText, threshold=0.9):
tokens_sSearch = _e(sSearch).split()
tokens_sText = _e(sText).split()
if not tokens_sSearch: return False
best_ratios = [max(cUtil.get_seq_match_ratio(token, token2) for token2 in tokens_sText) for token in tokens_sSearch]
return (sum(best_ratios) / len(best_ratios)) >= threshold
+1
View File
@@ -0,0 +1 @@
/home/matthiasberner/Schreibtisch/kodi/xstreamAddon/plugin.video.xstream/resources/lib/cache.py
+1
View File
@@ -0,0 +1 @@
/home/matthiasberner/Schreibtisch/kodi/xstreamAddon/plugin.video.xstream/resources/lib/captcha/captcha_helper.py
+1
View File
@@ -0,0 +1 @@
/home/matthiasberner/Schreibtisch/kodi/xstreamAddon/plugin.video.xstream/resources/lib/captcha/captcha_solver.py
+1
View File
@@ -0,0 +1 @@
/home/matthiasberner/Schreibtisch/kodi/xstreamAddon/plugin.video.xstream/resources/lib/config.py
+1
View File
@@ -0,0 +1 @@
/home/matthiasberner/Schreibtisch/kodi/xstreamAddon/plugin.video.xstream/resources/lib/gui/__init__.py
+1
View File
@@ -0,0 +1 @@
/home/matthiasberner/Schreibtisch/kodi/xstreamAddon/plugin.video.xstream/resources/lib/gui/contextElement.py
+1
View File
@@ -0,0 +1 @@
/home/matthiasberner/Schreibtisch/kodi/xstreamAddon/plugin.video.xstream/resources/lib/gui/gui.py
+1
View File
@@ -0,0 +1 @@
/home/matthiasberner/Schreibtisch/kodi/xstreamAddon/plugin.video.xstream/resources/lib/gui/guiElement.py
+1
View File
@@ -0,0 +1 @@
/home/matthiasberner/Schreibtisch/kodi/xstreamAddon/plugin.video.xstream/resources/lib/gui/hoster.py
+1
View File
@@ -0,0 +1 @@
/home/matthiasberner/Schreibtisch/kodi/xstreamAddon/plugin.video.xstream/resources/lib/handler/ParameterHandler.py
+1
View File
@@ -0,0 +1 @@
/home/matthiasberner/Schreibtisch/kodi/xstreamAddon/plugin.video.xstream/resources/lib/handler/__init__.py
@@ -0,0 +1,193 @@
# Echte xStream-Handler (Standard-Lib only)
import re
from urllib.parse import quote, urlencode
import gzip, ssl
from urllib.request import Request, urlopen, HTTPCookieProcessor, build_opener, HTTPSHandler
from urllib.error import HTTPError, URLError
from urllib.parse import urlparse
class _FakeConfig:
_settings = {
'cacheTime': '1', 'requestTimeout': '15',
'bypassDNSlock': 'false', 'volatileHtmlCache': 'false',
}
def getAddonInfo(self, k):
return {'profile': '/tmp', 'id': 'xstream', 'name': 'xStream',
'version': '1.0.0', 'icon': '', 'author': '', 'description': ''}.get(k, '')
def getSetting(self, k): return self._settings.get(k, '')
def setSetting(self, k, v): self._settings[k] = str(v)
def getLocalizedString(self, k): return f'[{k}]'
class _FakeCache:
def get(self, key, cache_time): return None
def set(self, key, data): pass
def clear(self): pass
class _FakeLogger:
def info(self, *a, **k): pass
def debug(self, *a, **k): pass
def error(self, *a, **k): pass
def _infoDialog(*a, **k): pass
class cRequestHandler:
persistent_openers = {}
UA_LIST = [
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:152.0) Gecko/20100101 Firefox/152.0',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.0.0 Safari/537.36',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.5 Safari/605.1.15',
]
def __init__(self, sUrl='', caching=True, ignoreErrors=False, method='GET',
data=None, compression=True, jspost=False, ssl_verify=False):
self._sUrl = str(sUrl) if sUrl else ''
self._sRealUrl = ''
self._USER_AGENT = self.UA_LIST[0]
self._aParameters = {}
self._headerEntries = {}
self._Status = ''
self._sResponseHeader = ''
self._ssl_verify = ssl_verify
self.caching = caching
self.method = method
self.data = data
self.ignoreErrors = ignoreErrors
self.compression = compression
self.jspost = jspost
self.requestTimeout = 15
self.__bRemoveNewLines = True
self.__bRemoveBreakLines = True
self.isMemoryCacheActive = False
self.__setDefaultHeader()
def __setDefaultHeader(self):
self.addHeaderEntry('User-Agent', self._USER_AGENT)
self.addHeaderEntry('Accept', '*/*')
self.addHeaderEntry('Accept-Language', 'en-US,en;q=0.5')
if self.compression:
self.addHeaderEntry('Accept-Encoding', 'gzip, deflate')
def removeNewLines(self, b): self.__bRemoveNewLines = b
def removeBreakLines(self, b): self.__bRemoveBreakLines = b
def addHeaderEntry(self, k, v): self._headerEntries[k] = v
def addParameters(self, k, v, Quote=False):
self._aParameters[k] = v if not Quote else quote(str(v))
def getHeaderEntry(self, k): return self._headerEntries.get(k)
def getResponseHeader(self): return self._sResponseHeader
def getRealUrl(self): return self._sRealUrl or self._sUrl
def getStatus(self): return self._Status
def getUrl(self): return self._sUrl
def setUrl(self, url):
self._sUrl = str(url)
return self
def ignoreDiscard(self, v): pass
def ignoreExpired(self, v): pass
def getHtml(self, encoding='utf-8'):
if not self._sUrl or self._sUrl in ('False', 'None', ''):
return ''
try:
url = self._sUrl
if self._aParameters and self.method == 'GET':
sep = '&' if '?' in url else '?'
url = url + sep + urlencode(self._aParameters)
req = Request(url, data=self.data, method=self.method)
for k, v in self._headerEntries.items():
try: req.add_header(k, v)
except: pass
ctx = None
if self._ssl_verify is False:
ctx = ssl._create_unverified_context()
opener = build_opener(HTTPCookieProcessor())
with opener.open(req, timeout=self.requestTimeout) as resp:
self._sRealUrl = resp.geturl()
self._sResponseHeader = str(resp.headers)
data = resp.read()
if self.compression and 'gzip' in resp.headers.get('Content-Encoding', ''):
try: data = gzip.decompress(data)
except: pass
text = data.decode(encoding, errors='replace')
if self.__bRemoveBreakLines:
text = re.sub(r'[\r\n]+', '\n', text)
if self.__bRemoveNewLines:
text = re.sub(r'\n+', ' ', text)
return text
except HTTPError as e:
self._Status = str(e.code)
return f'<!-- HTTPError {e.code} -->' if not self.ignoreErrors else ''
except URLError as e:
self._Status = str(e.reason)
return f'<!-- URLError {e.reason} -->' if not self.ignoreErrors else ''
except Exception as e:
self._Status = str(e)
return f'<!-- Error {e} -->' if not self.ignoreErrors else ''
def getJson(self):
import json
text = self.getHtml()
if text.strip().startswith('<!--'): return {}
try: return json.loads(text)
except: return {}
class cParser:
@staticmethod
def urlEncode(params):
if isinstance(params, dict): return urlencode(params)
return quote(str(params) if params else '')
@staticmethod
def parse(source, pattern, flags=0):
return re.findall(pattern, source, flags)
@staticmethod
def replace(pattern, replacement, source, count=0):
return re.sub(pattern, replacement, source, count)
@staticmethod
def searchSingle(result, searchPattern, outputParam=None, source=None):
text = source if source is not None else (str(result) if result else '')
match = re.search(searchPattern, text, re.S)
if not match: return None
return match.group(outputParam) if outputParam and outputParam <= match.lastindex else match.group(0)
@staticmethod
def htmlParse(html):
if not html: return []
text = re.sub(r'<script[^>]*>.*?</script>', '', html, flags=re.S|re.I)
text = re.sub(r'<style[^>]*>.*?</style>', '', text, flags=re.S|re.I)
text = re.sub(r'<[^>]+>', ' ', text)
text = re.sub(r'\s+', ' ', text).strip()
return [t.strip() for t in text.split(' ') if t.strip()]
class ParameterHandler:
def __init__(self, url=''):
self._params = {}
if url and '?' in url:
_, qs = url.split('?', 1)
for part in qs.split('&'):
if '=' in part:
k, v = part.split('=', 1)
self._params[k] = v
def getValue(self, key='', default=''):
return self._params.get(key, default)
def setParam(self, key, value):
self._params[str(key)] = str(value)
def getAll(self):
return self._params
+1
View File
@@ -0,0 +1 @@
/home/matthiasberner/Schreibtisch/kodi/xstreamAddon/plugin.video.xstream/resources/lib/handler/pluginHandler.py
@@ -0,0 +1,495 @@
# -*- coding: utf-8 -*-
# Python 3
import time
import xbmcgui
import re
import os
import hashlib
import json
import traceback
import ssl
import certifi
import socket
import zlib
from resources.lib.config import cConfig
from resources.lib.logger import logger
from resources.lib.cache import cCache
from resources.lib.tools import infoDialog
from xbmcvfs import translatePath
from urllib.parse import quote, urlencode, urlparse, quote_plus
from urllib.error import HTTPError, URLError
from urllib.request import HTTPHandler, HTTPSHandler, Request, HTTPCookieProcessor, build_opener, urlopen, HTTPRedirectHandler
from http.cookiejar import LWPCookieJar, Cookie
from http.client import HTTPException
from random import choice
from contextlib import contextmanager
@contextmanager
def _doh_resolution(hostname, ip):
"""DNS-Bypass via getaddrinfo-Patch: loest 'hostname' temporaer auf 'ip' auf.
Anders als ein direkter IP-Connect bleibt das SSL-Zertifikat gueltig, weil die
Verbindung weiterhin den echten Hostnamen kennt (SNI + Cert-Hostname stimmen).
Patch ist eng gekapselt und wird im finally IMMER zurueckgesetzt."""
if not ip:
yield
return
_orig_getaddrinfo = socket.getaddrinfo
def _patched(host, *args, **kwargs):
if host == hostname:
return _orig_getaddrinfo(ip, *args, **kwargs)
return _orig_getaddrinfo(host, *args, **kwargs)
socket.getaddrinfo = _patched
try:
yield
finally:
socket.getaddrinfo = _orig_getaddrinfo
class RedirectFilter(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, hdrs, newurl):
if cConfig().getSetting('bypassDNSlock', 'false') != 'true':
if 'notice.cuii' in newurl:
xbmcgui.Dialog().ok(cConfig().getLocalizedString(30265), cConfig().getLocalizedString(30260) + '\n' + cConfig().getLocalizedString(30261))
return None
return HTTPRedirectHandler.redirect_request(self, req, fp, code, msg, hdrs, newurl)
class cRequestHandler:
# useful for e.g. tmdb request where multiple requests are made within a loop
persistent_openers = {}
@staticmethod
def RandomUA():
# Random User Agents aktualisiert 01.07.2026 (Chrome 150 Stable ab 30.06.2026)
FF_USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:152.0) Gecko/20100101 Firefox/152.0'
OPERA_USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.0.0 Safari/537.36 OPR/133.0.0.0'
EDGE_USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.0.0 Safari/537.36 Edg/150.0.0.0'
CHROME_USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.0.0 Safari/537.36'
SAFARI_USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.5 Safari/605.1.15'
_User_Agents = [FF_USER_AGENT, OPERA_USER_AGENT, EDGE_USER_AGENT, CHROME_USER_AGENT, SAFARI_USER_AGENT]
return choice(_User_Agents)
def __init__(self, sUrl, caching=True, ignoreErrors=False, method='GET', data=None, compression=True, jspost=False, ssl_verify=False):
self._sUrl = self.__cleanupUrl(sUrl)
self._sRealUrl = ''
self._USER_AGENT = self.RandomUA()
self._aParameters = {}
self._headerEntries = {}
self._profilePath = translatePath(cConfig().getAddonInfo('profile'))
self._cachePath = ''
self._cookiePath = ''
self._Status = ''
self._sResponseHeader = ''
self._ssl_verify = ssl_verify
self.ignoreDiscard(False)
self.ignoreExpired(False)
self.caching = caching
self.method = method
self.data = data
self.ignoreErrors = ignoreErrors
self.compression = compression
self.jspost = jspost
self.cacheTime = int(cConfig().getSetting('cacheTime', 1)) * 3600 # Stunden * 3600 = Sekunden
self.requestTimeout = int(cConfig().getSetting('requestTimeout', 10))
self.bypassDNSlock = (cConfig().getSetting('bypassDNSlock', 'false') == 'true')
self.removeBreakLines(True)
self.removeNewLines(True)
self.__setDefaultHeader()
self.__setCachePath()
self.__setCookiePath()
self.isMemoryCacheActive = (cConfig().getSetting('volatileHtmlCache', 'false') == 'true')
if self.isMemoryCacheActive:
self._memCache = cCache()
socket.setdefaulttimeout(self.requestTimeout)
def getStatus(self):
return self._Status
def removeNewLines(self, bRemoveNewLines):
self.__bRemoveNewLines = bRemoveNewLines
def removeBreakLines(self, bRemoveBreakLines):
self.__bRemoveBreakLines = bRemoveBreakLines
def addHeaderEntry(self, sHeaderKey, sHeaderValue):
self._headerEntries[sHeaderKey] = sHeaderValue
def getHeaderEntry(self, sHeaderKey):
if sHeaderKey in self._headerEntries:
return self._headerEntries[sHeaderKey]
def addParameters(self, key, value, Quote=False):
self._aParameters[key] = value if not Quote else quote(str(value))
def getResponseHeader(self):
return self._sResponseHeader
def getRealUrl(self):
return self._sRealUrl
def getRequestUri(self):
return self._sUrl + '?' + urlencode(self._aParameters)
def __setDefaultHeader(self):
self.addHeaderEntry('User-Agent', self._USER_AGENT)
self.addHeaderEntry('Accept-Language', 'de,en-US;q=0.7,en;q=0.3')
self.addHeaderEntry('Accept', 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8')
if self.compression:
self.addHeaderEntry('Accept-Encoding', 'gzip, deflate')
self.addHeaderEntry('Connection', 'keep-alive')
self.addHeaderEntry('Keep-Alive', 'timeout=5')
@staticmethod
def __getDefaultHandler(ssl_verify):
if ssl_verify:
ssl_context = ssl.create_default_context(cafile=certifi.where())
ssl_context.check_hostname = True
ssl_context.verify_mode = ssl.CERT_REQUIRED
return [HTTPSHandler(context=ssl_context)]
else:
ssl_context = ssl.create_default_context()
ssl_context.check_hostname = False
ssl_context.verify_mode = ssl.CERT_NONE
return [HTTPSHandler(context=ssl_context)]
@staticmethod
def __cleanupUrl(url):
if not isinstance(url, str):
return ''
if not isinstance(url, str):
return ''
#p = urlparse(url)
#if p.query:
# query = quote_plus(p.query).replace('%3D', '=').replace('%26', '&')
# p = p._replace(query=p.query.replace(p.query, query))
#else:
# path = quote_plus(p.path).replace('%2F', '/').replace('%26', '&').replace('%3D', '=')
# p = p._replace(path=p.path.replace(p.path, path))
#return p.geturl()
return url
def request(self):
if self.caching and self.cacheTime > 0 and self.method == 'GET' and self.data is None:
if self.isMemoryCacheActive:
sContent = self.__readVolatileCache(self.getRequestUri(), self.cacheTime)
else:
sContent = self.__readPersistentCache(self.getRequestUri())
if sContent:
self._Status = '200'
return sContent
else:
logger.info('-> [requestHandler]: read html for %s' % self.getRequestUri())
# DNS-Bypass global ueber Setting (kein per-Site-Flag mehr)
if self.bypassDNSlock:
ip_override = self.__doh_request(self._sUrl)
else:
ip_override = None
_doh_host = urlparse(self._sUrl).hostname
cookieJar = LWPCookieJar(filename=self._cookiePath)
try:
cookieJar.load(ignore_discard=self.__bIgnoreDiscard, ignore_expires=self.__bIgnoreExpired)
except Exception as e:
logger.debug(e)
domain = urlparse(self._sUrl).netloc
if domain in cRequestHandler.persistent_openers:
opener = cRequestHandler.persistent_openers[domain]
else:
handlers = self.__getDefaultHandler(self._ssl_verify)
handlers += [HTTPHandler(), HTTPCookieProcessor(cookiejar=cookieJar), RedirectFilter()]
opener = build_opener(*handlers)
cRequestHandler.persistent_openers[domain] = opener
# Prepare parameters for GET/POST
if self.method == 'POST':
if self.data is not None:
if isinstance(self.data, dict):
# Default: form data
sParameters = urlencode(self.data).encode()
elif isinstance(self.data, str):
sParameters = self.data.encode()
else:
sParameters = self.data
else:
sParameters = None
else:
sParameters = json.dumps(self._aParameters).encode() if self.jspost else urlencode(self._aParameters, True).encode()
if len(sParameters) == 0:
sParameters = None
oRequest = Request(self._sUrl, sParameters if sParameters and len(sParameters) > 0 else None)
for key, value in self._headerEntries.items():
oRequest.add_header(key, value)
if self.method == 'POST' and 'Content-Type' not in self._headerEntries:
oRequest.add_header('Content-Type', 'application/x-www-form-urlencoded')
elif self.jspost:
oRequest.add_header('Content-Type', 'application/json')
cookieJar.add_cookie_header(oRequest)
try:
with _doh_resolution(_doh_host, ip_override):
oResponse = opener.open(oRequest)
except HTTPError as e:
if e.code >= 400:
self._Status = str(e.code)
data = e.fp.read()
if 'DDOS-GUARD' in str(data):
opener = build_opener(HTTPCookieProcessor(cookieJar))
opener.addheaders = [('User-agent', self._USER_AGENT), ('Referer', self._sUrl)]
response = opener.open('https://check.ddos-guard.net/check.js')
content = response.read().decode('utf-8', 'replace')
url2 = re.findall("Image.*?'([^']+)'; new", content)
url3 = urlparse(self._sUrl)
url3 = '%s://%s/%s' % (url3.scheme, url3.netloc, url2[0])
opener = build_opener(HTTPCookieProcessor(cookieJar))
opener.addheaders = [('User-agent', self._USER_AGENT), ('Referer', self._sUrl)]
opener.open(url3).read()
opener = build_opener(HTTPCookieProcessor(cookieJar))
opener.addheaders = [('User-agent', self._USER_AGENT), ('Referer', self._sUrl)]
oResponse = opener.open(self._sUrl, sParameters if len(sParameters) > 0 else None)
if not oResponse:
logger.error(' -> [requestHandler]: Failed DDOS-GUARD active: ' + self._sUrl)
return 'DDOS GUARD SCHUTZ'
elif 'cloudflare' in str(e.headers):
if not self.ignoreErrors:
infoDialog(cConfig().getLocalizedString(30829), icon='WARNING', time=10000)
logger.error(' -> [requestHandler]: Failed Cloudflare active: ' + self._sUrl)
return 'CLOUDFLARE-SCHUTZ AKTIV' # Meldung geht als "e.doc" in die exception nach default.py
else:
if not self.ignoreErrors:
xbmcgui.Dialog().ok('xStream', cConfig().getLocalizedString(30259) + ' {0} {1}'.format(self._sUrl, str(e)))
logger.error(' -> [requestHandler]: HTTPError ' + str(e) + ' Url: ' + self._sUrl)
return 'SEITE NICHT ERREICHBAR'
else:
if not self.ignoreErrors:
xbmcgui.Dialog().ok('xStream', cConfig().getLocalizedString(30259) + ' {0} {1}'.format(self._sUrl, str(e)))
logger.error(' -> [requestHandler]: HTTPError ' + str(e) + ' Url: ' + self._sUrl)
return 'SEITE NICHT ERREICHBAR'
except URLError as e:
if not self.ignoreErrors:
xbmcgui.Dialog().ok('xStream', str(e.reason))
logger.error(' -> [requestHandler]: URLError ' + str(e.reason) + ' Url: ' + self._sUrl)
return 'URL FEHLER'
except HTTPException as e:
if not self.ignoreErrors:
xbmcgui.Dialog().ok('xStream', str(e))
logger.error(' -> [requestHandler]: HTTPException ' + str(e) + ' Url: ' + self._sUrl)
return 'TIMEOUT'
self._sResponseHeader = oResponse.info()
content_encoding = self._sResponseHeader.get('Content-Encoding', '').lower()
if content_encoding:
raw_content = oResponse.read()
if content_encoding == 'gzip':
decompressed = zlib.decompress(raw_content, wbits=zlib.MAX_WBITS | 16)
elif content_encoding == 'deflate':
decompressed = zlib.decompress(raw_content, wbits=-zlib.MAX_WBITS)
else:
decompressed = raw_content
sContent = decompressed.decode('utf-8', 'replace')
else:
sContent = oResponse.read().decode('utf-8', 'replace')
if 'lazingfast' in sContent:
bf = cBF().resolve(self._sUrl, sContent, cookieJar, self._USER_AGENT, sParameters)
if bf:
sContent = bf
else:
logger.error(' -> [requestHandler]: Failed Blazingfast active: ' + self._sUrl)
try:
cookieJar.save(ignore_discard=self.__bIgnoreDiscard, ignore_expires=self.__bIgnoreExpired)
except Exception as e:
logger.error(' -> [requestHandler]: Failed save cookie: %s' % e)
self._sRealUrl = oResponse.geturl()
self._Status = oResponse.getcode() if self._sUrl == self._sRealUrl else '301'
if self.__bRemoveNewLines:
sContent = sContent.replace('\n', '').replace('\r\t', '')
if self.__bRemoveBreakLines:
sContent = sContent.replace('&nbsp;', '')
if self.caching and self.cacheTime > 0 and self.method == 'GET' and self.data is None:
if self.isMemoryCacheActive:
self.__writeVolatileCache(self.getRequestUri(), sContent)
else:
self.__writePersistentCache(self.getRequestUri(), sContent)
return sContent
def __setCookiePath(self):
cookieFile = os.path.join(self._profilePath, 'cookies')
if not os.path.exists(cookieFile):
os.makedirs(cookieFile)
if 'dummy' not in self._sUrl:
cookieFile = os.path.join(cookieFile, urlparse(self._sUrl).netloc.replace('.', '_') + '.txt')
if not os.path.exists(cookieFile):
open(cookieFile, 'w').close()
self._cookiePath = cookieFile
def getCookie(self, sCookieName, sDomain=''):
cookieJar = LWPCookieJar()
try:
cookieJar.load(self._cookiePath, self.__bIgnoreDiscard, self.__bIgnoreExpired)
except Exception as e:
logger.error(e)
for entry in cookieJar:
if entry.name == sCookieName:
if sDomain == '':
return entry
elif entry.domain == sDomain:
return entry
return False
def setCookie(self, oCookie):
cookieJar = LWPCookieJar()
try:
cookieJar.load(self._cookiePath, self.__bIgnoreDiscard, self.__bIgnoreExpired)
cookieJar.set_cookie(oCookie)
cookieJar.save(self._cookiePath, self.__bIgnoreDiscard, self.__bIgnoreExpired)
except Exception as e:
logger.error(e)
def ignoreDiscard(self, bIgnoreDiscard):
self.__bIgnoreDiscard = bIgnoreDiscard
def ignoreExpired(self, bIgnoreExpired):
self.__bIgnoreExpired = bIgnoreExpired
def __doh_request(self, url, doh_server="https://cloudflare-dns.com/dns-query"):
# Parse the URL
parsed_url = urlparse(url)
hostname = parsed_url.hostname
# Bewusst NICHT gecacht: DDoS-Guard-Edges rotieren, eine im RAM-Cache
# festgehaltene IP koennte innerhalb der TTL sterben -> immer frisch aufloesen.
params = urlencode({"name": hostname, "type": "A"})
doh_url = f"{doh_server}?{params}"
req = Request(doh_url)
req.add_header("Accept", "application/dns-json")
try:
response = urlopen(req, timeout=5)
response_text = response.read().decode("utf-8", "replace")
dns_response = json.loads(response_text)
if "Answer" not in dns_response:
raise Exception("Invalid DNS response")
# Ersten echten A-Record (type 1) aus der Answer-Liste nehmen statt stur [0]:
# bei CNAME-Ketten kann die IP erst weiter hinten stehen, [0] waere dann der CNAME.
ip_address = next((a["data"] for a in dns_response["Answer"] if a.get("type") == 1), None)
if not ip_address:
raise Exception("No A record in DNS answer")
return ip_address
except Exception as e:
logger.error(' -> [requestHandler]: DNS query failed: %s' % e)
return None
def __setCachePath(self):
cache = os.path.join(self._profilePath, 'htmlcache')
if not os.path.exists(cache):
os.makedirs(cache)
self._cachePath = cache
def __readPersistentCache(self, url):
h = hashlib.md5(url.encode('utf8')).hexdigest()
cacheFile = os.path.join(self._cachePath, h)
fileAge = self.getFileAge(cacheFile)
if 0 < fileAge < self.cacheTime:
try:
with open(cacheFile, 'rb') as f:
content = f.read().decode('utf8')
except Exception:
logger.error(' -> [requestHandler]: Could not read Cache')
if content:
logger.info(' -> [requestHandler]: read html for %s from cache' % url)
return content
return None
def __writePersistentCache(self, url, content):
try:
h = hashlib.md5(url.encode('utf8')).hexdigest()
with open(os.path.join(self._cachePath, h), 'wb') as f:
f.write(content.encode('utf8'))
except Exception:
logger.error(' -> [requestHandler]: Could not write Cache')
def __writeVolatileCache(self, url, content):
self._memCache.set(hashlib.md5(url.encode('utf8')).hexdigest(), content)
def __readVolatileCache(self, url, cache_time):
entry = self._memCache.get(hashlib.md5(url.encode('utf8')).hexdigest(), cache_time)
if entry:
logger.info('-> [requestHandler]: read html for %s from cache' % url)
return entry
@staticmethod
def getFileAge(cacheFile):
try:
return time.time() - os.stat(cacheFile).st_mtime
except Exception:
return 0
def clearCache(self, silent=False):
# clear volatile cache
if self.isMemoryCacheActive:
self._memCache.clear()
cRequestHandler.persistent_openers.clear()
# clear persistent cache
files = os.listdir(self._cachePath)
for file in files:
os.remove(os.path.join(self._cachePath, file))
if not silent and files:
infoDialog(cConfig().getLocalizedString(30405), icon='INFO')
class cBF:
def resolve(self, url, html, cookie_jar, user_agent, sParameters):
page = urlparse(url).scheme + '://' + urlparse(url).netloc
j = re.compile('<script[^>]src="([^"]+)').findall(html)
if j:
opener = build_opener(HTTPCookieProcessor(cookie_jar))
opener.addheaders = [('User-agent', user_agent), ('Referer', url)]
opener.open(page + j[0])
a = re.compile(r'xhr\.open\("GET","([^,]+)",').findall(html)
if a:
import random
aespage = page + a[0].replace('" + ww +"', str(random.randint(700, 1500)))
opener = build_opener(HTTPCookieProcessor(cookie_jar))
opener.addheaders = [('User-agent', user_agent), ('Referer', url)]
html = opener.open(aespage).read().decode('utf-8', 'replace')
cval = self.aes_decode(html)
cdata = re.compile('cookie="([^="]+).*?domain[^>]=([^;]+)').findall(html)
if cval and cdata:
c = Cookie(version=0, name=cdata[0][0], value=cval, port=None, port_specified=False, domain=cdata[0][1], domain_specified=True, domain_initial_dot=False, path="/", path_specified=True, secure=False, expires=time.time() + 21600, discard=False, comment=None, comment_url=None, rest={})
cookie_jar.set_cookie(c)
opener = build_opener(HTTPCookieProcessor(cookie_jar))
opener.addheaders = [('User-agent', user_agent), ('Referer', url)]
return opener.open(url, sParameters if len(sParameters) > 0 else None).read().decode('utf-8', 'replace')
@staticmethod
def aes_decode(html):
try:
import pyaes
keys = re.compile(r'toNumbers\("([^"]+)"').findall(html)
if keys:
from binascii import hexlify, unhexlify
msg = unhexlify(keys[2])
key = unhexlify(keys[0])
iv = unhexlify(keys[1])
decrypter = pyaes.Decrypter(pyaes.AESModeOfOperationCBC(key, iv))
plain_text = decrypter.feed(msg)
plain_text += decrypter.feed()
return hexlify(plain_text).decode()
except Exception as e:
logger.error(e)
+1
View File
@@ -0,0 +1 @@
/home/matthiasberner/Schreibtisch/kodi/xstreamAddon/plugin.video.xstream/resources/lib/jsunpacker.py
+1
View File
@@ -0,0 +1 @@
/home/matthiasberner/Schreibtisch/kodi/xstreamAddon/plugin.video.xstream/resources/lib/logger.py
+1
View File
@@ -0,0 +1 @@
/home/matthiasberner/Schreibtisch/kodi/xstreamAddon/plugin.video.xstream/resources/lib/player.py
+1
View File
@@ -0,0 +1 @@
/home/matthiasberner/Schreibtisch/kodi/xstreamAddon/plugin.video.xstream/resources/lib/tmdb.py
+1
View File
@@ -0,0 +1 @@
/home/matthiasberner/Schreibtisch/kodi/xstreamAddon/plugin.video.xstream/resources/lib/tmdbinfo.py
+1
View File
@@ -0,0 +1 @@
/home/matthiasberner/Schreibtisch/kodi/xstreamAddon/plugin.video.xstream/resources/lib/tools.py
+1
View File
@@ -0,0 +1 @@
/home/matthiasberner/Schreibtisch/kodi/xstreamAddon/plugin.video.xstream/resources/lib/trailer.py
+1
View File
@@ -0,0 +1 @@
/home/matthiasberner/Schreibtisch/kodi/xstreamAddon/plugin.video.xstream/resources/lib/updateManager.py
+1
View File
@@ -0,0 +1 @@
/home/matthiasberner/Schreibtisch/kodi/xstreamAddon/plugin.video.xstream/resources/lib/utils.py
+1
View File
@@ -0,0 +1 @@
/home/matthiasberner/Schreibtisch/kodi/xstreamAddon/plugin.video.xstream/resources/lib/vavoo/__init__.py
+1
View File
@@ -0,0 +1 @@
/home/matthiasberner/Schreibtisch/kodi/xstreamAddon/plugin.video.xstream/resources/lib/vavoo/auth.py
+1
View File
@@ -0,0 +1 @@
/home/matthiasberner/Schreibtisch/kodi/xstreamAddon/plugin.video.xstream/resources/lib/vavoo/vavoo.py
+1
View File
@@ -0,0 +1 @@
/home/matthiasberner/Schreibtisch/kodi/xstreamAddon/plugin.video.xstream/resources/lib/wrappers/meinecloud.py
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+1
View File
@@ -0,0 +1 @@
/home/matthiasberner/Schreibtisch/kodi/xstreamAddon/plugin.video.xstream/sites/animetoast.py
+1
View File
@@ -0,0 +1 @@
/home/matthiasberner/Schreibtisch/kodi/xstreamAddon/plugin.video.xstream/sites/aniworld.py
+1
View File
@@ -0,0 +1 @@
/home/matthiasberner/Schreibtisch/kodi/xstreamAddon/plugin.video.xstream/sites/api_sites.py

Some files were not shown because too many files have changed in this diff Show More