Repository: YaNesyTortiK/AnimeParsers
Pull request #7: Функция get_link возвращает байтовую строку вместо ссылки на потоковое видео для скачивания.
Author: login: mex1c0
Created: 2025-03-07T20:44:32Z | Merged: 2025-03-08T18:26:20Z

Вот пример запроса для "Ковбоя Бибопа" с озвучкой KansaiStudio
Запрос: 
"print(pars.get_link("1", "shikimori", 1, "559"))"

Ответ: 
"('\\xff\\xfarn\\x1a\\xb1n\\xab\\xf1~\\x06\\xde\\x9e\\xda\\xe9o?\\xe1~\\xb7\\xa1s&\\xe7\\xa9\\xff\\xf7\\xefN\\xb9\\xdf\\xde\\xe9\\xb3Jx\\xae\\x7f}\\xdb\\xaf{\\xd7\\xcd\\xbb\\xf3\\x8et\\xa7\\xbe\\xff\\x9fy\\xec\\xdbMy\\x9f\\xbd\\xa9\\xea\\x8f*\\x9f\\x9d+\\xde\\xbb*\\xab\\xa9\\xfb\\xa7z5\\xdbM\\xb9\\xd3}<\\xd3\\xcf\\xf7\\xebL\\xdc\\xe2\\xec\\x9f\\x', 720)"

Похоже на то что Kodik убрал шифрование ссылок на потоковое видео, поэтому функции "_convert_char" и "_convert" не нужны. Без них:
Запрос: 

"print(pars.get_link("1", "shikimori", 1, "559"))"
Ответ:

"('cloud.kodik-storage.com/useruploads/37065397-cf0c-4ea9-9269-718278450c77/a3af2015a72c6b8da50e3efdd6a7c3b1:2025030808', 720)"


Diff:
diff --git a/src/anime_parsers_ru/parser_kodik.py b/src/anime_parsers_ru/parser_kodik.py
index 8f1df30..3094209 100644
--- a/src/anime_parsers_ru/parser_kodik.py
+++ b/src/anime_parsers_ru/parser_kodik.py
@@ -530,7 +530,14 @@ def _generate_translations_dict(self, translations_div: Soup) -> dict:
             translations = [{"id": "0", "type": "Неизвестно", "name": "Неизвестно"}]
         return translations
 
-    def get_link(self, id: str, id_type: str, seria_num: int, translation_id: str) -> tuple[str, int]:
+    def get_link(
+        self,
+        id: str,
+        id_type: str,
+        seria_num: int,
+        translation_id: str,
+        crypted: bool = False,
+    ) -> tuple[str, int]:
         """
         ### Для использования требуется токен kodik
         Возвращает ссылку на видео файл.
@@ -539,6 +546,7 @@ def get_link(self, id: str, id_type: str, seria_num: int, translation_id: str) -
         :id_type: тип id (возможные: shikimori, kinopoisk, imdb)
         :seria_num: номер серии (если фильм или одно видео, укажите 0)
         :translation_id: id перевода (прим: Anilibria = 610, если неизвестно - 0)
+        :crypted: bool:
 
         Возвращает ссылку в стиле:
         //cloud.kodik-storage.com/useruploads/351182fc-e1ac-4521-a9e3-261303e69687/ba18e7c1a8ac055a61b0d2e528f9eb8c:2024062702/
@@ -555,61 +563,80 @@ def get_link(self, id: str, id_type: str, seria_num: int, translation_id: str) -
         if type(seria_num) == str and seria_num.isdigit():
             seria_num = int(seria_num)
         elif type(seria_num) != int:
-            raise ValueError(f'Для seria_num ожидался тип int, получен "{type(seria_num)}"')
+            raise ValueError(
+                f'Для seria_num ожидался тип int, получен "{type(seria_num)}"'
+            )
         if type(translation_id) == int:
             translation_id = str(translation_id)
         elif type(translation_id) != str:
-            raise ValueError(f'Для translation_id ожидался тип str, получен "{type(translation_id)}"')
-        
+            raise ValueError(
+                f'Для translation_id ожидался тип str, получен "{type(translation_id)}"'
+            )
 
         link = self._link_to_info(id, id_type)
         data = requests.get(link).text
-        soup = Soup(data, 'lxml') if self.USE_LXML else Soup(data, 'html.parser')
-        urlParams = data[data.find('urlParams')+13:]
-        urlParams = json.loads(urlParams[:urlParams.find(';')-1])
-        if translation_id != "0" and seria_num != 0: # Обычный сериал с известной озвучкой на более чем 1 серию
-            container = soup.find('div', {'class': 'serial-translations-box'}).find('select')
+        soup = Soup(data, "lxml") if self.USE_LXML else Soup(data, "html.parser")
+        urlParams = data[data.find("urlParams") + 13 :]
+        urlParams = json.loads(urlParams[: urlParams.find(";") - 1])
+        if (
+            translation_id != "0" and seria_num != 0
+        ):  # Обычный сериал с известной озвучкой на более чем 1 серию
+            container = soup.find("div", {"class": "serial-translations-box"}).find(
+                "select"
+            )
             media_hash = None
             media_id = None
-            for translation in container.find_all('option'):
-                if translation.get_attribute_list('data-id')[0] == translation_id:
-                    media_hash = translation.get_attribute_list('data-media-hash')[0]
-                    media_id = translation.get_attribute_list('data-media-id')[0]
+            for translation in container.find_all("option"):
+                if translation.get_attribute_list("data-id")[0] == translation_id:
+                    media_hash = translation.get_attribute_list("data-media-hash")[0]
+                    media_id = translation.get_attribute_list("data-media-id")[0]
                     break
-            url = f'https://kodik.info/serial/{media_id}/{media_hash}/720p?min_age=16&first_url=false&season=1&episode={seria_num}'
+            url = f"https://kodik.info/serial/{media_id}/{media_hash}/720p?min_age=16&first_url=false&season=1&episode={seria_num}"
             data = requests.get(url).text
-            soup = Soup(data, 'lxml') if self.USE_LXML else Soup(data, 'html.parser')
-        elif translation_id != "0" and seria_num == 0: # Фильм/одна серия с несколькими переводами
-            container = soup.find('div', {'class': 'movie-translations-box'}).find('select')
+            soup = Soup(data, "lxml") if self.USE_LXML else Soup(data, "html.parser")
+        elif (
+            translation_id != "0" and seria_num == 0
+        ):  # Фильм/одна серия с несколькими переводами
+            container = soup.find("div", {"class": "movie-translations-box"}).find(
+                "select"
+            )
             media_hash = None
             media_id = None
-            for translation in container.find_all('option'):
-                if translation.get_attribute_list('data-id')[0] == translation_id:
-                    media_hash = translation.get_attribute_list('data-media-hash')[0]
-                    media_id = translation.get_attribute_list('data-media-id')[0]
+            for translation in container.find_all("option"):
+                if translation.get_attribute_list("data-id")[0] == translation_id:
+                    media_hash = translation.get_attribute_list("data-media-hash")[0]
+                    media_id = translation.get_attribute_list("data-media-id")[0]
                     break
-            url = f'https://kodik.info/video/{media_id}/{media_hash}/720p?min_age=16&first_url=false&season=1&episode={seria_num}'
+            url = f"https://kodik.info/video/{media_id}/{media_hash}/720p?min_age=16&first_url=false&season=1&episode={seria_num}"
             data = requests.get(url).text
-            soup = Soup(data, 'lxml') if self.USE_LXML else Soup(data, 'html.parser')
-        script_url = soup.find_all('script')[1].get_attribute_list('src')[0]
-
-        hash_container = soup.find_all('script')[4].text
-        video_type = hash_container[hash_container.find('.type = \'')+9:]
-        video_type = video_type[:video_type.find('\'')]
-        video_hash = hash_container[hash_container.find('.hash = \'')+9:]
-        video_hash = video_hash[:video_hash.find('\'')]
-        video_id = hash_container[hash_container.find('.id = \'')+7:]
-        video_id = video_id[:video_id.find('\'')]
-
-        link_data, max_quality = self._get_link_with_data(video_type, video_hash, video_id, urlParams, script_url)
-
-        download_url = str(link_data).replace("https://", '')
-        download_url = download_url[2:-26] # :hls:manifest.m3u8
+            soup = Soup(data, "lxml") if self.USE_LXML else Soup(data, "html.parser")
+        script_url = soup.find_all("script")[1].get_attribute_list("src")[0]
+
+        hash_container = soup.find_all("script")[4].text
+        video_type = hash_container[hash_container.find(".type = '") + 9 :]
+        video_type = video_type[: video_type.find("'")]
+        video_hash = hash_container[hash_container.find(".hash = '") + 9 :]
+        video_hash = video_hash[: video_hash.find("'")]
+        video_id = hash_container[hash_container.find(".id = '") + 7 :]
+        video_id = video_id[: video_id.find("'")]
+
+        link_data, max_quality = self._get_link_with_data(
+            video_type, video_hash, video_id, urlParams, script_url, crypted
+        )
 
+        download_url = str(link_data).replace("https:", "")[:-27]
         return download_url, max_quality
-    
-    def _get_link_with_data(self, video_type: str, video_hash: str, video_id: str, urlParams: dict, script_url: str):
-        params={
+
+    def _get_link_with_data(
+        self,
+        video_type: str,
+        video_hash: str,
+        video_id: str,
+        urlParams: dict,
+        script_url: str,
+        crypted: bool,
+    ):
+        params = {
             "hash": video_hash,
             "id": video_id,
             "type": video_type,
@@ -623,24 +650,21 @@ def _get_link_with_data(self, video_type: str, video_hash: str, video_id: str, u
             'cdn_is_working': 'true',
         }
 
-        headers = {
-            "Content-Type": "application/x-www-form-urlencoded"
-        }
+        headers = {"Content-Type": "application/x-www-form-urlencoded"}
 
         post_link = self._get_post_link(script_url)
-        data = requests.post(f'https://kodik.info{post_link}', data=params, headers=headers).json()
-        url = self._convert(data['links']['360'][0]['src'])
-        max_quality = max([int(x) for x in data['links'].keys()])
-        try:
-            return b64decode(url.encode()), max_quality
-        except:
-            return str(b64decode(url.encode()+b'==')).replace("https:", ''), max_quality
-        
+        data = requests.post(f"https://kodik.info{post_link}", data=params, headers=headers).json()
+        data_url = data["links"]["360"][0]["src"]
+        url = data_url if not crypted else self._convert(data_url)
+        max_quality = max([int(x) for x in data["links"].keys()])
+
+        return url, max_quality
+
     def _convert_char(self, char: str):
         low = char.islower()
         alph = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
         if char.upper() in alph:
-            ch = alph[(alph.index(char.upper())+13)%len(alph)]
+            ch = alph[(alph.index(char.upper()) + 13) % len(alph)]
             if low:
                 return ch.lower()
             else:
@@ -650,11 +674,15 @@ def _convert_char(self, char: str):
 
     def _convert(self, string: str):
         # Декодирование строки со ссылкой
-        return "".join(map(self._convert_char, list(string)))
-    
+        crypted_url = "".join(map(self._convert_char, list(string)))
+        try:
+            return b64decode(crypted_url.encode())
+        except:
+            return str(b64decode(crypted_url.encode() + b"=="))
+
     def _get_post_link(self, script_url: str):
-        data = requests.get('https://kodik.info'+script_url).text
-        url = data[data.find("$.ajax")+30:data.find("cache:!1")-3]
+        data = requests.get("https://kodik.info" + script_url).text
+        url = data[data.find("$.ajax") + 30 : data.find("cache:!1") - 3]
         return b64decode(url.encode()).decode()
 
     @staticmethod
@@ -667,4 +695,4 @@ def get_token() -> str:
         data = requests.get(script_url).text
         token = data[data.find('token=')+7:]
         token = token[:token.find('"')]
-        return token
\ No newline at end of file
+        return token
diff --git a/src/anime_parsers_ru/parser_kodik_async.py b/src/anime_parsers_ru/parser_kodik_async.py
index 977bd05..071b6a5 100644
--- a/src/anime_parsers_ru/parser_kodik_async.py
+++ b/src/anime_parsers_ru/parser_kodik_async.py
@@ -538,7 +538,14 @@ def _generate_translations_dict(self, translations_div: Soup | None) -> dict:
             translations = [{"id": "0", "type": "Неизвестно", "name": "Неизвестно"}]
         return translations
 
-    async def get_link(self, id: str, id_type: str, seria_num: int, translation_id: str) -> tuple[str, int]:
+    async def get_link(
+        self,
+        id: str,
+        id_type: str,
+        seria_num: int,
+        translation_id: str,
+        crypted: bool = False,
+    ) -> tuple[str, int]:
         """
         ### Для использования требуется токен kodik
         Возвращает ссылку на видео файл.
@@ -611,14 +618,14 @@ async def get_link(self, id: str, id_type: str, seria_num: int, translation_id:
         video_id = hash_container[hash_container.find('.id = \'')+7:]
         video_id = video_id[:video_id.find('\'')]
 
-        link_data, max_quality = await self._get_link_with_data(video_type, video_hash, video_id, urlParams, script_url)
-
-        download_url = str(link_data).replace("https://", '')
-        download_url = download_url[2:-26] # :hls:manifest.m3u8
+        link_data, max_quality = await self._get_link_with_data(
+            video_type, video_hash, video_id, urlParams, script_url, crypted
+        )
 
+        download_url = str(link_data).replace("https:", "")[:-26]
         return download_url, max_quality
     
-    async def _get_link_with_data(self, video_type: str, video_hash: str, video_id: str, urlParams: dict, script_url: str):
+    async def _get_link_with_data(self, video_type: str, video_hash: str, video_id: str, urlParams: dict, script_url: str, crypted:bool):
         params={
             "hash": video_hash,
             "id": video_id,
@@ -640,18 +647,17 @@ async def _get_link_with_data(self, video_type: str, video_hash: str, video_id:
         post_link = await self._get_post_link(script_url)
         data = await self.requests.post(f'https://kodik.info{post_link}', data=params, headers=headers)
         data = data.json()
-        url = self._convert(data['links']['360'][0]['src'])
-        max_quality = max([int(x) for x in data['links'].keys()])
-        try:
-            return b64decode(url.encode()), max_quality
-        except:
-            return str(b64decode(url.encode()+b'==')).replace("https:", ''), max_quality
-        
+        data_url = data["links"]["360"][0]["src"]
+        url = data_url if not crypted else self._convert(data_url)
+        max_quality = max([int(x) for x in data["links"].keys()])
+
+        return url, max_quality
+
     def _convert_char(self, char: str):
         low = char.islower()
         alph = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
         if char.upper() in alph:
-            ch = alph[(alph.index(char.upper())+13)%len(alph)]
+            ch = alph[(alph.index(char.upper()) + 13) % len(alph)]
             if low:
                 return ch.lower()
             else:
@@ -661,14 +667,19 @@ def _convert_char(self, char: str):
 
     def _convert(self, string: str):
         # Декодирование строки со ссылкой
-        return "".join(map(self._convert_char, list(string)))
-    
+        crypted_url = "".join(map(self._convert_char, list(string)))
+        try:
+            return b64decode(crypted_url.encode())
+        except:
+            return str(b64decode(crypted_url.encode() + b"=="))
+
     async def _get_post_link(self, script_url: str):
         data = await self.requests.get('https://kodik.info'+script_url)
         data = data.text
         url = data[data.find("$.ajax")+30:data.find("cache:!1")-3]
         return b64decode(url.encode()).decode()
 
+
     @staticmethod
     async def get_token() -> str:
         """
@@ -694,4 +705,5 @@ def get_token_sync() -> str:
         data = data.text
         token = data[data.find('token=')+7:]
         token = token[:token.find('"')]
-        return token
\ No newline at end of file
+        return token
+


Reviews:
user: login: YaNesyTortiK
body: Привет, спасибо что заметил и исправил. Было бы неплохо вернуть в код функции convert и convert_char чтобы если кодик обратно вернул шифрование, можно было быстро подключить обратно. Желательно еще добавить в функцию get_link параметр `crypted: bool = False` которые если установить в True, будут использоваться функции расшифровки.
state: CHANGES_REQUESTED
author_association: OWNER
submitted_at: 2025-03-08T06:56:40Z
user: login: YaNesyTortiK
state: COMMENTED
author_association: OWNER
submitted_at: 2025-03-08T12:42:38Z
user: login: YaNesyTortiK
state: COMMENTED
author_association: OWNER
submitted_at: 2025-03-08T12:43:49Z
user: login: YaNesyTortiK
state: COMMENTED
author_association: OWNER
submitted_at: 2025-03-08T12:44:10Z
user: login: YaNesyTortiK
state: COMMENTED
author_association: OWNER
submitted_at: 2025-03-08T12:44:36Z
user: login: YaNesyTortiK
state: COMMENTED
author_association: OWNER
submitted_at: 2025-03-08T12:44:49Z
user: login: mex1c0
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-08T12:45:06Z
user: login: mex1c0
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-08T12:57:32Z
user: login: mex1c0
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-08T13:04:21Z
user: login: YaNesyTortiK
state: APPROVED
author_association: OWNER
submitted_at: 2025-03-08T18:25:56Z

Review comments:
diff_hunk: @@ -603,8 +603,9 @@ def get_link(self, id: str, id_type: str, seria_num: int, translation_id: str) -
 
         link_data, max_quality = self._get_link_with_data(video_type, video_hash, video_id, urlParams, script_url)
 
-        download_url = str(link_data).replace("https://", '')
-        download_url = download_url[2:-26] # :hls:manifest.m3u8
+        download_url = str(link_data).replace("https://", "")
path: src/anime_parsers_ru/parser_kodik.py
user: login: YaNesyTortiK
body: Не совсем понятен смысл изменения, если и убирать https, то убирать вместе с '//'
created_at: 2025-03-08T12:42:38Z
author_association: OWNER
diff_hunk: @@ -629,29 +630,13 @@ def _get_link_with_data(self, video_type: str, video_hash: str, video_id: str, u
 
         post_link = self._get_post_link(script_url)
         data = requests.post(f'https://kodik.info{post_link}', data=params, headers=headers).json()
-        url = self._convert(data['links']['360'][0]['src'])
path: src/anime_parsers_ru/parser_kodik.py
user: login: YaNesyTortiK
body: Добавить условие, чтобы при желании можно было использовать self._convert (по умолчанию должно без него)
created_at: 2025-03-08T12:43:48Z
author_association: OWNER
diff_hunk: @@ -629,29 +630,13 @@ def _get_link_with_data(self, video_type: str, video_hash: str, video_id: str, u
 
         post_link = self._get_post_link(script_url)
         data = requests.post(f'https://kodik.info{post_link}', data=params, headers=headers).json()
-        url = self._convert(data['links']['360'][0]['src'])
+        url = data['links']['360'][0]['src']
         max_quality = max([int(x) for x in data['links'].keys()])
         try:
-            return b64decode(url.encode()), max_quality
path: src/anime_parsers_ru/parser_kodik.py
user: login: YaNesyTortiK
body: Добавить условие, чтобы при желании можно было использовать self._convert (по умолчанию должно без него)
created_at: 2025-03-08T12:44:10Z
author_association: OWNER
diff_hunk: @@ -629,29 +630,13 @@ def _get_link_with_data(self, video_type: str, video_hash: str, video_id: str, u
 
         post_link = self._get_post_link(script_url)
         data = requests.post(f'https://kodik.info{post_link}', data=params, headers=headers).json()
-        url = self._convert(data['links']['360'][0]['src'])
+        url = data['links']['360'][0]['src']
         max_quality = max([int(x) for x in data['links'].keys()])
         try:
-            return b64decode(url.encode()), max_quality
+            return url, max_quality
         except:
-            return str(b64decode(url.encode()+b'==')).replace("https:", ''), max_quality
path: src/anime_parsers_ru/parser_kodik.py
user: login: YaNesyTortiK
body: Также добавить условие, если условие False, просто pass
created_at: 2025-03-08T12:44:36Z
author_association: OWNER
diff_hunk: @@ -629,29 +630,13 @@ def _get_link_with_data(self, video_type: str, video_hash: str, video_id: str, u
 
         post_link = self._get_post_link(script_url)
         data = requests.post(f'https://kodik.info{post_link}', data=params, headers=headers).json()
-        url = self._convert(data['links']['360'][0]['src'])
+        url = data['links']['360'][0]['src']
         max_quality = max([int(x) for x in data['links'].keys()])
         try:
-            return b64decode(url.encode()), max_quality
+            return url, max_quality
         except:
-            return str(b64decode(url.encode()+b'==')).replace("https:", ''), max_quality
-        
-    def _convert_char(self, char: str):
path: src/anime_parsers_ru/parser_kodik.py
user: login: YaNesyTortiK
body: Впернуть эти строки
created_at: 2025-03-08T12:44:49Z
author_association: OWNER
diff_hunk: @@ -629,29 +630,13 @@ def _get_link_with_data(self, video_type: str, video_hash: str, video_id: str, u
 
         post_link = self._get_post_link(script_url)
         data = requests.post(f'https://kodik.info{post_link}', data=params, headers=headers).json()
-        url = self._convert(data['links']['360'][0]['src'])
+        url = data['links']['360'][0]['src']
         max_quality = max([int(x) for x in data['links'].keys()])
         try:
-            return b64decode(url.encode()), max_quality
+            return url, max_quality
         except:
-            return str(b64decode(url.encode()+b'==')).replace("https:", ''), max_quality
-        
-    def _convert_char(self, char: str):
path: src/anime_parsers_ru/parser_kodik.py
user: login: mex1c0
body: да я понял я прост случайно тыкнул)
created_at: 2025-03-08T12:45:06Z
author_association: CONTRIBUTOR
diff_hunk: @@ -613,8 +613,9 @@ async def get_link(self, id: str, id_type: str, seria_num: int, translation_id:
 
         link_data, max_quality = await self._get_link_with_data(video_type, video_hash, video_id, urlParams, script_url)
 
-        download_url = str(link_data).replace("https://", '')
-        download_url = download_url[2:-26] # :hls:manifest.m3u8
+        download_url = str(link_data).replace("https://", "")
path: src/anime_parsers_ru/parser_kodik_async.py
user: login: YaNesyTortiK
body: Комментарии аналогично синхронной версии
created_at: 2025-03-08T12:45:22Z
author_association: OWNER
diff_hunk: @@ -603,8 +603,9 @@ def get_link(self, id: str, id_type: str, seria_num: int, translation_id: str) -
 
         link_data, max_quality = self._get_link_with_data(video_type, video_hash, video_id, urlParams, script_url)
 
-        download_url = str(link_data).replace("https://", '')
-        download_url = download_url[2:-26] # :hls:manifest.m3u8
+        download_url = str(link_data).replace("https://", "")
path: src/anime_parsers_ru/parser_kodik.py
user: login: mex1c0
body: Да понимаю о чем ты, но тут дело в том что кодик почему то при запросе сериалов возвращает:
Запрос:
get_link("1", "shikimori", 1, "559")  #Ковбой бибоп
Ответ:

"src": "//cloud.kodik-storage.com/useruploads/37065397-cf0c-4ea9-9269-718278450c77/8102f57af45ac4b1a8a51ce8cad7034f:2025030900/360.mp4:hls:manifest.m3u8",

А при запросе фильма например, возвращается:
Запрос:
get_link("32281", "shikimori", 0, "704") #Твое имя
Ответ:

"src": "https://cloud.kodik-cdn.com/animes/3ee7ee36ec0b55f9b4a15e045d5ff7f547019c8d/177d4f72cfb4dde202c4043eaa8eb076:2025030900/360.mp4:hls:manifest.m3u8"

Поэтому эти две строки необходимы, иначе будет разный выход, для фильмов и сериалов.
created_at: 2025-03-08T12:57:32Z
author_association: CONTRIBUTOR
diff_hunk: @@ -603,8 +603,9 @@ def get_link(self, id: str, id_type: str, seria_num: int, translation_id: str) -
 
         link_data, max_quality = self._get_link_with_data(video_type, video_hash, video_id, urlParams, script_url)
 
-        download_url = str(link_data).replace("https://", '')
-        download_url = download_url[2:-26] # :hls:manifest.m3u8
+        download_url = str(link_data).replace("https://", "")
path: src/anime_parsers_ru/parser_kodik.py
user: login: YaNesyTortiK
body: Тогда к этой вопросов нет
created_at: 2025-03-08T12:59:12Z
author_association: OWNER
diff_hunk: @@ -603,8 +603,9 @@ def get_link(self, id: str, id_type: str, seria_num: int, translation_id: str) -
 
         link_data, max_quality = self._get_link_with_data(video_type, video_hash, video_id, urlParams, script_url)
 
-        download_url = str(link_data).replace("https://", '')
-        download_url = download_url[2:-26] # :hls:manifest.m3u8
+        download_url = str(link_data).replace("https://", "")
path: src/anime_parsers_ru/parser_kodik.py
user: login: mex1c0
body: сорян затупил надо просто оставить так
```suggestion
        download_url = str(link_data).replace("https:", "")
```
created_at: 2025-03-08T13:04:21Z
author_association: CONTRIBUTOR

PR comments:
user: login: YaNesyTortiK
created_at: 2025-03-08T14:48:14Z
body: А че ж его так раздуло то, там всего строк 40 от силы должно было быть
author_association: OWNER
user: login: mex1c0
created_at: 2025-03-08T14:51:07Z
body: Я писал у себя локально похоже backfromatter отформатировал
author_association: CONTRIBUTOR
user: login: YaNesyTortiK
created_at: 2025-03-08T14:53:45Z
body: Я конечно не против автоформата, но потом не найти что после какого коммита появилось
author_association: OWNER
user: login: mex1c0
created_at: 2025-03-08T14:55:06Z
body: Сорян, я просто не заметил сейчас постараюсь все вернуть.
author_association: CONTRIBUTOR
user: login: YaNesyTortiK
created_at: 2025-03-08T14:56:50Z
body: Можешь не торопиться сильно, я только к ночи смогу замержить и сбилдить новую версию
author_association: OWNER