-
-
Notifications
You must be signed in to change notification settings - Fork 1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* feat: add playlist class
- Loading branch information
Showing
5 changed files
with
262 additions
and
28 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,167 @@ | ||
from __future__ import annotations | ||
from typing import TYPE_CHECKING, ClassVar, Iterator, Optional | ||
from ..exceptions import InvalidResponseException | ||
|
||
if TYPE_CHECKING: | ||
from ..tiktok import TikTokApi | ||
from .video import Video | ||
from .user import User | ||
|
||
|
||
class Playlist: | ||
""" | ||
A TikTok video playlist. | ||
Example Usage: | ||
.. code-block:: python | ||
playlist = api.playlist(id='7426714779919797038') | ||
""" | ||
|
||
parent: ClassVar[TikTokApi] | ||
|
||
id: Optional[str] | ||
"""The ID of the playlist.""" | ||
name: Optional[str] | ||
"""The name of the playlist.""" | ||
video_count: Optional[int] | ||
"""The video count of the playlist.""" | ||
creator: Optional[User] | ||
"""The creator of the playlist.""" | ||
cover_url: Optional[str] | ||
"""The cover URL of the playlist.""" | ||
as_dict: dict | ||
"""The raw data associated with this Playlist.""" | ||
|
||
def __init__( | ||
self, | ||
id: Optional[str] = None, | ||
data: Optional[dict] = None, | ||
): | ||
""" | ||
You must provide the playlist id or playlist data otherwise this | ||
will not function correctly. | ||
""" | ||
|
||
if id is None and data.get("id") is None: | ||
raise TypeError("You must provide id parameter.") | ||
|
||
self.id = id | ||
|
||
if data is not None: | ||
self.as_dict = data | ||
self.__extract_from_data() | ||
|
||
async def info(self, **kwargs) -> dict: | ||
""" | ||
Returns a dictionary of information associated with this Playlist. | ||
Returns: | ||
dict: A dictionary of information associated with this Playlist. | ||
Raises: | ||
InvalidResponseException: If TikTok returns an invalid response, or one we don't understand. | ||
Example Usage: | ||
.. code-block:: python | ||
user_data = await api.playlist(id='7426714779919797038').info() | ||
""" | ||
|
||
id = getattr(self, "id", None) | ||
if not id: | ||
raise TypeError( | ||
"You must provide the playlist id when creating this class to use this method." | ||
) | ||
|
||
url_params = { | ||
"mixId": id, | ||
"msToken": kwargs.get("ms_token"), | ||
} | ||
|
||
resp = await self.parent.make_request( | ||
url="https://www.tiktok.com/api/mix/detail/", | ||
params=url_params, | ||
headers=kwargs.get("headers"), | ||
session_index=kwargs.get("session_index"), | ||
) | ||
|
||
if resp is None: | ||
raise InvalidResponseException(resp, "TikTok returned an invalid response.") | ||
|
||
self.as_dict = resp["mixInfo"] | ||
self.__extract_from_data() | ||
return resp | ||
|
||
async def videos(self, count=30, cursor=0, **kwargs) -> Iterator[Video]: | ||
""" | ||
Returns an iterator of videos in this User's playlist. | ||
Returns: | ||
Iterator[dict]: An iterator of videos in this User's playlist. | ||
Raises: | ||
InvalidResponseException: If TikTok returns an invalid response, or one we don't understand. | ||
Example Usage: | ||
.. code-block:: python | ||
playlist_videos = await api.playlist(id='7426714779919797038').videos() | ||
""" | ||
id = getattr(self, "id", None) | ||
if id is None or id == "": | ||
await self.info(**kwargs) | ||
|
||
found = 0 | ||
while found < count: | ||
params = { | ||
"mixId": id, | ||
"count": min(count, 30), | ||
"cursor": cursor, | ||
} | ||
|
||
resp = await self.parent.make_request( | ||
url="https://www.tiktok.com/api/mix/item_list/", | ||
params=params, | ||
headers=kwargs.get("headers"), | ||
session_index=kwargs.get("session_index"), | ||
) | ||
|
||
if resp is None: | ||
raise InvalidResponseException( | ||
resp, "TikTok returned an invalid response." | ||
) | ||
|
||
for video in resp.get("itemList", []): | ||
yield self.parent.video(data=video) | ||
found += 1 | ||
|
||
if not resp.get("hasMore", False): | ||
return | ||
|
||
cursor = resp.get("cursor") | ||
|
||
def __extract_from_data(self): | ||
data = self.as_dict | ||
keys = data.keys() | ||
|
||
if "mixInfo" in keys: | ||
data = data["mixInfo"] | ||
|
||
self.id = data.get("id", None) or data.get("mixId", None) | ||
self.name = data.get("name", None) or data.get("mixName", None) | ||
self.video_count = data.get("videoCount", None) | ||
self.creator = self.parent.user(data=data.get("creator", {})) | ||
self.cover_url = data.get("cover", None) | ||
|
||
if None in [self.id, self.name, self.video_count, self.creator, self.cover_url]: | ||
User.parent.logger.error( | ||
f"Failed to create Playlist with data: {data}\nwhich has keys {data.keys()}" | ||
) | ||
|
||
def __repr__(self): | ||
return self.__str__() | ||
|
||
def __str__(self): | ||
id = getattr(self, "id", None) | ||
return f"TikTokApi.playlist(id='{id}'')" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
from TikTokApi import TikTokApi | ||
import asyncio | ||
import os | ||
|
||
ms_token = os.environ.get( | ||
"ms_token", None | ||
) # set your own ms_token, think it might need to have visited a profile | ||
|
||
|
||
async def user_example(): | ||
async with TikTokApi() as api: | ||
await api.create_sessions(ms_tokens=[ms_token], num_sessions=1, sleep_after=3) | ||
user = api.user("therock") | ||
|
||
async for playlist in user.playlists(count=3): | ||
print(playlist) | ||
print(playlist.name) | ||
|
||
async for video in playlist.videos(count=3): | ||
print(video) | ||
print(video.url) | ||
|
||
if __name__ == "__main__": | ||
asyncio.run(user_example()) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
from TikTokApi import TikTokApi | ||
import os | ||
import pytest | ||
|
||
playlist_id="7281443725770476321" | ||
playlist_name="Doctor Who" | ||
playlist_creator="bbc" | ||
|
||
ms_token = os.environ.get("ms_token", None) | ||
|
||
|
||
@pytest.mark.asyncio | ||
async def test_playlist_info(): | ||
api = TikTokApi() | ||
async with api: | ||
await api.create_sessions(ms_tokens=[ms_token], num_sessions=1, sleep_after=3) | ||
playlist = api.playlist(id=playlist_id) | ||
await playlist.info() | ||
|
||
assert playlist.id == playlist_id | ||
assert playlist.name == playlist_name | ||
assert playlist.creator.username == playlist_creator | ||
assert playlist.video_count > 0 | ||
assert playlist.cover_url is not None | ||
assert playlist.as_dict is not None | ||
|
||
@pytest.mark.asyncio | ||
async def test_playlist_videos(): | ||
api = TikTokApi() | ||
async with api: | ||
await api.create_sessions(ms_tokens=[ms_token], num_sessions=1, sleep_after=3) | ||
playlist = api.playlist(id=playlist_id) | ||
|
||
count = 0 | ||
async for video in playlist.videos(count=30): | ||
count += 1 | ||
|
||
assert count >= 30 |