Today I started reporting the error again: "Error on Download"
Is this the case for everyone? Or is it just me?
I looked at the output and it seems that there was an error in obtaining pkey three
i didn't know when it's was fixed!
It's true that the error occurred when getting the pkey. I referred to another author's method to get the pkey and solved it, but I don't know how to send the code. The easiest way is to copy the pkey parameter on the m3u8 to stripchat.py and replace the corresponding parameter.
Please explain more or provide code i didn't understand . please sir
I’ve done some quick debugging on the livestream web page and found that the MP4 URLs in the .m3u8 playlists are no longer encrypted as before.
We can now obtain an unencrypted playlist by appending the query parameters playlistType=standard&psch=v1&pkey={pkey} to the original PlaylistVariants URL:
def getPlaylistVariants(self, url):
- url = "https://edge-hls.{host}/hls/{id}{vr}/master/{id}{vr}{auto}.m3u8".format(
+ url = "https://edge-hls.{host}/hls/{id}{vr}/master/{id}{vr}{auto}.m3u8?psch=v1&pkey={pkey}&playlistType=standard".format(
host='doppiocdn.' + random.choice(['org', 'com', 'net']),
id=self.lastInfo["streamName"],
vr='_vr' if self.vr else '',
auto='_auto' if not self.vr else '',
+ pkey=self._pkey,
)
The pkey value can be extracted from the Native.js file, which is referenced in the main bundle:
@@ -121,1 +121,9 @@ def getInitialData(cls):
+ native_js_name = re.findall('require[(]"./(Native.*?[.]js)"[)]', cls._main_js_data)[0]
+
+ r = requests.get(f"{mmp_base}/{native_js_name}", headers=cls.headers)
+ if r.status_code != 200:
+ raise Exception("Failed to fetch native.js")
+ cls._native_js_data = r.content.decode('utf-8')
+ cls._pkey = re.findall('pkey ?: ?"(.*?)"', cls._native_js_data)[0]
+ print(f'pkey={cls._pkey}')
Since the .m3u8 files are now unencrypted, we can skip the decryption step by setting the m3u_processor to None:
- self.getVideo = lambda _, url, filename: getVideoNativeHLS(self, url, filename, liveplatform.m3u_decoder)
+ self.getVideo = lambda _, url, filename: getVideoNativeHLS(self, url, filename, None)
This workaround is confirmed working as of 2025-10-28T02:05:04+08:00 but should be treated as fragile.
It’s unclear whether this shift to unencrypted playlists is temporary (e.g., during A/B testing or infrastructure migration) or a permanent relaxation of anti-scraping measures.
I’m new to this project and don’t have historical context on how often the platform changes its APIs.
@lossless1024
I’ve done some quick debugging on the livestream web page and found that the MP4 URLs in the
.m3u8playlists are no longer encrypted as before.We can now obtain an unencrypted playlist by appending the query parameters
playlistType=standard&psch=v1&pkey={pkey}to the originalPlaylistVariantsURL:def getPlaylistVariants(self, url):
url = "https://edge-hls.{host}/hls/{id}{vr}/master/{id}{vr}{auto}.m3u8".format(
url = "https://edge-hls.{host}/hls/{id}{vr}/master/{id}{vr}{auto}.m3u8?psch=v1&pkey={pkey}&playlistType=standard".format( host='doppiocdn.' + random.choice(['org', 'com', 'net']), id=self.lastInfo["streamName"], vr='_vr' if self.vr else '', auto='_auto' if not self.vr else '',pkey=self._pkey, )The
pkeyvalue can be extracted from theNative.jsfile, which is referenced in the main bundle:@@ -121,1 +121,9 @@ def getInitialData(cls):
native_js_name = re.findall('require[(]"./(Native.*?[.]js)"[)]', cls._main_js_data)[0]r = requests.get(f"{mmp_base}/{native_js_name}", headers=cls.headers)if r.status_code != 200:raise Exception("Failed to fetch native.js")cls._native_js_data = r.content.decode('utf-8')cls._pkey = re.findall('pkey ?: ?"(.*?)"', cls._native_js_data)[0]print(f'pkey={cls._pkey}')Since the
.m3u8files are now unencrypted, we can skip the decryption step by setting them3u_processortoNone:
self.getVideo = lambda _, url, filename: getVideoNativeHLS(self, url, filename, liveplatform.m3u_decoder)
self.getVideo = lambda _, url, filename: getVideoNativeHLS(self, url, filename, None)This workaround is confirmed working as of 2025-10-28T02:05:04+08:00 but should be treated as fragile.
It’s unclear whether this shift to unencrypted playlists is temporary (e.g., during A/B testing or infrastructure migration) or a permanent relaxation of anti-scraping measures.
I’m new to this project and don’t have historical context on how often the platform changes its APIs.
From what I can tell they are still encrypted. I changed the lines you indicated and still see error on download. Can anyone else confirm?
the above solution by @Rukkhadevata generates many small (less than few MB) .mp4 files as the output,
the above solution by @Rukkhadevata generates many small (less than few MB) .mp4 files as the output,
His code has no issues. There’s just one more place you need to remember to change. In the getPlaylistVariants(self, url) function in stripchat.py., on the second-to-last line:
return [variant | {'url': f'{variant["url"]}{"&" if "?" in variant["url"] else "?"}psch={psch}'}]
—you just need to remove psch={psch}.
Can someone post a working stripchat.py file? I apologize but after making the changes now the downloader crashes on start.
Can someone post a working stripchat.py file? I apologize but after making the changes now the downloader crashes on start.
Please post the error message so I can see if I can help resolve this issue for you.
The error doesn't stay on screen. Here is my stripchat.py file and what about stripchatvr.py?
https://1drv.ms/u/c/10a6a739b0c7e81e/EeF7ZQQU9HhCqEtWqVUoGgcB4aN9n0bl-dXpeDKEqCHf5g?e=AWQcFr
The error doesn't stay on screen. Here is my stripchat.py file and what about stripchatvr.py?
https://1drv.ms/u/c/10a6a739b0c7e81e/EeF7ZQQU9HhCqEtWqVUoGgcB4aN9n0bl-dXpeDKEqCHf5g?e=AWQcFr
@mikeymatrix70
You forgot to add the "get pkey" section mentioned by @Rukkhadevata in the getInitialData() function.
Try adding the following code("get pkey" section) to the end of the getInitialData() function and run it again.
def getInitialData(cls):
...
# get pkey
print(cls._main_js_data)
native_js_name = re.findall('require[(]"./(Native.*?[.]js)"[)]', cls._main_js_data)[0]
r = requests.get(f"{mmp_base}/{native_js_name}", headers=cls.headers)
if r.status_code != 200:
raise Exception("Failed to fetch native.js")
cls._native_js_data = r.content.decode('utf-8')
cls._pkey = re.findall('pkey ?: ?"(.*?)"', cls._native_js_data)[0]
print(f'pkey={cls._pkey}')
His code has no issues. There’s just one more place you need to remember to change. In the
getPlaylistVariants(self, url)function in stripchat.py., on the second-to-last line:
return [variant | {'url': f'{variant["url"]}{"&" if "?" in variant["url"] else "?"}psch={psch}'}]—you just need to remove
psch={psch}.
thanks for a hint @gao4433, I removed &pkey={pkey} and now works! Credits to @Rukkhadevata,
Will the code be updated to account for this or will this be something we will have to manually change for now? Thanks
The issue right now is for whatever reason theres two PSCH's in the master and the first one doesn't work, thats the one we are grabbing. Just make it try all of them and save the one that works for that session until restart or if it fails again.
The issue right now is for whatever reason theres two PSCH's in the master and the first one doesn't work, thats the one we are grabbing. Just make it try all of them and save the one that works for that session until restart or if it fails again.
A temporarily feasible solution comes from @Rukkhadevata. You just need to replace the code in StreaMonitor\streamonitor\sites\stripchat.py.
import itertools
import random
import re
import time
import requests
import base64
import hashlib
from streamonitor.bot import Bot
from streamonitor.downloaders.hls import getVideoNativeHLS
from streamonitor.enums import Status
class StripChat(Bot):
site = 'StripChat'
siteslug = 'SC'
_static_data = None
_main_js_data = None
_doppio_js_data = None
_mouflon_keys: dict = None
_cached_keys: dict[str, bytes] = None
def __init__(self, username):
if StripChat._static_data is None:
StripChat._static_data = {}
try:
self.getInitialData()
except Exception as e:
StripChat._static_data = None
raise e
while StripChat._static_data == {}:
time.sleep(1)
super().__init__(username)
self.vr = False
self.getVideo = lambda _, url, filename: getVideoNativeHLS(self, url, filename, None)
@classmethod
def getInitialData(cls):
r = requests.get('https://hu.stripchat.com/api/front/v3/config/static', headers=cls.headers)
if r.status_code != 200:
raise Exception("Failed to fetch static data from StripChat")
StripChat._static_data = r.json().get('static')
mmp_origin = StripChat._static_data['features']['MMPExternalSourceOrigin']
mmp_version = StripChat._static_data['featuresV2']['playerModuleExternalLoading']['mmpVersion']
mmp_base = f"{mmp_origin}/v{mmp_version}"
r = requests.get(f"{mmp_base}/main.js", headers=cls.headers)
if r.status_code != 200:
raise Exception("Failed to fetch main.js from StripChat")
StripChat._main_js_data = r.content.decode('utf-8')
native_js_name = re.findall('require[(]"./(Native.*?[.]js)"[)]', StripChat._main_js_data)[0]
r = requests.get(f"{mmp_base}/{native_js_name}", headers=cls.headers)
if r.status_code != 200:
raise Exception("Failed to fetch native.js")
cls._native_js_data = r.content.decode('utf-8')
# 5. 从 Native.js 提取 pkey
cls._pkey = re.findall('pkey ?: ?"(.*?)"', cls._native_js_data)[0]
doppio_js_name = re.findall('require[(]"./(Doppio.*?[.]js)"[)]', StripChat._main_js_data)[0]
r = requests.get(f"{mmp_base}/{doppio_js_name}", headers=cls.headers)
if r.status_code != 200:
raise Exception("Failed to fetch doppio.js from StripChat")
StripChat._doppio_js_data = r.content.decode('utf-8')
@classmethod
def m3u_decoder(cls, content):
_mouflon_file_attr = "#EXT-X-MOUFLON:FILE:"
_mouflon_filename = 'media.mp4'
def _decode(encrypted_b64: str, key: str) -> str:
if cls._cached_keys is None:
cls._cached_keys = {}
hash_bytes = cls._cached_keys[key] if key in cls._cached_keys \
else cls._cached_keys.setdefault(key, hashlib.sha256(key.encode("utf-8")).digest())
encrypted_data = base64.b64decode(encrypted_b64 + "==")
return bytes(a ^ b for (a, b) in zip(encrypted_data, itertools.cycle(hash_bytes))).decode("utf-8")
_, pkey = StripChat._getMouflonFromM3U(content)
decoded = ''
lines = content.splitlines()
last_decoded_file = None
for line in lines:
if line.startswith(_mouflon_file_attr):
last_decoded_file = _decode(line[len(_mouflon_file_attr):], cls.getMouflonDecKey(pkey))
elif line.endswith(_mouflon_filename) and last_decoded_file:
decoded += (line.replace(_mouflon_filename, last_decoded_file)) + '\n'
last_decoded_file = None
else:
decoded += line + '\n'
return decoded
@classmethod
def getMouflonDecKey(cls, pkey):
if cls._mouflon_keys is None:
cls._mouflon_keys = {}
return cls._mouflon_keys[pkey] if pkey in cls._mouflon_keys \
else cls._mouflon_keys.setdefault(pkey, re.findall(f'"{pkey}:(.*?)"', cls._doppio_js_data)[0])
@staticmethod
def _getMouflonFromM3U(m3u8_doc):
if '#EXT-X-MOUFLON:' in m3u8_doc:
_mouflon_start = m3u8_doc.find('#EXT-X-MOUFLON:')
if _mouflon_start > 0:
_mouflon = m3u8_doc[_mouflon_start:m3u8_doc.find('\n', _mouflon_start)].strip().split(':')
psch = _mouflon[2]
pkey = _mouflon[3]
return psch, pkey
return None, None
def getWebsiteURL(self):
return "https://stripchat.com/" + self.username
def getVideoUrl(self):
return self.getWantedResolutionPlaylist(None)
def getPlaylistVariants(self, url):
url = "https://edge-hls.{host}/hls/{id}{vr}/master/{id}{vr}{auto}.m3u8?psch=v1&pkey={pkey}&playlistType=standard".format(
host='doppiocdn.' + random.choice(['org', 'com', 'net']),
id=self.lastInfo["streamName"],
vr='_vr' if self.vr else '',
auto='_auto' if not self.vr else '',
pkey=self._pkey,
)
result = requests.get(url, headers=self.headers, cookies=self.cookies)
m3u8_doc = result.content.decode("utf-8")
psch, pkey = StripChat._getMouflonFromM3U(m3u8_doc)
variants = super().getPlaylistVariants(m3u_data=m3u8_doc)
return [variant | {'url': f'{variant["url"]}{"&" if "?" in variant["url"] else "?"}psch={psch}'}
for variant in variants]
@staticmethod
def uniq(length=16):
chars = ''.join(chr(i) for i in range(ord('a'), ord('z')+1))
chars += ''.join(chr(i) for i in range(ord('0'), ord('9')+1))
return ''.join(random.choice(chars) for _ in range(length))
def getStatus(self):
r = requests.get(
f'https://stripchat.com/api/front/v2/models/username/{self.username}/cam?uniq={StripChat.uniq()}',
headers=self.headers
)
try:
data = r.json()
except requests.exceptions.JSONDecodeError:
self.log('Failed to parse JSON response')
return Status.UNKNOWN
if 'cam' not in data:
if 'error' in data:
error = data['error']
if error == 'Not Found':
return Status.NOTEXIST
self.logger.warn(f'Status returned error: {error}')
return Status.UNKNOWN
self.lastInfo = {'model': data['user']['user']}
if isinstance(data['cam'], dict):
self.lastInfo |= data['cam']
status = self.lastInfo['model'].get('status')
if status == "public" and self.lastInfo["isCamAvailable"] and self.lastInfo["isCamActive"]:
return Status.PUBLIC
if status in ["private", "groupShow", "p2p", "virtualPrivate", "p2pVoice"]:
return Status.PRIVATE
if status in ["off", "idle"]:
return Status.OFFLINE
if self.lastInfo['model'].get('isDeleted') is True:
return Status.NOTEXIST
if data['user'].get('isGeoBanned') is True:
return Status.RESTRICTED
self.logger.warn(f'Got unknown status: {status}')
return Status.UNKNOWN
I'm trying to understand how decryption works.
So, from following m3u8 chunk I'm extracting Zokee2OhPh9kugh4 as key and
0KPWVjC9TC6Vl6vhv3WfI6cgFPPD6Pfokf2LWOKkywqn0tgBQOUge9aXreS5NPZ/oCUZmq6toKK6jM9vwtE
as encrypted base64 data.
#EXTM3U
#EXT-X-VERSION:6
#EXT-X-MOUFLON:PSCH:v1:Zokee2OhPh9kugh4
#EXT-X-TARGETDURATION:2
#EXT-X-SERVER-CONTROL:CAN-BLOCK-RELOAD=YES,PART-HOLD-BACK=1.500
#EXT-X-PART-INF:PART-TARGET=0.500
#EXT-X-INDEPENDENT-SEGMENTS
#EXT-X-MEDIA-SEQUENCE:2568
#EXT-X-DISCONTINUITY-SEQUENCE:0
#EXT-X-MAP:URI="https://media-hls.doppiocdn.net/b-hls-12/139856891/139856891_720p_h264_init_keHMFhUEKlXl9e7B.mp4"
#EXT-X-MOUFLON:FILE:0KPWVjC9TC6Vl6vhv3WfI6cgFPPD6Pfokf2LWOKkywqn0tgBQOUge9aXreS5NPZ/oCUZmq6toKK6jM9vwtE
#EXT-X-PART:DURATION=0.500,URI="https://media-hls.doppiocdn.net/b-hls-12/139856891/media.mp4",INDEPENDENT=YES
#EXT-X-MOUFLON:FILE:0KPWVjC9TC6Vl6vhv3WfI6cgFPPD6Pfokf2LWOKkywqn0tgBQOUge9aXreS5NPZ/oCUZmq6toKK6jc9vwtE
#EXT-X-PART:DURATION=0.500,URI="https://media-hls.doppiocdn.net/b-hls-12/139856891/media.mp4"
#EXT-X-MOUFLON:FILE:0KPWVjC9TC6Vl6vhv3WfI6cgFPPD6Pfokf2LWOKkywqn0tgBQOUge9aXreS5NPZ/oCUZmq6toKK6js9vwtE
#EXT-X-PART:DURATION=0.500,URI="https://media-hls.doppiocdn.net/b-hls-12/139856891/media.mp4"
#EXT-X-MOUFLON:FILE:0KPWVjC9TC6Vl6vhv3WfI6cgFPPD6Pfokf2LWOKkywqn0tgBQOUge9aXreS5NPZ/oCUZmq6toKK6j89vwtE
#EXT-X-PART:DURATION=0.500,URI="https://media-hls.doppiocdn.net/b-hls-12/139856891/media.mp4"
#EXT-X-PROGRAM-DATE-TIME:2025-10-28T09:56:36.903+0000
#EXTINF:2.000
After that I'm trying to decrypt it:
import itertools
import base64
import hashlib
def decode(encrypted_b64: str, key: str) -> str:
hash_bytes = hashlib.sha256(key.encode("utf-8")).digest()
encrypted_data = base64.b64decode(encrypted_b64 + "==")
return bytes(a ^ b for (a, b) in zip(encrypted_data, itertools.cycle(hash_bytes))).decode("utf-8")
if __name__ == '__main__':
encrypted_b64 = "0KPWVjC9TC6Vl6vhv3WfI6cgFPPD6Pfokf2LWOKkywqn0tgBQOUge9aXreS5NPZ/oCUZmq6toKK6jM9vwtE"
key = "Zokee2OhPh9kugh4"
print(decode(encrypted_b64, key))
But I'm getting invalid utf8.
What I'm doing wrong?
I don't know how much help it will be since others have posted solutions. This seems to have worked for me:
stripchat.py
@staticmethod
def _getMouflonFromM3U(m3u8_doc):
if '#EXT-X-MOUFLON:' in m3u8_doc:
# we have the mouflon extention in the m3u8
filtered_iterator = filter(
lambda line: "#EXT-X-MOUFLON:PSCH" in line,
m3u8_doc.split('\n')
)
# there seems to be cases where theres 2 of these keys, may need to figure out
# for now, let's just take the last one
lines = list(filtered_iterator)
if len(lines) > 0:
last_line = lines[-1]
# #EXT-X-MOUFLON:PSCH:v1:{key}
[_, _, psch, pkey] = last_line.strip().split(':')
return psch, pkey
_mouflon_start = m3u8_doc.find('#EXT-X-MOUFLON:')
if _mouflon_start > 0:
_mouflon = m3u8_doc[_mouflon_start:m3u8_doc.find('\n', _mouflon_start)].strip().split(':')
psch = _mouflon[2]
pkey = _mouflon[3]
return psch, pkey
return None, None
Oh, now I understand, no actual decryption is needed now, only passing pkey while requesting the playlist. Thank you all.
key = "Zokee2OhPh9kugh4"
Replace it with Quean4cai9boJa5a which is the real key mapped from Zokee2OhPh9kugh4.
Zokee2OhPh9kugh4 is called pkey (the query param name when requesting the media playlist), or keyId (term used in Doppio.js when parsing the #EXT-X-MOUFLON tag).
The decode function needs key which is mapped from keyId. You can find related code at def getMouflonDecKey(cls, pkey)
File "C:\Users\AppData\Local\Programs\Python\Python312\Lib\site-packages\urllib3\connectionpool.py", line 1093, in _validate_conn conn.connect() File "C:\Users\AppData\Local\Programs\Python\Python312\Lib\site-packages\urllib3\connection.py", line 790, in connect sock_and_verified = _ssl_wrap_socket_and_match_hostname( ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Users\AppData\Local\Programs\Python\Python312\Lib\site-packages\urllib3\connection.py", line 969, in ssl_wrap_socket_and_match_hostname ssl_sock = ssl_wrap_socket( ^^^^^^^^^^^^^^^^ File "C:\Users\AppData\Local\Programs\Python\Python312\Lib\site-packages\urllib3\util\ssl.py", line 480, in ssl_wrap_socket ssl_sock = ssl_wrap_socket_impl(sock, context, tls_in_tls, server_hostname) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Users\AppData\Local\Programs\Python\Python312\Lib\site-packages\urllib3\util\ssl.py", line 524, in _ssl_wrap_socket_impl return ssl_context.wrap_socket(sock, server_hostname=server_hostname) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Users\AppData\Local\Programs\Python\Python312\Lib\ssl.py", line 455, in wrap_socket return self.sslsocket_class._create( ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Users\AppData\Local\Programs\Python\Python312\Lib\ssl.py", line 1041, in _create self.do_handshake() File "C:\Users\AppData\Local\Programs\Python\Python312\Lib\ssl.py", line 1319, in do_handshake self._sslobj.do_handshake() ssl.SSLEOFError: [SSL: UNEXPECTED_EOF_WHILE_READING] EOF occurred in violation of protocol (_ssl.c:1010)
During handling of the above exception, another exception occurred:
Anyone explain this error ??
Pkey seems to be a fixed "Zokee2OhPh9kugh4". If so, can the program skip the step of obtaining pkey and directly use "Zokee2OhPh9kugh4" as the value.When the program no longer retrieves pkey, the error output will be greatly reduced.
For python 3.8.10
The issue right now is for whatever reason theres two PSCH's in the master and the first one doesn't work, thats the one we are grabbing. Just make it try all of them and save the one that works for that session until restart or if it fails again.
A temporarily feasible solution comes from @Rukkhadevata. You just need to replace the code in
StreaMonitor\streamonitor\sites\stripchat.py.
+from typing import Dict
class StripChat(Bot):
site = 'StripChat'
siteslug = 'SC'
_static_data = None
_main_js_data = None
_doppio_js_data = None
_mouflon_keys: dict = None
- _cached_keys: dict[str, bytes] = None
+ _cached_keys: Dict[str, bytes] = None
def getPlaylistVariants(self, url):
url = "https://edge-hls.{host}/hls/{id}{vr}/master/{id}{vr}{auto}.m3u8?psch=v1&pkey={pkey}&playlistType=standard".format(
host='doppiocdn.' + random.choice(['org', 'com', 'net']),
id=self.lastInfo["streamName"],
vr='_vr' if self.vr else '',
auto='_auto' if not self.vr else '',
pkey=self._pkey,
)
result = requests.get(url, headers=self.headers, cookies=self.cookies)
m3u8_doc = result.content.decode("utf-8")
psch, pkey = StripChat._getMouflonFromM3U(m3u8_doc)
variants = super().getPlaylistVariants(m3u_data=m3u8_doc)
- return [variant | {'url': f'{variant["url"]}{"&" if "?" in variant["url"] else "?"}psch={psch}'} for variant in variants]
+ return [{**variant, 'url': f'{variant["url"]}{"&" if "?" in variant["url"] else "?"}psch={psch}'} for variant in variants]
I pushed a fix to this issue
The "Error on Download" message is still appearing.
download with ffmpeg works again on stripchat
download with ffmpeg works again on stripchat
Really? SC is no longer encrypted? I'll try it later.
C:\Users\Downloads\StreaMonitor-master\StreaMonitor-master> python Downloader.py
Traceback (most recent call last):
File "C:\Users\Downloads\StreaMonitor-master\StreaMonitor-master\Downloader.py", line 43, in
what's the issue now ?
C:\Users\Downloads\StreaMonitor-master\StreaMonitor-master> python Downloader.py Traceback (most recent call last): File "C:\Users\Downloads\StreaMonitor-master\StreaMonitor-master\Downloader.py", line 43, in main() File "C:\Users\Downloads\StreaMonitor-master\StreaMonitor-master\Downloader.py", line 25, in main streamers = config.loadStreamers() ^^^^^^^^^^^^^^^^^^^^^^ File "C:\Users\Downloads\StreaMonitor-master\StreaMonitor-master\streamonitor\config.py", line 47, in loadStreamers streamer_bot = bot_class.fromConfig(streamer) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Users\Downloads\StreaMonitor-master\StreaMonitor-master\streamonitor\bot.py", line 345, in fromConfig instance = cls(username=data['username']) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Users\Downloads\StreaMonitor-master\StreaMonitor-master\streamonitor\sites\stripchat.py", line 31, in init raise e File "C:\Users\Downloads\StreaMonitor-master\StreaMonitor-master\streamonitor\sites\stripchat.py", line 28, in init self.getInitialData() File "C:\Users\Downloads\StreaMonitor-master\StreaMonitor-master\streamonitor\sites\stripchat.py", line 54, in getInitialData native_js_name = re.findall('require[(]"./(Native.*?[.]js)"[)]', StripChat._main_js_data)[0] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^ IndexError: list index out of range
what's the issue now ?
It seems like SC changed, once again, the way the stream data is loaded. I think this issue https://github.com/lossless1024/StreaMonitor/issues/261 is the same.
I added a key cache, so you can populate stripchat_mouflon_keys.json with known decryption keys. The format is {"pkey": "decryption_key"}. Like in #260