Skip to content

Commit

Permalink
Rollover to 0.2.1 (see desc)
Browse files Browse the repository at this point in the history
* #54 - Add "url" as a computed field for Video objects
* #56 - Remove "sorted_by" from the documentation
* #57 - Add a default error code value for unknown errors
  • Loading branch information
Russell-Newton committed Jul 17, 2023
1 parent 01067f8 commit eed3d95
Show file tree
Hide file tree
Showing 5 changed files with 57 additions and 155 deletions.
4 changes: 4 additions & 0 deletions docs/source/users/explanation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ but this does come with its limitations.

TikTokPy grabs information in two steps:

.. warning::
The following explanation is only valid for library versions prior to 0.2.0. This page will eventually be updated to
reflect the new behavior.

Grabbing Preloaded Content
==========================

Expand Down
36 changes: 0 additions & 36 deletions docs/source/users/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -121,41 +121,6 @@ Given a :ref:`User` object, you can retrieve that creator's most recent videos.
user page will be used for video data scraping. Specifying a limit can be useful if you only want the most
recent videos.

Iterate Over Sorted Videos
--------------------------

Unfortunately, this strategy is not perfect. TikTok does not provide a direct way to sort :ref:`Video`, so you will
only be able to perform the sorting on videos that are picked up by TikTokPy during scraping. More can be retrieved by
setting ``scroll_down_time`` to something like 10 seconds in the API constructor. The ``videos`` (async) iterator that
exists on :ref:`User` and :ref:`Challenge` objects contains a function called ``sorted_by()`` that has the same
signature as the builtin ``sorted()`` but is faster if you want to sort on :ref:`VideoStats` or ``create_time``.

.. tabs::

.. code-tab:: py TikTokAPI

from tiktokapipy.api import TikTokAPI

def do_something():
with TikTokAPI() as api:
user = api.user(user_tag)
for video in user.videos.sorted_by(key=lambda vid: vid.stats.digg_count, reverse=True):
...

.. code-tab:: py AsyncTikTokAPI

from tiktokapipy.async_api import AsyncTikTokAPI

async def do_something():
async with AsyncTikTokAPI() as api:
user = await api.user(user_tag)
async for video in user.videos.sorted_by(key=lambda vid: vid.stats.digg_count, reverse=True):
...

.. note::
All other video data besides the unique ID and stats are grabbed at iteration time, so if you would like to sort on
something else you should just go with ``sorted()``. This helps keep the memory footprint low.

Iterate Over Popular Videos Tagged with a Challenge
---------------------------------------------------

Expand Down Expand Up @@ -184,7 +149,6 @@ TikTok refers to hashtags as "Challenges" internally. You can iterate over popul
async for video in challenge.videos:
...

You can also sort these by create time with ``challenge.videos.sorted_by(lambda vid: vid.create_time)``.

.. note::
By default, the number of videos that can be iterated over is not limited. This can be changed by specifying a
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "tiktokapipy"
version = "0.2.0post2"
version = "0.2.1"
authors = [
{ name="Russell Newton", email="[email protected]" },
]
Expand Down
86 changes: 46 additions & 40 deletions src/tiktokapipy/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
from collections import defaultdict


class TikTokAPIError(Exception):
"""Raised when the API encounters an error"""

Expand All @@ -8,43 +11,46 @@ class TikTokAPIWarning(RuntimeWarning):
pass


ERROR_CODES = {
0: "OK",
450: "CLIENT_PAGE_ERROR",
10000: "VERIFY_CODE",
10101: "SERVER_ERROR_NOT_500",
10102: "USER_NOT_LOGIN",
10111: "NET_ERROR",
10113: "SHARK_SLIDE",
10114: "SHARK_BLOCK",
10119: "LIVE_NEED_LOGIN",
10202: "USER_NOT_EXIST",
10203: "MUSIC_NOT_EXIST",
10204: "VIDEO_NOT_EXIST",
10205: "HASHTAG_NOT_EXIST",
10208: "EFFECT_NOT_EXIST",
10209: "HASHTAG_BLACK_LIST",
10210: "LIVE_NOT_EXIST",
10211: "HASHTAG_SENSITIVITY_WORD",
10212: "HASHTAG_UNSHELVE",
10213: "VIDEO_LOW_AGE_M",
10214: "VIDEO_LOW_AGE_T",
10215: "VIDEO_ABNORMAL",
10216: "VIDEO_PRIVATE_BY_USER",
10217: "VIDEO_FIRST_REVIEW_UNSHELVE",
10218: "MUSIC_UNSHELVE",
10219: "MUSIC_NO_COPYRIGHT",
10220: "VIDEO_UNSHELVE_BY_MUSIC",
10221: "USER_BAN",
10222: "USER_PRIVATE",
10223: "USER_FTC",
10224: "GAME_NOT_EXIST",
10225: "USER_UNIQUE_SENSITIVITY",
10227: "VIDEO_NEED_RECHECK",
10228: "VIDEO_RISK",
10229: "VIDEO_R_MASK",
10230: "VIDEO_RISK_MASK",
10231: "VIDEO_GEOFENCE_BLOCK",
10404: "FYP_VIDEO_LIST_LIMIT",
"undefined": "MEDIA_ERROR",
}
ERROR_CODES = defaultdict(
lambda: "Unknown Error",
{
0: "OK",
450: "CLIENT_PAGE_ERROR",
10000: "VERIFY_CODE",
10101: "SERVER_ERROR_NOT_500",
10102: "USER_NOT_LOGIN",
10111: "NET_ERROR",
10113: "SHARK_SLIDE",
10114: "SHARK_BLOCK",
10119: "LIVE_NEED_LOGIN",
10202: "USER_NOT_EXIST",
10203: "MUSIC_NOT_EXIST",
10204: "VIDEO_NOT_EXIST",
10205: "HASHTAG_NOT_EXIST",
10208: "EFFECT_NOT_EXIST",
10209: "HASHTAG_BLACK_LIST",
10210: "LIVE_NOT_EXIST",
10211: "HASHTAG_SENSITIVITY_WORD",
10212: "HASHTAG_UNSHELVE",
10213: "VIDEO_LOW_AGE_M",
10214: "VIDEO_LOW_AGE_T",
10215: "VIDEO_ABNORMAL",
10216: "VIDEO_PRIVATE_BY_USER",
10217: "VIDEO_FIRST_REVIEW_UNSHELVE",
10218: "MUSIC_UNSHELVE",
10219: "MUSIC_NO_COPYRIGHT",
10220: "VIDEO_UNSHELVE_BY_MUSIC",
10221: "USER_BAN",
10222: "USER_PRIVATE",
10223: "USER_FTC",
10224: "GAME_NOT_EXIST",
10225: "USER_UNIQUE_SENSITIVITY",
10227: "VIDEO_NEED_RECHECK",
10228: "VIDEO_RISK",
10229: "VIDEO_R_MASK",
10230: "VIDEO_RISK_MASK",
10231: "VIDEO_GEOFENCE_BLOCK",
10404: "FYP_VIDEO_LIST_LIMIT",
"undefined": "MEDIA_ERROR",
},
)
84 changes: 6 additions & 78 deletions src/tiktokapipy/models/video.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,9 @@

from datetime import datetime
from functools import cached_property
from io import BytesIO
from typing import Any, ForwardRef, List, Optional, Union

from playwright.async_api import BrowserContext as AsyncBrowserContext
from playwright.sync_api import BrowserContext as SyncBrowserContext
from pydantic import AliasChoices, Field, computed_field
from tiktokapipy import TikTokAPIError
from tiktokapipy.models import CamelCaseModel, TitleCaseModel
Expand Down Expand Up @@ -84,7 +82,7 @@ class MusicData(CamelCaseModel):

id: int
title: str
play_url: str
play_url: Optional[str] = None
author_name: Optional[str] = None
duration: int
original: bool
Expand Down Expand Up @@ -256,81 +254,11 @@ def creator(self) -> Union[DeferredUserGetterAsync, DeferredUserGetterSync]:
else:
return DeferredUserGetterSync(self._api, unique_id)

async def download_async(self) -> BytesIO:
if self.image_post:
raise TikTokAPIError(
"Downloading slide shows is not directly supported yet."
)
if self._api is None:
raise TikTokAPIError(
"A TikTokAPI must be attached to video._api before retrieving creator data."
)
if isinstance(self._api.context, SyncBrowserContext):
raise TikTokAPIError(
"Attempting to use TikTokAPI in an asynchronous context. Use `download_sync()` instead."
)

from playwright.async_api import Page

page: Page = await self._api.context.new_page()
response = await page.goto(
self.video.download_addr, referer="https://www.tiktok.com"
)
return BytesIO(await response.body())

def download_sync(self) -> BytesIO:
if self.image_post:
raise TikTokAPIError(
"Downloading slide shows is not directly supported yet."
)
if self._api is None:
raise TikTokAPIError(
"A TikTokAPI must be attached to video._api before retrieving creator data."
)
if isinstance(self._api.context, AsyncBrowserContext):
raise TikTokAPIError(
"Attempting to use AsyncTikTokAPI in a synchronous context. Use `await download_async()` instead."
)

from playwright.sync_api import Page

page: Page = self._api.context.new_page()
page.add_init_script(
"""
if (navigator.webdriver === false) {
// Post Chrome 89.0.4339.0 and already good
} else if (navigator.webdriver === undefined) {
// Pre Chrome 89.0.4339.0 and already good
} else {
// Pre Chrome 88.0.4291.0 and needs patching
delete Object.getPrototypeOf(navigator).webdriver
}
"""
)
# response = page.goto(self.video.download_addr)
print(page.context.cookies())
if len(page.context.cookies()) == 0:
page.goto("https://www.tiktok.com")
page.reload()
page.wait_for_timeout(5000)
print(page.context.cookies())
cookie_header = "; ".join(
f"{cookie['name']}={cookie['value']}"
for cookie in page.context.cookies()
if cookie["domain"] == ".tiktok.com"
)
response2 = page.request.get(
self.video.download_addr,
headers={
"Referer": "https://www.tiktok.com",
"Sec-Fetch-Dest": "video",
"Sec-Fetch-Mode": "no-cors",
"Sec-Fetch-Site": "same-site",
"Cookie": cookie_header,
},
)
page.close()
return BytesIO(response2.body())
@computed_field(repr=False)
@cached_property
def url(self) -> str:
"""The url to the video on TikTok."""
return video_link(self.id)


del Challenge, LightChallenge, Comment, LightUser, User, UserStats
Expand Down

0 comments on commit eed3d95

Please sign in to comment.