Files
xstreamDownloader/do_fix.py
T

183 lines
8.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/bash
BASE=/home/matthiasberner/Schreibtisch/kodi/xstreamDownloader
# 1. Fix tools.py - replace with fixed version
cat > "$BASE/xstream/resources/lib/tools.py" << 'ENDOFFILE'
# tools.py - Fixed: all string inputs normalized via _e()
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
def platform(): return 'Linux'
def changelog(): pass
def textBox(heading, announce): pass
def infoDialog(message, heading='', icon='', time=5000, sound=False): pass
class cParser:
@staticmethod
def _get_compiled_pattern(pattern, flags=0):
return re.compile(pattern, flags)
@staticmethod
def _replaceSpecialCharacters(s):
try:
for t in (('\\/', '/'), ('&amp;', '&'), ('\\u00c4', 'Ä'), ('\\u00e4', 'ä'),
('\\u00d6', 'Ö'), ('\\u00f6', 'ö'), ('\\u00dc', 'Ü'), ('\\u00fc', 'ü'),
('\\u00df', 'ß'), ('\\u2013', '-'), ('\\u00b2', '²'), ('\\u00b3', '³'),
('\\u00e9', 'é'), ('\\u2018', "'"), ('\\u201e', '"'), ('\\u201c', '"'),
('\\u00c9', 'É'), ('\\u2026', '...'), ('\\u202f', 'h'), ('\\u2019', "'"),
('\\u0308', '̈'), ('\\u00e8', 'è'), ('#038;', ''), ('\\u00f8', 'ø'),
('', '/'), ('\\u00e1', 'á'), ('&#8211;', '-'), ('&#8220;', '"'), ('&#8222;', '"'),
('&#8217;', "'"), ('&#8230;', '…'), ('\\u00bc', '¼'), ('\\u00bd', '½'), ('\\u00be', '¾'),
('\\u2153', '⅓'), ('\\u002A', '*')):
s = s.replace(*t)
for h in (('\\/', '/'), ('&#x26;', '&'), ('&#039;', "'"), ("&#39;", "'"),
('&#xC4;', 'Ä'), ('&#xE4;', 'ä'), ('&#xD6;', 'Ö'), ('&#xF6;', 'ö'),
('&#xDC;', 'Ü'), ('&#xFC;', 'ü'), ('&#xDF;', 'ß'), ('&#xB2;', '²'),
('&#xDC;', '³'), ('&#xBC;', '¼'), ('&#xBD;', '½'), ('&#xBE;', '¾'),
('&#8531;', '⅓'), ('&#8727;', '*')):
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(_e(sHtmlContent))
if matches:
if matches.lastindex is not None and matches.lastindex >= 1:
return True, cParser._replaceSpecialCharacters(matches.group(1))
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(_e(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, _e(sValue))
@staticmethod
def search(pattern, sValue, ignoreCase=True):
flags = re.IGNORECASE if ignoreCase else 0
return cParser._get_compiled_pattern(pattern, flags).search(_e(sValue))
@staticmethod
def escape(sValue): return re.escape(_e(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, _e(sValue))
@staticmethod
def unescape(text):
def fixup(m):
t = m.group(0)
if not t.endswith(';'): t += ';'
if t[:2] == '&#':
try:
if t[:3] == '&#x': return chr(int(t[3:-1], 16))
return chr(int(t[2:-1]))
except ValueError: pass
else:
try: return chr(name2codepoint[t[1:-1]])
except KeyError: pass
return t
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): return cUtil.removeHtmlTags(text if text else '')
@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) + 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
ENDOFFILE
echo "tools.py written"
# 2. Fix requestHandler.py
python3 << 'PYEOF'
with open('/home/matthiasberner/Schreibtisch/kodi/xstreamDownloader/xstream/resources/lib/handler/requestHandler.py') as f:
c = f.read()
old = ' self._sUrl = self.__cleanupUrl(sUrl)'
new = ' if not isinstance(sUrl, str): sUrl = ""\n self._sUrl = self.__cleanupUrl(sUrl)'
if old in c and 'not isinstance' not in c:
c = c.replace(old, new)
with open('/home/matthiasberner/Schreibtisch/kodi/xstreamDownloader/xstream/resources/lib/handler/requestHandler.py', 'w') as f:
f.write(c)
print('requestHandler fixed')
else:
print('requestHandler already fixed or pattern missing')
PYEOF
# 3. Kill and restart server
fuser -k 8765/tcp 2>/dev/null
sleep 1
cd /home/matthiasberner/Schreibtisch/kodi/xstreamDownloader
python3 mini_app.py >> /tmp/server.log 2>&1 &
sleep 3
echo "=== Test ==="
curl -s http://127.0.0.1:8765/api/sites | python3 -c "import sys,json; d=json.load(sys.stdin); print(f'Sites OK: {len(d)}')" 2>&1
curl -s http://127.0.0.1:8765/api/sites/hdfilme/entries | python3 -c "import sys,json; d=json.load(sys.stdin); print(f'Entries: {len(d)}')" 2>&1
tail -8 /tmp/server.log