stefanodvx / crunpyroll

Async API wrapper for Crunchyroll
MIT License
74 stars 21 forks source link

You need to implement this function to fix "TOO_MANY_ACTIVE_STREAMS" issue @inks007 #21

Closed fabrebatalla18 closed 7 months ago

fabrebatalla18 commented 7 months ago

When you request more than 20 streams in CR they blocked temporally the account because right no crunpyroll is not closing streaming after requested, so you need to implement this function (@inks007): https://github.com/smirgol/plugin.video.crunchyroll/commit/23ade04177096fa890a0b9617efcc7abc2f112aa

I tried to do it with this method:

from crunpyroll import enums
from crunpyroll import types
import requests
import crunpyroll

class ClearActiveStream:
    async def clear_active_stream(self: "crunpyroll.Client", episode_id: str, token: str):
        if not episode_id or not token:
            return

        try:
            await self.session.retrieve()
            response = await self.api_request(
                method="DELETE",
                endpoint=f"v1/token/{episode_id}/{token}",
                host=enums.APIHost.PLAY_SERVICE
            )
        except requests.exceptions.RequestException as e:
            # catch timeout or any other possible exception
            print(e)
            return

and then on client.py this change:

    @staticmethod
    def parse_response(response: httpx.Response) -> Optional[Union[Dict, str]]:
        status_code = response.status_code
        text_content = response.text
        message = f"[{status_code}] {text_content}"
        try:
            content = response.json()
        except json.JSONDecodeError:
            content = response.text
        if status_code != 200:
            if status_code == 204:
                return content
            raise CrunpyrollException(message)
        return content

It is possible that there is a better solution, perhaps you who have more experience modifying crunpyroll know how to improve it.

inks007 commented 7 months ago

Thanks for the heads up. The delete method seems to work, but the response.text seems to always be empty even if you pass incorrect tokens...

Now I run into HTTP 503 Forbidden when accessing the segments of the following stream too quickly. Perhaps due to rate-limiting on the server-side.

fabrebatalla18 commented 7 months ago

Thanks for the heads up. The delete method seems to work, but the response.text seems to always be empty even if you pass incorrect tokens...

Now I run into HTTP 503 Forbidden when accessing the segments of the following stream too quickly. Perhaps due to rate-limiting on the server-side.

I use delete active streams after i download the files of that episode, i think is the correct way to dont get any error, dont you think?

Vorffeed commented 7 months ago

I have a problem when I launch the episode download and put as fabrebatalla18 says await client.delete_active_stream behind the download function to run when it finishes I get the following error, what exactly am I doing wrong? Do I have to call some missing import? Do I have to make another way the call to perform the download, or it is not done this way?

Sorry if you don't understand me well, since I use translator, and I don't understand much of how phyton works since I am trying to adapt to my needs a script that was given to me.

import crunpyroll
import asyncio
import sys
import os
import requests
from pywidevine.cdm import Cdm
from pywidevine.pssh import PSSH
from pywidevine.device import Device
from uuid import uuid4

client = crunpyroll.Client(
*
*
*
)

async def main():
    await client.start()
*
*
*
    #We launch the download
    # yt_dlp_url contains download instructions with yt_dlp.exe
    os.system(yt_dlp_url)

    # We wait for it to finish downloading
    await client.delete_active_stream(
    streams.media_id,
    token=streams.token
    )

    # Get Widevine PSSH from manifest
*
*
*
    cdm.parse_license(session_id, license)
    print()
    for key in cdm.get_keys(session_id, "CONTENT"):
        print((f"{key.kid.hex}:{key.key.hex()}"),file=open('./Temporales/Key.txt','a'))
    cdm.close(session_id)

asyncio.run(main())
  File "E:\Crunchy Videos\Temporales\crunpyroll_cr2.py", line 117, in <module>
    asyncio.run(main())
  File "C:\Python311\Lib\asyncio\runners.py", line 190, in run
    return runner.run(main)
           ^^^^^^^^^^^^^^^^
  File "C:\Python311\Lib\asyncio\runners.py", line 118, in run
    return self._loop.run_until_complete(task)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Python311\Lib\asyncio\base_events.py", line 653, in run_until_complete
    return future.result()
           ^^^^^^^^^^^^^^^
  File "E:\Crunchy Videos\Temporales\crunpyroll_cr2.py", line 106, in main
    license = await client.get_license(
              ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Python311\Lib\site-packages\crunpyroll\methods\get_license.py", line 34, in get_license
    response = await self.api_request(
               ^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Python311\Lib\site-packages\crunpyroll\client.py", line 117, in api_request
    return Client.parse_response(response, method=method)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Python311\Lib\site-packages\crunpyroll\client.py", line 90, in parse_response
    raise CrunpyrollException(message)
crunpyroll.errors.CrunpyrollException: [401] "Unauthorized"`
Vorffeed commented 7 months ago

I update, if I put it as in the example of @inks007 it works, but before it gave me the same error 503 that's why I changed as fabrebatalla18 said, but I get that error >< testing now that it goes as inks017 has put it I have tried to download 4 files in a row and I see that I get 4 sessions in connected devices, so I do not know if it really works or is something else, because this should no longer happen, the multiple sessions, right?

    cdm.parse_license(session_id, license)
    print()
    for key in cdm.get_keys(session_id, "CONTENT"):
        print((f"{key.kid.hex}:{key.key.hex()}"),file=open('./Temporales/Key.txt','a'))
    cdm.close(session_id)

    # We wait for it to finish downloading
    await client.delete_active_stream(
    streams.media_id,
    token=streams.token
    )

asyncio.run(main())

Captra

inks007 commented 7 months ago

Where the line says device_id=str(uuid4()),, replace str(uuid4()) with a fixed UUID.

You can generate a UUID below:

from uuid import uuid4
print(str(uuid4()))
Vorffeed commented 7 months ago

Yes, in the end I did just that to have a uuid and leave it fixed.