diff --git a/.sphinx/conf.py b/.sphinx/conf.py index 9641c14e..f98655e8 100644 --- a/.sphinx/conf.py +++ b/.sphinx/conf.py @@ -16,7 +16,7 @@ project = "TikTokAPI" copyright = "2023, David Teather" author = "David Teather" -release = "v6.5.2" +release = "v7.0.0" # -- General configuration --------------------------------------------------- # https://www.sphinx-doc.org/en/main/usage/configuration.html#general-configuration diff --git a/CITATION.cff b/CITATION.cff index 23d02af5..94df0aef 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -5,5 +5,5 @@ authors: orcid: "https://orcid.org/0000-0002-9467-4676" title: "TikTokAPI" url: "https://github.com/davidteather/tiktok-api" -version: 6.5.2 -date-released: 2024-08-24 +version: 7.0.0 +date-released: 2025-01-20 diff --git a/README.md b/README.md index b0810e57..ddd2953f 100644 --- a/README.md +++ b/README.md @@ -4,11 +4,11 @@ This is an unofficial api wrapper for TikTok.com in python. With this api you ar [![DOI](https://zenodo.org/badge/188710490.svg)](https://zenodo.org/badge/latestdoi/188710490) [![LinkedIn](https://img.shields.io/badge/LinkedIn-0077B5?style=for-the-badge&logo=linkedin&logoColor=white&style=flat-square)](https://www.linkedin.com/in/davidteather/) [![Sponsor Me](https://img.shields.io/static/v1?label=Sponsor&message=%E2%9D%A4&logo=GitHub)](https://github.com/sponsors/davidteather) [![GitHub release (latest by date)](https://img.shields.io/github/v/release/davidteather/TikTok-Api)](https://github.com/davidteather/TikTok-Api/releases) [![GitHub](https://img.shields.io/github/license/davidteather/TikTok-Api)](https://github.com/davidteather/TikTok-Api/blob/main/LICENSE) [![Downloads](https://pepy.tech/badge/tiktokapi)](https://pypi.org/project/TikTokApi/) ![](https://visitor-badge.laobi.icu/badge?page_id=davidteather.TikTok-Api) [![Support Server](https://img.shields.io/discord/783108952111579166.svg?color=7289da&logo=discord&style=flat-square)](https://discord.gg/yyPhbfma6f) -This api is designed to **retrieve data** TikTok. It **can not be used post or upload** content to TikTok on the behalf of a user. It has **no support any user-authenticated routes**, if you can't access it while being logged out on their website you can't access it here. +This api is designed to **retrieve data** TikTok. It **can not be used post or upload** content to TikTok on the behalf of a user. It has **no support for any user-authenticated routes**, if you can't access it while being logged out on their website you can't access it here. ## Sponsors -These sponsors have paid to be placed here and beyond that I do not have any affiliation with them, the TikTokAPI package will always be free and open-source. If you wish to be a sponsor of this project check out my [GitHub sponsors page](https://github.com/sponsors/davidteather). +These sponsors have paid to be placed here or are my own affiliate links which I may earn a commission from, and beyond that I do not have any affiliation with them. The TikTokAPI package will always be free and open-source. If you wish to be a sponsor of this project check out my [GitHub sponsors page](https://github.com/sponsors/davidteather).
@@ -33,6 +33,14 @@ These sponsors have paid to be placed here and beyond that I do not have any aff TikTok Captcha Solver: Bypass any TikTok captcha in just two lines of code.
Scale your TikTok automation and get unblocked with SadCaptcha.
+
+ + TikTok Captcha Solver + +
+ Cheap, Reliable Proxies: Supercharge your web scraping with fast, reliable proxies. Try 10 free datacenter proxies today! +
+
## Table of Contents @@ -93,7 +101,8 @@ docker run -v TikTokApi --rm tiktokapi:latest python3 your_script.py ### Common Issues -Please don't open an issue if you're experiencing one of these just comment if the provided solution do not work for you. +- **EmptyResponseException** - this means TikTok is blocking the request and detects you're a bot. This can be a problem with your setup or the library itself + - you may need a proxy to successfuly scrape TikTok, I've made a [web scraping lesson](https://github.com/davidteather/everything-web-scraping/tree/main/002-proxies) explaining the differences of "tiers" of proxies, I've personally had success with [webshare's residential proxies](https://www.webshare.io/?referral_code=3x5812idzzzp) (affiliate link), but you might have success on their free data center IPs or a cheaper competitor. - **Browser Has no Attribute** - make sure you ran `python3 -m playwright install`, if your error persists try the [playwright-python](https://github.com/microsoft/playwright-python) quickstart guide and diagnose issues from there. @@ -114,7 +123,7 @@ ms_token = os.environ.get("ms_token", None) # get your own ms_token from your co async def trending_videos(): async with TikTokApi() as api: - await api.create_sessions(ms_tokens=[ms_token], num_sessions=1, sleep_after=3) + await api.create_sessions(ms_tokens=[ms_token], num_sessions=1, sleep_after=3, browser=os.getenv("TIKTOK_BROWSER", "chromium")) async for video in api.trending.videos(count=30): print(video) print(video.as_dict) diff --git a/TikTokApi/api/playlist.py b/TikTokApi/api/playlist.py new file mode 100644 index 00000000..cc7a465e --- /dev/null +++ b/TikTokApi/api/playlist.py @@ -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}'')" diff --git a/TikTokApi/api/user.py b/TikTokApi/api/user.py index 5877472b..33da47d9 100644 --- a/TikTokApi/api/user.py +++ b/TikTokApi/api/user.py @@ -5,6 +5,7 @@ if TYPE_CHECKING: from ..tiktok import TikTokApi from .video import Video + from .playlist import Playlist class User: @@ -87,12 +88,12 @@ async def info(self, **kwargs) -> dict: self.__extract_from_data() return resp - async def playlists(self, count=20, cursor=0, **kwargs) -> Iterator[dict]: + async def playlists(self, count=20, cursor=0, **kwargs) -> Iterator[Playlist]: """ - Returns a dictionary of information associated with this User's playlist. + Returns a user's playlists. Returns: - dict: A dictionary of information associated with this User's playlist. + async iterator/generator: Yields TikTokApi.playlist objects. Raises: InvalidResponseException: If TikTok returns an invalid response, or one we don't understand. @@ -100,7 +101,8 @@ async def playlists(self, count=20, cursor=0, **kwargs) -> Iterator[dict]: Example Usage: .. code-block:: python - user_data = await api.user(username='therock').playlist() + async for playlist in await api.user(username='therock').playlists(): + # do something """ sec_uid = getattr(self, "sec_uid", None) @@ -109,30 +111,30 @@ async def playlists(self, count=20, cursor=0, **kwargs) -> Iterator[dict]: found = 0 while found < count: - params = { - "secUid": sec_uid, - "count": 20, - "cursor": cursor, - } - - resp = await self.parent.make_request( - url="https://www.tiktok.com/api/user/playlist", - 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 playlist in resp.get("playList", []): - yield playlist - found += 1 - - if not resp.get("hasMore", False): - return - - cursor = resp.get("cursor") + params = { + "secUid": self.sec_uid, + "count": min(count, 20), + "cursor": cursor, + } + + resp = await self.parent.make_request( + url="https://www.tiktok.com/api/user/playlist", + 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 playlist in resp.get("playList", []): + yield self.parent.playlist(data=playlist) + found += 1 + + if not resp.get("hasMore", False): + return + + cursor = resp.get("cursor") async def videos(self, count=30, cursor=0, **kwargs) -> Iterator[Video]: diff --git a/TikTokApi/stealth/js/navigator_userAgent.py b/TikTokApi/stealth/js/navigator_userAgent.py index a89d2196..fa4f14f5 100644 --- a/TikTokApi/stealth/js/navigator_userAgent.py +++ b/TikTokApi/stealth/js/navigator_userAgent.py @@ -1,8 +1,16 @@ navigator_userAgent = """ // replace Headless references in default useragent -const current_ua = navigator.userAgent +const current_ua = navigator.userAgent; Object.defineProperty(Object.getPrototypeOf(navigator), 'userAgent', { - get: () => opts.navigator_user_agent || current_ua.replace('HeadlessChrome/', 'Chrome/') -}) - + get: () => { + try { + if (typeof opts !== 'undefined' && opts.navigator_user_agent) { + return opts.navigator_user_agent; + } + } catch (error) { + console.warn('Error accessing opts:', error); + } + return current_ua.replace('HeadlessChrome/', 'Chrome/'); + } +}); """ diff --git a/TikTokApi/tiktok.py b/TikTokApi/tiktok.py index 23c2e604..35b5372a 100644 --- a/TikTokApi/tiktok.py +++ b/TikTokApi/tiktok.py @@ -6,7 +6,7 @@ import time import json -from playwright.async_api import async_playwright +from playwright.async_api import async_playwright, TimeoutError from urllib.parse import urlencode, quote, urlparse from .stealth import stealth_async from .helpers import random_choice @@ -18,6 +18,7 @@ from .api.comment import Comment from .api.trending import Trending from .api.search import Search +from .api.playlist import Playlist from .exceptions import ( InvalidJSONException, @@ -55,6 +56,7 @@ class TikTokApi: comment = Comment trending = Trending search = Search + playlist = Playlist def __init__(self, logging_level: int = logging.WARN, logger_name: str = None): """ @@ -77,6 +79,7 @@ def __init__(self, logging_level: int = logging.WARN, logger_name: str = None): Comment.parent = self Trending.parent = self Search.parent = self + Playlist.parent = self def __create_logger(self, name: str, level: int = logging.DEBUG): """Create a logger for the class.""" @@ -143,6 +146,7 @@ async def __create_session( sleep_after: int = 1, cookies: dict = None, suppress_resource_load_types: list[str] = None, + timeout: int = 30000, ): """Create a TikTokPlaywrightSession""" if ms_token is not None: @@ -177,8 +181,20 @@ def handle_request(request): if request.resource_type in suppress_resource_load_types else route.continue_(), ) + + # Set the navigation timeout + page.set_default_navigation_timeout(timeout) await page.goto(url) + await page.goto(url) # hack: tiktok blocks first request not sure why, likely bot detection + + # by doing this, we are simulate scroll event using mouse to `avoid` bot detection + x, y = random.randint(0, 50), random.randint(0, 50) + a, b = random.randint(1, 50), random.randint(100, 200) + + await page.mouse.move(x, y) + await page.wait_for_load_state("networkidle") + await page.mouse.move(a, b) session = TikTokPlaywrightSession( context, @@ -213,7 +229,8 @@ async def create_sessions( cookies: list[dict] = None, suppress_resource_load_types: list[str] = None, browser: str = "chromium", - executable_path: str = None + executable_path: str = None, + timeout: int = 30000, ): """ Create sessions for use within the TikTokApi class. @@ -232,8 +249,9 @@ async def create_sessions( override_browser_args (list[dict]): A list of dictionaries containing arguments to pass to the browser. cookies (list[dict]): A list of cookies to use for the sessions, you can get these from your cookies after visiting TikTok. suppress_resource_load_types (list[str]): Types of resources to suppress playwright from loading, excluding more types will make playwright faster.. Types: document, stylesheet, image, media, font, script, textrack, xhr, fetch, eventsource, websocket, manifest, other. - browser (str): specify either firefox or chromium, default is chromium + browser (str): firefox, chromium, or webkit; default is chromium executable_path (str): Path to the browser executable + timeout (int): The timeout in milliseconds for page navigation Example Usage: .. code-block:: python @@ -271,6 +289,7 @@ async def create_sessions( sleep_after=sleep_after, cookies=random_choice(cookies), suppress_resource_load_types=suppress_resource_load_types, + timeout=timeout, ) for _ in range(num_sessions) ) @@ -363,7 +382,23 @@ async def run_fetch_script(self, url: str, headers: dict, **kwargs): async def generate_x_bogus(self, url: str, **kwargs): """Generate the X-Bogus header for a url""" _, session = self._get_session(**kwargs) - await session.page.wait_for_function("window.byted_acrawler !== undefined") + + max_attempts = 5 + attempts = 0 + while attempts < max_attempts: + attempts += 1 + try: + timeout_time = random.randint(5000, 20000) + await session.page.wait_for_function("window.byted_acrawler !== undefined", timeout=timeout_time) + break + except TimeoutError as e: + if attempts == max_attempts: + raise TimeoutError(f"Failed to load tiktok after {max_attempts} attempts, consider using a proxy") + + try_urls = ["https://www.tiktok.com/foryou", "https://www.tiktok.com", "https://www.tiktok.com/@tiktok", "https://www.tiktok.com/foryou"] + + await session.page.goto(random.choice(try_urls)) + result = await session.page.evaluate( f'() => {{ return window.byted_acrawler.frontierSign("{url}") }}' ) @@ -452,7 +487,7 @@ async def make_request( raise Exception("TikTokApi.run_fetch_script returned None") if result == "": - raise EmptyResponseException(result, "TikTok returned an empty response") + raise EmptyResponseException(result, "TikTok returned an empty response. They are detecting you're a bot, try some of these: headless=False, browser='webkit', consider using a proxy") try: data = json.loads(result) diff --git a/examples/comment_example.py b/examples/comment_example.py index cb7bbe4f..ccad61b5 100644 --- a/examples/comment_example.py +++ b/examples/comment_example.py @@ -8,7 +8,7 @@ async def get_comments(): async with TikTokApi() as api: - await api.create_sessions(ms_tokens=[ms_token], num_sessions=1, sleep_after=3) + await api.create_sessions(ms_tokens=[ms_token], num_sessions=1, sleep_after=3, browser=os.getenv("TIKTOK_BROWSER", "chromium")) video = api.video(id=video_id) count = 0 async for comment in video.comments(count=30): diff --git a/examples/hashtag_example.py b/examples/hashtag_example.py index 0ddb94d4..dfc540bd 100644 --- a/examples/hashtag_example.py +++ b/examples/hashtag_example.py @@ -7,7 +7,7 @@ async def get_hashtag_videos(): async with TikTokApi() as api: - await api.create_sessions(ms_tokens=[ms_token], num_sessions=1, sleep_after=3) + await api.create_sessions(ms_tokens=[ms_token], num_sessions=1, sleep_after=3, browser=os.getenv("TIKTOK_BROWSER", "chromium")) tag = api.hashtag(name="funny") async for video in tag.videos(count=30): print(video) diff --git a/examples/playlist_example.py b/examples/playlist_example.py new file mode 100644 index 00000000..be7c296b --- /dev/null +++ b/examples/playlist_example.py @@ -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()) diff --git a/examples/search_example.py b/examples/search_example.py index 3391abfd..4da0a344 100644 --- a/examples/search_example.py +++ b/examples/search_example.py @@ -9,7 +9,7 @@ async def search_users(): async with TikTokApi() as api: - await api.create_sessions(ms_tokens=[ms_token], num_sessions=1, sleep_after=3) + await api.create_sessions(ms_tokens=[ms_token], num_sessions=1, sleep_after=3, browser=os.getenv("TIKTOK_BROWSER", "chromium")) async for user in api.search.users("david teather", count=10): print(user) diff --git a/examples/sound_example.py b/examples/sound_example.py index 7a1853c3..2130651e 100644 --- a/examples/sound_example.py +++ b/examples/sound_example.py @@ -8,7 +8,7 @@ async def sound_videos(): async with TikTokApi() as api: - await api.create_sessions(ms_tokens=[ms_token], num_sessions=1, sleep_after=3) + await api.create_sessions(ms_tokens=[ms_token], num_sessions=1, sleep_after=3, browser=os.getenv("TIKTOK_BROWSER", "chromium")) async for sound in api.sound(id=sound_id).videos(count=30): print(sound) print(sound.as_dict) diff --git a/examples/trending_example.py b/examples/trending_example.py index a743863a..31a33dd1 100644 --- a/examples/trending_example.py +++ b/examples/trending_example.py @@ -7,7 +7,7 @@ async def trending_videos(): async with TikTokApi() as api: - await api.create_sessions(ms_tokens=[ms_token], num_sessions=1, sleep_after=3) + await api.create_sessions(ms_tokens=[ms_token], num_sessions=1, sleep_after=3, browser=os.getenv("TIKTOK_BROWSER", "chromium")) async for video in api.trending.videos(count=30): print(video) print(video.as_dict) diff --git a/examples/user_example.py b/examples/user_example.py index f2b9f72e..137da1f4 100644 --- a/examples/user_example.py +++ b/examples/user_example.py @@ -9,7 +9,7 @@ async def user_example(): async with TikTokApi() as api: - await api.create_sessions(ms_tokens=[ms_token], num_sessions=1, sleep_after=3) + await api.create_sessions(ms_tokens=[ms_token], num_sessions=1, sleep_after=3, browser=os.getenv("TIKTOK_BROWSER", "chromium")) user = api.user("therock") user_data = await user.info() print(user_data) diff --git a/examples/video_example.py b/examples/video_example.py index 90f28606..6c8dcafa 100644 --- a/examples/video_example.py +++ b/examples/video_example.py @@ -9,7 +9,7 @@ async def get_video_example(): async with TikTokApi() as api: - await api.create_sessions(ms_tokens=[ms_token], num_sessions=1, sleep_after=3) + await api.create_sessions(ms_tokens=[ms_token], num_sessions=1, sleep_after=3, browser=os.getenv("TIKTOK_BROWSER", "chromium")) video = api.video( url="https://www.tiktok.com/@davidteathercodes/video/7074717081563942186" ) diff --git a/imgs/webshare.png b/imgs/webshare.png new file mode 100644 index 00000000..5cb3da4c Binary files /dev/null and b/imgs/webshare.png differ diff --git a/setup.py b/setup.py index 89b90621..a8196998 100644 --- a/setup.py +++ b/setup.py @@ -8,7 +8,7 @@ setuptools.setup( name="TikTokApi", packages=setuptools.find_packages(), - version="6.5.2", + version="7.0.0", license="MIT", description="The Unofficial TikTok API Wrapper in Python 3.", author="David Teather", @@ -27,6 +27,8 @@ "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", ], python_requires=">=3.9", ) diff --git a/tests/test_comments.py b/tests/test_comments.py index 198b484f..11efdc07 100644 --- a/tests/test_comments.py +++ b/tests/test_comments.py @@ -10,7 +10,7 @@ async def test_comment_page(): api = TikTokApi() async with api: - await api.create_sessions(ms_tokens=[ms_token], num_sessions=1, sleep_after=3) + await api.create_sessions(ms_tokens=[ms_token], num_sessions=1, sleep_after=3, browser=os.getenv("TIKTOK_BROWSER", "chromium")) video = api.video(id=video_id) count = 0 async for comment in video.comments(count=100): diff --git a/tests/test_hashtag.py b/tests/test_hashtag.py index 56adde35..a8cebdef 100644 --- a/tests/test_hashtag.py +++ b/tests/test_hashtag.py @@ -10,7 +10,7 @@ async def test_hashtag_videos(): api = TikTokApi(logging_level=logging.INFO) async with api: - await api.create_sessions(ms_tokens=[ms_token], num_sessions=1, sleep_after=3) + await api.create_sessions(ms_tokens=[ms_token], num_sessions=1, sleep_after=3, browser=os.getenv("TIKTOK_BROWSER", "chromium")) tag = api.hashtag(name="funny") video_count = 0 async for video in tag.videos(count=30): @@ -23,7 +23,7 @@ async def test_hashtag_videos(): async def test_hashtag_videos_multi_page(): api = TikTokApi(logging_level=logging.INFO) async with api: - await api.create_sessions(ms_tokens=[ms_token], num_sessions=1, sleep_after=3) + await api.create_sessions(ms_tokens=[ms_token], num_sessions=1, sleep_after=3, browser=os.getenv("TIKTOK_BROWSER", "chromium")) tag = api.hashtag(name="funny", id="5424") video_count = 0 async for video in tag.videos(count=100): @@ -36,7 +36,7 @@ async def test_hashtag_videos_multi_page(): async def test_hashtag_info(): api = TikTokApi(logging_level=logging.INFO) async with api: - await api.create_sessions(ms_tokens=[ms_token], num_sessions=1, sleep_after=3) + await api.create_sessions(ms_tokens=[ms_token], num_sessions=1, sleep_after=3, browser=os.getenv("TIKTOK_BROWSER", "chromium")) tag = api.hashtag(name="funny") await tag.info() @@ -48,7 +48,7 @@ async def test_hashtag_info(): async def test_non_latin1(): api = TikTokApi(logging_level=logging.INFO) async with api: - await api.create_sessions(ms_tokens=[ms_token], num_sessions=1, sleep_after=3) + await api.create_sessions(ms_tokens=[ms_token], num_sessions=1, sleep_after=3, browser=os.getenv("TIKTOK_BROWSER", "chromium")) tag = api.hashtag(name="селфи") await tag.info() diff --git a/tests/test_integration.py b/tests/test_integration.py index 5459b7a2..0364b520 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -8,7 +8,7 @@ @pytest.mark.asyncio async def test_hashtag_videos(): async with TikTokApi() as api: - await api.create_sessions(ms_tokens=[ms_token], num_sessions=1, sleep_after=3) + await api.create_sessions(ms_tokens=[ms_token], num_sessions=1, sleep_after=3, browser=os.getenv("TIKTOK_BROWSER", "chromium")) tag_name = "funny" count = 0 async for video in api.hashtag(name=tag_name).videos(count=1): diff --git a/tests/test_playlist.py b/tests/test_playlist.py new file mode 100644 index 00000000..ddbbfdca --- /dev/null +++ b/tests/test_playlist.py @@ -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 diff --git a/tests/test_search.py b/tests/test_search.py index 43231ac3..0b6fd654 100644 --- a/tests/test_search.py +++ b/tests/test_search.py @@ -8,7 +8,7 @@ async def test_users_single_page(): api = TikTokApi() async with api: - await api.create_sessions(ms_tokens=[ms_token], num_sessions=1, sleep_after=3) + await api.create_sessions(ms_tokens=[ms_token], num_sessions=1, sleep_after=3, browser=os.getenv("TIKTOK_BROWSER", "chromium")) count = 0 async for user in api.search.users("therock", count=10): count += 1 @@ -20,7 +20,7 @@ async def test_users_single_page(): async def test_users_multi_page(): api = TikTokApi() async with api: - await api.create_sessions(ms_tokens=[ms_token], num_sessions=1, sleep_after=3) + await api.create_sessions(ms_tokens=[ms_token], num_sessions=1, sleep_after=3, browser=os.getenv("TIKTOK_BROWSER", "chromium")) count = 0 async for user in api.search.users("therock", count=50): count += 1 diff --git a/tests/test_sound.py b/tests/test_sound.py index 4a506236..8df9553c 100644 --- a/tests/test_sound.py +++ b/tests/test_sound.py @@ -10,7 +10,7 @@ async def test_sound_videos(): api = TikTokApi() async with api: - await api.create_sessions(ms_tokens=[ms_token], num_sessions=1, sleep_after=3) + await api.create_sessions(ms_tokens=[ms_token], num_sessions=1, sleep_after=3, browser=os.getenv("TIKTOK_BROWSER", "chromium")) sound = api.sound(id=song_id) video_count = 0 async for video in sound.videos(count=100): @@ -23,9 +23,9 @@ async def test_sound_videos(): async def test_sound_info(): api = TikTokApi() async with api: - await api.create_sessions(ms_tokens=[ms_token], num_sessions=1, sleep_after=3) + await api.create_sessions(ms_tokens=[ms_token], num_sessions=1, sleep_after=3, browser=os.getenv("TIKTOK_BROWSER", "chromium")) sound = api.sound(id=song_id) await sound.info() assert sound.id == song_id assert sound.title == "Face Off - Dwayne Johnson" - assert sound.duration == 45 + assert sound.duration == 60 diff --git a/tests/test_trending.py b/tests/test_trending.py index 7a83445e..65519fb1 100644 --- a/tests/test_trending.py +++ b/tests/test_trending.py @@ -6,10 +6,10 @@ @pytest.mark.asyncio -async def test_user_info(): +async def test_trending(): api = TikTokApi() async with api: - await api.create_sessions(ms_tokens=[ms_token], num_sessions=1, sleep_after=3) + await api.create_sessions(ms_tokens=[ms_token], num_sessions=1, sleep_after=3, browser=os.getenv("TIKTOK_BROWSER", "chromium")) count = 0 async for video in api.trending.videos(count=100): count += 1 diff --git a/tests/test_user.py b/tests/test_user.py index d0f07373..7ce2bfdc 100644 --- a/tests/test_user.py +++ b/tests/test_user.py @@ -13,7 +13,7 @@ async def test_user_info(): api = TikTokApi() async with api: - await api.create_sessions(ms_tokens=[ms_token], num_sessions=1, sleep_after=3) + await api.create_sessions(ms_tokens=[ms_token], num_sessions=1, sleep_after=3, browser=os.getenv("TIKTOK_BROWSER", "chromium")) user = api.user(username=username) await user.info() @@ -25,7 +25,7 @@ async def test_user_info(): async def test_user_videos(): api = TikTokApi() async with api: - await api.create_sessions(ms_tokens=[ms_token], num_sessions=1, sleep_after=3) + await api.create_sessions(ms_tokens=[ms_token], num_sessions=1, sleep_after=3, browser=os.getenv("TIKTOK_BROWSER", "chromium")) user = api.user(username=username, sec_uid=sec_uid, user_id=user_id) count = 0 @@ -38,7 +38,7 @@ async def test_user_videos(): async def test_user_likes(): api = TikTokApi() async with api: - await api.create_sessions(ms_tokens=[ms_token], num_sessions=1, sleep_after=3) + await api.create_sessions(ms_tokens=[ms_token], num_sessions=1, sleep_after=3, browser=os.getenv("TIKTOK_BROWSER", "chromium")) user = api.user( username="publicliketest", sec_uid="MS4wLjABAAAAHjhwCIwmvzVZfRrDAZ2aZy74LciLnoyaPfM2rrX9N7bwbWMFuwTFG4YrByYvsH5c", @@ -54,7 +54,7 @@ async def test_user_likes(): async def test_user_playlists(): api = TikTokApi() async with api: - await api.create_sessions(ms_tokens=[ms_token], num_sessions=1, sleep_after=3) + await api.create_sessions(ms_tokens=[ms_token], num_sessions=1, sleep_after=3, browser=os.getenv("TIKTOK_BROWSER", "chromium")) user = api.user(username="mrbeast") count = 0 diff --git a/tests/test_video.py b/tests/test_video.py index 625886f4..47469c0e 100644 --- a/tests/test_video.py +++ b/tests/test_video.py @@ -9,7 +9,7 @@ async def test_video_id_from_url(): api = TikTokApi() async with api: - await api.create_sessions(ms_tokens=[ms_token], num_sessions=1, sleep_after=3) + await api.create_sessions(ms_tokens=[ms_token], num_sessions=1, sleep_after=3, browser=os.getenv("TIKTOK_BROWSER", "chromium")) expected_id = "7074717081563942186" video = api.video( @@ -28,7 +28,7 @@ async def test_video_id_from_url(): async def test_video_info(): api = TikTokApi() async with api: - await api.create_sessions(ms_tokens=[ms_token], num_sessions=1, sleep_after=3) + await api.create_sessions(ms_tokens=[ms_token], num_sessions=1, sleep_after=3, browser=os.getenv("TIKTOK_BROWSER", "chromium")) video_id = "7074717081563942186" video = api.video( url="https://www.tiktok.com/@davidteathercodes/video/7074717081563942186" @@ -45,7 +45,7 @@ async def test_video_bytes(): pytest.skip("Not implemented yet") api = TikTokApi() async with api: - await api.create_sessions(ms_tokens=[ms_token], num_sessions=1, sleep_after=3) + await api.create_sessions(ms_tokens=[ms_token], num_sessions=1, sleep_after=3, browser=os.getenv("TIKTOK_BROWSER", "chromium")) video_id = "7107272719166901550" video = api.video(id=video_id) @@ -57,7 +57,7 @@ async def test_video_bytes(): async def test_related_videos(): api = TikTokApi() async with api: - await api.create_sessions(ms_tokens=[ms_token], num_sessions=1, sleep_after=3) + await api.create_sessions(ms_tokens=[ms_token], num_sessions=1, sleep_after=3, browser=os.getenv("TIKTOK_BROWSER", "chromium")) video_id = "7107272719166901550" video = api.video(id=video_id) count = 0