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).
+
+
+
+
+
+ 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