Skip to content

Commit

Permalink
v0.8.2 🎊
Browse files Browse the repository at this point in the history
  • Loading branch information
isaackogan committed Apr 12, 2022
1 parent 14663d6 commit 8d5f204
Show file tree
Hide file tree
Showing 10 changed files with 245 additions and 115 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ dist
venv
TikTokLive.egg-info
requirements.txt
testing.py
36 changes: 25 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ A python library to receive and decode livestream events such as comments and gi
connects to the WebCast service using only a user's `unique_id` and allows you to join your livestream as well as that of other streamers. No credentials are required to use TikTok-Live-Connector.

This library is a nearly 1:1 python implementation of the Javascript
[TikTok-Livestream-Chat-Connector](https://github.com/zerodytrash/TikTok-Livestream-Chat-Connector)
[TikTok-Live-Connector](https://github.com/zerodytrash/TikTok-Livestream-Chat-Connector)
by [@zerodytrash](https://github.com/zerodytrash/) meant to serve as an alternative for users who feel more comfortable working in Python.

This is **not** an official API. It's a reverse engineering and research project.
Expand Down Expand Up @@ -43,6 +43,13 @@ and will take time to produce. You will be updated through e-mail about their pr
GoFundMe donations are in **CAD** (Canadian Dollars), because I am a Canadian. The number you type into GoFundMe **MUST** be the value listed in CAD. If the item costs $5 USD, you must put $7 into the GoFundMe, as that
is the Canadian conversion. If you under-pay, you **will be** asked to transfer the difference via PayPal.

### Thermal Printing

| Item | Price (USD) | Price (CAD) | Description |
|------------|-------------|-------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Standard | ~ $30 | CA$ 40 | Print events to a Thermal Printer. For example, printing a comment with the user's name, message, and profile picture. This is a HIGHLY advanced wrapper allowing you to print to a USB printer (must have ESCPOS) like it's nothing. |
| Setup Help | ~ $10 | CA$ 15 | Up to 45 minutes of 1-on-1 setup time. I will conference with you in a voice call on Discord and remotely control your PC via AnyDesk. I will install the software on the spot, with you. Must have a working phone with Discord, so I can see the printer in case I need to. |_

### Regular Items

| Item | Price (USD) | Price (CAD) | Description |
Expand All @@ -54,13 +61,6 @@ is the Canadian conversion. If you under-pay, you **will be** asked to transfer
| Custom Option | Contact for a quote | Contact for a quote | See something not on here? Contact for a custom quote. Please not that we reserve the right to add your commission to this shop for others to purchase after it is done. The goal of this shop is to support charity, so the more purchasable items, the better. |
| Donation | Choose your contribution | Choose your contribution | Donate to the fund for the sake of it. Every dollar makes a difference. |

### Thermal Printing

| Item | Price (USD) | Price (CAD) | Description |
|------------|-------------|-------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Standard | ~ $30 | CA$ 40 | Print events to a Thermal Printer. For example, printing a comment with the user's name, message, and profile picture. This is a HIGHLY advanced wrapper allowing you to print to a USB printer (must have ESCPOS) like it's nothing. |
| Setup Help | ~ $10 | CA$ 15 | Up to 45 minutes of 1-on-1 setup time. I will conference with you in a voice call on Discord and remotely control your PC via AnyDesk. I will install the software on the spot, with you. Must have a working phone with Discord, so I can see the printer in case I need to. |

## Getting started

1. Install the module via pip
Expand Down Expand Up @@ -223,20 +223,34 @@ async def on_join(event: JoinEvent):
### `gift`

Triggered every time a gift arrives. Extra information can be gleamed off the `available_gifts` client attribute.
> **NOTE:** Users have the capability to send gifts in a streak. This increases the `data.gift.repeat_count` value until the user terminates the streak. During this time new gift events are triggered again and again with an increased `data.gift.repeat_count` value. It should be noted that after the end of the streak, another gift event is triggered, which signals the end of the streak via `data.gift.repeat_end`:`1`. This applies only to gifts with `data.gift.gift_type`:`1`. This means that even if the user sends a `gift_type`:`1` gift only once, you will receive the event twice. Once with `repeat_end`:`0` and once with `repeat_end`:`1`. Therefore, the event should be handled as follows:
> **NOTE:** Users have the capability to send gifts in a streak. This increases the `data.gift.repeat_count` value until the user terminates the streak. During this time new gift events are triggered again and again with an increased `data.gift.repeat_count` value. It should be noted that after the end of the streak, another gift event is triggered, which signals the end of the streak via `data.gift.repeat_end`:`1`. This applies only to gifts with `data.gift.gift_type`:`1`. This means that even if the user sends a `gift_type`:`1` gift only once, you will receive the event twice. Once with `repeat_end`:`0` and once with `repeat_end`:`1`. Therefore, the event should be handled as follows in one of TWO ways:
```python
@client.on("gift")
async def on_gift(event: GiftEvent):
# If it's type 1 and the streak is over
if event.gift.gift_type == 1 and event.gift.repeat_end == 1:
print(f"{event.user.uniqueId} sent {event.gift.repeat_count}x \"{event.gift.extended_gift.name}\"")
if event.gift.gift_type == 1:
if event.gift.repeat_end == 1:
print(f"{event.user.uniqueId} sent {event.gift.repeat_count}x \"{event.gift.extended_gift.name}\"")

# It's not type 1, which means it can't have a streak & is automatically over
elif event.gift.gift_type != 1:
print(f"{event.user.uniqueId} sent \"{event.gift.extended_gift.name}\"")
```

```python
@client.on("gift")
async def on_gift(event: GiftEvent):
# If it's type 1 and the streak is over
if event.gift.streakable:
if not event.gift.streaking:
print(f"{event.user.uniqueId} sent {event.gift.repeat_count}x \"{event.gift.extended_gift.name}\"")

# It's not type 1, which means it can't have a streak & is automatically over
else:
print(f"{event.user.uniqueId} sent \"{event.gift.extended_gift.name}\"")
```

### `follow`

Triggered every time someone follows the streamer.
Expand Down
1 change: 0 additions & 1 deletion TikTokLive/client/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,6 @@ async def _connect(self) -> str:
message = str(ex)

self.__connecting = False
await self._http.session.close()
raise FailedConnection(message)

def _disconnect(self) -> None:
Expand Down
14 changes: 7 additions & 7 deletions TikTokLive/client/client.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import json
from typing import Optional, Type

from dacite import from_dict
Expand Down Expand Up @@ -29,7 +28,7 @@ def __init__(self, unique_id: str, debug: bool = False, **options):

BaseClient.__init__(self, unique_id, **options)
AsyncIOEventEmitter.__init__(self, self.loop)

async def _connect(self) -> str:
"""
Wrap connection in a connect event
Expand Down Expand Up @@ -92,7 +91,9 @@ def __parse_message(self, webcast_message: dict) -> Optional[AbstractEvent]:

# Viewer update
if webcast_message["type"] == "WebcastRoomUserSeqMessage":
self._viewer_count = webcast_message["viewerCount"]
count: Optional[int] = webcast_message.get("viewerCount")
self._viewer_count = count if count is not None else self._viewer_count

return from_dict_plus(
ViewerCountUpdateEvent,
webcast_message
Expand All @@ -111,12 +112,11 @@ def __parse_message(self, webcast_message: dict) -> Optional[AbstractEvent]:
self._disconnect()
return LiveEndEvent()

# Gift
# Gift Handling
if webcast_message["type"] == "WebcastGiftMessage":
gift: Optional[str] = webcast_message.get("giftJson")
webcast_message["gift"] = json.loads(gift)
webcast_message["gift"] = webcast_message
event: GiftEvent = from_dict(GiftEvent, webcast_message)
event.gift.extended_gift = self.available_gifts.get(event.gift.gift_id)
event.gift.extended_gift = self.available_gifts.get(event.gift.giftId)
event._as_dict = webcast_message
return event

Expand Down
16 changes: 9 additions & 7 deletions TikTokLive/client/http.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import urllib.parse
from typing import Dict, Union, Optional

import aiohttp as aiohttp
from aiohttp import ClientSession

from TikTokLive.client.proxy import ProxyContainer
from TikTokLive.proto.utilities import deserialize_message
Expand Down Expand Up @@ -43,7 +43,6 @@ def __init__(self, headers: Optional[Dict[str, str]] = None, timeout_ms: Optiona
"""

self.timeout: int = int((timeout_ms if isinstance(timeout_ms, int) else 10000) / 1000)
self.session: aiohttp.ClientSession = aiohttp.ClientSession(trust_env=trust_env)
self.proxy_container: ProxyContainer = proxy_container if proxy_container is not None else ProxyContainer(enabled=False)
self.headers: Dict[str, str] = {
**self.DEFAULT_REQUEST_HEADERS,
Expand All @@ -60,10 +59,11 @@ async def __aiohttp_get_bytes(self, url: str, params: dict = None) -> bytes:
:raises: asyncio.TimeoutError
"""

request_url: str = f"{url}?{urllib.parse.urlencode(params if params is not None else dict())}"
async with self.session.get(request_url, headers=self.headers, timeout=self.timeout, proxy=self.proxy_container.get()) as request:
return await request.read()

async with ClientSession() as session:
async with session.get(request_url, headers=self.headers, timeout=self.timeout, proxy=self.proxy_container.get()) as request:
return await request.read()

async def __aiohttp_get_json(self, url: str, params: dict) -> dict:
"""
Expand All @@ -77,8 +77,10 @@ async def __aiohttp_get_json(self, url: str, params: dict) -> dict:
"""

request_url: str = f"{url}?{urllib.parse.urlencode(params if params is not None else dict())}"
async with self.session.get(request_url, headers=self.headers, timeout=self.timeout, proxy=self.proxy_container.get()) as request:
return await request.json()

async with ClientSession() as session:
async with session.get(request_url, headers=self.headers, timeout=self.timeout, proxy=self.proxy_container.get()) as request:
return await request.json()

async def get_livestream_page_html(self, unique_id: str) -> str:
"""
Expand Down
20 changes: 19 additions & 1 deletion TikTokLive/proto/tiktok_schema.proto
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,25 @@ message WebcastMemberMessage {

message WebcastGiftMessage {
User user = 7;
string giftJson = 22;

// New Data Structure since 04/2022
int32 giftId = 2;
int32 repeatCount = 5;
int32 repeatEnd = 9;

WebcastGiftMessageGiftDetails giftDetails = 15;
}

message WebcastGiftMessageGiftDetails {
WebcastGiftMessageGiftImage giftImage = 1;
string giftName = 16;
string describe = 2;
int32 giftType = 11;
int32 diamondCount = 12;
}

message WebcastGiftMessageGiftImage {
string giftPictureUrl = 1;
}


Expand Down
Loading

0 comments on commit 8d5f204

Please sign in to comment.