diff --git a/README.md b/README.md index a0b62ea..488ad91 100644 --- a/README.md +++ b/README.md @@ -48,24 +48,33 @@ Import ``` from TikTokAPI import TikTokAPI ``` +Get your keys from Cookie. You can get them from the Applications tab in Chrome developer console. +By default it used hardcoded values which may not work after some time. +The keys to extract are `s_v_web_id` and `tt_webid` +``` +cookie = { + "s_v_web_id": "", + "tt_webid": "" +} +``` Get the most trending Videos on TikTok ``` -api = TikTokAPI() +api = TikTokAPI(cookie=cookie) retval = api.getTrending(count=5) ``` Get a user by name ``` -api = TikTokAPI() +api = TikTokAPI(cookie=cookie) user_obj = api.getUserByName("fcbarcelona") ``` Get videos of a user ``` -api = TikTokAPI() +api = TikTokAPI(cookie=cookie) user_videos = api.getVideosByUserName("fcbarcelona") ``` Get likes of a user ``` -api = TikTokAPI() +api = TikTokAPI(cookie=cookie) user_videos = api.getLikesByUserName("fcbarcelona") ``` diff --git a/TikTokAPI/tiktok_browser.py b/TikTokAPI/tiktok_browser.py index 773d341..c242326 100644 --- a/TikTokAPI/tiktok_browser.py +++ b/TikTokAPI/tiktok_browser.py @@ -1,12 +1,13 @@ import os import asyncio -from pyppeteer import launch +from pyppeteer import launcher from .utils import python_list2_web_list class TikTokBrowser: def __init__(self, user_agent): + self.userAgent = user_agent self.args = [ "--no-sandbox", @@ -51,10 +52,19 @@ def __init__(self, user_agent): self.tiktok_dummy_page = "file://" + os.path.join(parent_folder, "website", "tiktok.html") def fetch_auth_params(self, url, language='en'): + + # If not running in the main thread, it's necessary to create & set a + # new event loop. + try: + asyncio.get_event_loop() + except RuntimeError: + asyncio.set_event_loop(asyncio.new_event_loop()) + return asyncio.get_event_loop().run_until_complete(self.async_fetch_auth_params(url, language)) async def async_fetch_auth_params(self, url, language): - browser = await launch(self.options) + pplauncher = launcher.Launcher(self.options) + browser = await pplauncher.launch() page = await browser.newPage() await page.evaluateOnNewDocument("""() => { @@ -80,5 +90,7 @@ async def async_fetch_auth_params(self, url, language): return token; }''') + await page.close() await browser.close() + await pplauncher.killChrome() return signature diff --git a/TikTokAPI/tiktokapi.py b/TikTokAPI/tiktokapi.py index 7a0611b..580728a 100644 --- a/TikTokAPI/tiktokapi.py +++ b/TikTokAPI/tiktokapi.py @@ -1,28 +1,37 @@ import os +import string import random import urllib.parse from .utils import random_key, build_get_url, get_req_json, get_req_content, get_req_text from .tiktok_browser import TikTokBrowser +class VideoException(Exception): + pass + + class TikTokAPI(object): - def __init__(self, language='en', browser_lang="en-US", timezone="Asia/Kolkata", region='IN', cookie=None): + def __init__(self, cookie=None, language='en', browser_lang="en-US", timezone="Asia/Kolkata", region='IN'): self.base_url = "https://t.tiktok.com/api" self.user_agent = "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:79.0) Gecko/20100101 Firefox/79.0" + if cookie is None: + cookie = {} + self.verifyFp = cookie.get("s_v_web_id", "verify_kjf974fd_y7bupmR0_3uRm_43kF_Awde_8K95qt0GcpBk") + self.tt_webid = cookie.get("tt_webid", "6913027209393473025") + self.headers = { - "User-Agent": self.user_agent, + 'Host': 't.tiktok.com', + 'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:79.0) Gecko/20100101 Firefox/79.0', + 'Referer': 'https://www.tiktok.com/', + 'Cookie': 'tt_webid_v2={}; tt_webid={}'.format(self.tt_webid, self.tt_webid) } self.language = language self.browser_lang = browser_lang self.timezone = timezone self.region = region - if cookie is None: - self.verifyFp = random_key(16) - else: - self.verifyFp = cookie self.default_params = { "aid": "1988", "app_name": "tiktok_web", @@ -60,7 +69,7 @@ def __init__(self, language='en', browser_lang="en-US", timezone="Asia/Kolkata", def send_get_request(self, url, params, extra_headers=None): url = build_get_url(url, params) - did = str(random.randint(10000, 999999999)) + did = ''.join(random.choice(string.digits) for num in range(19)) url = build_get_url(url, {self.did_key: did}, append=True) signature = self.tiktok_browser.fetch_auth_params(url, language=self.language) url = build_get_url(url, {self.signature_key: signature}, append=True) @@ -95,9 +104,10 @@ def getTrending(self, count=30): return self.send_get_request(url, params) def getUserByName(self, user_name): - url = self.base_url + "/user/detail/" + url = "https://t.tiktok.com/node/share/user/@" + user_name params = { - "uniqueId": user_name + "uniqueId": user_name, + "validUniqueId": user_name, } for key, val in self.default_params.items(): params[key] = val @@ -126,7 +136,7 @@ def getVideosByUserName(self, user_name, count=30): for key, val in self.default_params.items(): params[key] = val return self.send_get_request(url, params) - + def getLikesByUserName(self, user_name, count=30): user_data = self.getUserByName(user_name) user_obj = user_data["userInfo"]["user"] @@ -164,7 +174,7 @@ def getVideosByHashTag(self, hashTag, count=30): hashTag = hashTag.replace("#", "") hashTag_obj = self.getHashTag(hashTag) hashTag_id = hashTag_obj["challengeInfo"]["challenge"]["id"] - url = "https://m.tiktok.com/share/item/list" + url = self.base_url + "/challenge/item_list/" req_default_params = { "secUid": "", "type": "3", @@ -174,8 +184,9 @@ def getVideosByHashTag(self, hashTag, count=30): "recType": "" } params = { - "id": str(hashTag_id), + "challengeID": str(hashTag_id), "count": str(count), + "cursor": "0", } for key, val in req_default_params.items(): params[key] = val @@ -194,7 +205,7 @@ def getMusic(self, music_id): return self.send_get_request(url, params) def getVideosByMusic(self, music_id, count=30): - url = "https://m.tiktok.com/share/item/list" + url = self.base_url + "/music/item_list/" req_default_params = { "secUid": "", "type": "4", @@ -204,8 +215,9 @@ def getVideosByMusic(self, music_id, count=30): "recType": "" } params = { - "id": str(music_id), + "musicID": str(music_id), "count": str(count), + "cursor": "0", } for key, val in req_default_params.items(): params[key] = val @@ -225,22 +237,22 @@ def getVideoById(self, video_id): def downloadVideoById(self, video_id, save_path): video_info = self.getVideoById(video_id) - video_url = video_info["itemInfo"]["itemStruct"]["video"]["downloadAddr"] - headers = {"User-Agent": "okhttp"} - video_data = get_req_content(video_url, params=None, headers=headers) + video_url = video_info["itemInfo"]["itemStruct"]["video"]["playAddr"] + video_data = get_req_content(video_url, params=None, headers=self.headers) with open(save_path, 'wb') as f: f.write(video_data) def downloadVideoByIdNoWatermark(self, video_id, save_path): video_info = self.getVideoById(video_id) video_url = video_info["itemInfo"]["itemStruct"]["video"]["downloadAddr"] - headers = {"User-Agent": "okhttp"} - video_data = get_req_text(video_url, params=None, headers=headers) + video_data = get_req_text(video_url, params=None, headers=self.headers) pos = video_data.find("vid:") + if pos == -1: + raise VideoException("Video without watermark not available in new videos") video_url_no_wm = "https://api2-16-h2.musical.ly/aweme/v1/play/?video_id={" \ "}&vr_type=0&is_play_url=1&source=PackSourceEnum_PUBLISH&media_type=4" \ - .format(video_data[pos+4:pos+36]) - headers = {"User-Agent": "okhttp"} - video_data_no_wm = get_req_content(video_url_no_wm, params=None, headers=headers) + .format(video_data[pos + 4:pos + 36]) + + video_data_no_wm = get_req_content(video_url_no_wm, params=None, headers=self.headers) with open(save_path, 'wb') as f: f.write(video_data_no_wm) diff --git a/TikTokAPI/utils.py b/TikTokAPI/utils.py index 5587769..4470fa7 100644 --- a/TikTokAPI/utils.py +++ b/TikTokAPI/utils.py @@ -24,16 +24,19 @@ def build_get_url(base_url, params, append=False): def get_req_json(url, params=None, headers=None): + headers["Host"] = url.split("/")[2] r = requests.get(url, params=params, headers=headers) return json.loads(r.text) def get_req_content(url, params=None, headers=None): + headers["Host"] = url.split("/")[2] r = requests.get(url, params=params, headers=headers) return r.content def get_req_text(url, params=None, headers=None): + headers["Host"] = url.split("/")[2] r = requests.get(url, params=params, headers=headers) return r.text diff --git a/setup.py b/setup.py index 0b9877b..e9dcfc2 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ setuptools.setup( name="PyTikTokAPI", packages=setuptools.find_packages(), - version="0.0.4", + version="0.0.5", license='MIT', author="Avilash Kumar", author_email="avilashkumar4@gmail.com", diff --git a/tests/cookie.json b/tests/cookie.json new file mode 100644 index 0000000..46eac0d --- /dev/null +++ b/tests/cookie.json @@ -0,0 +1,4 @@ +{ + "s_v_web_id": "verify_kjf974fd_y7bupmR0_3uRm_43kF_Awde_8K95qt0GcpBk", + "tt_webid": "6913027209393473025" +} \ No newline at end of file diff --git a/tests/test_hashtag.py b/tests/test_hashtag.py index ba87dbb..c2a6439 100644 --- a/tests/test_hashtag.py +++ b/tests/test_hashtag.py @@ -1,14 +1,15 @@ import argparse from TikTokAPI import TikTokAPI +from utils import read_json_from_file def getHashTag(hashTag): - api = TikTokAPI() + api = TikTokAPI(read_json_from_file("cookie.json")) return api.getHashTag(hashTag) def getVideosByHashTag(hashTag): - api = TikTokAPI() + api = TikTokAPI(read_json_from_file("cookie.json")) return api.getVideosByHashTag(hashTag, count=10) diff --git a/tests/test_music.py b/tests/test_music.py index bce7cd5..2f942f6 100644 --- a/tests/test_music.py +++ b/tests/test_music.py @@ -1,14 +1,15 @@ import argparse from TikTokAPI import TikTokAPI +from utils import read_json_from_file def getMusic(music_id): - api = TikTokAPI() + api = TikTokAPI(read_json_from_file("cookie.json")) return api.getMusic(music_id) def getVideosByMusic(music_id): - api = TikTokAPI() + api = TikTokAPI(read_json_from_file("cookie.json")) return api.getVideosByMusic(music_id, count=10) diff --git a/tests/test_trending.py b/tests/test_trending.py index 652c709..3f43e38 100644 --- a/tests/test_trending.py +++ b/tests/test_trending.py @@ -1,6 +1,7 @@ from TikTokAPI import TikTokAPI +from utils import read_json_from_file -api = TikTokAPI() +api = TikTokAPI(read_json_from_file("cookie.json")) retval = api.getTrending(count=5) print("Trending Videos") print(retval) diff --git a/tests/test_user.py b/tests/test_user.py index 1f1d942..a0f84d6 100644 --- a/tests/test_user.py +++ b/tests/test_user.py @@ -1,19 +1,20 @@ import argparse from TikTokAPI import TikTokAPI +from utils import read_json_from_file def getUser(user_name): - api = TikTokAPI() + api = TikTokAPI(read_json_from_file("cookie.json")) return api.getUserByName(user_name) def getVideosByUserName(user_name): - api = TikTokAPI() + api = TikTokAPI(read_json_from_file("cookie.json")) return api.getVideosByUserName(user_name, count=1) def getLikesByUserName(user_name): - api = TikTokAPI() + api = TikTokAPI(read_json_from_file("cookie.json")) return api.getLikesByUserName(user_name, count=1) diff --git a/tests/test_video.py b/tests/test_video.py index ae0efc8..f000bc0 100644 --- a/tests/test_video.py +++ b/tests/test_video.py @@ -1,19 +1,20 @@ import argparse from TikTokAPI import TikTokAPI +from utils import read_json_from_file def getVideoById(video_id): - api = TikTokAPI() + api = TikTokAPI(read_json_from_file("cookie.json")) return api.getVideoById(video_id) def downloadVideoById(video_id): - api = TikTokAPI() + api = TikTokAPI(read_json_from_file("cookie.json")) api.downloadVideoById(video_id, video_id+".mp4") def downloadVideoByIdNoWatermark(video_id): - api = TikTokAPI() + api = TikTokAPI(read_json_from_file("cookie.json")) api.downloadVideoByIdNoWatermark(video_id, video_id+"_no_wm.mp4") diff --git a/tests/utils.py b/tests/utils.py new file mode 100644 index 0000000..8903eb9 --- /dev/null +++ b/tests/utils.py @@ -0,0 +1,9 @@ +import json + + +def read_json_from_file(filepath): + with open(filepath, 'r', encoding='utf-8') as json_file: + data = json.load(json_file) + if type(data) is str: + return json.loads(data) + return data