isaackogan/TikTokLive

TypeError: issubclass() arg 1 must be a class

Closed this issue · 25 comments

Traceback (most recent call last):
File "...\test.py", line 24, in
client.run()
File "...\test.py", line 21, in run
return self._client.run()
^^^^^^^^^^^^^^^^^^
File "...\Lib\site-packages\TikTokLive\client\client.py", line 170, in run
return self._asyncio_loop.run_until_complete(self.connect(**kwargs))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Program Files\Python311\Lib\asyncio\base_events.py", line 653, in run_until_complete
return future.result()
^^^^^^^^^^^^^^^
File "...\Lib\site-packages\TikTokLive\client\client.py", line 155, in connect
await task
File "...\Lib\site-packages\TikTokLive\client\client.py", line 203, in _client_loop
async for event in self._ws_loop(initial_response):
File "...\Lib\site-packages\TikTokLive\client\client.py", line 240, in _ws_loop
for event in self.parse_webcast_response(response_message):
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "...\Lib\site-packages\TikTokLive\client\client.py", line 322, in parse_webcast_response
proto_event: ProtoEvent = event_type().parse(response.payload)
^^^^^^^^^^^^
File "", line 16, in init
File "...\Lib\site-packages\betterproto_init
.py", line 631, in post_init
for field_name, meta in self.betterproto.meta_by_field_name.items():
^^^^^^^^^^^^^^^^^
File "...\Lib\site-packages\betterproto_init
.py", line 698, in getattribute
value = super().getattribute(name)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "...\Lib\site-packages\betterproto_init
.py", line 754, in betterproto
meta = ProtoClassMetadata(self.class)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "...\Lib\site-packages\betterproto_init
.py", line 568, in init
self.default_gen = self.get_default_gen(cls, fields)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "...\Lib\site-packages\betterproto_init
.py", line 575, in _get_default_gen
return {field.name: cls.get_field_default_gen(field) for field in fields}
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "...\Lib\site-packages\betterproto_init
.py", line 575, in
return {field.name: cls.get_field_default_gen(field) for field in fields}
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "...\Lib\site-packages\betterproto_init
.py", line 910, in _get_field_default_gen
elif issubclass(t, Enum):
^^^^^^^^^^^^^^^^^^^
TypeError: issubclass() arg 1 must be a class

I'm getting the exactly same error. Any fix for this ?

OS: Win 11
Python version: 3.10.10
Last version of TikTokLive

I need debug...or code...or anything...

This one might be occurring in Windows only, as I'm also using it.
It is pretty straight forward, after you start the client, after some seconds, it crashes with this error.

It might happen with any example code on this platform, I was using the client just to print the comments, while testing

This happened to me too. Windows. With Example codes.

Even exemples codes trigger this error, it might be due to some gifts I think... but not sure. If i'm starting this on a Live Stream with 1-2 people that doens't have activity it doesn't throw this error, but if i start it on a stream with a higher amount of people into the Live Stream it throws very quickly.

And also along that error is one more that is quickly throwed and randomly:

Traceback (most recent call last):
File "D:\BibFullTikTok\main.py", line 172, in
asyncio.run(check_loop())
File "C:\Program Files\Python310\lib\asyncio\runners.py", line 44, in run
return loop.run_until_complete(main)
File "C:\Program Files\Python310\lib\asyncio\base_events.py", line 649, in run_until_complete
return future.result()
File "D:\BibFullTikTok\main.py", line 166, in check_loop
await client.connect()
File "D:\BibFullTikTok\venv\lib\site-packages\TikTokLive\client\client.py", line 155, in connect
await task
File "D:\BibFullTikTok\venv\lib\site-packages\TikTokLive\client\client.py", line 203, in _client_loop
async for event in self._ws_loop(initial_response):
File "D:\BibFullTikTok\venv\lib\site-packages\TikTokLive\client\client.py", line 240, in _ws_loop
for event in self.parse_webcast_response(response_message):
File "D:\BibFullTikTok\venv\lib\site-packages\TikTokLive\client\client.py", line 322, in parse_webcast_response
proto_event: ProtoEvent = event_type().parse(response.payload)
File "D:\BibFullTikTok\venv\lib\site-packages\betterproto_init
.py", line 1015, in parse
value = self.postprocess_single(
File "D:\BibFullTikTok\venv\lib\site-packages\betterproto_init
.py", line 955, in postprocess_single
value = cls().parse(value)
File "D:\BibFullTikTok\venv\lib\site-packages\betterproto_init
.py", line 1015, in parse
value = self.postprocess_single(
File "D:\BibFullTikTok\venv\lib\site-packages\betterproto_init
.py", line 955, in postprocess_single
value = cls().parse(value)
File "D:\BibFullTikTok\venv\lib\site-packages\betterproto_init
.py", line 1008, in parse
decoded, pos = decode_varint(parsed.value, pos)
File "D:\BibFullTikTok\venv\lib\site-packages\betterproto_init
.py", line 487, in decode_varint
raise ValueError("Too many bytes when decoding varint.")
ValueError: Too many bytes when decoding varint.

Process finished with exit code 1

someone give me some code i can reproduce this with please

import aiohttp
import re
import datetime

from TikTokLive import TikTokLiveClient
from TikTokLive.client.logger import LogLevel
from TikTokLive.events import ConnectEvent, CommentEvent, GiftEvent, JoinEvent, LikeEvent

client: TikTokLiveClient = TikTokLiveClient(
    unique_id="@biancabiiiiib"
)



async def reg_db(user, nickname, avatar, level, following, followers, last_seen):
    url = 'http://localhost:5000/add_user'
    data = {
        'user': user,
        'nickname': nickname,
        'avatar': avatar,
        'level': level,
        'following_count': following,
        'follower_count': followers,
        'song_credits': 0,
        'last_seen': last_seen,
        'creation_date': datetime.datetime.now().isoformat()
    }
    async with aiohttp.ClientSession() as session:
        async with session.post(url, json=data) as response:
            return await response.text()


async def taptap(user, nickname, count):
    url = 'http://localhost:5000/add_taptap'
    data = {
        'user': user,
        'nickname': nickname,
        'count': count
    }
    async with aiohttp.ClientSession() as session:
        async with session.post(url, json=data) as response:
            return await response.text()


async def giftz(user, nickname, gift_name, count):
    url = 'http://localhost:5000/add_gift'
    data = {
        'user': user,
        'nickname': nickname,
        'gift_name': gift_name,
        'count': count
    }
    async with aiohttp.ClientSession() as session:
        async with session.post(url, json=data) as response:
            return await response.text()


async def play(user, video):
    url = f'http://localhost:5000/play/{user}/{video}'
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as response:
            return await response.text()


@client.on(ConnectEvent)
async def on_connect(event: ConnectEvent):
    client.logger.info(f"Connected to @{event.unique_id}!")


async def on_comment(event: CommentEvent) -> None:
    video_id_pattern = r"(?:v=|\/)([0-9A-Za-z_-]{10,12})(?:&|\/|$)"
    match = re.search(video_id_pattern, event.comment)
    if match:
        video_id = match.group(1)
        await play(event.user.unique_id, video_id)


@client.on(JoinEvent)
async def on_join(event: JoinEvent):
    #client.logger.info(f"Joined the live @{event.user.unique_id}!")
    badge_list = event.user.badge_list
    str_value = ""
    if badge_list and len(badge_list) > 0:
        str_value = badge_list[0].combine.str

    await reg_db(event.user.unique_id, event.user.nickname, event.user.avatar_thumb.url_list[2], str_value,
           event.user.follow_info.following_count, event.user.follow_info.follower_count,
           datetime.datetime.now().isoformat())


@client.on(LikeEvent)
async def on_like(event: LikeEvent):
    #client.logger.info(f"TapTap @{event.user.unique_id} -> {event.count}!")
    await taptap(event.user.unique_id, event.user.nickname, event.count)


@client.on(GiftEvent)
async def on_gift(event: GiftEvent):
    if event.gift.streakable and not event.streaking:
        await giftz(event.user.unique_id, event.user.nickname, event.gift.name, event.gift.count)

    # Non-streakable gift
    elif not event.gift.streakable:
        await giftz(event.user.unique_id, event.user.nickname, event.gift.name, event.gift.diamond_count)

        badge_list = event.user.badge_list
        str_value = ""
        if badge_list and len(badge_list) > 0:
            str_value = badge_list[0].combine.str

        await reg_db(event.user.unique_id, event.user.nickname, event.user.avatar_thumb.url_list[2], str_value,
                     event.user.follow_info.following_count, event.user.follow_info.follower_count,
                     datetime.datetime.now().isoformat())


async def check_loop():
    # Run 24/7
    while True:

        # Check if they're live
        while not await client.is_live():
            client.logger.info("Client is currently not live. Checking again in 60 seconds.")
            await asyncio.sleep(120)  # Spamming the endpoint will get you blocked

        # Connect once they become live
        client.logger.info("Requested client is live!")
        await client.connect()

client.add_listener(CommentEvent, on_comment)

if __name__ == '__main__':
    client.logger.setLevel(LogLevel.INFO.value)
    asyncio.run(check_loop())

And also the API :

from flask import Flask, request, jsonify
from flask_sqlalchemy import SQLAlchemy
from datetime import datetime, timedelta
import subprocess

app = Flask(__name__)
ytcast_path = r"C:\Users\twiff\Downloads\ytcast-v1.4.0-windows-amd64\ytcast.exe"

# Configure your MySQL database; replace USERNAME, PASSWORD, SERVER, and DBNAME with your actual database credentials
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+pymysql://root:@127.0.0.1/bib_live?charset=utf8mb4'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
app.config['SQLALCHEMY_ENGINE_OPTIONS'] = {
    'connect_args': {
        'init_command': "SET time_zone='+03:00';",  # Bucharest timezone offset
    }
}

db = SQLAlchemy(app)


class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    user = db.Column(db.String(80), nullable=False)
    nickname = db.Column(db.String(80), nullable=True)
    avatar = db.Column(db.String(200), nullable=True)
    level = db.Column(db.String(200), nullable=True, default=0)
    following_count = db.Column(db.Integer, default=0, nullable=True)
    follower_count = db.Column(db.Integer, default=0, nullable=True)
    song_credits = db.Column(db.Integer, default=0)
    last_seen = db.Column(db.DateTime, default=datetime.utcnow)
    creation_date = db.Column(db.DateTime, default=datetime.utcnow)

    def __repr__(self):
        return '<User %r>' % self.nickname


class TapTap(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    user = db.Column(db.String(80), nullable=False)
    nickname = db.Column(db.String(80), nullable=True)
    count = db.Column(db.Integer, nullable=True)
    creation_date = db.Column(db.DateTime, default=datetime.utcnow)

    def __repr__(self):
        return '<User %r>' % self.nickname


class Gifts(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    user = db.Column(db.String(80), nullable=False)
    nickname = db.Column(db.String(80), nullable=True)
    gift_name = db.Column(db.String(200), nullable=True)
    count = db.Column(db.Integer, nullable=True)
    creation_date = db.Column(db.DateTime, default=datetime.utcnow)

    def __repr__(self):
        return '<User %r>' % self.nickname


class Play(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    user = db.Column(db.String(80), nullable=False)
    nickname = db.Column(db.String(80), nullable=True)
    creation_date = db.Column(db.DateTime, default=datetime.utcnow)

    def __repr__(self):
        return '<User %r>' % self.nickname

#with app.app_context():
#    db.create_all()


@app.route('/add_user', methods=['POST'])
def add_or_update_user():
    data = request.json
    user_identifier = data.get('user')  # Assuming 'user' is a unique identifier

    # Query the database for the user
    existing_user = User.query.filter_by(user=user_identifier).first()

    if existing_user:
        # If the user exists, update the last_seen time
        existing_user.last_seen = datetime.utcnow()
        db.session.commit()
        return jsonify({'message': 'User last_seen updated successfully!'}), 200
    else:
        # If the user does not exist, add a new user
        new_user = User(
            user=data.get('user'),
            nickname=data.get('nickname'),
            avatar=data.get('avatar'),
            level=data.get('level', 1),
            following_count=data.get('following_count', 0),
            follower_count=data.get('follower_count', 0),
            song_credits=data.get('song_credits', 0),
            last_seen=data.get('last_seen', datetime.utcnow()),
            creation_date=data.get('creation_date', datetime.utcnow())
        )
        db.session.add(new_user)
        db.session.commit()
        return jsonify({'message': 'User added successfully!'}), 201


@app.route('/add_taptap', methods=['POST'])
def add_taptapz():
    data = request.json
    new_taptap = TapTap(
        user=data.get('user'),
        nickname=data.get('nickname'),
        count=data.get('count'),
        creation_date=data.get('creation_date', datetime.utcnow())
    )
    db.session.add(new_taptap)
    db.session.commit()
    return jsonify({'message': 'User added successfully!'}), 201


@app.route('/add_gift', methods=['POST'])
def add_giftz():
    data = request.json
    new_gift = Gifts(
        user=data.get('user'),
        nickname=data.get('nickname'),
        gift_name=data.get('gift_name'),
        count=data.get('count'),
        creation_date=data.get('creation_date', datetime.utcnow())
    )
    db.session.add(new_gift)
    db.session.commit()
    return jsonify({'message': 'User added successfully!'}), 201


@app.route('/update_play/<int:play_id>', methods=['POST'])
def update_play(play_id):
    play_to_update = Play.query.get(play_id)

    if play_to_update:
        # Update the creation_date field to the current date and time
        play_to_update.creation_date = datetime.utcnow()

        db.session.commit()
        return jsonify({'message': 'Play creation date updated successfully!'}), 200
    else:
        return jsonify({'message': 'Play record not found.'}), 404


@app.route('/play/<user_identifier>/<video>', methods=['GET'])
def check_song_credits_and_play(user_identifier, video):
    # Query the database for the user by the unique identifier
    user = User.query.filter_by(user=user_identifier).first()
    print(user)
    if user:
        # Check if the user has song credits
        if user.song_credits <= 0:
            return jsonify({
                'message': 'User has no song credits.',
                'song_credits': user.song_credits,
                'allow_play': False
            }), 200

        # Get the most recent item from the Play table for the user
        last_play = Play.query.filter_by(id=1).order_by(Play.creation_date.desc()).first()
        print(last_play)
        if last_play:
            da = datetime.utcnow() + timedelta(hours=2)
            time_diff = da - last_play.creation_date
            # Check if the time difference is less than 60 seconds
            print(time_diff)
            print(time_diff.total_seconds())
            if time_diff.total_seconds() < 60:
                return jsonify({
                    'message': 'Cannot play the song. Wait until 60 seconds have passed since the last play.',
                    'allow_play': False
                }), 200

        # Deduct one song credit because the user is allowed to play the song
        user.song_credits -= 1
        command = [ytcast_path, "-d", "e4e54d33", "https://youtu.be/"+ video]
        try:
            result = subprocess.run(command, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
            print("Output:", result.stdout)
        except subprocess.CalledProcessError as e:
            print("Error:", e.stderr)
        last_play.creation_date = datetime.utcnow() + timedelta(hours=2)
        db.session.commit()
        return jsonify({
            'message': 'Song played successfully. One credit deducted.',
            'song_credits': user.song_credits,
            'allow_play': True
        }), 200
    else:
        return jsonify({'message': 'User not found.'}), 404


if __name__ == '__main__':
    app.run(debug=True)

I commented out:
#client.add_listener(GiftEvent, on_gift)
and still got the error. so its not something with gifts..

With how much stuff was added to proto in this latest update, I wouldn't be surprised if there's data types returned by tiktok that are being improperly handled from the webcast response.

The following code will crash within seconds if you insert a unique id of a live user that has a lot of events being passed:

from TikTokLive.client.client import TikTokLiveClient
from TikTokLive.client.logger import LogLevel
from TikTokLive.events import ConnectEvent

client: TikTokLiveClient = TikTokLiveClient(
    unique_id="@" # Insert a unique id of a live user that has a lot of events being passed
)


@client.on(ConnectEvent)
async def on_connect(event: ConnectEvent):
    client.logger.info(f"Connected to @{event.unique_id}!")

if __name__ == '__main__':
    # Enable debug info
    client.logger.setLevel(LogLevel.INFO.value)

    # Connect
    client.run()

The interim solution is there should be a proper error handler that doesn't crash the client LOL...then we can fix this specific proto error

The interim solution is there should be a proper error handler that doesn't crash the client LOL...then we can fix this specific proto error

You should probably add exception handling that captures the payload so it's easier to find which ones the schema fails to parse properly at the very least.

Unfortunately I will internally combust it if I have to add another thing onto my plate right now. Waaaay too stressed for this. PRs welcome or I'll get to it when I have a sec.

Any update on this error ? :<

Yes,

I am embarassed. This was a stupid issue.

The internal Type I created "MessageType" shadowed a TikTok proto field also called "MessageType". This caused an instance of the TypeVar to be passed to an is_subclass in the proto type-checker, throwing the familiar and horrifying error.

Release: https://github.com/isaackogan/TikTokLive/releases/tag/v6.0.1-post1
Commit: 5faa492
Install: pip install TikTokLive==6.0.1.post1

Will close this in 2 days if the issue is resolved.

Thank you for the fix. I'm running it now, it looks like it was fixed and it does work. I'll keep an eye on it and if theres any other bug i'll report it.

Also check the error with "AttributeError: 'GiftEvent' object has no attribute 'streakable'"

Exception in callback AsyncIOEventEmitter._emit_run.<locals>.callback(<Task finishe...streakable'")>) at D:\BibFullTikTok\venv\lib\site-packages\pyee\asyncio.py:69
handle: <Handle AsyncIOEventEmitter._emit_run.<locals>.callback(<Task finishe...streakable'")>) at D:\BibFullTikTok\venv\lib\site-packages\pyee\asyncio.py:69>
Traceback (most recent call last):
  File "C:\Program Files\Python310\lib\asyncio\events.py", line 80, in _run
    self._context.run(self._callback, *self._args)
  File "D:\BibFullTikTok\venv\lib\site-packages\pyee\asyncio.py", line 77, in callback
    self.emit("error", exc)
  File "D:\BibFullTikTok\venv\lib\site-packages\pyee\base.py", line 211, in emit
    self._emit_handle_potential_error(event, args[0] if args else None)
  File "D:\BibFullTikTok\venv\lib\site-packages\pyee\base.py", line 169, in _emit_handle_potential_error
    raise error
  File "D:\BibFullTikTok\main.py", line 111, in on_gift
    if event.gift.streakable and not event.streaking:
  File "D:\BibFullTikTok\venv\lib\site-packages\betterproto\__init__.py", line 698, in __getattribute__
    value = super().__getattribute__(name)
  File "D:\BibFullTikTok\venv\lib\site-packages\TikTokLive\events\proto_events.py", line 99, in streaking
    if not self.streakable:
  File "D:\BibFullTikTok\venv\lib\site-packages\betterproto\__init__.py", line 698, in __getattribute__
    value = super().__getattribute__(name)
AttributeError: 'GiftEvent' object has no attribute 'streakable'

Also check the error with "AttributeError: 'GiftEvent' object has no attribute 'streakable'"

Way ahead of you.

pip install TikTokLive==6.0.1.post2

Found another one after 13 minutes of running it

Traceback (most recent call last):
  File "D:\BibFullTikTok\venv\lib\site-packages\websockets\legacy\protocol.py", line 963, in transfer_data
    message = await self.read_message()
  File "D:\BibFullTikTok\venv\lib\site-packages\websockets\legacy\protocol.py", line 1033, in read_message
    frame = await self.read_data_frame(max_size=self.max_size)
  File "D:\BibFullTikTok\venv\lib\site-packages\websockets\legacy\protocol.py", line 1108, in read_data_frame
    frame = await self.read_frame(max_size)
  File "D:\BibFullTikTok\venv\lib\site-packages\websockets\legacy\protocol.py", line 1165, in read_frame
    frame = await Frame.read(
  File "D:\BibFullTikTok\venv\lib\site-packages\websockets\legacy\framing.py", line 68, in read
    data = await reader(2)
  File "C:\Program Files\Python310\lib\asyncio\streams.py", line 705, in readexactly
    raise exceptions.IncompleteReadError(incomplete, n)
asyncio.exceptions.IncompleteReadError: 0 bytes read on a total of 2 expected bytes

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "D:\BibFullTikTok\main.py", line 147, in <module>
    asyncio.run(check_loop())
  File "C:\Program Files\Python310\lib\asyncio\runners.py", line 44, in run
    return loop.run_until_complete(main)
  File "C:\Program Files\Python310\lib\asyncio\base_events.py", line 649, in run_until_complete
    return future.result()
  File "D:\BibFullTikTok\main.py", line 141, in check_loop
    await client.connect()
  File "D:\BibFullTikTok\venv\lib\site-packages\TikTokLive\client\client.py", line 156, in connect
    await task
  File "D:\BibFullTikTok\venv\lib\site-packages\TikTokLive\client\client.py", line 204, in _client_loop
    async for event in self._ws_loop(initial_response):
  File "D:\BibFullTikTok\venv\lib\site-packages\TikTokLive\client\client.py", line 228, in _ws_loop
    async for response_message in self._ws.connect(*self._build_connect_info(initial_response)):
  File "D:\BibFullTikTok\venv\lib\site-packages\TikTokLive\client\ws\ws_client.py", line 145, in connect
    async for webcast_message in self.connect_loop(uri, headers):
  File "D:\BibFullTikTok\venv\lib\site-packages\TikTokLive\client\ws\ws_client.py", line 181, in connect_loop
    async for message in websocket:
  File "D:\BibFullTikTok\venv\lib\site-packages\websockets\legacy\protocol.py", line 498, in __aiter__
    yield await self.recv()
  File "D:\BibFullTikTok\venv\lib\site-packages\websockets\legacy\protocol.py", line 568, in recv
    await self.ensure_open()
  File "D:\BibFullTikTok\venv\lib\site-packages\websockets\legacy\protocol.py", line 939, in ensure_open
    raise self.connection_closed_exc()
websockets.exceptions.ConnectionClosedError: no close frame received or sent

Process finished with exit code 1

(venv) D:\BibFullTikTok>pip install TikTokLive==6.0.1.post2
ERROR: Ignored the following yanked versions: 4.3.2
ERROR: Could not find a version that satisfies the requirement TikTokLive==6.0.1.post2 (from versions: 0.0.1, 0.6.9, 0.7.0, 0.7.5, 0.8.0, 0.8.2, 0.8.5, 0.8.6, 0.8.9, 4.2.0, 4.2.5, 4.2.6, 4.3.0, 4.3.3, 4.3.5, 4.3.6, 4.3.7, 4.3.8, 4.5.0, 4.5.1, 4.5.2, 5.0.0, 5.0.1, 5.0.5, 5.0.6, 5.0.7, 5.0.8, 6.0.0rc1, 6.0.0, 6.0.0.post1, 6.0.1, 6.0.1.post1, 6.0.2.post1)
ERROR: No matching distribution found for TikTokLive==6.0.1.post2

My dumb-ass self named it 6.0.2.post1...

Found another one after 13 minutes of running it

Traceback (most recent call last):
  File "D:\BibFullTikTok\venv\lib\site-packages\websockets\legacy\protocol.py", line 963, in transfer_data
    message = await self.read_message()
  File "D:\BibFullTikTok\venv\lib\site-packages\websockets\legacy\protocol.py", line 1033, in read_message
    frame = await self.read_data_frame(max_size=self.max_size)
  File "D:\BibFullTikTok\venv\lib\site-packages\websockets\legacy\protocol.py", line 1108, in read_data_frame
    frame = await self.read_frame(max_size)
  File "D:\BibFullTikTok\venv\lib\site-packages\websockets\legacy\protocol.py", line 1165, in read_frame
    frame = await Frame.read(
  File "D:\BibFullTikTok\venv\lib\site-packages\websockets\legacy\framing.py", line 68, in read
    data = await reader(2)
  File "C:\Program Files\Python310\lib\asyncio\streams.py", line 705, in readexactly
    raise exceptions.IncompleteReadError(incomplete, n)
asyncio.exceptions.IncompleteReadError: 0 bytes read on a total of 2 expected bytes

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "D:\BibFullTikTok\main.py", line 147, in <module>
    asyncio.run(check_loop())
  File "C:\Program Files\Python310\lib\asyncio\runners.py", line 44, in run
    return loop.run_until_complete(main)
  File "C:\Program Files\Python310\lib\asyncio\base_events.py", line 649, in run_until_complete
    return future.result()
  File "D:\BibFullTikTok\main.py", line 141, in check_loop
    await client.connect()
  File "D:\BibFullTikTok\venv\lib\site-packages\TikTokLive\client\client.py", line 156, in connect
    await task
  File "D:\BibFullTikTok\venv\lib\site-packages\TikTokLive\client\client.py", line 204, in _client_loop
    async for event in self._ws_loop(initial_response):
  File "D:\BibFullTikTok\venv\lib\site-packages\TikTokLive\client\client.py", line 228, in _ws_loop
    async for response_message in self._ws.connect(*self._build_connect_info(initial_response)):
  File "D:\BibFullTikTok\venv\lib\site-packages\TikTokLive\client\ws\ws_client.py", line 145, in connect
    async for webcast_message in self.connect_loop(uri, headers):
  File "D:\BibFullTikTok\venv\lib\site-packages\TikTokLive\client\ws\ws_client.py", line 181, in connect_loop
    async for message in websocket:
  File "D:\BibFullTikTok\venv\lib\site-packages\websockets\legacy\protocol.py", line 498, in __aiter__
    yield await self.recv()
  File "D:\BibFullTikTok\venv\lib\site-packages\websockets\legacy\protocol.py", line 568, in recv
    await self.ensure_open()
  File "D:\BibFullTikTok\venv\lib\site-packages\websockets\legacy\protocol.py", line 939, in ensure_open
    raise self.connection_closed_exc()
websockets.exceptions.ConnectionClosedError: no close frame received or sent

Process finished with exit code 1

Did the stream stop perhaps? This error suggests the connection was closed by TikTok "suddenly".

Nope the stream was still active, watching it.

Welp, sorry to say I have put in all the time I have today. I will float this by the folks at TikTokLiveJava and see if they've seen this...that's all I can do.

Reading up on it, what you are describing seems like a client issue, not a TikTokLive issue.

See here: https://websockets.readthedocs.io/en/stable/faq/common.html#what-does-connectionclosederror-no-close-frame-received-or-sent-mean

I'm going to close this as resolved. Please open another issue @Unchangeable if you continue to face your separate issue, so I can fix it.