From a41c48555e80d304d468b242453831a3338f088f Mon Sep 17 00:00:00 2001 From: David Teather <34144122+davidteather@users.noreply.github.com> Date: Tue, 25 Aug 2020 01:15:38 -0500 Subject: [PATCH 01/27] Update get video url for working with byhashtag --- TikTokApi/tiktok.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/TikTokApi/tiktok.py b/TikTokApi/tiktok.py index 3595aaf9..a27c69fc 100644 --- a/TikTokApi/tiktok.py +++ b/TikTokApi/tiktok.py @@ -587,7 +587,10 @@ def get_Video_By_TikTok(self, data, proxy=None): :param data: A TikTok object :param proxy: The IP address of your proxy. """ - api_url = data['video']['downloadAddr'] + try: + api_url = data['video']['downloadAddr'] + except: + api_url = data['itemInfos']['video']['urls'][0] return self.get_Video_By_DownloadURL(api_url, proxy=proxy) def get_Video_By_DownloadURL(self, download_url, proxy=None): From cc5985da9f07f8e830d2116c5a9dc3c54c8077ee Mon Sep 17 00:00:00 2001 From: David Teather <34144122+davidteather@users.noreply.github.com> Date: Tue, 25 Aug 2020 01:23:22 -0500 Subject: [PATCH 02/27] Added return types --- TikTokApi/tiktok.py | 68 ++++++++++++++++++++++----------------------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/TikTokApi/tiktok.py b/TikTokApi/tiktok.py index a27c69fc..d7e7197a 100644 --- a/TikTokApi/tiktok.py +++ b/TikTokApi/tiktok.py @@ -47,7 +47,7 @@ def __init__(self, debug=False, request_delay=None): self.request_delay = request_delay - def getData(self, api_url, b, language='en', proxy=None): + def getData(self, api_url, b, language='en', proxy=None) -> dict: """Returns a dictionary of a response from TikTok. :param api_url: the base string without a signature @@ -90,7 +90,7 @@ def getData(self, api_url, b, language='en', proxy=None): raise Exception('Invalid Response') - def getBytes(self, api_url, b, proxy=None): + def getBytes(self, api_url, b, proxy=None) -> bytes: """Returns bytes of a response from TikTok. :param api_url: the base string without a signature @@ -112,7 +112,7 @@ def getBytes(self, api_url, b, proxy=None): }, proxies=self.__format_proxy(proxy)) return r.content - def trending(self, count=30, language='en', region='US', proxy=None): + def trending(self, count=30, language='en', region='US', proxy=None) -> dict: """ Gets trending TikToks """ @@ -147,7 +147,7 @@ def trending(self, count=30, language='en', region='US', proxy=None): return response[:count] - def userPosts(self, userID, secUID, count=30, language='en', region='US', proxy=None): + def userPosts(self, userID, secUID, count=30, language='en', region='US', proxy=None) -> dict: """Returns a dictionary listing TikToks given a user's ID and secUID :param userID: The userID of the user, which TikTok assigns. @@ -190,7 +190,7 @@ def userPosts(self, userID, secUID, count=30, language='en', region='US', proxy= return response[:count] - def byUsername(self, username, count=30, proxy=None, language='en', region='US'): + def byUsername(self, username, count=30, proxy=None, language='en', region='US') -> dict: """Returns a dictionary listing TikToks given a user's username. :param username: The username of the user. @@ -205,7 +205,7 @@ def byUsername(self, username, count=30, proxy=None, language='en', region='US') data = self.getUserObject(username, proxy=proxy) return self.userPosts(data['id'], data['secUid'], count=count, proxy=proxy, language=language, region=region) - def userLiked(self, userID, secUID, count=30, language='en', region='US', proxy=None): + def userLiked(self, userID, secUID, count=30, language='en', region='US', proxy=None) -> dict: """Returns a dictionary listing TikToks that a given a user has liked. Note: The user's likes must be public @@ -257,7 +257,7 @@ def userLiked(self, userID, secUID, count=30, language='en', region='US', proxy= return response[:count] - def userLikedbyUsername(self, username, count=30, proxy=None, language='en', region='US'): + def userLikedbyUsername(self, username, count=30, proxy=None, language='en', region='US') -> dict: """Returns a dictionary listing TikToks a user has liked by username. Note: The user's likes must be public @@ -273,7 +273,7 @@ def userLikedbyUsername(self, username, count=30, proxy=None, language='en', reg data = self.getUserObject(username, proxy=proxy) return self.userLiked(data['id'], data['secUid'], count=count, proxy=proxy, language=language, region=region) - def bySound(self, id, count=30, language='en', proxy=None): + def bySound(self, id, count=30, language='en', proxy=None) -> dict: """Returns a dictionary listing TikToks with a specific sound. :param id: The sound id to search by. @@ -313,7 +313,7 @@ def bySound(self, id, count=30, language='en', proxy=None): return response[:count] - def getMusicObject(self, id, language='en', proxy=None): + def getMusicObject(self, id, language='en', proxy=None) -> dict: """Returns a music object for a specific sound id. :param id: The sound id to search by. @@ -326,7 +326,7 @@ def getMusicObject(self, id, language='en', proxy=None): b = browser(api_url, proxy=proxy) return self.getData(api_url, b, proxy=proxy) - def byHashtag(self, hashtag, count=30, language='en', proxy=None, region='US'): + def byHashtag(self, hashtag, count=30, language='en', proxy=None, region='US') -> dict: """Returns a dictionary listing TikToks with a specific hashtag. :param hashtag: The hashtag to search by. @@ -368,7 +368,7 @@ def byHashtag(self, hashtag, count=30, language='en', proxy=None, region='US'): return response[:count] - def getHashtagObject(self, hashtag, language='en', proxy=None): + def getHashtagObject(self, hashtag, language='en', proxy=None) -> dict: """Returns a hashtag object. :param hashtag: The hashtag to search by. @@ -381,7 +381,7 @@ def getHashtagObject(self, hashtag, language='en', proxy=None): b = browser(api_url, proxy=proxy) return self.getData(api_url, b, proxy=proxy) - def getRecommendedTikToksByVideoID(self, id, language='en', proxy=None): + def getRecommendedTikToksByVideoID(self, id, language='en', proxy=None) -> dict: """Returns a dictionary listing reccomended TikToks for a specific TikTok video. :param id: The id of the video to get suggestions for. @@ -394,7 +394,7 @@ def getRecommendedTikToksByVideoID(self, id, language='en', proxy=None): b = browser(api_url, proxy=proxy) return self.getData(api_url, b, proxy=proxy)['body'] - def getTikTokById(self, id, language='en', proxy=None): + def getTikTokById(self, id, language='en', proxy=None) -> dict: """Returns a dictionary of a specific TikTok. :param id: The id of the TikTok you want to get the object for. @@ -407,7 +407,7 @@ def getTikTokById(self, id, language='en', proxy=None): b = browser(api_url, proxy=proxy) return self.getData(api_url, b, proxy=proxy) - def getTikTokByUrl(self, url, language='en', proxy=None): + def getTikTokByUrl(self, url, language='en', proxy=None) -> dict: """Returns a dictionary of a TikTok object by url. :param url: The TikTok url you want to retrieve. @@ -423,7 +423,7 @@ def getTikTokByUrl(self, url, language='en', proxy=None): return self.getTikTokById(post_id, language=language, proxy=proxy) - def discoverHashtags(self, proxy=None): + def discoverHashtags(self, proxy=None) -> dict: """Discover page, consists challenges (hashtags) :param proxy: The IP address of a proxy server. @@ -432,7 +432,7 @@ def discoverHashtags(self, proxy=None): b = browser(api_url, proxy=proxy) return self.getData(api_url, b, proxy=proxy)['body'][1]['exploreList'] - def discoverMusic(self, proxy=None): + def discoverMusic(self, proxy=None) -> dict: """Discover page, consists of music :param proxy: The IP address of a proxy server. @@ -441,7 +441,7 @@ def discoverMusic(self, proxy=None): b = browser(api_url, proxy=proxy) return self.getData(api_url, b, proxy=proxy)['body'][2]['exploreList'] - def getUserObject(self, username, language='en', proxy=None): + def getUserObject(self, username, language='en', proxy=None) -> dict: """Gets a user object (dictionary) :param username: The username of the user. @@ -451,7 +451,7 @@ def getUserObject(self, username, language='en', proxy=None): """ return self.getUser(username, language, proxy=proxy)['userInfo']['user'] - def getUser(self, username, language='en', proxy=None): + def getUser(self, username, language='en', proxy=None) -> dict: """Gets the full exposed user object :param username: The username of the user. @@ -464,7 +464,7 @@ def getUser(self, username, language='en', proxy=None): b = browser(api_url, proxy=proxy) return self.getData(api_url, b, proxy=proxy) - def getSuggestedUsersbyID(self, userId='6745191554350760966', count=30 ,language='en', proxy=None): + def getSuggestedUsersbyID(self, userId='6745191554350760966', count=30 ,language='en', proxy=None) -> list: """Returns suggested users given a different TikTok user. :param userId: The id of the user to get suggestions for. @@ -480,7 +480,7 @@ def getSuggestedUsersbyID(self, userId='6745191554350760966', count=30 ,language res.append(x['cardItem']) return res[:count] - def getSuggestedUsersbyIDCrawler(self, count=30, startingId='6745191554350760966', language='en', proxy=None): + def getSuggestedUsersbyIDCrawler(self, count=30, startingId='6745191554350760966', language='en', proxy=None) -> list: """Crawls for listing of all user objects it can find. :param count: The amount of users to crawl for. @@ -503,7 +503,7 @@ def getSuggestedUsersbyIDCrawler(self, count=30, startingId='6745191554350760966 return users[:count] - def getSuggestedHashtagsbyID(self, count=30, userId='6745191554350760966', language='en', proxy=None): + def getSuggestedHashtagsbyID(self, count=30, userId='6745191554350760966', language='en', proxy=None) -> list: """Returns suggested hashtags given a TikTok user. :param userId: The id of the user to get suggestions for. @@ -519,7 +519,7 @@ def getSuggestedHashtagsbyID(self, count=30, userId='6745191554350760966', langu res.append(x['cardItem']) return res[:count] - def getSuggestedHashtagsbyIDCrawler(self, count=30, startingId='6745191554350760966', language='en', proxy=None): + def getSuggestedHashtagsbyIDCrawler(self, count=30, startingId='6745191554350760966', language='en', proxy=None) -> list: """Crawls for as many hashtags as it can find. :param count: The amount of users to crawl for. @@ -542,7 +542,7 @@ def getSuggestedHashtagsbyIDCrawler(self, count=30, startingId='6745191554350760 return hashtags[:count] - def getSuggestedMusicbyID(self, count=30, userId='6745191554350760966', language='en', proxy=None): + def getSuggestedMusicbyID(self, count=30, userId='6745191554350760966', language='en', proxy=None) -> list: """Returns suggested music given a TikTok user. :param userId: The id of the user to get suggestions for. @@ -558,7 +558,7 @@ def getSuggestedMusicbyID(self, count=30, userId='6745191554350760966', language res.append(x['cardItem']) return res[:count] - def getSuggestedMusicIDCrawler(self, count=30, startingId='6745191554350760966', language='en', proxy=None): + def getSuggestedMusicIDCrawler(self, count=30, startingId='6745191554350760966', language='en', proxy=None) -> list: """Crawls for hashtags. :param count: The amount of users to crawl for. @@ -581,7 +581,7 @@ def getSuggestedMusicIDCrawler(self, count=30, startingId='6745191554350760966', return musics[:count] - def get_Video_By_TikTok(self, data, proxy=None): + def get_Video_By_TikTok(self, data, proxy=None) -> bytes: """Downloads video from TikTok using a TikTok object :param data: A TikTok object @@ -593,7 +593,7 @@ def get_Video_By_TikTok(self, data, proxy=None): api_url = data['itemInfos']['video']['urls'][0] return self.get_Video_By_DownloadURL(api_url, proxy=proxy) - def get_Video_By_DownloadURL(self, download_url, proxy=None): + def get_Video_By_DownloadURL(self, download_url, proxy=None) -> bytes: """Downloads video from TikTok using download url in a TikTok object :param download_url: The download url key value in a TikTok object. @@ -602,7 +602,7 @@ def get_Video_By_DownloadURL(self, download_url, proxy=None): b = browser(download_url, proxy=proxy) return self.getBytes(download_url, b, proxy=proxy) - def get_Video_By_Url(self, video_url, return_bytes=0, chromedriver_path=None): + def get_Video_By_Url(self, video_url, return_bytes=0, chromedriver_path=None) -> bytes: """(DEPRECRATED) Gets the source url of a given url for a tiktok @@ -612,7 +612,7 @@ def get_Video_By_Url(self, video_url, return_bytes=0, chromedriver_path=None): """ raise Exception("Deprecated. Other Methods Work Better.") - def get_Video_No_Watermark_ID(self, video_id, return_bytes=0, proxy=None): + def get_Video_No_Watermark_ID(self, video_id, return_bytes=0, proxy=None) -> bytes: """Returns a TikTok video with no water mark :param video_id: The ID of the TikTok you want to download @@ -637,7 +637,7 @@ def get_Video_No_Watermark_ID(self, video_id, return_bytes=0, proxy=None): video_url_no_wm, params=None, headers=headers) return video_data_no_wm.content - def get_Video_No_Watermark_Faster(self, video_url, return_bytes=0, proxy=None): + def get_Video_No_Watermark_Faster(self, video_url, return_bytes=0, proxy=None) -> bytes: """No Water Mark method, but may be faster :param video_url: The url of the video you want to download @@ -647,7 +647,7 @@ def get_Video_No_Watermark_Faster(self, video_url, return_bytes=0, proxy=None): video_id = video_url.split("/video/")[1].split("?")[0] return self.get_Video_No_Watermark_ID(video_id, return_bytes, proxy=proxy) - def get_Video_No_Watermark(self, video_url, return_bytes=0, proxy=None): + def get_Video_No_Watermark(self, video_url, return_bytes=0, proxy=None) -> bytes: """Gets the video with no watermark :param video_url: The url of the video you want to download @@ -701,7 +701,7 @@ def get_Video_No_Watermark(self, video_url, return_bytes=0, proxy=None): # PRIVATE METHODS # - def __format_proxy(self, proxy): + def __format_proxy(self, proxy) -> dict: """ Formats the proxy object """ @@ -713,13 +713,13 @@ def __format_proxy(self, proxy): else: return None - def __get_js(self, proxy=None): + def __get_js(self, proxy=None) -> str: return requests.get("https://sf16-muse-va.ibytedtos.com/obj/rc-web-sdk-gcs/acrawler.js", proxies=self.__format_proxy(proxy)).text - def __format_new_params__(self, parm): + def __format_new_params__(self, parm) -> str: return parm.replace("/", "%2F").replace(" ", "+").replace(";", "%3B") - def __add_new_params__(self): + def __add_new_params__(self) -> str: return "aid=1988&app_name=tiktok_web&device_platform=web&referer=&user_agent={}&cookie_enabled=true".format(self.__format_new_params__(self.userAgent)) + \ "&screen_width={}&screen_height={}&browser_language={}&browser_platform={}&browser_name={}&browser_version={}".format(self.width, self.height, self.browser_language, self.browser_platform, self.browser_name, self.browser_version) + \ "&browser_online=true&timezone_name={}&priority_region=&appId=1233&appType=m&isAndroid=false&isMobile=false".format(self.timezone_name) + \ From d5ccbeef6d2c35c3a6e615048785dbb716771fc4 Mon Sep 17 00:00:00 2001 From: "Michael D. Hoyle" Date: Tue, 25 Aug 2020 16:37:02 -0400 Subject: [PATCH 03/27] Add userPage and getUserPager methods --- TikTokApi/tiktok.py | 63 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/TikTokApi/tiktok.py b/TikTokApi/tiktok.py index d7e7197a..53b36c4b 100644 --- a/TikTokApi/tiktok.py +++ b/TikTokApi/tiktok.py @@ -205,6 +205,69 @@ def byUsername(self, username, count=30, proxy=None, language='en', region='US') data = self.getUserObject(username, proxy=proxy) return self.userPosts(data['id'], data['secUid'], count=count, proxy=proxy, language=language, region=region) + def userPage( + self, userID, secUID, page_size=30, before=0, after=0, language='en', + region='US', proxy=None + ) -> dict: + """Returns a dictionary listing of one page of TikToks given a user's ID and secUID + + :param userID: The userID of the user, which TikTok assigns. + :param secUID: The secUID of the user, which TikTok assigns. + :param page_size: The number of posts to return per page. + :param min_cursor: time stamp for the earliest TikTok to retrieve + :param max_cursor: time stamp for the latest TikTok to retrieve + :param language: The 2 letter code of the language to return. + Note: Doesn't seem to have an affect. + :param region: The 2 letter region code. + Note: Doesn't seem to have an affect. + :param proxy: The IP address of a proxy to make requests from. + """ + api_url = ( + "https://m.tiktok.com/api/item_list/?{}&count={}&id={}&type=1&secUid={}" + "&minCursor={}&maxCursor={}&sourceType=8&appId=1233®ion={}&language={}".format( + self.__add_new_params__(), page_size, str(userID), str(secUID), + after, before, region, language + ) + ) + b = browser(api_url, proxy=proxy) + return self.getData(api_url, b, proxy=proxy) + + def getUserPager(self, username, page_size=30, proxy=None, language='en', region='US'): + """Returns a generator to page through a user's feed + + :param username: The username of the user. + :param page_size: The number of posts to return in a page. + :param language: The 2 letter code of the language to return. + Note: Doesn't seem to have an affect. + :param region: The 2 letter region code. + Note: Doesn't seem to have an affect. + :param proxy: The IP address of a proxy to make requests from. + """ + data = self.getUserObject(username, proxy=proxy) + + max_cursor = 0 + + while True: + page = None + + resp = self.userPage( + data['id'], data['secUid'], page_size=page_size, + before=max_cursor, proxy=proxy, language=language, region=region + ) + + try: + page = resp['items'] + except KeyError: + # No mo results + return + + max_cursor = resp['maxCursor'] + + yield page + + if not resp['hasMore']: + return # all done + def userLiked(self, userID, secUID, count=30, language='en', region='US', proxy=None) -> dict: """Returns a dictionary listing TikToks that a given a user has liked. Note: The user's likes must be public From f71de0899f1cb6c7833649a8bd5931e6bddb0908 Mon Sep 17 00:00:00 2001 From: "Michael D. Hoyle" Date: Tue, 25 Aug 2020 17:03:55 -0400 Subject: [PATCH 04/27] clean up some params --- TikTokApi/tiktok.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/TikTokApi/tiktok.py b/TikTokApi/tiktok.py index 53b36c4b..b3e5849b 100644 --- a/TikTokApi/tiktok.py +++ b/TikTokApi/tiktok.py @@ -214,8 +214,8 @@ def userPage( :param userID: The userID of the user, which TikTok assigns. :param secUID: The secUID of the user, which TikTok assigns. :param page_size: The number of posts to return per page. - :param min_cursor: time stamp for the earliest TikTok to retrieve - :param max_cursor: time stamp for the latest TikTok to retrieve + :param after: time stamp for the earliest TikTok to retrieve + :param before: time stamp for the latest TikTok to retrieve :param language: The 2 letter code of the language to return. Note: Doesn't seem to have an affect. :param region: The 2 letter region code. @@ -232,11 +232,13 @@ def userPage( b = browser(api_url, proxy=proxy) return self.getData(api_url, b, proxy=proxy) - def getUserPager(self, username, page_size=30, proxy=None, language='en', region='US'): + def getUserPager(self, username, page_size=30, before=0, after=0, proxy=None, language='en', region='US'): """Returns a generator to page through a user's feed :param username: The username of the user. :param page_size: The number of posts to return in a page. + :param after: time stamp for the earliest TikTok to retrieve + :param before: time stamp for the latest TikTok to retrieve :param language: The 2 letter code of the language to return. Note: Doesn't seem to have an affect. :param region: The 2 letter region code. @@ -245,14 +247,10 @@ def getUserPager(self, username, page_size=30, proxy=None, language='en', region """ data = self.getUserObject(username, proxy=proxy) - max_cursor = 0 - while True: - page = None - resp = self.userPage( data['id'], data['secUid'], page_size=page_size, - before=max_cursor, proxy=proxy, language=language, region=region + before=before, after=after, proxy=proxy, language=language, region=region ) try: @@ -261,7 +259,7 @@ def getUserPager(self, username, page_size=30, proxy=None, language='en', region # No mo results return - max_cursor = resp['maxCursor'] + before = resp['maxCursor'] yield page From f9f194b7d8802347e2f70d1f66eec5e6e6a4df35 Mon Sep 17 00:00:00 2001 From: "Michael D. Hoyle" Date: Wed, 26 Aug 2020 10:37:45 -0400 Subject: [PATCH 05/27] Add demo script --- TikTokApi/demo_pager.py | 45 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 TikTokApi/demo_pager.py diff --git a/TikTokApi/demo_pager.py b/TikTokApi/demo_pager.py new file mode 100644 index 00000000..82034a95 --- /dev/null +++ b/TikTokApi/demo_pager.py @@ -0,0 +1,45 @@ +from TikTokApi import TikTokApi + +api = TikTokApi(debug=True) + +def printPage(page): + """Just prints out each post with timestamp and description""" + for post in page: + print("{}: {}".format(post['createTime'], post['desc'])) + +count = 20 +username = 'diply' + +# count and list all of the posts for a given user with the pager +total = 0 + +pager = api.getUserPager(username, page_size=count) + +for page in pager: + printPage(page) + total += len(page) + +print('{} has {} posts'.format(username, total)) +all_posts = total + +# List all of the posts for a given user after a certain date +AUG_19 = 1597889080000 # 2020-8-19 22:04:40 to be precise. Must be ms-precision UNIX timestamp +user = api.getUserObject(username) +page = api.userPage(user['id'], user['secUid'], page_size=30, after=AUG_19) + +printPage(page['items']) +new_posts = len(page['items']) +print('{} has {} posts after {}'.format(username, new_posts, AUG_19)) + + +# Count and list all of the posts before a certain date for a given user with the pager + +total = 0 +pager = api.getUserPager(username, page_size=count, before=AUG_19) + +for page in pager: + printPage(page) + total += len(page) + +print('{} has {} posts from before {}'.format(username, total, AUG_19)) +print('Should be {}'.format(all_posts - new_posts)) From d7140526f15a6d4637c7e5e0dad39f1bdbf8270a Mon Sep 17 00:00:00 2001 From: "Michael D. Hoyle" Date: Wed, 26 Aug 2020 10:42:00 -0400 Subject: [PATCH 06/27] automatic whitespace fixes --- TikTokApi/tiktok.py | 47 ++++++++++++++++++++++----------------------- 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/TikTokApi/tiktok.py b/TikTokApi/tiktok.py index d7e7197a..be9adbbb 100644 --- a/TikTokApi/tiktok.py +++ b/TikTokApi/tiktok.py @@ -56,7 +56,7 @@ def getData(self, api_url, b, language='en', proxy=None) -> dict: :param language: The two digit language code to make requests to TikTok with. Note: This doesn't seem to actually change things from the API. - + :param proxy: The IP address of a proxy server to request from. """ if self.request_delay != None: @@ -99,7 +99,7 @@ def getBytes(self, api_url, b, proxy=None) -> bytes: :param language: The two digit language code to make requests to TikTok with. Note: This doesn't seem to actually change things from the API. - + :param proxy: The IP address of a proxy server to request from. """ url = api_url + \ @@ -158,7 +158,7 @@ def userPosts(self, userID, secUID, count=30, language='en', region='US', proxy= Note: Doesn't seem to have an affect. :param region: The 2 letter region code. Note: Doesn't seem to have an affect. - :param proxy: The IP address of a proxy to make requests from. + :param proxy: The IP address of a proxy to make requests from. """ response = [] maxCount = 50 @@ -200,7 +200,7 @@ def byUsername(self, username, count=30, proxy=None, language='en', region='US') Note: Doesn't seem to have an affect. :param region: The 2 letter region code. Note: Doesn't seem to have an affect. - :param proxy: The IP address of a proxy to make requests from. + :param proxy: The IP address of a proxy to make requests from. """ data = self.getUserObject(username, proxy=proxy) return self.userPosts(data['id'], data['secUid'], count=count, proxy=proxy, language=language, region=region) @@ -217,7 +217,7 @@ def userLiked(self, userID, secUID, count=30, language='en', region='US', proxy= Note: Doesn't seem to have an affect. :param region: The 2 letter region code. Note: Doesn't seem to have an affect. - :param proxy: The IP address of a proxy to make requests from. + :param proxy: The IP address of a proxy to make requests from. """ response = [] maxCount = 50 @@ -268,7 +268,7 @@ def userLikedbyUsername(self, username, count=30, proxy=None, language='en', reg Note: Doesn't seem to have an affect. :param region: The 2 letter region code. Note: Doesn't seem to have an affect. - :param proxy: The IP address of a proxy to make requests from. + :param proxy: The IP address of a proxy to make requests from. """ data = self.getUserObject(username, proxy=proxy) return self.userLiked(data['id'], data['secUid'], count=count, proxy=proxy, language=language, region=region) @@ -284,7 +284,7 @@ def bySound(self, id, count=30, language='en', proxy=None) -> dict: Note: Doesn't seem to have an affect. :param region: The 2 letter region code. Note: Doesn't seem to have an affect. - :param proxy: The IP address of a proxy to make requests from. + :param proxy: The IP address of a proxy to make requests from. """ response = [] maxCount = 50 @@ -319,7 +319,7 @@ def getMusicObject(self, id, language='en', proxy=None) -> dict: :param id: The sound id to search by. :param language: The 2 letter code of the language to return. Note: Doesn't seem to have an affect. - :param proxy: The IP address of a proxy to make requests from. + :param proxy: The IP address of a proxy to make requests from. """ api_url = "https://m.tiktok.com/api/music/detail/?{}&musicId={}&language={}&verifyFp=".format( self.__add_new_params__(), str(id), language) @@ -336,7 +336,7 @@ def byHashtag(self, hashtag, count=30, language='en', proxy=None, region='US') - Note: Doesn't seem to have an affect. :param region: The 2 letter region code. Note: Doesn't seem to have an affect. - :param proxy: The IP address of a proxy to make requests from. + :param proxy: The IP address of a proxy to make requests from. """ id = self.getHashtagObject(hashtag)['challengeInfo']['challenge']['id'] response = [] @@ -374,7 +374,7 @@ def getHashtagObject(self, hashtag, language='en', proxy=None) -> dict: :param hashtag: The hashtag to search by. :param language: The 2 letter code of the language to return. Note: Doesn't seem to have an affect. - :param proxy: The IP address of a proxy to make requests from. + :param proxy: The IP address of a proxy to make requests from. """ api_url = "https://m.tiktok.com/api/challenge/detail/?{}&challengeName={}&language={}".format( self.__add_new_params__(), str(hashtag.encode('utf-8'))[2:-1].replace("\\x", "%").upper(), language) @@ -387,7 +387,7 @@ def getRecommendedTikToksByVideoID(self, id, language='en', proxy=None) -> dict: :param id: The id of the video to get suggestions for. :param language: The 2 letter code of the language to return. Note: Doesn't seem to have an affect. - :param proxy: The IP address of a proxy to make requests from. + :param proxy: The IP address of a proxy to make requests from. """ api_url = "https://m.tiktok.com/share/item/list?{}&secUid=&id={}&type=0&count=24&minCursor=0&maxCursor=0&shareUid=&recType=3&lang={}&verifyFp=".format( self.__add_new_params__(), id, language) @@ -400,7 +400,7 @@ def getTikTokById(self, id, language='en', proxy=None) -> dict: :param id: The id of the TikTok you want to get the object for. :param language: The 2 letter code of the language to return. Note: Doesn't seem to have an affect. - :param proxy: The IP address of a proxy to make requests from. + :param proxy: The IP address of a proxy to make requests from. """ api_url = "https://m.tiktok.com/api/item/detail/?{}&itemId={}&language={}&verifyFp=".format( self.__add_new_params__(), id, language) @@ -413,7 +413,7 @@ def getTikTokByUrl(self, url, language='en', proxy=None) -> dict: :param url: The TikTok url you want to retrieve. :param language: The 2 letter code of the language to return. Note: Doesn't seem to have an affect. - :param proxy: The IP address of a proxy to make requests from. + :param proxy: The IP address of a proxy to make requests from. """ if "@" in url and "/video/" in url: post_id = url.split("/video/")[1].split("?")[0] @@ -447,7 +447,7 @@ def getUserObject(self, username, language='en', proxy=None) -> dict: :param username: The username of the user. :param language: The 2 letter code of the language to return. Note: Doesn't seem to have an affect. - :param proxy: The IP address of a proxy to make requests from. + :param proxy: The IP address of a proxy to make requests from. """ return self.getUser(username, language, proxy=proxy)['userInfo']['user'] @@ -457,7 +457,7 @@ def getUser(self, username, language='en', proxy=None) -> dict: :param username: The username of the user. :param language: The 2 letter code of the language to return. Note: Doesn't seem to have an affect. - :param proxy: The IP address of a proxy to make requests from. + :param proxy: The IP address of a proxy to make requests from. """ api_url = "https://m.tiktok.com/api/user/detail/?{}&uniqueId={}&language={}".format( self.__add_new_params__(), username, language) @@ -469,7 +469,7 @@ def getSuggestedUsersbyID(self, userId='6745191554350760966', count=30 ,language :param userId: The id of the user to get suggestions for. :param count: The amount of users to return. - :param proxy: The IP address of a proxy to make requests from. + :param proxy: The IP address of a proxy to make requests from. """ api_url = "https://m.tiktok.com/node/share/discover?{}&noUser=0&pageId={}&userId={}&userCount={}&scene=15&verifyFp=".format( self.__add_new_params__(), userId, userId, str(count)) @@ -486,7 +486,7 @@ def getSuggestedUsersbyIDCrawler(self, count=30, startingId='6745191554350760966 :param count: The amount of users to crawl for. :param startingId: The ID of a TikTok user to start at. :param language: The language parameter. - :param proxy: The IP address of a proxy to make requests from. + :param proxy: The IP address of a proxy to make requests from. """ users = [] unusedIDS = [startingId] @@ -508,7 +508,7 @@ def getSuggestedHashtagsbyID(self, count=30, userId='6745191554350760966', langu :param userId: The id of the user to get suggestions for. :param count: The amount of users to return. - :param proxy: The IP address of a proxy to make requests from. + :param proxy: The IP address of a proxy to make requests from. """ api_url = "https://m.tiktok.com/node/share/discover?{}&noUser=0&pageId={}&userId={}&userCount={}&scene=15&verifyFp=".format( self.__add_new_params__(), userId, userId, str(count)) @@ -525,7 +525,7 @@ def getSuggestedHashtagsbyIDCrawler(self, count=30, startingId='6745191554350760 :param count: The amount of users to crawl for. :param startingId: The ID of a TikTok user to start at. :param language: The language parameter. - :param proxy: The IP address of a proxy to make requests from. + :param proxy: The IP address of a proxy to make requests from. """ hashtags = [] ids = self.getSuggestedUsersbyIDCrawler( @@ -547,7 +547,7 @@ def getSuggestedMusicbyID(self, count=30, userId='6745191554350760966', language :param userId: The id of the user to get suggestions for. :param count: The amount of users to return. - :param proxy: The IP address of a proxy to make requests from. + :param proxy: The IP address of a proxy to make requests from. """ api_url = "https://m.tiktok.com/node/share/discover?{}&noUser=0&pageId={}&userId={}&userCount={}&scene=15&verifyFp=".format( self.__add_new_params__(), userId, userId, str(count)) @@ -564,7 +564,7 @@ def getSuggestedMusicIDCrawler(self, count=30, startingId='6745191554350760966', :param count: The amount of users to crawl for. :param startingId: The ID of a TikTok user to start at. :param language: The language parameter. - :param proxy: The IP address of a proxy to make requests from. + :param proxy: The IP address of a proxy to make requests from. """ musics = [] ids = self.getSuggestedUsersbyIDCrawler( @@ -608,13 +608,13 @@ def get_Video_By_Url(self, video_url, return_bytes=0, chromedriver_path=None) -> video_url - the url of the video return_bytes - 0 is just the url, 1 is the actual video bytes - chromedriver_path - path to your chrome driver executable + chromedriver_path - path to your chrome driver executable """ raise Exception("Deprecated. Other Methods Work Better.") def get_Video_No_Watermark_ID(self, video_id, return_bytes=0, proxy=None) -> bytes: """Returns a TikTok video with no water mark - + :param video_id: The ID of the TikTok you want to download :param return_bytes: Set this to 1 if you want bytes, 0 if you want url. :param proxy: The IP address of your proxy. @@ -725,4 +725,3 @@ def __add_new_params__(self) -> str: "&browser_online=true&timezone_name={}&priority_region=&appId=1233&appType=m&isAndroid=false&isMobile=false".format(self.timezone_name) + \ "&isIOS=false&OS=windows&did=" + \ str(random.randint(10000, 999999999)) - From 2659e0561ad99aca48419191283e1d93a68ef4da Mon Sep 17 00:00:00 2001 From: "Michael D. Hoyle" Date: Thu, 27 Aug 2020 09:26:14 -0400 Subject: [PATCH 07/27] Move the demo script --- TikTokApi/demo_pager.py => examples/demoUserPager.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename TikTokApi/demo_pager.py => examples/demoUserPager.py (100%) diff --git a/TikTokApi/demo_pager.py b/examples/demoUserPager.py similarity index 100% rename from TikTokApi/demo_pager.py rename to examples/demoUserPager.py From eb9ba3cc0e079092e81f7d7ecaa085d988172be6 Mon Sep 17 00:00:00 2001 From: "Michael D. Hoyle" Date: Thu, 27 Aug 2020 10:19:31 -0400 Subject: [PATCH 08/27] Add tests for user pager --- tests/test_userPager.py | 49 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 tests/test_userPager.py diff --git a/tests/test_userPager.py b/tests/test_userPager.py new file mode 100644 index 00000000..ab94fd9c --- /dev/null +++ b/tests/test_userPager.py @@ -0,0 +1,49 @@ +from TikTokApi import TikTokApi + + +class TestUserPager: + """Test the pager returned by getUserPager""" + def test_page_size(self): + """Pages should be pretty close to the specified size""" + api = TikTokApi() + + pager = api.getUserPager('therock', page_size=5) + + page = pager.__next__() + assert abs(len(page)-5) <= 2 + + page = pager.__next__() + assert abs(len(page)-5) <= 2 + + def test_user_pager_before(self): + """Should always request therock's first 19 tiktoks across 2 pages""" + APR_24 = 1587757436000 # 2020-04-24 15:43:56 to be precise. Must be ms-precision timestamp + + api = TikTokApi() + pager = api.getUserPager('therock', page_size=10, before=APR_24) + + total_tts = 0 + pages = 0 + for page in pager: + pages += 1 + total_tts += len(page) + + assert pages == 2 + assert total_tts == 19 + + def test_user_pager_before_after(self): + """Should always request the 7 tiktoks between those times""" + APR_24 = 1587757437000 # 2020-04-24 15:43:57 + AUG_10 = 1597076218000 # 2020-08-10 12:16:58 + + api = TikTokApi() + pager = api.getUserPager('therock', page_size=3, after=APR_24, before=AUG_10) + + total_tts = 0 + pages = 0 + for page in pager: + pages += 1 + total_tts += len(page) + + assert pages == 3 + assert total_tts == 7 From 7ecfc58fe9290ae55afa8d8b5c4e2fc59f787198 Mon Sep 17 00:00:00 2001 From: "Michael D. Hoyle" Date: Thu, 27 Aug 2020 10:20:06 -0400 Subject: [PATCH 09/27] update user pager demo to be more manageable --- examples/demoUserPager.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/examples/demoUserPager.py b/examples/demoUserPager.py index 82034a95..5104ed21 100644 --- a/examples/demoUserPager.py +++ b/examples/demoUserPager.py @@ -1,14 +1,17 @@ +from datetime import datetime from TikTokApi import TikTokApi api = TikTokApi(debug=True) + def printPage(page): """Just prints out each post with timestamp and description""" for post in page: - print("{}: {}".format(post['createTime'], post['desc'])) + print("{}: {}".format(datetime.fromtimestamp(post['createTime']), post['desc'])) + count = 20 -username = 'diply' +username = 'therock' # count and list all of the posts for a given user with the pager total = 0 @@ -23,23 +26,24 @@ def printPage(page): all_posts = total # List all of the posts for a given user after a certain date -AUG_19 = 1597889080000 # 2020-8-19 22:04:40 to be precise. Must be ms-precision UNIX timestamp + +APR_24 = 1587757438000 # 2020-04-24 15:43:58 to be precise. Must be ms-precision UNIX timestamp user = api.getUserObject(username) -page = api.userPage(user['id'], user['secUid'], page_size=30, after=AUG_19) +page = api.userPage(user['id'], user['secUid'], page_size=30, after=APR_24) printPage(page['items']) new_posts = len(page['items']) -print('{} has {} posts after {}'.format(username, new_posts, AUG_19)) +print('{} has {} posts after {}'.format(username, new_posts, APR_24)) # Count and list all of the posts before a certain date for a given user with the pager total = 0 -pager = api.getUserPager(username, page_size=count, before=AUG_19) +pager = api.getUserPager(username, page_size=count, before=APR_24) for page in pager: printPage(page) total += len(page) -print('{} has {} posts from before {}'.format(username, total, AUG_19)) +print('{} has {} posts from before {}'.format(username, total, APR_24)) print('Should be {}'.format(all_posts - new_posts)) From 2c467ff83ff9e0b2acf7a833a8138264c236b69a Mon Sep 17 00:00:00 2001 From: "Michael D. Hoyle" Date: Thu, 27 Aug 2020 10:22:42 -0400 Subject: [PATCH 10/27] add note on running examples to README --- README.md | 10 +++++++++- examples/__init__.py | 0 2 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 examples/__init__.py diff --git a/README.md b/README.md index 90c4dc3b..d8054f75 100644 --- a/README.md +++ b/README.md @@ -94,6 +94,14 @@ for tiktok in trending: print(len(trending)) ``` + +To run the example scripts from the repository root, make sure you use the +module form of python the interpreter + +```sh +python -m examples.getTrending +``` + [Here's](https://gist.github.com/davidteather/7c30780bbc30772ba11ec9e0b909e99d) an example of what a tiktok dictionary looks like. ## Detailed Documentation @@ -364,4 +372,4 @@ See also the list of [contributors](https://github.com/davidteather/TikTok-Api/c ## License -This project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for details \ No newline at end of file +This project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for details diff --git a/examples/__init__.py b/examples/__init__.py new file mode 100644 index 00000000..e69de29b From 098780c60cb4af6f968ea5f4cea847bb36c973a9 Mon Sep 17 00:00:00 2001 From: "Michael D. Hoyle" Date: Wed, 26 Aug 2020 10:51:20 -0400 Subject: [PATCH 11/27] remove some unused imports --- TikTokApi/tiktok.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/TikTokApi/tiktok.py b/TikTokApi/tiktok.py index be9adbbb..3939d2ac 100644 --- a/TikTokApi/tiktok.py +++ b/TikTokApi/tiktok.py @@ -1,10 +1,7 @@ - -import pyppeteer import random import requests from .browser import browser import time -from selenium import webdriver class TikTokApi: From f4504798b3b4b3e7cd8fb86fd2782b4dfc1fbc7f Mon Sep 17 00:00:00 2001 From: "Michael D. Hoyle" Date: Wed, 26 Aug 2020 10:51:38 -0400 Subject: [PATCH 12/27] Make line length limit more reasonable --- setup.cfg | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 224a7795..e91ee50a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,2 +1,5 @@ [metadata] -description-file = README.md \ No newline at end of file +description-file = README.md + +[flake8] +max-line-length = 120 From f294b28d97a515d6849e73fbdd6b64e065ef350e Mon Sep 17 00:00:00 2001 From: "Michael D. Hoyle" Date: Wed, 26 Aug 2020 10:51:59 -0400 Subject: [PATCH 13/27] break up a long string If you're not aware, python automatically concatenates adjacent string literals if they're inside a bound context (between parentheses, braces etc), so this is a great way to keep string literals easy-to-read. --- TikTokApi/tiktok.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/TikTokApi/tiktok.py b/TikTokApi/tiktok.py index 3939d2ac..5704ecd5 100644 --- a/TikTokApi/tiktok.py +++ b/TikTokApi/tiktok.py @@ -15,7 +15,11 @@ def __init__(self, debug=False, request_delay=None): if debug: print("Class initialized") - self.userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.125 Safari/537.36" + self.userAgent = ( + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " + "AppleWebKit/537.36 (KHTML, like Gecko) " + "Chrome/84.0.4147.125 Safari/537.36" + ) # Get Browser Params b = browser('newParam', newParams=True) @@ -41,7 +45,6 @@ def __init__(self, debug=False, request_delay=None): self.width = "1920" self.height = "1080" - self.request_delay = request_delay def getData(self, api_url, b, language='en', proxy=None) -> dict: From 7681c7597af72ff8475e29c95c1445f0459c4259 Mon Sep 17 00:00:00 2001 From: "Michael D. Hoyle" Date: Wed, 26 Aug 2020 10:54:44 -0400 Subject: [PATCH 14/27] Use identity operators to compare to `None` `None` is a singleton, so using the identity operator (`is`) is a safer way to check for it. Equivalence operator (`==`) may attempt to coerce `None` to another type, which can have unintended consequences. --- TikTokApi/tiktok.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/TikTokApi/tiktok.py b/TikTokApi/tiktok.py index 5704ecd5..3b9d866d 100644 --- a/TikTokApi/tiktok.py +++ b/TikTokApi/tiktok.py @@ -59,7 +59,7 @@ def getData(self, api_url, b, language='en', proxy=None) -> dict: :param proxy: The IP address of a proxy server to request from. """ - if self.request_delay != None: + if self.request_delay is not None: time.sleep(self.request_delay) url = b.url + \ "&verifyFp=" + b.verifyFp + \ @@ -705,7 +705,7 @@ def __format_proxy(self, proxy) -> dict: """ Formats the proxy object """ - if proxy != None: + if proxy is not None: return { 'http': proxy, 'https': proxy From c87ae3dce0b7f7bede00f84c37b77f5883b6cf5d Mon Sep 17 00:00:00 2001 From: "Michael D. Hoyle" Date: Wed, 26 Aug 2020 11:00:59 -0400 Subject: [PATCH 15/27] dont use bare except That intercepts *every* exception, including ones like termination signals. Specifying the base `Exception` class limits the scope to exceptions that aren't under-the-hood functionality, but it's better to be specific about what exception classes are being handled, and then have a top-level exception handler that takes care of anything unexpected. --- TikTokApi/tiktok.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/TikTokApi/tiktok.py b/TikTokApi/tiktok.py index 3b9d866d..0236c84f 100644 --- a/TikTokApi/tiktok.py +++ b/TikTokApi/tiktok.py @@ -83,7 +83,7 @@ def getData(self, api_url, b, language='en', proxy=None) -> dict: }, proxies=self.__format_proxy(proxy)) try: return r.json() - except: + except Exception: print(r.request.headers) print("Converting response to JSON failed response is below (probably empty)") print(r.text) @@ -237,7 +237,7 @@ def userLiked(self, userID, secUID, count=30, language='en', region='US', proxy= try: res['items'] - except: + except Exception: if self.debug: print("Most Likely User's List is Empty") return [] @@ -589,7 +589,7 @@ def get_Video_By_TikTok(self, data, proxy=None) -> bytes: """ try: api_url = data['video']['downloadAddr'] - except: + except Exception: api_url = data['itemInfos']['video']['urls'][0] return self.get_Video_By_DownloadURL(api_url, proxy=proxy) From a298a98a1cbe2947202798d15af7327b795355f9 Mon Sep 17 00:00:00 2001 From: "Michael D. Hoyle" Date: Wed, 26 Aug 2020 11:11:20 -0400 Subject: [PATCH 16/27] whitespace tweaks around operators and commas --- TikTokApi/tiktok.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/TikTokApi/tiktok.py b/TikTokApi/tiktok.py index 0236c84f..a22a9c2e 100644 --- a/TikTokApi/tiktok.py +++ b/TikTokApi/tiktok.py @@ -127,7 +127,7 @@ def trending(self, count=30, language='en', region='US', proxy=None) -> dict: else: realCount = maxCount api_url = "https://m.tiktok.com/api/item_list/?{}&count={}&id=1&type=5&secUid=&maxCursor={}&minCursor=0&sourceType=12&appId=1233®ion={}&language={}".format( - self.__add_new_params__() ,str(realCount), str(maxCursor), str(region), str(language)) + self.__add_new_params__(), str(realCount), str(maxCursor), str(region), str(language)) b = browser(api_url, language=language, proxy=proxy) res = self.getData(api_url, b, proxy=proxy) @@ -140,7 +140,7 @@ def trending(self, count=30, language='en', region='US', proxy=None) -> dict: print("TikTok isn't sending more TikToks beyond this point.") return response - realCount = count-len(response) + realCount = count - len(response) maxCursor = res['maxCursor'] first = False @@ -183,7 +183,7 @@ def userPosts(self, userID, secUID, count=30, language='en', region='US', proxy= print("TikTok isn't sending more TikToks beyond this point.") return response - realCount = count-len(response) + realCount = count - len(response) maxCursor = res['maxCursor'] first = False @@ -250,7 +250,7 @@ def userLiked(self, userID, secUID, count=30, language='en', region='US', proxy= print("TikTok isn't sending more TikToks beyond this point.") return response - realCount = count-len(response) + realCount = count - len(response) maxCursor = res['maxCursor'] first = False @@ -297,7 +297,7 @@ def bySound(self, id, count=30, language='en', proxy=None) -> dict: realCount = maxCount api_url = "https://m.tiktok.com/share/item/list?{}&secUid=&id={}&type=4&count={}&minCursor=0&maxCursor={}&shareUid=&lang={}&verifyFp=".format( - self.__add_new_params__(),str(id), str(realCount), str(maxCursor), str(language)) + self.__add_new_params__(), str(id), str(realCount), str(maxCursor), str(language)) b = browser(api_url, proxy=proxy) res = self.getData(api_url, b, proxy=proxy) @@ -308,7 +308,7 @@ def bySound(self, id, count=30, language='en', proxy=None) -> dict: print("TikTok isn't sending more TikToks beyond this point.") return response - realCount = count-len(response) + realCount = count - len(response) maxCursor = res['body']['maxCursor'] return response[:count] @@ -363,7 +363,7 @@ def byHashtag(self, hashtag, count=30, language='en', proxy=None, region='US') - print("TikTok isn't sending more TikToks beyond this point.") return response - realCount = count-len(response) + realCount = count - len(response) maxCursor = res['body']['maxCursor'] return response[:count] @@ -464,7 +464,7 @@ def getUser(self, username, language='en', proxy=None) -> dict: b = browser(api_url, proxy=proxy) return self.getData(api_url, b, proxy=proxy) - def getSuggestedUsersbyID(self, userId='6745191554350760966', count=30 ,language='en', proxy=None) -> list: + def getSuggestedUsersbyID(self, userId='6745191554350760966', count=30, language='en', proxy=None) -> list: """Returns suggested users given a different TikTok user. :param userId: The id of the user to get suggestions for. @@ -628,7 +628,7 @@ def get_Video_No_Watermark_ID(self, video_id, return_bytes=0, proxy=None) -> byt return None 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]) + .format(video_data[pos + 4:pos + 36]) if return_bytes == 0: return video_url_no_wm else: From 435d60a768cd49e55b3971465c547ad06e33263a Mon Sep 17 00:00:00 2001 From: "Michael D. Hoyle" Date: Wed, 26 Aug 2020 11:12:51 -0400 Subject: [PATCH 17/27] remove unused variable --- TikTokApi/tiktok.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/TikTokApi/tiktok.py b/TikTokApi/tiktok.py index a22a9c2e..6c6a8213 100644 --- a/TikTokApi/tiktok.py +++ b/TikTokApi/tiktok.py @@ -340,15 +340,9 @@ def byHashtag(self, hashtag, count=30, language='en', proxy=None, region='US') - """ id = self.getHashtagObject(hashtag)['challengeInfo']['challenge']['id'] response = [] - maxCount = 50 maxCursor = 0 while len(response) < count: - if count < maxCount: - realCount = count - else: - realCount = maxCount - api_url = "https://m.tiktok.com/share/item/list?{}®ion={}&secUid=&id={}&type=3&count={}&minCursor=0&maxCursor={}&shareUid=&recType=&lang={}".format( self.__add_new_params__(), region, str( id), str(count), str(maxCursor), language @@ -363,7 +357,6 @@ def byHashtag(self, hashtag, count=30, language='en', proxy=None, region='US') - print("TikTok isn't sending more TikToks beyond this point.") return response - realCount = count - len(response) maxCursor = res['body']['maxCursor'] return response[:count] From f5f48b53bbf1927a0552e524c3b7d5033296d65e Mon Sep 17 00:00:00 2001 From: "Michael D. Hoyle" Date: Wed, 26 Aug 2020 11:36:54 -0400 Subject: [PATCH 18/27] remove an unused parameter The api url is already added to the browser that's passed to getData, so no need to pass it in again. --- TikTokApi/tiktok.py | 39 ++++++++++++++++++--------------------- 1 file changed, 18 insertions(+), 21 deletions(-) diff --git a/TikTokApi/tiktok.py b/TikTokApi/tiktok.py index 6c6a8213..061f3075 100644 --- a/TikTokApi/tiktok.py +++ b/TikTokApi/tiktok.py @@ -47,7 +47,7 @@ def __init__(self, debug=False, request_delay=None): self.request_delay = request_delay - def getData(self, api_url, b, language='en', proxy=None) -> dict: + def getData(self, b, language='en', proxy=None) -> dict: """Returns a dictionary of a response from TikTok. :param api_url: the base string without a signature @@ -77,9 +77,6 @@ def getData(self, api_url, b, language='en', proxy=None) -> dict: 'sec-fetch-mode': 'cors', 'sec-fetch-site': 'same-site', "user-agent": b.userAgent - - - }, proxies=self.__format_proxy(proxy)) try: return r.json() @@ -90,7 +87,7 @@ def getData(self, api_url, b, language='en', proxy=None) -> dict: raise Exception('Invalid Response') - def getBytes(self, api_url, b, proxy=None) -> bytes: + def getBytes(self, b, proxy=None) -> bytes: """Returns bytes of a response from TikTok. :param api_url: the base string without a signature @@ -129,7 +126,7 @@ def trending(self, count=30, language='en', region='US', proxy=None) -> dict: api_url = "https://m.tiktok.com/api/item_list/?{}&count={}&id=1&type=5&secUid=&maxCursor={}&minCursor=0&sourceType=12&appId=1233®ion={}&language={}".format( self.__add_new_params__(), str(realCount), str(maxCursor), str(region), str(language)) b = browser(api_url, language=language, proxy=proxy) - res = self.getData(api_url, b, proxy=proxy) + res = self.getData(b, proxy=proxy) if 'items' in res.keys(): for t in res['items']: @@ -173,7 +170,7 @@ def userPosts(self, userID, secUID, count=30, language='en', region='US', proxy= api_url = "https://m.tiktok.com/api/item_list/?{}&count={}&id={}&type=1&secUid={}&maxCursor={}&minCursor=0&sourceType=8&appId=1233®ion={}&language={}".format( self.__add_new_params__(), str(realCount), str(userID), str(secUID), str(maxCursor), str(region), str(language)) b = browser(api_url, proxy=proxy) - res = self.getData(api_url, b, proxy=proxy) + res = self.getData(b, proxy=proxy) if 'items' in res.keys(): for t in res['items']: @@ -233,7 +230,7 @@ def userLiked(self, userID, secUID, count=30, language='en', region='US', proxy= api_url = "https://m.tiktok.com/api/item_list/?{}&count={}&id={}&type=2&secUid={}&maxCursor={}&minCursor=0&sourceType=9&appId=1233®ion={}&language={}&verifyFp=".format( self.__add_new_params__(), str(realCount), str(userID), str(secUID), str(maxCursor), str(region), str(language)) b = browser(api_url, proxy=proxy) - res = self.getData(api_url, b, proxy=proxy) + res = self.getData(b, proxy=proxy) try: res['items'] @@ -299,7 +296,7 @@ def bySound(self, id, count=30, language='en', proxy=None) -> dict: api_url = "https://m.tiktok.com/share/item/list?{}&secUid=&id={}&type=4&count={}&minCursor=0&maxCursor={}&shareUid=&lang={}&verifyFp=".format( self.__add_new_params__(), str(id), str(realCount), str(maxCursor), str(language)) b = browser(api_url, proxy=proxy) - res = self.getData(api_url, b, proxy=proxy) + res = self.getData(b, proxy=proxy) for t in res['body']['itemListData']: response.append(t) @@ -324,7 +321,7 @@ def getMusicObject(self, id, language='en', proxy=None) -> dict: api_url = "https://m.tiktok.com/api/music/detail/?{}&musicId={}&language={}&verifyFp=".format( self.__add_new_params__(), str(id), language) b = browser(api_url, proxy=proxy) - return self.getData(api_url, b, proxy=proxy) + return self.getData(b, proxy=proxy) def byHashtag(self, hashtag, count=30, language='en', proxy=None, region='US') -> dict: """Returns a dictionary listing TikToks with a specific hashtag. @@ -348,7 +345,7 @@ def byHashtag(self, hashtag, count=30, language='en', proxy=None, region='US') - id), str(count), str(maxCursor), language ) b = browser(api_url, proxy=proxy) - res = self.getData(api_url, b, proxy=proxy, language=language) + res = self.getData(b, proxy=proxy, language=language) for t in res['body']['itemListData']: response.append(t) @@ -372,7 +369,7 @@ def getHashtagObject(self, hashtag, language='en', proxy=None) -> dict: api_url = "https://m.tiktok.com/api/challenge/detail/?{}&challengeName={}&language={}".format( self.__add_new_params__(), str(hashtag.encode('utf-8'))[2:-1].replace("\\x", "%").upper(), language) b = browser(api_url, proxy=proxy) - return self.getData(api_url, b, proxy=proxy) + return self.getData(b, proxy=proxy) def getRecommendedTikToksByVideoID(self, id, language='en', proxy=None) -> dict: """Returns a dictionary listing reccomended TikToks for a specific TikTok video. @@ -385,7 +382,7 @@ def getRecommendedTikToksByVideoID(self, id, language='en', proxy=None) -> dict: api_url = "https://m.tiktok.com/share/item/list?{}&secUid=&id={}&type=0&count=24&minCursor=0&maxCursor=0&shareUid=&recType=3&lang={}&verifyFp=".format( self.__add_new_params__(), id, language) b = browser(api_url, proxy=proxy) - return self.getData(api_url, b, proxy=proxy)['body'] + return self.getData(b, proxy=proxy)['body'] def getTikTokById(self, id, language='en', proxy=None) -> dict: """Returns a dictionary of a specific TikTok. @@ -398,7 +395,7 @@ def getTikTokById(self, id, language='en', proxy=None) -> dict: api_url = "https://m.tiktok.com/api/item/detail/?{}&itemId={}&language={}&verifyFp=".format( self.__add_new_params__(), id, language) b = browser(api_url, proxy=proxy) - return self.getData(api_url, b, proxy=proxy) + return self.getData(b, proxy=proxy) def getTikTokByUrl(self, url, language='en', proxy=None) -> dict: """Returns a dictionary of a TikTok object by url. @@ -423,7 +420,7 @@ def discoverHashtags(self, proxy=None) -> dict: """ api_url = "https://m.tiktok.com/node/share/discover?{}&noUser=1&userCount=30&scene=0&verifyFp=".format(self.__add_new_params__()) b = browser(api_url, proxy=proxy) - return self.getData(api_url, b, proxy=proxy)['body'][1]['exploreList'] + return self.getData(b, proxy=proxy)['body'][1]['exploreList'] def discoverMusic(self, proxy=None) -> dict: """Discover page, consists of music @@ -432,7 +429,7 @@ def discoverMusic(self, proxy=None) -> dict: """ api_url = "https://m.tiktok.com/node/share/discover?{}&noUser=1&userCount=30&scene=0&verifyFp=".format(self.__add_new_params__()) b = browser(api_url, proxy=proxy) - return self.getData(api_url, b, proxy=proxy)['body'][2]['exploreList'] + return self.getData(b, proxy=proxy)['body'][2]['exploreList'] def getUserObject(self, username, language='en', proxy=None) -> dict: """Gets a user object (dictionary) @@ -455,7 +452,7 @@ def getUser(self, username, language='en', proxy=None) -> dict: api_url = "https://m.tiktok.com/api/user/detail/?{}&uniqueId={}&language={}".format( self.__add_new_params__(), username, language) b = browser(api_url, proxy=proxy) - return self.getData(api_url, b, proxy=proxy) + return self.getData(b, proxy=proxy) def getSuggestedUsersbyID(self, userId='6745191554350760966', count=30, language='en', proxy=None) -> list: """Returns suggested users given a different TikTok user. @@ -469,7 +466,7 @@ def getSuggestedUsersbyID(self, userId='6745191554350760966', count=30, language b = browser(api_url, proxy=proxy) res = [] - for x in self.getData(api_url, b, proxy=proxy)['body'][0]['exploreList']: + for x in self.getData(b, proxy=proxy)['body'][0]['exploreList']: res.append(x['cardItem']) return res[:count] @@ -508,7 +505,7 @@ def getSuggestedHashtagsbyID(self, count=30, userId='6745191554350760966', langu b = browser(api_url, proxy=proxy) res = [] - for x in self.getData(api_url, b, proxy=proxy)['body'][1]['exploreList']: + for x in self.getData(b, proxy=proxy)['body'][1]['exploreList']: res.append(x['cardItem']) return res[:count] @@ -547,7 +544,7 @@ def getSuggestedMusicbyID(self, count=30, userId='6745191554350760966', language b = browser(api_url, proxy=proxy) res = [] - for x in self.getData(api_url, b, proxy=proxy)['body'][2]['exploreList']: + for x in self.getData(b, proxy=proxy)['body'][2]['exploreList']: res.append(x['cardItem']) return res[:count] @@ -593,7 +590,7 @@ def get_Video_By_DownloadURL(self, download_url, proxy=None) -> bytes: :param proxy: The IP for your proxy. """ b = browser(download_url, proxy=proxy) - return self.getBytes(download_url, b, proxy=proxy) + return self.getBytes(b, proxy=proxy) def get_Video_By_Url(self, video_url, return_bytes=0, chromedriver_path=None) -> bytes: """(DEPRECRATED) From ef572c560d8e7c317d1c041bf8047391933ca510 Mon Sep 17 00:00:00 2001 From: "Michael D. Hoyle" Date: Wed, 26 Aug 2020 11:39:40 -0400 Subject: [PATCH 19/27] Use urllib.parse.urlencode to make querystrings --- TikTokApi/tiktok.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/TikTokApi/tiktok.py b/TikTokApi/tiktok.py index 061f3075..7f8b8fee 100644 --- a/TikTokApi/tiktok.py +++ b/TikTokApi/tiktok.py @@ -1,7 +1,9 @@ import random import requests -from .browser import browser import time +from urllib.parse import urlencode + +from .browser import browser class TikTokApi: @@ -61,9 +63,9 @@ def getData(self, b, language='en', proxy=None) -> dict: """ if self.request_delay is not None: time.sleep(self.request_delay) - url = b.url + \ - "&verifyFp=" + b.verifyFp + \ - "&_signature=" + b.signature + + query = {'verifyFp': b.verifyFp, '_signature': b.signature} + url = "{}&{}".format(b.url, urlencode(query)) r = requests.get(url, headers={ 'authority': 'm.tiktok.com', "method": "GET", @@ -99,9 +101,8 @@ def getBytes(self, b, proxy=None) -> bytes: :param proxy: The IP address of a proxy server to request from. """ - url = api_url + \ - "&_verifyFp=" + b.verifyFp + \ - "&_signature=" + b.signature + query = {'verifyFp': b.verifyFp, '_signature': b.signature} + url = "{}&{}".format(b.url, urlencode(query)) r = requests.get(url, headers={"method": "GET", "accept-encoding": "gzip, deflate, br", "referrer": b.referrer, From b57e5f414cab671386becea46c3c5dfc6be8c54d Mon Sep 17 00:00:00 2001 From: "Michael D. Hoyle" Date: Wed, 26 Aug 2020 11:46:02 -0400 Subject: [PATCH 20/27] More urlencode --- TikTokApi/tiktok.py | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/TikTokApi/tiktok.py b/TikTokApi/tiktok.py index 7f8b8fee..18373e5c 100644 --- a/TikTokApi/tiktok.py +++ b/TikTokApi/tiktok.py @@ -6,6 +6,9 @@ from .browser import browser +BASE_URL = "https://m.tiktok.com/" + + class TikTokApi: def __init__(self, debug=False, request_delay=None): """The TikTokApi class. Used to interact with TikTok. @@ -124,8 +127,22 @@ def trending(self, count=30, language='en', region='US', proxy=None) -> dict: realCount = count else: realCount = maxCount - api_url = "https://m.tiktok.com/api/item_list/?{}&count={}&id=1&type=5&secUid=&maxCursor={}&minCursor=0&sourceType=12&appId=1233®ion={}&language={}".format( - self.__add_new_params__(), str(realCount), str(maxCursor), str(region), str(language)) + + query = { + 'count': realCount, + 'id': 1, + 'type': 5, + 'secUid': '', + 'maxCursor': maxCursor, + 'minCursor': 0, + 'sourceType': 12, + 'appId': 1233, + 'region': region, + 'language': language + } + api_url = "{}api/item_list/?{}&{}".format( + BASE_URL, self.__add_new_params__(), urlencode(query) + ) b = browser(api_url, language=language, proxy=proxy) res = self.getData(b, proxy=proxy) From d156ba3a237ce8fcd22c701e6957d63dbe9cd9db Mon Sep 17 00:00:00 2001 From: "Michael D. Hoyle" Date: Wed, 26 Aug 2020 12:14:02 -0400 Subject: [PATCH 21/27] use urlencode in most endpoint methods --- TikTokApi/tiktok.py | 174 +++++++++++++++++++++++++++++++++++++------- 1 file changed, 147 insertions(+), 27 deletions(-) diff --git a/TikTokApi/tiktok.py b/TikTokApi/tiktok.py index 18373e5c..c1c33395 100644 --- a/TikTokApi/tiktok.py +++ b/TikTokApi/tiktok.py @@ -185,8 +185,22 @@ def userPosts(self, userID, secUID, count=30, language='en', region='US', proxy= realCount = count else: realCount = maxCount - api_url = "https://m.tiktok.com/api/item_list/?{}&count={}&id={}&type=1&secUid={}&maxCursor={}&minCursor=0&sourceType=8&appId=1233®ion={}&language={}".format( - self.__add_new_params__(), str(realCount), str(userID), str(secUID), str(maxCursor), str(region), str(language)) + + query = { + 'count': realCount, + 'id': userID, + 'type': 1, + 'secUid': secUID, + 'maxCursor': maxCursor, + 'minCursor': 0, + 'sourceType': 8, + 'appId': 1233, + 'region': region, + 'language': language + } + api_url = "{}api/item_list/?{}&{}".format( + BASE_URL, self.__add_new_params__(), urlencode(query) + ) b = browser(api_url, proxy=proxy) res = self.getData(b, proxy=proxy) @@ -245,8 +259,21 @@ def userLiked(self, userID, secUID, count=30, language='en', region='US', proxy= else: realCount = maxCount - api_url = "https://m.tiktok.com/api/item_list/?{}&count={}&id={}&type=2&secUid={}&maxCursor={}&minCursor=0&sourceType=9&appId=1233®ion={}&language={}&verifyFp=".format( - self.__add_new_params__(), str(realCount), str(userID), str(secUID), str(maxCursor), str(region), str(language)) + query = { + 'count': realCount, + 'id': userID, + 'type': 2, + 'secUid': secUID, + 'maxCursor': maxCursor, + 'minCursor': 0, + 'sourceType': 9, + 'appId': 1233, + 'region': region, + 'language': language + } + api_url = "{}api/item_list/?{}&{}".format( + BASE_URL, self.__add_new_params__(), urlencode(query) + ) b = browser(api_url, proxy=proxy) res = self.getData(b, proxy=proxy) @@ -311,8 +338,19 @@ def bySound(self, id, count=30, language='en', proxy=None) -> dict: else: realCount = maxCount - api_url = "https://m.tiktok.com/share/item/list?{}&secUid=&id={}&type=4&count={}&minCursor=0&maxCursor={}&shareUid=&lang={}&verifyFp=".format( - self.__add_new_params__(), str(id), str(realCount), str(maxCursor), str(language)) + query = { + 'count': realCount, + 'id': id, + 'type': 4, + 'secUid': '', + 'maxCursor': maxCursor, + 'minCursor': 0, + 'shareUid': '', + 'lang': language + } + api_url = "{}share/item/list?{}&{}".format( + BASE_URL, self.__add_new_params__(), urlencode(query) + ) b = browser(api_url, proxy=proxy) res = self.getData(b, proxy=proxy) @@ -336,8 +374,13 @@ def getMusicObject(self, id, language='en', proxy=None) -> dict: Note: Doesn't seem to have an affect. :param proxy: The IP address of a proxy to make requests from. """ - api_url = "https://m.tiktok.com/api/music/detail/?{}&musicId={}&language={}&verifyFp=".format( - self.__add_new_params__(), str(id), language) + query = { + 'musicId': id, + 'lang': language + } + api_url = "{}api/music/detail/?{}&{}".format( + BASE_URL, self.__add_new_params__(), urlencode(query) + ) b = browser(api_url, proxy=proxy) return self.getData(b, proxy=proxy) @@ -358,9 +401,20 @@ def byHashtag(self, hashtag, count=30, language='en', proxy=None, region='US') - maxCursor = 0 while len(response) < count: - api_url = "https://m.tiktok.com/share/item/list?{}®ion={}&secUid=&id={}&type=3&count={}&minCursor=0&maxCursor={}&shareUid=&recType=&lang={}".format( - self.__add_new_params__(), region, str( - id), str(count), str(maxCursor), language + query = { + 'count': count, + 'id': id, + 'type': 3, + 'secUid': '', + 'maxCursor': maxCursor, + 'minCursor': 0, + 'shareUid': '', + 'recType': '', + 'region': region, + 'lang': language, + } + api_url = "{}share/item/list?{}&{}".format( + BASE_URL, self.__add_new_params__(), urlencode(query) ) b = browser(api_url, proxy=proxy) res = self.getData(b, proxy=proxy, language=language) @@ -384,8 +438,13 @@ def getHashtagObject(self, hashtag, language='en', proxy=None) -> dict: Note: Doesn't seem to have an affect. :param proxy: The IP address of a proxy to make requests from. """ - api_url = "https://m.tiktok.com/api/challenge/detail/?{}&challengeName={}&language={}".format( - self.__add_new_params__(), str(hashtag.encode('utf-8'))[2:-1].replace("\\x", "%").upper(), language) + query = { + 'challengeName': str(hashtag.encode('utf-8'))[2:-1].replace("\\x", "%").upper(), + 'language': language + } + api_url = "{}api/challenge/detail/?{}&{}".format( + BASE_URL, self.__add_new_params__(), urlencode(query) + ) b = browser(api_url, proxy=proxy) return self.getData(b, proxy=proxy) @@ -397,8 +456,20 @@ def getRecommendedTikToksByVideoID(self, id, language='en', proxy=None) -> dict: Note: Doesn't seem to have an affect. :param proxy: The IP address of a proxy to make requests from. """ - api_url = "https://m.tiktok.com/share/item/list?{}&secUid=&id={}&type=0&count=24&minCursor=0&maxCursor=0&shareUid=&recType=3&lang={}&verifyFp=".format( - self.__add_new_params__(), id, language) + query = { + 'count': 24, + 'id': id, + 'type': 0, + 'secUid': '', + 'maxCursor': 0, + 'minCursor': 0, + 'shareUid': '', + 'recType': 3, + 'lang': language, + } + api_url = "{}share/item/list?{}&{}".format( + BASE_URL, self.__add_new_params__(), urlencode(query) + ) b = browser(api_url, proxy=proxy) return self.getData(b, proxy=proxy)['body'] @@ -410,8 +481,14 @@ def getTikTokById(self, id, language='en', proxy=None) -> dict: Note: Doesn't seem to have an affect. :param proxy: The IP address of a proxy to make requests from. """ - api_url = "https://m.tiktok.com/api/item/detail/?{}&itemId={}&language={}&verifyFp=".format( - self.__add_new_params__(), id, language) + + query = { + 'itemId': id, + 'lang': language, + } + api_url = "{}api/item/detail/?{}&{}".format( + BASE_URL, self.__add_new_params__(), urlencode(query) + ) b = browser(api_url, proxy=proxy) return self.getData(b, proxy=proxy) @@ -436,7 +513,14 @@ def discoverHashtags(self, proxy=None) -> dict: :param proxy: The IP address of a proxy server. """ - api_url = "https://m.tiktok.com/node/share/discover?{}&noUser=1&userCount=30&scene=0&verifyFp=".format(self.__add_new_params__()) + query = { + 'noUser': 1, + 'userCount': 30, + 'scene': 0 + } + api_url = "{}node/share/discover?{}&{}".format( + BASE_URL, self.__add_new_params__(), urlencode(query) + ) b = browser(api_url, proxy=proxy) return self.getData(b, proxy=proxy)['body'][1]['exploreList'] @@ -445,7 +529,14 @@ def discoverMusic(self, proxy=None) -> dict: :param proxy: The IP address of a proxy server. """ - api_url = "https://m.tiktok.com/node/share/discover?{}&noUser=1&userCount=30&scene=0&verifyFp=".format(self.__add_new_params__()) + query = { + 'noUser': 1, + 'userCount': 30, + 'scene': 0 + } + api_url = "{}node/share/discover?{}&{}".format( + BASE_URL, self.__add_new_params__(), urlencode(query) + ) b = browser(api_url, proxy=proxy) return self.getData(b, proxy=proxy)['body'][2]['exploreList'] @@ -467,8 +558,13 @@ def getUser(self, username, language='en', proxy=None) -> dict: Note: Doesn't seem to have an affect. :param proxy: The IP address of a proxy to make requests from. """ - api_url = "https://m.tiktok.com/api/user/detail/?{}&uniqueId={}&language={}".format( - self.__add_new_params__(), username, language) + query = { + 'uniqueId': username, + 'language': language + } + api_url = "{}api/user/detail/?{}&{}".format( + BASE_URL, self.__add_new_params__(), urlencode(query) + ) b = browser(api_url, proxy=proxy) return self.getData(b, proxy=proxy) @@ -479,8 +575,16 @@ def getSuggestedUsersbyID(self, userId='6745191554350760966', count=30, language :param count: The amount of users to return. :param proxy: The IP address of a proxy to make requests from. """ - api_url = "https://m.tiktok.com/node/share/discover?{}&noUser=0&pageId={}&userId={}&userCount={}&scene=15&verifyFp=".format( - self.__add_new_params__(), userId, userId, str(count)) + query = { + 'noUser': 0, + 'pageId': userId, + 'userId': userId, + 'userCount': count, + 'scene': 15 + } + api_url = "{}node/share/discover?{}&{}".format( + BASE_URL, self.__add_new_params__(), urlencode(query) + ) b = browser(api_url, proxy=proxy) res = [] @@ -518,8 +622,16 @@ def getSuggestedHashtagsbyID(self, count=30, userId='6745191554350760966', langu :param count: The amount of users to return. :param proxy: The IP address of a proxy to make requests from. """ - api_url = "https://m.tiktok.com/node/share/discover?{}&noUser=0&pageId={}&userId={}&userCount={}&scene=15&verifyFp=".format( - self.__add_new_params__(), userId, userId, str(count)) + query = { + 'noUser': 0, + 'pageId': userId, + 'userId': userId, + 'userCount': count, + 'scene': 15 + } + api_url = "{}node/share/discover?{}&{}".format( + BASE_URL, self.__add_new_params__(), urlencode(query) + ) b = browser(api_url, proxy=proxy) res = [] @@ -557,8 +669,16 @@ def getSuggestedMusicbyID(self, count=30, userId='6745191554350760966', language :param count: The amount of users to return. :param proxy: The IP address of a proxy to make requests from. """ - api_url = "https://m.tiktok.com/node/share/discover?{}&noUser=0&pageId={}&userId={}&userCount={}&scene=15&verifyFp=".format( - self.__add_new_params__(), userId, userId, str(count)) + query = { + 'noUser': 0, + 'pageId': userId, + 'userId': userId, + 'userCount': count, + 'scene': 15 + } + api_url = "{}node/share/discover?{}&{}".format( + BASE_URL, self.__add_new_params__(), urlencode(query) + ) b = browser(api_url, proxy=proxy) res = [] From 742456b90e4b9397b0a0bb36826ceb8d517dc350 Mon Sep 17 00:00:00 2001 From: "Michael D. Hoyle" Date: Wed, 26 Aug 2020 12:34:15 -0400 Subject: [PATCH 22/27] use urlencode in __add_new_parameters --- TikTokApi/tiktok.py | 35 +++++++++++++++++++++++++++++------ 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/TikTokApi/tiktok.py b/TikTokApi/tiktok.py index c1c33395..0c571d69 100644 --- a/TikTokApi/tiktok.py +++ b/TikTokApi/tiktok.py @@ -842,14 +842,37 @@ def __format_proxy(self, proxy) -> dict: return None def __get_js(self, proxy=None) -> str: - return requests.get("https://sf16-muse-va.ibytedtos.com/obj/rc-web-sdk-gcs/acrawler.js", proxies=self.__format_proxy(proxy)).text + return requests.get( + "https://sf16-muse-va.ibytedtos.com/obj/rc-web-sdk-gcs/acrawler.js", + proxies=self.__format_proxy(proxy) + ).text def __format_new_params__(self, parm) -> str: return parm.replace("/", "%2F").replace(" ", "+").replace(";", "%3B") def __add_new_params__(self) -> str: - return "aid=1988&app_name=tiktok_web&device_platform=web&referer=&user_agent={}&cookie_enabled=true".format(self.__format_new_params__(self.userAgent)) + \ - "&screen_width={}&screen_height={}&browser_language={}&browser_platform={}&browser_name={}&browser_version={}".format(self.width, self.height, self.browser_language, self.browser_platform, self.browser_name, self.browser_version) + \ - "&browser_online=true&timezone_name={}&priority_region=&appId=1233&appType=m&isAndroid=false&isMobile=false".format(self.timezone_name) + \ - "&isIOS=false&OS=windows&did=" + \ - str(random.randint(10000, 999999999)) + query = { + 'aid': 1988, + 'app_name': 'tiktok_web', + 'device_platform': 'web', + 'referer': '', + 'user_agent': self.__format_new_params__(self.userAgent), + 'cookie_enabled': True, + 'screen_width': self.width, + 'screen_height': self.height, + 'browser_language': self.browser_language, + 'browser_platform': self.browser_platform, + 'browser_name': self.browser_name, + 'browser_version': self.browser_version, + 'browser_online': True, + 'timezone_name': self.timezone_name, + 'priority_region': '', + 'appId': 1233, + 'appType': 'm', + 'isAndroid': False, + 'isMobile': False, + 'isIOS': False, + 'OS': 'windows', + 'did': random.randint(10000, 999999999), + } + return urlencode(query) From d2d1513fc031fec7c8c55cc5bc4e9410ca34a9b5 Mon Sep 17 00:00:00 2001 From: "Michael D. Hoyle" Date: Wed, 26 Aug 2020 12:37:40 -0400 Subject: [PATCH 23/27] fix some long lines --- TikTokApi/tiktok.py | 41 +++++++++++++++++++++++++++-------------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/TikTokApi/tiktok.py b/TikTokApi/tiktok.py index 0c571d69..363872bb 100644 --- a/TikTokApi/tiktok.py +++ b/TikTokApi/tiktok.py @@ -504,7 +504,9 @@ def getTikTokByUrl(self, url, language='en', proxy=None) -> dict: post_id = url.split("/video/")[1].split("?")[0] else: raise Exception( - "URL format not supported. Below is an example of a supported url.\nhttps://www.tiktok.com/@therock/video/6829267836783971589") + "URL format not supported. Below is an example of a supported url.\n" + "https://www.tiktok.com/@therock/video/6829267836783971589" + ) return self.getTikTokById(post_id, language=language, proxy=proxy) @@ -592,7 +594,9 @@ def getSuggestedUsersbyID(self, userId='6745191554350760966', count=30, language res.append(x['cardItem']) return res[:count] - def getSuggestedUsersbyIDCrawler(self, count=30, startingId='6745191554350760966', language='en', proxy=None) -> list: + def getSuggestedUsersbyIDCrawler( + self, count=30, startingId='6745191554350760966', language='en', proxy=None + ) -> list: """Crawls for listing of all user objects it can find. :param count: The amount of users to crawl for. @@ -639,7 +643,9 @@ def getSuggestedHashtagsbyID(self, count=30, userId='6745191554350760966', langu res.append(x['cardItem']) return res[:count] - def getSuggestedHashtagsbyIDCrawler(self, count=30, startingId='6745191554350760966', language='en', proxy=None) -> list: + def getSuggestedHashtagsbyIDCrawler( + self, count=30, startingId='6745191554350760966', language='en', proxy=None + ) -> list: """Crawls for as many hashtags as it can find. :param count: The amount of users to crawl for. @@ -754,9 +760,11 @@ def get_Video_No_Watermark_ID(self, video_id, return_bytes=0, proxy=None) -> byt pos = video_data.find("vid:") if pos == -1: return None - 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" \ + 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]) + ) if return_bytes == 0: return video_url_no_wm else: @@ -792,13 +800,16 @@ def get_Video_No_Watermark(self, video_url, return_bytes=0, proxy=None) -> bytes check = data.split('video":{"urls":["') if len(check) > 1: contentURL = check[1].split("\"")[0] - r = requests.get(contentURL, headers={"method": "GET", - "accept-encoding": "gzip, deflate, br", - 'accept-Language': 'en-US,en;q=0.9', - 'Range': 'bytes=0-200000', - 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9', - "user-agent": self.userAgent, - }, proxies=self.__format_proxy(proxy)) + r = requests.get(contentURL, headers={ + "method": "GET", + "accept-encoding": "gzip, deflate, br", + 'accept-Language': 'en-US,en;q=0.9', + 'Range': 'bytes=0-200000', + 'Accept': + 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;' + 'q=0.8,application/signed-exchange;v=b3;q=0.9', + "user-agent": self.userAgent, + }, proxies=self.__format_proxy(proxy)) tmp = r.text.split("vid:") if len(tmp) > 1: @@ -814,8 +825,10 @@ def get_Video_No_Watermark(self, video_url, return_bytes=0, proxy=None) -> bytes else: key = "" - cleanVideo = "https://api2-16-h2.musical.ly/aweme/v1/play/?video_id=" + \ - key + "&line=0&ratio=default&media_type=4&vr_type=0" + cleanVideo = ( + "https://api2-16-h2.musical.ly/aweme/v1/play/?video_id={}&line=0&ratio=default" + "&media_type=4&vr_type=0" + ).format(key) b = browser(cleanVideo, find_redirect=True, proxy=proxy) print(b.redirect_url) From 1a5ad598f369ead6d3bc55bb0e00514760d2c741 Mon Sep 17 00:00:00 2001 From: David Teather <34144122+davidteather@users.noreply.github.com> Date: Mon, 7 Sep 2020 16:34:41 -0500 Subject: [PATCH 24/27] Things --- .gitignore | 3 ++- TikTokApi/__init__.py | 3 ++- TikTokApi/__pycache__/__init__.cpython-38.pyc | Bin 0 -> 205 bytes TikTokApi/__pycache__/browser.cpython-38.pyc | Bin 0 -> 5415 bytes .../__pycache__/get_acrawler.cpython-38.pyc | Bin 0 -> 37961 bytes TikTokApi/__pycache__/stealth.cpython-38.pyc | Bin 0 -> 16490 bytes TikTokApi/__pycache__/tiktok.cpython-38.pyc | Bin 0 -> 29198 bytes TikTokApi/__pycache__/user.cpython-38.pyc | Bin 0 -> 464 bytes TikTokApi/browser.py | 4 +++- TikTokApi/tiktok.py | 21 ++++++++++-------- 10 files changed, 19 insertions(+), 12 deletions(-) create mode 100644 TikTokApi/__pycache__/__init__.cpython-38.pyc create mode 100644 TikTokApi/__pycache__/browser.cpython-38.pyc create mode 100644 TikTokApi/__pycache__/get_acrawler.cpython-38.pyc create mode 100644 TikTokApi/__pycache__/stealth.cpython-38.pyc create mode 100644 TikTokApi/__pycache__/tiktok.cpython-38.pyc create mode 100644 TikTokApi/__pycache__/user.cpython-38.pyc diff --git a/.gitignore b/.gitignore index 60f0ae6f..8fed264a 100644 --- a/.gitignore +++ b/.gitignore @@ -16,4 +16,5 @@ dist/* .pytest_cache/* test.mp4 test.txt -.pytest_cache/* \ No newline at end of file +.pytest_cache/* +tests/__pycache__/* \ No newline at end of file diff --git a/TikTokApi/__init__.py b/TikTokApi/__init__.py index 27643c41..08183763 100644 --- a/TikTokApi/__init__.py +++ b/TikTokApi/__init__.py @@ -1 +1,2 @@ -from TikTokApi.tiktok import TikTokApi \ No newline at end of file +from TikTokApi.tiktok import TikTokApi +#from TikTokApi.user import TikTokUser \ No newline at end of file diff --git a/TikTokApi/__pycache__/__init__.cpython-38.pyc b/TikTokApi/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cdadce7c0bc35f2cb4887bc5a473f080bec9a4d5 GIT binary patch literal 205 zcmWIL<>g`kg5pJC@qR%1F^Gc(44TX@fuanW zjJG&LGP6VSvmFaE{WO`P1mHZqlFaOq{Olr-F)JC0n1K|S_~qbi6%$&VT2vg9Qj(fj z662DeT$-DjS5h3~o>}5iniK;uLKkQvlnG+R$7kkcmc+;F6;$5hu*uC&Da}c>V+0!d H8HgDG2w*fI literal 0 HcmV?d00001 diff --git a/TikTokApi/__pycache__/browser.cpython-38.pyc b/TikTokApi/__pycache__/browser.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..505bc2926ff4c2e1e251be5d316e865206bc75a2 GIT binary patch literal 5415 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 literal 0 HcmV?d00001 diff --git a/TikTokApi/__pycache__/get_acrawler.cpython-38.pyc b/TikTokApi/__pycache__/get_acrawler.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b4b630268279d87601971d07afbfb71b2dc032ee GIT binary patch literal 37961 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 Cy9-?a literal 0 HcmV?d00001 diff --git a/TikTokApi/__pycache__/stealth.cpython-38.pyc b/TikTokApi/__pycache__/stealth.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..20e8007210b643779e91e8d350e4b3c990b7e7b5 GIT binary patch literal 16490 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 literal 0 HcmV?d00001 diff --git a/TikTokApi/browser.py b/TikTokApi/browser.py index 27f81869..d0f3a5b9 100644 --- a/TikTokApi/browser.py +++ b/TikTokApi/browser.py @@ -133,9 +133,11 @@ async def start(self): self.verifyFp = ''.join(random.choice( string.ascii_lowercase + string.ascii_uppercase + string.digits) for i in range(16)) + self.did = str(random.randint(10000, 999999999)) + await self.page.evaluate("() => { " + get_acrawler() + " }") self.signature = await self.page.evaluate('''() => { - var url = "''' + self.url + "&verifyFp=" + self.verifyFp + '''" + var url = "''' + self.url + "&verifyFp=" + self.verifyFp + '''&did=''' + self.did + '''" var token = window.byted_acrawler.sign({url: url}); return token; }''') diff --git a/TikTokApi/tiktok.py b/TikTokApi/tiktok.py index f9a3a449..37c1dea3 100644 --- a/TikTokApi/tiktok.py +++ b/TikTokApi/tiktok.py @@ -67,7 +67,7 @@ def getData(self, b, language='en', proxy=None) -> dict: if self.request_delay is not None: time.sleep(self.request_delay) - query = {'verifyFp': b.verifyFp, '_signature': b.signature} + query = {'verifyFp': b.verifyFp, 'did': b.did, '_signature': b.signature} url = "{}&{}".format(b.url, urlencode(query)) r = requests.get(url, headers={ 'authority': 'm.tiktok.com', @@ -81,11 +81,14 @@ def getData(self, b, language='en', proxy=None) -> dict: 'sec-fetch-dest': 'empty', 'sec-fetch-mode': 'cors', 'sec-fetch-site': 'same-site', - "user-agent": b.userAgent + "user-agent": b.userAgent, + 'cookie': 'tt_webid_v2=' + b.did }, proxies=self.__format_proxy(proxy)) try: return r.json() - except Exception: + except Exception as e: + if self.debug: + print(e) print(r.request.headers) print("Converting response to JSON failed response is below (probably empty)") print(r.text) @@ -445,7 +448,7 @@ def getMusicObject(self, id, language='en', proxy=None) -> dict: b = browser(api_url, proxy=proxy) return self.getData(b, proxy=proxy) - def byHashtag(self, hashtag, count=30, language='en', proxy=None, region='US') -> dict: + def byHashtag(self, hashtag, count=30, language='en', proxy=None, region='') -> dict: """Returns a dictionary listing TikToks with a specific hashtag. :param hashtag: The hashtag to search by. @@ -471,7 +474,7 @@ def byHashtag(self, hashtag, count=30, language='en', proxy=None, region='US') - 'minCursor': 0, 'shareUid': '', 'recType': '', - 'region': region, + 'priority_region': region, 'lang': language, } api_url = "{}share/item/list?{}&{}".format( @@ -931,14 +934,15 @@ def __add_new_params__(self) -> str: 'device_platform': 'web', 'referer': '', 'user_agent': self.__format_new_params__(self.userAgent), - 'cookie_enabled': True, + 'cookie_enabled': 'true', 'screen_width': self.width, 'screen_height': self.height, 'browser_language': self.browser_language, 'browser_platform': self.browser_platform, 'browser_name': self.browser_name, 'browser_version': self.browser_version, - 'browser_online': True, + 'browser_online': 'true', + 'ac': '4g', 'timezone_name': self.timezone_name, 'priority_region': '', 'appId': 1233, @@ -946,7 +950,6 @@ def __add_new_params__(self) -> str: 'isAndroid': False, 'isMobile': False, 'isIOS': False, - 'OS': 'windows', - 'did': random.randint(10000, 999999999), + 'OS': 'windows' } return urlencode(query) From 81c607c98905ad15bc1ea7006deda60130ceaddc Mon Sep 17 00:00:00 2001 From: David Teather <34144122+davidteather@users.noreply.github.com> Date: Mon, 7 Sep 2020 19:24:16 -0500 Subject: [PATCH 25/27] Fix #251 --- TikTokApi/__pycache__/tiktok.cpython-38.pyc | Bin 29198 -> 29189 bytes TikTokApi/tiktok.py | 6 ++++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/TikTokApi/__pycache__/tiktok.cpython-38.pyc b/TikTokApi/__pycache__/tiktok.cpython-38.pyc index 6fcbe7c84fb89f6583e69ab7f1f720b76c8aa5d5..a730c8dfccdf50ed060652ecffacedee855dfe73 100644 GIT binary patch delta 3986 zcmbVPdvH|c75CoV>?WIyc|ik~M}b1QK-Sa%MnWDWK@7x2F;HD1;b!-;yJTPa?hP>! zyHtxD!zxO(&((sZx3r+G?bNaD==3$?OlNLq`k3*Lrgb{)^zlc=KYD)WhTUYL zDN{21^4)XKIp6P`@0|19Jn^n_@?E87e@#u`I{J6v7YBx)K40@vKyl>7KzEhnGouM3 zZ8)}4y;V^*$hP3>4>{hWW;P=lNMHB?l?0Ao7-QpxX^D`W3Wk);b|Lth+P@a>HNXbI zMnE%xRxOrceG{M+uo<8Nwg9#Q*rp1rHhH#oXs-ms=_H+0$HPPUS-G%Q&Hb*JJ; zQka%gB@8{)n;p+sqSO9;L&%%o2}OlqPVi<53z^T%)S_~Kg zMCJKlqy1jfj5@?z_ds|%LBT87IVZW87)^C*;)b=K_TM{l)QDOl30=R-s}j62dfK?c z)b9CUm$w-^XccbAh}Q8?frw(XR0Rl6!~qF;Zsm%);>LWALYF7{me~JXdDxSm&sOt^ zF<1$J37F5D&min31Sg?+#3-~Z0;h71F}YKSthh`djaLi%Q!@5DYfSHkQV~q!=%%?m zuUZ6hZ2h`BuBd|GwN%0U-kMVsNBHz+)^~bV&CiE8N@O(E4RR9Uq*yhz=_j6+BIjvN zy$`CD?ve5F4?rI^;O1+u9BFA*2IZ$)mgML24{KG|LcdtQ+V$=M^Dd>*<)bRA+QZoX zih?D+T(OJnu8;av$93Uhpm4NV63xcxNsu-qhUyMJ4~%|5{0t;jZO2-K>@}h&2IU`b=6UnSdT9d=Vh$m?_J^-DM1HhGzg*Rs!c}PzHW>ZT7 zPJ37jICa!&x9MiW(&Mw;b`2w=Jll>x;ks~RCWoOX)v3ya zoZ7aE#_Q>AYYuTN0g)r}j?c(MvoQlX$ffkC;Z%(0a)wy=P&9XJh({29iC6JZnpAPh z-rD{Vwf`h(P_1#vFyrzPg@@yShXM2REjY&y;>APU1eZEOsGUD`t*GX%abMZ>-OsA) z?;}!6r_%abGT#6~;i41{>TKW-a-^ z-N5VEGMZrMR7Q0X>$i+_n3`+8_13L8bMfmov<() zHb-0*hI=5;FWt-E;C+5~XSDsDDZfA8LMvs&I8T@h6v`y zFXsD;RL;%_)A1x@CP(e~De6HzPC-AyWX=r+)f@k7(4~?s1ueeChVLI(rY61(8eH)I zllFJ7nYJ%IZYHCquwlVj{f>R&o{MOcTMllj&Opqqr!$d<4(?HE&>l2``RePDza3ht zbkGGrv*uO%`g{N7@m++`C0V6!xxt}M5PI4aA(G8_k0oQ<#ACFax?hG9x5%gT-uV6W zIQ?OmaBM%M6&(hK#+(n=c6xNYN0OPC+aNoj<$mlqmE_gP4ku&c2QUKLNMKf$Ig%CM zhcsljkG!vJ|18#AU7y2>cbpA}2i8wXdU;l{UD2g@lN0c8?5x`KD6~9cEnH}Tjeusk zJN|lGQ8-nrN25m0Y97!t@o}05;zy(v@$F!#Yw}~*Jq~zGb|+5e!JR&}Z=_#Za8CIA zP1FWGB*LAoT&eh`bu)s~hy;ZZOVTB34UdbI;3NgzVNn)L7wmAjP6hov5*>{MqS=h) zH!{}#eFKiYC%I8s>Ua~Dl@qI=+C)%LT~{6Q$)j&Adz&PhcMiuAyB7tWD{G50Jc<{+nSrm1?W?$0}o0{1b>(pI$8ANVPWN>(5crw>tg_ zI`Qm;sV$aO>D9_Uxhb7i?vQ8F38fQbJAiNC7wJ6BkX)MydH&*Sl6PlX^6)Qi0Q3=r ziRaZN;gS*!LDu1tV3=cH{%)?_ReWpgYOa!}L?BG91J}$-wq$o5ei1~A1dD80T-1cl z|0$8b$;R^C!Sp?>UIV-ecpZSo6F&p|62QfjlYh-ES0XYvcIKv+ nA>9IKA#j3J->KwCh^7nODk|h?0KtC-R$$eHzh&j$$Cm#G-;7m1 delta 3990 zcmbVPYj9L&8SdHL>~1zmbHgSe27xwY38bMB$fZCCfiw^|lqO&gDd}d<$?i$^mhbEm zl2(_NQaXpJ?Pwh@Kl<~9^=~qt$-Z>7vK&+8-UN0Vzg6Us$5&uj$sFYr90(cH>|PVtNe?i?3TZ&Zm8Ie zEeOpiqFw&G`fz1GO%8>EWwtx6Cq=6qtl6e?$p>pTR~>{b9~j_M^5vTPdWhR4X~7>e{gIu(Y-oB}JH{x6riLy>Y2OgibtyF2~3WI7L}BzU?h{Yd7ZL zJkPH8(kSS6LdN0m!#;Ar@z-HF*1BC8lux$S=a%vh1xs&)ej$H@H#-65TZ*a6p<0%; zD<>@R<&0fp?c3pVDQ9F~+lKwHe9pUiA>}hv@Xef5e5q3rNGkdF9*^}#+r#c0i?DZ4 z)xt{<46NcE2u%~%)bb)jGilwfOba6=jLgia(4z+R#xsza0e~xog)e6svsirqkaum4 zhN#<6Fkn|vsofPeW0`O?Uu`$gGM0PW_Qf0(jtxcQxzVw;?0aq&9XHj|{VVneqrJ+d z2szuik=pCU&dsOTmVlTfvbI-GMpBv%AEe{qh;El;(`j9-9+aTdHpC+^zsRc)lscC< zXYK0R;_5$35|nG4GR$~+ibBIQ;32?Le+%~Ur?K%FE`n=&T97+`+t*OWRpV?~)!omz znnEN-&EXS}=CXSn3nfn8?!c-;)Ox3($eg+DN^qr|Csg4z$RCY z4>@+@I8q@*tshlkdqp^@r4qI`5=$8oUA*8d>WS28ouAO_H{((}U}nN1VI&E}Nah>`)ZHaFHo&gl2$9;pN_dIQc#J33N z@@BKe%gk^B0v*M7`P+QYpYHB*<@m@;*e(!tGKVYSJGg=({XGb9dw3C}?*c9X<^U%M z7TYhD>Qnvs9%0&ULo?ZG+ecOpg`;HjBTVMw z_@A_Y2&o&Q?M-A&BVvk|ae}@21MBR)S5YP%C%2UuSmx3jVIt>F9#JX~Cu+g`^zD&< z99gTh(*r=g=2h$F`~Kzj{s@O&k)`3C>re?IoG?W*tu1&?8(OEhK*QMwWGL1lp9&v{ zo~G&itvJHAy+$Jn2AamA57%8ZwLRlTQgaIAPDnW)JGP&+>Zwsf6W4GEY}G)1EjgYN zSFze`b&Y+X-1#KNoLx_0#5d;6;fD1~TD{(@_*_Ay_&Gb^QSFke?omj&1(R-so+iLn zc{uvB_QK+9WjGSi)0yo9;bb&R{Xo1|@Gox7%7wn{Rr&%mBrW5B7T(yM$KN6W#1CdlR z`#=+`_dXXEYF9?hruwNm~U(8?~}B7YHYtA&8ABo3** zX^MGj+6>+zeThx@?jRG{l1Rw>N0%QPLz=MDs;5^_Q;PU`3 z)2A`|3g9`wH~`P0?TOT0-ROiV0uULNzn`j;>!u#7yfRg<49T~ry8Z*F^GM+U diff --git a/TikTokApi/tiktok.py b/TikTokApi/tiktok.py index 37c1dea3..4143bba4 100644 --- a/TikTokApi/tiktok.py +++ b/TikTokApi/tiktok.py @@ -141,6 +141,7 @@ def trending(self, count=30, language='en', region='US', proxy=None) -> dict: 'sourceType': 12, 'appId': 1233, 'region': region, + 'priority_region': region, 'language': language } api_url = "{}api/item_list/?{}&{}".format( @@ -199,6 +200,7 @@ def userPosts(self, userID, secUID, count=30, language='en', region='US', proxy= 'sourceType': 8, 'appId': 1233, 'region': region, + 'priority_region': region, 'language': language } api_url = "{}api/item_list/?{}&{}".format( @@ -333,6 +335,7 @@ def userLiked(self, userID, secUID, count=30, language='en', region='US', proxy= 'sourceType': 9, 'appId': 1233, 'region': region, + 'priority_region': region, 'language': language } api_url = "{}api/item_list/?{}&{}".format( @@ -932,7 +935,7 @@ def __add_new_params__(self) -> str: 'aid': 1988, 'app_name': 'tiktok_web', 'device_platform': 'web', - 'referer': '', + 'Referer': '', 'user_agent': self.__format_new_params__(self.userAgent), 'cookie_enabled': 'true', 'screen_width': self.width, @@ -944,7 +947,6 @@ def __add_new_params__(self) -> str: 'browser_online': 'true', 'ac': '4g', 'timezone_name': self.timezone_name, - 'priority_region': '', 'appId': 1233, 'appType': 'm', 'isAndroid': False, From 6cb9f4bc9efcf5b9af2a401060cf6b3d376ea4c8 Mon Sep 17 00:00:00 2001 From: David Teather <34144122+davidteather@users.noreply.github.com> Date: Mon, 7 Sep 2020 19:35:30 -0500 Subject: [PATCH 26/27] Fix userPager --- TikTokApi/__pycache__/tiktok.cpython-38.pyc | Bin 29189 -> 29204 bytes TikTokApi/tiktok.py | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/TikTokApi/__pycache__/tiktok.cpython-38.pyc b/TikTokApi/__pycache__/tiktok.cpython-38.pyc index a730c8dfccdf50ed060652ecffacedee855dfe73..fb54afe58e99f9e4bc3dba12afb0bd4577748b0b 100644 GIT binary patch delta 4544 zcmbVPdr*|u73c24K6xyU7LW*00a-ys@xdc10!l!FMgetwkY)D+md7sKU5t>mtMQRe zS}n=7jkP8zPrb7v(vX(Vsk=*Erx#H`c+G7>gmzM5gT)fS8bJ6mnrD!RC*Pc%Is?x zTP#ZB7vArR?Ft6`JcR_f8DJrBnYq$Gpaes_Pz=Q{V=L5=_}2|}xj4@UECv(+mJm4Y z{64Ig1BwAF00zKHKnXy~Sg~3yE+u6;O0g^hNbYj+$DB0re$ozGJkhG@FL+zjIS`+wUVg<`s6vqr7m=Z))YVC@s zKfj~Hj+jGcFFR!M8V*^##zVGHOxS#sg^k@Y{ASn~w$T42;tRR+&2iyaubCXal)F+S zrrGnJ3nzJPnK3mn`w|a1!shdqu;Y2gV){=J-A-Y+8ny+ms_WBk7$TK)4`&2T+(xRA3 zfz9B2kYFU1%Nu1;MN{T9XbBxksF{q~+Sa9bLRM&Z%WP&XhJ+yn56%Q@MOs6n#w_&#yC zCc`GL-{$U6{-%DM(_)OwbjWGqy-?r>1OPK#leufu1Bnbejl2U~eFU0ii!!*0^8lZ; z#fPK2>tkKw)}kVoAXx6s!f45+P#JxC$qqAa5=V3MrWI>#!ethNU+PE;e^Ic!`Rta+ z$xBc82^DHQ73v)-)H`Z*UaWCRCCrkAI)?HZvtmDIp^E;3{B578)cAX;)D$R(Y9 z-EU;CY`8SBVTG9;QEONHhp|CXTavR8Q5Z4ZeHu)36qonQVwYFzZH;0Vf31p*e`p}+ z^XPr}ih85ufH5NZWxz0}xQW1NyGy{pbdW%!_sP>V*r)J^KpF%L0bnfUNZu~(cns7q zV0MIUnj=E9;X!w>Gvw}=7!UU__;x`VX5K{5HFKw~LbrR$d--Na4`mAE$kQVN4H$Ih zX_L-wZ5y1hQtrh3W7wwmR*?12LX(q+*33wI6ps&a6QM9;$K8nQM zGvw4~9zTY=r)-R7DBZ-5($U}5@`^=P^)uhM#R2vUsdj=ql{i;Z=OcUdq5?GVCSd*hJ-h6^l`(Ccix6AoDVIA|Q#$gnD)qmFh zh*1Ks)n(^LU7k$DGznY~H7(uDpCy9dAa0i2r>?F41&f@fz01@`FGZbKe_`vq{L7H~ z3SgGeuvfEq+}v9iQhFPyHH*R344CU7-$e)1bn?l19!#_5M;R)W&wQLUc2L}DnQz0x z8f+V^t2wM*Z^$r2q`V#m2%&Tu=^;Z`X(sOWdjq|i$Y<4stcB&=rRlJLs|w_-Wy-tJ%SEqs!t~9)&5!AU~(ihH_K&OrT8-ybhagZUH<&2&oD3Sr$kJUHjZNGmwlZ}e&4Ni5z_J6U6 z6uyY#sNs5v$xeEa4jQQlz6}NhhQ9>(7T^+K1TZ}*bLewfCT31hGy1$i83oNuQD|~^ zP!Ntuvo1H?&)!vUH2sAw|I{E%Rrpj8_;;lEhb^CFeU`72|@lcBxJn5D-Q2o&BoQMyB|kymF+3Acj2%UuumFxbWa*fR?qBN!xGR{H16eV zyH5OeZze0H*M?@1q!|=tnStG&`aFi8{~yV8;?EU^Oo}` zXgTzlXzVN#Pr0i*9;NMAE<~iuuhWWhhIusY3(XqZYO(D;zgKUu2f(F&_|q(;RSC5E zy!;x3V7|*XLG!l<_^Y56ii$QjD^{OvJIfvz21OR-Q&`D+C;VFl!jB0ihoO{?D&}v> zxSa52vn}cc?>Z*zW2c!rW$C+X5>fUg1Y0?U_Fi|xUOfeYBnp^GWIosVKKCzXoT_W@S{uL00({4Kyw0kU53 z;meiM+T`nQ3UqJm^G#L>w}GWa+~`ji^ZL`XRyxWZ4kF delta 4543 zcmbtXdr(x@8RzUS%f8s%02X{9Dp*%hG`>g_<>dpV_(H9VikH0?SRT8}xr+vbke5l! z!%TCMCR%GWO{-Ls)?U+U5>sQx#A&B?+CMt&ooPC0@<-L_Oxu}GI_YEj{mxw%B*r=| z3_rg6-S0c+d!KV(_=N5Mg!#8*XL~Z}=gq5In_m`Z{KFn*igTVyx9QX)?OL~H8qB+x zv00+rH?7EYbm;LI&mje#1#l6VSzPPs)$}BvEBbwN*+OI3_qMIB1n)zDS%BGqIRqgu zp9K0|zyiQRfDNz+uoxh9+@Q+E>w)pX3XsbIQhJ3rEfE6h{trzbag8a)WmyZNsblF%+@H^lct+%N|(i6F8-Rcgsl~`a%YN@ z<=H~Xt?;H@Gc!bg@gy6+Dn7_9$#ke$n|tgT}utYQR>+)j86nd55!b%0HPM!*)pR=_qu%3}}zDxPG% zwu1@-WYQd^dxbKsC?Ei)ovNOv13;yX4R{-% z9q-);YFuqX%U zfKd+O5m{@n;)>jb?7S1~B!TH#r}eGmJkD?0*NsnFg5pAOZ#{>cjcDlZ^xOf5VhPdh8#+Z%|1@%es5@XrxTll1j9z zY{kC88T$t1U>SDad3vO}lS@<6lQ(6M><)85fSoa}FW6&e>x8mo+>mtWwlRe(0|Ypm zB8`0sEgVSjXq-orea&2Ji_#$moB4wPq@Pk+0a|W61nMEct<~2M@|eLkExAU~+mlM$ z(E64REt33S5@KexTWeT#G2-q<>?&Q_2}%yi+vbGcinG3;*WJ>WGUq48qve@4wpSdu ze=1uhPTfC!m-NiT6C`XpwOAysYKTapONr1R>P;kQ5Z!U4hpdswpM)_ZR$3NOQu3Yr z33~Mxqi*SRTit#lsX+gGTKMBw@74@(8TCi-;!)XBS1P73yYa^gzl{xwPgfLr;SC=V z2xL<#8l#` zN^SLAxjc+8z1jnbE}GS7Lb>5^N?(>p+W8QxGS1if*<9Q|axSOLo4+(Svy+q*!@1@?M)|x_S2!abftz_5 zrGX97rsZex28=Np_L{DU!qqyO zMh(=QcdQe>gXBXVE6U%-dSiyo@&K5l&UkUWzLX6bSL+LGLsG%=8&W9$20HO@<1n3E ziK+2!(-~=xMNN32*hVyEjIJT35T7W5=*7 zrtRHAZ|pihCX=#n{bcr{acKR=Y}dC)fWHP96Bt~ctZaUVs0Mz5a5&|-^m&Xkw`<;$ za<@Ni9M9emSL;K@yiI>+)-ZbmtEUaOYD!Ln?_x8``u89puVSY_od%o%ybc(h1!>H! z>xEsZsiT|rsH)2-m{}BsMx~8{@T4^BLSqqo-}tcc1}po*AdHmsND%n{H|N_D9f6Ynn>EF}#!w zt4PD1Z^~nS<3!V9_GPpY4Sab#SA%xQ!VUY>_}*AoTv3~Qc^BRI<$QaK1SS|$cm9pB z=|+C@KbZ3zRz5EZltmLvYACLB>wGTpK6C7es`vA!iR|AecDCOqjwv;5`{?mnF;rw2 zeoPeQ5mRZ@=gv}kG##zcm}>P|6{M`&r0JqnwRm$>FkAKlII>V*1SR)P`7Gz+2gJQO5~X%}VSZW0>P5AXtufB32W|OJLDtSouuupv z8!$&~Z+o{q&Fl{-k%*Q^&Z$>oZM`&N_(hTmJF6tuQn>`ZX8=!&_3itI;0JwImC?VD zZ&WDU5Ge;BDApjC1&{lT6&)ll?BkjmrPDnrlO+S@v*ynjmsUUz)nI1Q7j2|7?1{u< z$t*3F+_I_O6yazU+a!)euZ=ey?a5?_MCa0G)v21g4`4NB%IL<)p=E z-NfI-vm8%90`)fFEx@|~bQ8Y<_ys@~1a4g6oM!8GQk96_ECcR=NLYN>lP^L&`4i^{ u?+XTrg&(Bm-!{ALU%rW+X~8Pd)U!2kig;GB%_GVb7kdiWcJbSurT+mIW(BhV diff --git a/TikTokApi/tiktok.py b/TikTokApi/tiktok.py index 4143bba4..189e7cdd 100644 --- a/TikTokApi/tiktok.py +++ b/TikTokApi/tiktok.py @@ -264,7 +264,7 @@ def userPage( ) ) b = browser(api_url, proxy=proxy) - return self.getData(api_url, b, proxy=proxy) + return self.getData(b, proxy=proxy) def getUserPager(self, username, page_size=30, before=0, after=0, proxy=None, language='en', region='US'): """Returns a generator to page through a user's feed From ac7419ce7e4f84e061e9428bf8d3bc2184b2bff0 Mon Sep 17 00:00:00 2001 From: David Teather <34144122+davidteather@users.noreply.github.com> Date: Mon, 7 Sep 2020 19:39:04 -0500 Subject: [PATCH 27/27] version bump --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 7413152a..5717a1e4 100644 --- a/setup.py +++ b/setup.py @@ -8,7 +8,7 @@ setuptools.setup( name = 'TikTokApi', packages = ['TikTokApi'], - version = '3.4.6', + version = '3.4.7', license='MIT', description = 'The Unofficial TikTok API Wrapper in Python 3.', author = 'David Teather',